···2use jacquard_common::IntoStatic;
3use std::collections::HashMap;
45-use crate::place_wisp::fs::{Directory, EntryNode};
67/// Extract blob information from a directory tree
8/// Returns a map of file paths to their blob refs and CIDs
···2use jacquard_common::IntoStatic;
3use std::collections::HashMap;
45+use wisp_lexicons::place_wisp::fs::{Directory, EntryNode};
67/// Extract blob information from a directory tree
8/// Returns a map of file paths to their blob refs and CIDs
···001// @generated by jacquard-lexicon. DO NOT EDIT.
2//
3// This file was automatically generated from Lexicon schemas.
···1+extern crate alloc;
2+3// @generated by jacquard-lexicon. DO NOT EDIT.
4//
5// This file was automatically generated from Lexicon schemas.
+13-15
cli/src/main.rs
···1-mod builder_types;
2-mod place_wisp;
3mod cid;
4mod blob_map;
5mod metadata;
···28use futures::stream::{self, StreamExt};
29use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
3031-use place_wisp::fs::*;
32-use place_wisp::settings::*;
3334/// Maximum number of concurrent file uploads to the PDS
35const MAX_CONCURRENT_UPLOADS: usize = 2;
···512 let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
513 let chunk_size = subfs_utils::estimate_directory_size(chunk);
514515- let chunk_manifest = crate::place_wisp::subfs::SubfsRecord::new()
516 .root(convert_fs_dir_to_subfs_dir(chunk.clone()))
517 .file_count(Some(chunk_file_count as i64))
518 .created_at(Datetime::now())
···535 // Each chunk reference MUST have flat: true to merge chunk contents
536 println!(" → Creating parent subfs with {} chunk references...", chunk_uris.len());
537 use jacquard_common::CowStr;
538- use crate::place_wisp::fs::{Subfs};
539540 // Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
541 let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···565 let parent_tid = Tid::now_0();
566 let parent_rkey = parent_tid.to_string();
567568- let parent_manifest = crate::place_wisp::subfs::SubfsRecord::new()
569 .root(parent_root_subfs)
570 .file_count(Some(largest_dir.file_count as i64))
571 .created_at(Datetime::now())
···584 let subfs_tid = Tid::now_0();
585 let subfs_rkey = subfs_tid.to_string();
586587- let subfs_manifest = crate::place_wisp::subfs::SubfsRecord::new()
588 .root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
589 .file_count(Some(largest_dir.file_count as i64))
590 .created_at(Datetime::now())
···952953/// Convert fs::Directory to subfs::Directory
954/// They have the same structure, but different types
955-fn convert_fs_dir_to_subfs_dir(fs_dir: place_wisp::fs::Directory<'static>) -> place_wisp::subfs::Directory<'static> {
956- use place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
957958 let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
959 let node = match entry.node {
960- place_wisp::fs::EntryNode::File(file) => {
961 SubfsEntryNode::File(Box::new(SubfsFile::new()
962 .r#type(file.r#type)
963 .blob(file.blob)
···966 .base64(file.base64)
967 .build()))
968 }
969- place_wisp::fs::EntryNode::Directory(dir) => {
970 SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
971 }
972- place_wisp::fs::EntryNode::Subfs(subfs) => {
973 // Nested subfs in the directory we're converting
974 // Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
975- SubfsEntryNode::Subfs(Box::new(place_wisp::subfs::Subfs::new()
976 .r#type(subfs.r#type)
977 .subject(subfs.subject)
978 .build()))
979 }
980- place_wisp::fs::EntryNode::Unknown(unknown) => {
981 SubfsEntryNode::Unknown(unknown)
982 }
983 };
···001mod cid;
2mod blob_map;
3mod metadata;
···26use futures::stream::{self, StreamExt};
27use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
2829+use wisp_lexicons::place_wisp::fs::*;
30+use wisp_lexicons::place_wisp::settings::*;
3132/// Maximum number of concurrent file uploads to the PDS
33const MAX_CONCURRENT_UPLOADS: usize = 2;
···510 let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
511 let chunk_size = subfs_utils::estimate_directory_size(chunk);
512513+ let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
514 .root(convert_fs_dir_to_subfs_dir(chunk.clone()))
515 .file_count(Some(chunk_file_count as i64))
516 .created_at(Datetime::now())
···533 // Each chunk reference MUST have flat: true to merge chunk contents
534 println!(" → Creating parent subfs with {} chunk references...", chunk_uris.len());
535 use jacquard_common::CowStr;
536+ use wisp_lexicons::place_wisp::fs::{Subfs};
537538 // Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
539 let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···563 let parent_tid = Tid::now_0();
564 let parent_rkey = parent_tid.to_string();
565566+ let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
567 .root(parent_root_subfs)
568 .file_count(Some(largest_dir.file_count as i64))
569 .created_at(Datetime::now())
···582 let subfs_tid = Tid::now_0();
583 let subfs_rkey = subfs_tid.to_string();
584585+ let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
586 .root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
587 .file_count(Some(largest_dir.file_count as i64))
588 .created_at(Datetime::now())
···950951/// Convert fs::Directory to subfs::Directory
952/// They have the same structure, but different types
953+fn convert_fs_dir_to_subfs_dir(fs_dir: wisp_lexicons::place_wisp::fs::Directory<'static>) -> wisp_lexicons::place_wisp::subfs::Directory<'static> {
954+ use wisp_lexicons::place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
955956 let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
957 let node = match entry.node {
958+ wisp_lexicons::place_wisp::fs::EntryNode::File(file) => {
959 SubfsEntryNode::File(Box::new(SubfsFile::new()
960 .r#type(file.r#type)
961 .blob(file.blob)
···964 .base64(file.base64)
965 .build()))
966 }
967+ wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => {
968 SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
969 }
970+ wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => {
971 // Nested subfs in the directory we're converting
972 // Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
973+ SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new()
974 .r#type(subfs.r#type)
975 .subject(subfs.subject)
976 .build()))
977 }
978+ wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => {
979 SubfsEntryNode::Unknown(unknown)
980 }
981 };
-9
cli/src/mod.rs
···1-// @generated by jacquard-lexicon. DO NOT EDIT.
2-//
3-// This file was automatically generated from Lexicon schemas.
4-// Any manual changes will be overwritten on the next regeneration.
5-6-pub mod builder_types;
7-8-#[cfg(feature = "place_wisp")]
9-pub mod place_wisp;
···554 }
555 /// State trait tracking which required fields have been set
556 pub trait State: sealed::Sealed {
557- type Name;
558 type Node;
0559 }
560 /// Empty state - all required fields are unset
561 pub struct Empty(());
562 impl sealed::Sealed for Empty {}
563 impl State for Empty {
564- type Name = Unset;
565 type Node = Unset;
566- }
567- ///State transition - sets the `name` field to Set
568- pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
569- impl<S: State> sealed::Sealed for SetName<S> {}
570- impl<S: State> State for SetName<S> {
571- type Name = Set<members::name>;
572- type Node = S::Node;
573 }
574 ///State transition - sets the `node` field to Set
575 pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
576 impl<S: State> sealed::Sealed for SetNode<S> {}
577 impl<S: State> State for SetNode<S> {
578- type Name = S::Name;
579 type Node = Set<members::node>;
00000000580 }
581 /// Marker types for field names
582 #[allow(non_camel_case_types)]
583 pub mod members {
00584 ///Marker type for the `name` field
585 pub struct name(());
586- ///Marker type for the `node` field
587- pub struct node(());
588 }
589}
590···657impl<'a, S> EntryBuilder<'a, S>
658where
659 S: entry_state::State,
660- S::Name: entry_state::IsSet,
661 S::Node: entry_state::IsSet,
0662{
663 /// Build the final struct
664 pub fn build(self) -> Entry<'a> {
···749pub struct File<'a> {
750 /// True if blob content is base64-encoded (used to bypass PDS content sniffing)
751 #[serde(skip_serializing_if = "std::option::Option::is_none")]
752- pub base64: Option<bool>,
753 /// Content blob ref
754 #[serde(borrow)]
755 pub blob: jacquard_common::types::blob::BlobRef<'a>,
756 /// Content encoding (e.g., gzip for compressed files)
757 #[serde(skip_serializing_if = "std::option::Option::is_none")]
758 #[serde(borrow)]
759- pub encoding: Option<jacquard_common::CowStr<'a>>,
760 /// Original MIME type before compression
761 #[serde(skip_serializing_if = "std::option::Option::is_none")]
762 #[serde(borrow)]
763- pub mime_type: Option<jacquard_common::CowStr<'a>>,
764 #[serde(borrow)]
765 pub r#type: jacquard_common::CowStr<'a>,
766}
···994pub struct Fs<'a> {
995 pub created_at: jacquard_common::types::string::Datetime,
996 #[serde(skip_serializing_if = "std::option::Option::is_none")]
997- pub file_count: Option<i64>,
998 #[serde(borrow)]
999 pub root: crate::place_wisp::fs::Directory<'a>,
1000 #[serde(borrow)]
···1011 }
1012 /// State trait tracking which required fields have been set
1013 pub trait State: sealed::Sealed {
01014 type Site;
1015 type Root;
1016- type CreatedAt;
1017 }
1018 /// Empty state - all required fields are unset
1019 pub struct Empty(());
1020 impl sealed::Sealed for Empty {}
1021 impl State for Empty {
01022 type Site = Unset;
1023 type Root = Unset;
1024- type CreatedAt = Unset;
00000001025 }
1026 ///State transition - sets the `site` field to Set
1027 pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
1028 impl<S: State> sealed::Sealed for SetSite<S> {}
1029 impl<S: State> State for SetSite<S> {
01030 type Site = Set<members::site>;
1031 type Root = S::Root;
1032- type CreatedAt = S::CreatedAt;
1033 }
1034 ///State transition - sets the `root` field to Set
1035 pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1036 impl<S: State> sealed::Sealed for SetRoot<S> {}
1037 impl<S: State> State for SetRoot<S> {
1038- type Site = S::Site;
1039- type Root = Set<members::root>;
1040 type CreatedAt = S::CreatedAt;
1041- }
1042- ///State transition - sets the `created_at` field to Set
1043- pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1044- impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1045- impl<S: State> State for SetCreatedAt<S> {
1046 type Site = S::Site;
1047- type Root = S::Root;
1048- type CreatedAt = Set<members::created_at>;
1049 }
1050 /// Marker types for field names
1051 #[allow(non_camel_case_types)]
1052 pub mod members {
001053 ///Marker type for the `site` field
1054 pub struct site(());
1055 ///Marker type for the `root` field
1056 pub struct root(());
1057- ///Marker type for the `created_at` field
1058- pub struct created_at(());
1059 }
1060}
1061···1162impl<'a, S> FsBuilder<'a, S>
1163where
1164 S: fs_state::State,
01165 S::Site: fs_state::IsSet,
1166 S::Root: fs_state::IsSet,
1167- S::CreatedAt: fs_state::IsSet,
1168{
1169 /// Build the final struct
1170 pub fn build(self) -> Fs<'a> {
···1306)]
1307#[serde(rename_all = "camelCase")]
1308pub struct Subfs<'a> {
1309- /// If true, the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false (default), the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure.
1310 #[serde(skip_serializing_if = "std::option::Option::is_none")]
1311- pub flat: Option<bool>,
1312 /// AT-URI pointing to a place.wisp.subfs record containing this subtree.
1313 #[serde(borrow)]
1314 pub subject: jacquard_common::types::string::AtUri<'a>,
···554 }
555 /// State trait tracking which required fields have been set
556 pub trait State: sealed::Sealed {
0557 type Node;
558+ type Name;
559 }
560 /// Empty state - all required fields are unset
561 pub struct Empty(());
562 impl sealed::Sealed for Empty {}
563 impl State for Empty {
0564 type Node = Unset;
565+ type Name = Unset;
000000566 }
567 ///State transition - sets the `node` field to Set
568 pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
569 impl<S: State> sealed::Sealed for SetNode<S> {}
570 impl<S: State> State for SetNode<S> {
0571 type Node = Set<members::node>;
572+ type Name = S::Name;
573+ }
574+ ///State transition - sets the `name` field to Set
575+ pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
576+ impl<S: State> sealed::Sealed for SetName<S> {}
577+ impl<S: State> State for SetName<S> {
578+ type Node = S::Node;
579+ type Name = Set<members::name>;
580 }
581 /// Marker types for field names
582 #[allow(non_camel_case_types)]
583 pub mod members {
584+ ///Marker type for the `node` field
585+ pub struct node(());
586 ///Marker type for the `name` field
587 pub struct name(());
00588 }
589}
590···657impl<'a, S> EntryBuilder<'a, S>
658where
659 S: entry_state::State,
0660 S::Node: entry_state::IsSet,
661+ S::Name: entry_state::IsSet,
662{
663 /// Build the final struct
664 pub fn build(self) -> Entry<'a> {
···749pub struct File<'a> {
750 /// True if blob content is base64-encoded (used to bypass PDS content sniffing)
751 #[serde(skip_serializing_if = "std::option::Option::is_none")]
752+ pub base64: std::option::Option<bool>,
753 /// Content blob ref
754 #[serde(borrow)]
755 pub blob: jacquard_common::types::blob::BlobRef<'a>,
756 /// Content encoding (e.g., gzip for compressed files)
757 #[serde(skip_serializing_if = "std::option::Option::is_none")]
758 #[serde(borrow)]
759+ pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
760 /// Original MIME type before compression
761 #[serde(skip_serializing_if = "std::option::Option::is_none")]
762 #[serde(borrow)]
763+ pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
764 #[serde(borrow)]
765 pub r#type: jacquard_common::CowStr<'a>,
766}
···994pub struct Fs<'a> {
995 pub created_at: jacquard_common::types::string::Datetime,
996 #[serde(skip_serializing_if = "std::option::Option::is_none")]
997+ pub file_count: std::option::Option<i64>,
998 #[serde(borrow)]
999 pub root: crate::place_wisp::fs::Directory<'a>,
1000 #[serde(borrow)]
···1011 }
1012 /// State trait tracking which required fields have been set
1013 pub trait State: sealed::Sealed {
1014+ type CreatedAt;
1015 type Site;
1016 type Root;
01017 }
1018 /// Empty state - all required fields are unset
1019 pub struct Empty(());
1020 impl sealed::Sealed for Empty {}
1021 impl State for Empty {
1022+ type CreatedAt = Unset;
1023 type Site = Unset;
1024 type Root = Unset;
1025+ }
1026+ ///State transition - sets the `created_at` field to Set
1027+ pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1028+ impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1029+ impl<S: State> State for SetCreatedAt<S> {
1030+ type CreatedAt = Set<members::created_at>;
1031+ type Site = S::Site;
1032+ type Root = S::Root;
1033 }
1034 ///State transition - sets the `site` field to Set
1035 pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
1036 impl<S: State> sealed::Sealed for SetSite<S> {}
1037 impl<S: State> State for SetSite<S> {
1038+ type CreatedAt = S::CreatedAt;
1039 type Site = Set<members::site>;
1040 type Root = S::Root;
01041 }
1042 ///State transition - sets the `root` field to Set
1043 pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1044 impl<S: State> sealed::Sealed for SetRoot<S> {}
1045 impl<S: State> State for SetRoot<S> {
001046 type CreatedAt = S::CreatedAt;
000001047 type Site = S::Site;
1048+ type Root = Set<members::root>;
01049 }
1050 /// Marker types for field names
1051 #[allow(non_camel_case_types)]
1052 pub mod members {
1053+ ///Marker type for the `created_at` field
1054+ pub struct created_at(());
1055 ///Marker type for the `site` field
1056 pub struct site(());
1057 ///Marker type for the `root` field
1058 pub struct root(());
001059 }
1060}
1061···1162impl<'a, S> FsBuilder<'a, S>
1163where
1164 S: fs_state::State,
1165+ S::CreatedAt: fs_state::IsSet,
1166 S::Site: fs_state::IsSet,
1167 S::Root: fs_state::IsSet,
01168{
1169 /// Build the final struct
1170 pub fn build(self) -> Fs<'a> {
···1306)]
1307#[serde(rename_all = "camelCase")]
1308pub struct Subfs<'a> {
1309+ /// If true (default), the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false, the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure.
1310 #[serde(skip_serializing_if = "std::option::Option::is_none")]
1311+ pub flat: std::option::Option<bool>,
1312 /// AT-URI pointing to a place.wisp.subfs record containing this subtree.
1313 #[serde(borrow)]
1314 pub subject: jacquard_common::types::string::AtUri<'a>,
···725pub struct File<'a> {
726 /// True if blob content is base64-encoded (used to bypass PDS content sniffing)
727 #[serde(skip_serializing_if = "std::option::Option::is_none")]
728- pub base64: Option<bool>,
729 /// Content blob ref
730 #[serde(borrow)]
731 pub blob: jacquard_common::types::blob::BlobRef<'a>,
732 /// Content encoding (e.g., gzip for compressed files)
733 #[serde(skip_serializing_if = "std::option::Option::is_none")]
734 #[serde(borrow)]
735- pub encoding: Option<jacquard_common::CowStr<'a>>,
736 /// Original MIME type before compression
737 #[serde(skip_serializing_if = "std::option::Option::is_none")]
738 #[serde(borrow)]
739- pub mime_type: Option<jacquard_common::CowStr<'a>>,
740 #[serde(borrow)]
741 pub r#type: jacquard_common::CowStr<'a>,
742}
···751 }
752 /// State trait tracking which required fields have been set
753 pub trait State: sealed::Sealed {
754- type Type;
755 type Blob;
0756 }
757 /// Empty state - all required fields are unset
758 pub struct Empty(());
759 impl sealed::Sealed for Empty {}
760 impl State for Empty {
761- type Type = Unset;
762 type Blob = Unset;
763- }
764- ///State transition - sets the `type` field to Set
765- pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
766- impl<S: State> sealed::Sealed for SetType<S> {}
767- impl<S: State> State for SetType<S> {
768- type Type = Set<members::r#type>;
769- type Blob = S::Blob;
770 }
771 ///State transition - sets the `blob` field to Set
772 pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
773 impl<S: State> sealed::Sealed for SetBlob<S> {}
774 impl<S: State> State for SetBlob<S> {
0775 type Type = S::Type;
776- type Blob = Set<members::blob>;
000000777 }
778 /// Marker types for field names
779 #[allow(non_camel_case_types)]
780 pub mod members {
00781 ///Marker type for the `type` field
782 pub struct r#type(());
783- ///Marker type for the `blob` field
784- pub struct blob(());
785 }
786}
787···905impl<'a, S> FileBuilder<'a, S>
906where
907 S: file_state::State,
908- S::Type: file_state::IsSet,
909 S::Blob: file_state::IsSet,
0910{
911 /// Build the final struct
912 pub fn build(self) -> File<'a> {
···970pub struct SubfsRecord<'a> {
971 pub created_at: jacquard_common::types::string::Datetime,
972 #[serde(skip_serializing_if = "std::option::Option::is_none")]
973- pub file_count: Option<i64>,
974 #[serde(borrow)]
975 pub root: crate::place_wisp::subfs::Directory<'a>,
976}
···1260 }
1261 /// State trait tracking which required fields have been set
1262 pub trait State: sealed::Sealed {
1263- type Type;
1264 type Subject;
01265 }
1266 /// Empty state - all required fields are unset
1267 pub struct Empty(());
1268 impl sealed::Sealed for Empty {}
1269 impl State for Empty {
1270- type Type = Unset;
1271 type Subject = Unset;
1272- }
1273- ///State transition - sets the `type` field to Set
1274- pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1275- impl<S: State> sealed::Sealed for SetType<S> {}
1276- impl<S: State> State for SetType<S> {
1277- type Type = Set<members::r#type>;
1278- type Subject = S::Subject;
1279 }
1280 ///State transition - sets the `subject` field to Set
1281 pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1282 impl<S: State> sealed::Sealed for SetSubject<S> {}
1283 impl<S: State> State for SetSubject<S> {
1284- type Type = S::Type;
1285 type Subject = Set<members::subject>;
000000001286 }
1287 /// Marker types for field names
1288 #[allow(non_camel_case_types)]
1289 pub mod members {
1290- ///Marker type for the `type` field
1291- pub struct r#type(());
1292 ///Marker type for the `subject` field
1293 pub struct subject(());
001294 }
1295}
1296···1363impl<'a, S> SubfsBuilder<'a, S>
1364where
1365 S: subfs_state::State,
1366- S::Type: subfs_state::IsSet,
1367 S::Subject: subfs_state::IsSet,
01368{
1369 /// Build the final struct
1370 pub fn build(self) -> Subfs<'a> {
···725pub struct File<'a> {
726 /// True if blob content is base64-encoded (used to bypass PDS content sniffing)
727 #[serde(skip_serializing_if = "std::option::Option::is_none")]
728+ pub base64: std::option::Option<bool>,
729 /// Content blob ref
730 #[serde(borrow)]
731 pub blob: jacquard_common::types::blob::BlobRef<'a>,
732 /// Content encoding (e.g., gzip for compressed files)
733 #[serde(skip_serializing_if = "std::option::Option::is_none")]
734 #[serde(borrow)]
735+ pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
736 /// Original MIME type before compression
737 #[serde(skip_serializing_if = "std::option::Option::is_none")]
738 #[serde(borrow)]
739+ pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
740 #[serde(borrow)]
741 pub r#type: jacquard_common::CowStr<'a>,
742}
···751 }
752 /// State trait tracking which required fields have been set
753 pub trait State: sealed::Sealed {
0754 type Blob;
755+ type Type;
756 }
757 /// Empty state - all required fields are unset
758 pub struct Empty(());
759 impl sealed::Sealed for Empty {}
760 impl State for Empty {
0761 type Blob = Unset;
762+ type Type = Unset;
000000763 }
764 ///State transition - sets the `blob` field to Set
765 pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
766 impl<S: State> sealed::Sealed for SetBlob<S> {}
767 impl<S: State> State for SetBlob<S> {
768+ type Blob = Set<members::blob>;
769 type Type = S::Type;
770+ }
771+ ///State transition - sets the `type` field to Set
772+ pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
773+ impl<S: State> sealed::Sealed for SetType<S> {}
774+ impl<S: State> State for SetType<S> {
775+ type Blob = S::Blob;
776+ type Type = Set<members::r#type>;
777 }
778 /// Marker types for field names
779 #[allow(non_camel_case_types)]
780 pub mod members {
781+ ///Marker type for the `blob` field
782+ pub struct blob(());
783 ///Marker type for the `type` field
784 pub struct r#type(());
00785 }
786}
787···905impl<'a, S> FileBuilder<'a, S>
906where
907 S: file_state::State,
0908 S::Blob: file_state::IsSet,
909+ S::Type: file_state::IsSet,
910{
911 /// Build the final struct
912 pub fn build(self) -> File<'a> {
···970pub struct SubfsRecord<'a> {
971 pub created_at: jacquard_common::types::string::Datetime,
972 #[serde(skip_serializing_if = "std::option::Option::is_none")]
973+ pub file_count: std::option::Option<i64>,
974 #[serde(borrow)]
975 pub root: crate::place_wisp::subfs::Directory<'a>,
976}
···1260 }
1261 /// State trait tracking which required fields have been set
1262 pub trait State: sealed::Sealed {
01263 type Subject;
1264+ type Type;
1265 }
1266 /// Empty state - all required fields are unset
1267 pub struct Empty(());
1268 impl sealed::Sealed for Empty {}
1269 impl State for Empty {
01270 type Subject = Unset;
1271+ type Type = Unset;
0000001272 }
1273 ///State transition - sets the `subject` field to Set
1274 pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1275 impl<S: State> sealed::Sealed for SetSubject<S> {}
1276 impl<S: State> State for SetSubject<S> {
01277 type Subject = Set<members::subject>;
1278+ type Type = S::Type;
1279+ }
1280+ ///State transition - sets the `type` field to Set
1281+ pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1282+ impl<S: State> sealed::Sealed for SetType<S> {}
1283+ impl<S: State> State for SetType<S> {
1284+ type Subject = S::Subject;
1285+ type Type = Set<members::r#type>;
1286 }
1287 /// Marker types for field names
1288 #[allow(non_camel_case_types)]
1289 pub mod members {
001290 ///Marker type for the `subject` field
1291 pub struct subject(());
1292+ ///Marker type for the `type` field
1293+ pub struct r#type(());
1294 }
1295}
1296···1363impl<'a, S> SubfsBuilder<'a, S>
1364where
1365 S: subfs_state::State,
01366 S::Subject: subfs_state::IsSet,
1367+ S::Type: subfs_state::IsSet,
1368{
1369 /// Build the final struct
1370 pub fn build(self) -> Subfs<'a> {
+12-12
cli/src/pull.rs
···1use crate::blob_map;
2use crate::download;
3use crate::metadata::SiteMetadata;
4-use crate::place_wisp::fs::*;
5use crate::subfs_utils;
6use jacquard::CowStr;
7use jacquard::prelude::IdentityResolver;
···410) -> miette::Result<Directory<'static>> {
411 use jacquard_common::IntoStatic;
412 use jacquard_common::types::value::from_data;
413- use crate::place_wisp::subfs::SubfsRecord;
414415- let mut all_subfs_map: HashMap<String, crate::place_wisp::subfs::Directory> = HashMap::new();
416 let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new());
417418 if to_fetch.is_empty() {
···516517/// Extract subfs URIs from a subfs::Directory (helper for pull)
518fn extract_subfs_uris_from_subfs_dir(
519- directory: &crate::place_wisp::subfs::Directory,
520 current_path: String,
521) -> Vec<(String, String)> {
522 let mut uris = Vec::new();
···529 };
530531 match &entry.node {
532- crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
533 uris.push((subfs_node.subject.to_string(), full_path.clone()));
534 }
535- crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
536 let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path);
537 uris.extend(nested);
538 }
···546/// Recursively replace subfs nodes with their actual content
547fn replace_subfs_with_content(
548 directory: Directory,
549- subfs_map: &HashMap<String, crate::place_wisp::subfs::Directory>,
550 current_path: String,
551) -> Directory<'static> {
552 use jacquard_common::IntoStatic;
···628}
629630/// Convert a subfs entry to a fs entry (they have the same structure but different types)
631-fn convert_subfs_entry_to_fs(subfs_entry: crate::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
632 use jacquard_common::IntoStatic;
633634 let node = match subfs_entry.node {
635- crate::place_wisp::subfs::EntryNode::File(file) => {
636 EntryNode::File(Box::new(
637 File::new()
638 .r#type(file.r#type.into_static())
···643 .build()
644 ))
645 }
646- crate::place_wisp::subfs::EntryNode::Directory(dir) => {
647 let converted_entries: Vec<Entry<'static>> = dir
648 .entries
649 .into_iter()
···657 .build()
658 ))
659 }
660- crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
661 // Nested subfs should have been expanded already - if we get here, it means expansion failed
662 // Treat it like a directory reference that should have been expanded
663 eprintln!(" ⚠️ Warning: unexpanded nested subfs at path, treating as empty directory");
···668 .build()
669 ))
670 }
671- crate::place_wisp::subfs::EntryNode::Unknown(unknown) => {
672 EntryNode::Unknown(unknown)
673 }
674 };
···1use crate::blob_map;
2use crate::download;
3use crate::metadata::SiteMetadata;
4+use wisp_lexicons::place_wisp::fs::*;
5use crate::subfs_utils;
6use jacquard::CowStr;
7use jacquard::prelude::IdentityResolver;
···410) -> miette::Result<Directory<'static>> {
411 use jacquard_common::IntoStatic;
412 use jacquard_common::types::value::from_data;
413+ use wisp_lexicons::place_wisp::subfs::SubfsRecord;
414415+ let mut all_subfs_map: HashMap<String, wisp_lexicons::place_wisp::subfs::Directory> = HashMap::new();
416 let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new());
417418 if to_fetch.is_empty() {
···516517/// Extract subfs URIs from a subfs::Directory (helper for pull)
518fn extract_subfs_uris_from_subfs_dir(
519+ directory: &wisp_lexicons::place_wisp::subfs::Directory,
520 current_path: String,
521) -> Vec<(String, String)> {
522 let mut uris = Vec::new();
···529 };
530531 match &entry.node {
532+ wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
533 uris.push((subfs_node.subject.to_string(), full_path.clone()));
534 }
535+ wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
536 let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path);
537 uris.extend(nested);
538 }
···546/// Recursively replace subfs nodes with their actual content
547fn replace_subfs_with_content(
548 directory: Directory,
549+ subfs_map: &HashMap<String, wisp_lexicons::place_wisp::subfs::Directory>,
550 current_path: String,
551) -> Directory<'static> {
552 use jacquard_common::IntoStatic;
···628}
629630/// Convert a subfs entry to a fs entry (they have the same structure but different types)
631+fn convert_subfs_entry_to_fs(subfs_entry: wisp_lexicons::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
632 use jacquard_common::IntoStatic;
633634 let node = match subfs_entry.node {
635+ wisp_lexicons::place_wisp::subfs::EntryNode::File(file) => {
636 EntryNode::File(Box::new(
637 File::new()
638 .r#type(file.r#type.into_static())
···643 .build()
644 ))
645 }
646+ wisp_lexicons::place_wisp::subfs::EntryNode::Directory(dir) => {
647 let converted_entries: Vec<Entry<'static>> = dir
648 .entries
649 .into_iter()
···657 .build()
658 ))
659 }
660+ wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
661 // Nested subfs should have been expanded already - if we get here, it means expansion failed
662 // Treat it like a directory reference that should have been expanded
663 eprintln!(" ⚠️ Warning: unexpanded nested subfs at path, treating as empty directory");
···668 .build()
669 ))
670 }
671+ wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(unknown) => {
672 EntryNode::Unknown(unknown)
673 }
674 };
···9 type MethodConfigOrHandler,
10 createServer as createXrpcServer,
11} from '@atproto/xrpc-server'
12-import { schemas } from './lexicons'
1314export function createServer(options?: XrpcOptions): Server {
15 return new Server(options)
···9 type MethodConfigOrHandler,
10 createServer as createXrpcServer,
11} from '@atproto/xrpc-server'
12+import { schemas } from './lexicons.js'
1314export function createServer(options?: XrpcOptions): Server {
15 return new Server(options)
+1-1
packages/@wisp/lexicons/src/lexicons.ts
···7 ValidationError,
8 type ValidationResult,
9} from '@atproto/lexicon'
10-import { type $Typed, is$typed, maybe$typed } from './util'
1112export const schemaDict = {
13 PlaceWispFs: {
···7 ValidationError,
8 type ValidationResult,
9} from '@atproto/lexicon'
10+import { type $Typed, is$typed, maybe$typed } from './util.js'
1112export const schemaDict = {
13 PlaceWispFs: {