+1
.gitignore
+1
.gitignore
+2
.tangled/workflows/deploy-wisp.yml
+2
.tangled/workflows/deploy-wisp.yml
+2
-1
.tangled/workflows/test.yml
+2
-1
.tangled/workflows/test.yml
···
7
dependencies:
8
nixpkgs:
9
- git
10
github:NixOS/nixpkgs/nixpkgs-unstable:
11
- bun
12
···
16
export PATH="$HOME/.nix-profile/bin:$PATH"
17
18
# have to regenerate otherwise it wont install necessary dependencies to run
19
-
rm -rf bun.lock package-lock.json
20
bun install
21
22
- name: run all tests
···
7
dependencies:
8
nixpkgs:
9
- git
10
+
- findutils
11
github:NixOS/nixpkgs/nixpkgs-unstable:
12
- bun
13
···
17
export PATH="$HOME/.nix-profile/bin:$PATH"
18
19
# have to regenerate otherwise it wont install necessary dependencies to run
20
+
find . -type f \( -name "bun.lock" -o -name "package-lock.json" \) -delete
21
bun install
22
23
- name: run all tests
+9
-8
apps/hosting-service/package.json
+9
-8
apps/hosting-service/package.json
···
10
"backfill": "tsx src/index.ts --backfill"
11
},
12
"dependencies": {
13
-
"@wisp/lexicons": "workspace:*",
14
-
"@wisp/constants": "workspace:*",
15
-
"@wisp/observability": "workspace:*",
16
-
"@wisp/atproto-utils": "workspace:*",
17
-
"@wisp/database": "workspace:*",
18
-
"@wisp/fs-utils": "workspace:*",
19
-
"@wisp/safe-fetch": "workspace:*",
20
"@atproto/api": "^0.17.4",
21
"@atproto/identity": "^0.4.9",
22
"@atproto/lexicon": "^0.5.2",
23
"@atproto/sync": "^0.1.36",
24
"@atproto/xrpc": "^0.7.5",
25
"@hono/node-server": "^1.19.6",
26
"hono": "^4.10.4",
27
"mime-types": "^2.1.35",
28
"multiformats": "^13.4.1",
29
-
"postgres": "^3.4.5"
30
},
31
"devDependencies": {
32
"@types/bun": "^1.3.1",
···
10
"backfill": "tsx src/index.ts --backfill"
11
},
12
"dependencies": {
13
"@atproto/api": "^0.17.4",
14
"@atproto/identity": "^0.4.9",
15
"@atproto/lexicon": "^0.5.2",
16
"@atproto/sync": "^0.1.36",
17
"@atproto/xrpc": "^0.7.5",
18
"@hono/node-server": "^1.19.6",
19
+
"@wisp/atproto-utils": "workspace:*",
20
+
"@wisp/constants": "workspace:*",
21
+
"@wisp/database": "workspace:*",
22
+
"@wisp/fs-utils": "workspace:*",
23
+
"@wisp/lexicons": "workspace:*",
24
+
"@wisp/observability": "workspace:*",
25
+
"@wisp/safe-fetch": "workspace:*",
26
"hono": "^4.10.4",
27
"mime-types": "^2.1.35",
28
"multiformats": "^13.4.1",
29
+
"postgres": "^3.4.5",
30
+
"tiered-storage": "1.0.3"
31
},
32
"devDependencies": {
33
"@types/bun": "^1.3.1",
+39
-5
apps/hosting-service/src/index.ts
+39
-5
apps/hosting-service/src/index.ts
···
4
import { createLogger, initializeGrafanaExporters } from '@wisp/observability';
5
import { mkdirSync, existsSync } from 'fs';
6
import { backfillCache } from './lib/backfill';
7
-
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode } from './lib/db';
8
9
// Initialize Grafana exporters if configured
10
initializeGrafanaExporters({
···
16
17
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
18
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
19
20
// Parse CLI arguments
21
const args = process.argv.slice(2);
···
47
48
firehose.start();
49
50
// Run backfill if requested
51
if (backfillOnStartup) {
52
console.log('๐ Backfill requested, starting cache backfill...');
53
backfillCache({
54
skipExisting: true,
55
-
concurrency: 3,
56
}).then((stats) => {
57
console.log('โ
Cache backfill completed');
58
}).catch((err) => {
···
75
port: PORT,
76
});
77
78
console.log(`
79
-
Wisp Hosting Service
80
81
Server: http://localhost:${PORT}
82
Health: http://localhost:${PORT}/health
83
-
Cache: ${CACHE_DIR}
84
-
Firehose: Connected to Firehose
85
Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'}
86
`);
87
88
// Graceful shutdown
···
90
console.log('\n๐ Shutting down...');
91
firehose.stop();
92
stopDomainCacheCleanup();
93
server.close();
94
process.exit(0);
95
});
···
98
console.log('\n๐ Shutting down...');
99
firehose.stop();
100
stopDomainCacheCleanup();
101
server.close();
102
process.exit(0);
103
});
···
4
import { createLogger, initializeGrafanaExporters } from '@wisp/observability';
5
import { mkdirSync, existsSync } from 'fs';
6
import { backfillCache } from './lib/backfill';
7
+
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode, closeDatabase } from './lib/db';
8
+
import { storage, getStorageConfig } from './lib/storage';
9
10
// Initialize Grafana exporters if configured
11
initializeGrafanaExporters({
···
17
18
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
19
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
20
+
const BACKFILL_CONCURRENCY = process.env.BACKFILL_CONCURRENCY
21
+
? parseInt(process.env.BACKFILL_CONCURRENCY)
22
+
: undefined; // Let backfill.ts default (10) apply
23
24
// Parse CLI arguments
25
const args = process.argv.slice(2);
···
51
52
firehose.start();
53
54
+
// Optional: Bootstrap hot cache from warm tier on startup
55
+
const BOOTSTRAP_HOT_ON_STARTUP = process.env.BOOTSTRAP_HOT_ON_STARTUP === 'true';
56
+
const BOOTSTRAP_HOT_LIMIT = process.env.BOOTSTRAP_HOT_LIMIT ? parseInt(process.env.BOOTSTRAP_HOT_LIMIT) : 100;
57
+
58
+
if (BOOTSTRAP_HOT_ON_STARTUP) {
59
+
console.log(`๐ฅ Bootstrapping hot cache (top ${BOOTSTRAP_HOT_LIMIT} items)...`);
60
+
storage.bootstrapHot(BOOTSTRAP_HOT_LIMIT)
61
+
.then((loaded: number) => {
62
+
console.log(`โ
Bootstrapped ${loaded} items into hot cache`);
63
+
})
64
+
.catch((err: unknown) => {
65
+
console.error('โ Hot cache bootstrap error:', err);
66
+
});
67
+
}
68
+
69
// Run backfill if requested
70
if (backfillOnStartup) {
71
console.log('๐ Backfill requested, starting cache backfill...');
72
backfillCache({
73
skipExisting: true,
74
+
concurrency: BACKFILL_CONCURRENCY,
75
}).then((stats) => {
76
console.log('โ
Cache backfill completed');
77
}).catch((err) => {
···
94
port: PORT,
95
});
96
97
+
// Get storage configuration for display
98
+
const storageConfig = getStorageConfig();
99
+
100
console.log(`
101
+
Wisp Hosting Service with Tiered Storage
102
103
Server: http://localhost:${PORT}
104
Health: http://localhost:${PORT}/health
105
Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'}
106
+
Backfill: ${backfillOnStartup ? `ENABLED (concurrency: ${BACKFILL_CONCURRENCY || 10})` : 'DISABLED'}
107
+
108
+
Tiered Storage Configuration:
109
+
Hot Cache: ${storageConfig.hotCacheSize} (${storageConfig.hotCacheCount} items max)
110
+
Warm Cache: ${storageConfig.warmCacheSize} (${storageConfig.warmEvictionPolicy} eviction)
111
+
Cold Storage: S3 - ${storageConfig.s3Bucket}
112
+
S3 Region: ${storageConfig.s3Region}
113
+
S3 Endpoint: ${storageConfig.s3Endpoint}
114
+
S3 Prefix: ${storageConfig.s3Prefix}
115
+
Metadata Bucket: ${storageConfig.metadataBucket}
116
+
117
+
Firehose: Connecting...
118
`);
119
120
// Graceful shutdown
···
122
console.log('\n๐ Shutting down...');
123
firehose.stop();
124
stopDomainCacheCleanup();
125
+
await closeDatabase();
126
server.close();
127
process.exit(0);
128
});
···
131
console.log('\n๐ Shutting down...');
132
firehose.stop();
133
stopDomainCacheCleanup();
134
+
await closeDatabase();
135
server.close();
136
process.exit(0);
137
});
+65
-57
apps/hosting-service/src/lib/backfill.ts
+65
-57
apps/hosting-service/src/lib/backfill.ts
···
60
console.log(`โ๏ธ Limited to ${maxSites} sites for backfill`);
61
}
62
63
-
// Process sites in batches
64
-
const batches: typeof sites[] = [];
65
-
for (let i = 0; i < sites.length; i += concurrency) {
66
-
batches.push(sites.slice(i, i + concurrency));
67
-
}
68
-
69
let processed = 0;
70
-
for (const batch of batches) {
71
-
await Promise.all(
72
-
batch.map(async (site) => {
73
-
try {
74
-
// Check if already cached
75
-
if (skipExisting && isCached(site.did, site.rkey)) {
76
-
stats.skipped++;
77
-
processed++;
78
-
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
79
-
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
80
-
return;
81
-
}
82
83
-
// Fetch site record
84
-
const siteData = await fetchSiteRecord(site.did, site.rkey);
85
-
if (!siteData) {
86
-
stats.failed++;
87
-
processed++;
88
-
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
89
-
console.log(`โ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
90
-
return;
91
-
}
92
-
93
-
// Get PDS endpoint
94
-
const pdsEndpoint = await getPdsForDid(site.did);
95
-
if (!pdsEndpoint) {
96
-
stats.failed++;
97
-
processed++;
98
-
logger.error('PDS not found during backfill', null, { did: site.did });
99
-
console.log(`โ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
100
-
return;
101
-
}
102
103
-
// Mark site as being cached to prevent serving stale content during update
104
-
markSiteAsBeingCached(site.did, site.rkey);
105
106
-
try {
107
-
// Download and cache site
108
-
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
109
-
// Clear redirect rules cache since the site was updated
110
-
clearRedirectRulesCache(site.did, site.rkey);
111
-
stats.cached++;
112
-
processed++;
113
-
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
114
-
console.log(`โ
[${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
115
-
} finally {
116
-
// Always unmark, even if caching fails
117
-
unmarkSiteAsBeingCached(site.did, site.rkey);
118
-
}
119
-
} catch (err) {
120
stats.failed++;
121
processed++;
122
-
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
123
-
console.log(`โ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
124
}
125
-
})
126
-
);
127
}
128
129
stats.duration = Date.now() - startTime;
130
···
60
console.log(`โ๏ธ Limited to ${maxSites} sites for backfill`);
61
}
62
63
+
// Process sites with sliding window concurrency pool
64
+
const executing = new Set<Promise<void>>();
65
let processed = 0;
66
67
+
for (const site of sites) {
68
+
// Create task for this site
69
+
const processSite = async () => {
70
+
try {
71
+
// Check if already cached
72
+
if (skipExisting && await isCached(site.did, site.rkey)) {
73
+
stats.skipped++;
74
+
processed++;
75
+
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
76
+
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
77
+
return;
78
+
}
79
80
+
// Fetch site record
81
+
const siteData = await fetchSiteRecord(site.did, site.rkey);
82
+
if (!siteData) {
83
+
stats.failed++;
84
+
processed++;
85
+
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
86
+
console.log(`โ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
87
+
return;
88
+
}
89
90
+
// Get PDS endpoint
91
+
const pdsEndpoint = await getPdsForDid(site.did);
92
+
if (!pdsEndpoint) {
93
stats.failed++;
94
processed++;
95
+
logger.error('PDS not found during backfill', null, { did: site.did });
96
+
console.log(`โ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
97
+
return;
98
+
}
99
+
100
+
// Mark site as being cached to prevent serving stale content during update
101
+
markSiteAsBeingCached(site.did, site.rkey);
102
+
103
+
try {
104
+
// Download and cache site
105
+
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
106
+
// Clear redirect rules cache since the site was updated
107
+
clearRedirectRulesCache(site.did, site.rkey);
108
+
stats.cached++;
109
+
processed++;
110
+
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
111
+
console.log(`โ
[${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
112
+
} finally {
113
+
// Always unmark, even if caching fails
114
+
unmarkSiteAsBeingCached(site.did, site.rkey);
115
}
116
+
} catch (err) {
117
+
stats.failed++;
118
+
processed++;
119
+
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
120
+
console.log(`โ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
121
+
}
122
+
};
123
+
124
+
// Add to executing pool and remove when done
125
+
const promise = processSite().finally(() => executing.delete(promise));
126
+
executing.add(promise);
127
+
128
+
// When pool is full, wait for at least one to complete
129
+
if (executing.size >= concurrency) {
130
+
await Promise.race(executing);
131
+
}
132
}
133
+
134
+
// Wait for all remaining tasks to complete
135
+
await Promise.all(executing);
136
137
stats.duration = Date.now() - startTime;
138
+35
-43
apps/hosting-service/src/lib/cache.ts
+35
-43
apps/hosting-service/src/lib/cache.ts
···
1
-
// In-memory LRU cache for file contents and metadata
2
3
interface CacheEntry<T> {
4
value: T;
5
size: number;
···
96
return true;
97
}
98
99
-
// Invalidate all entries for a specific site
100
-
invalidateSite(did: string, rkey: string): number {
101
-
const prefix = `${did}:${rkey}:`;
102
-
let count = 0;
103
-
104
-
for (const key of Array.from(this.cache.keys())) {
105
-
if (key.startsWith(prefix)) {
106
-
this.delete(key);
107
-
count++;
108
-
}
109
-
}
110
-
111
-
return count;
112
-
}
113
-
114
-
// Get cache size
115
size(): number {
116
return this.cache.size;
117
}
···
127
return { ...this.stats };
128
}
129
130
-
// Get cache hit rate
131
getHitRate(): number {
132
const total = this.stats.hits + this.stats.misses;
133
return total === 0 ? 0 : (this.stats.hits / total) * 100;
134
}
135
}
136
137
-
// File metadata cache entry
138
-
export interface FileMetadata {
139
-
encoding?: 'gzip';
140
-
mimeType: string;
141
-
}
142
-
143
-
// Global cache instances
144
-
const FILE_CACHE_SIZE = 100 * 1024 * 1024; // 100MB
145
-
const FILE_CACHE_COUNT = 500;
146
-
const METADATA_CACHE_COUNT = 2000;
147
-
148
-
export const fileCache = new LRUCache<Buffer>(FILE_CACHE_SIZE, FILE_CACHE_COUNT);
149
-
export const metadataCache = new LRUCache<FileMetadata>(1024 * 1024, METADATA_CACHE_COUNT); // 1MB for metadata
150
export const rewrittenHtmlCache = new LRUCache<Buffer>(50 * 1024 * 1024, 200); // 50MB for rewritten HTML
151
152
-
// Helper to generate cache keys
153
export function getCacheKey(did: string, rkey: string, filePath: string, suffix?: string): string {
154
const base = `${did}:${rkey}:${filePath}`;
155
return suffix ? `${base}:${suffix}` : base;
156
}
157
158
-
// Invalidate all caches for a site
159
-
export function invalidateSiteCache(did: string, rkey: string): void {
160
-
const fileCount = fileCache.invalidateSite(did, rkey);
161
-
const metaCount = metadataCache.invalidateSite(did, rkey);
162
-
const htmlCount = rewrittenHtmlCache.invalidateSite(did, rkey);
163
164
-
console.log(`[Cache] Invalidated site ${did}:${rkey} - ${fileCount} files, ${metaCount} metadata, ${htmlCount} HTML`);
165
}
166
167
// Track sites currently being cached (to prevent serving stale cache during updates)
···
183
}
184
185
// Get overall cache statistics
186
-
export function getCacheStats() {
187
return {
188
-
files: fileCache.getStats(),
189
-
fileHitRate: fileCache.getHitRate(),
190
-
metadata: metadataCache.getStats(),
191
-
metadataHitRate: metadataCache.getHitRate(),
192
rewrittenHtml: rewrittenHtmlCache.getStats(),
193
rewrittenHtmlHitRate: rewrittenHtmlCache.getHitRate(),
194
sitesBeingCached: sitesBeingCached.size,
···
1
+
/**
2
+
* Cache management for wisp-hosting-service
3
+
*
4
+
* With tiered storage, most caching is handled transparently.
5
+
* This module tracks sites being cached and manages rewritten HTML cache.
6
+
*/
7
8
+
import { storage } from './storage';
9
+
10
+
// In-memory LRU cache for rewritten HTML (for path rewriting in subdomain routes)
11
interface CacheEntry<T> {
12
value: T;
13
size: number;
···
104
return true;
105
}
106
107
size(): number {
108
return this.cache.size;
109
}
···
119
return { ...this.stats };
120
}
121
122
getHitRate(): number {
123
const total = this.stats.hits + this.stats.misses;
124
return total === 0 ? 0 : (this.stats.hits / total) * 100;
125
}
126
}
127
128
+
// Rewritten HTML cache: stores HTML after path rewriting for subdomain routes
129
export const rewrittenHtmlCache = new LRUCache<Buffer>(50 * 1024 * 1024, 200); // 50MB for rewritten HTML
130
131
+
// Helper to generate cache keys for rewritten HTML
132
export function getCacheKey(did: string, rkey: string, filePath: string, suffix?: string): string {
133
const base = `${did}:${rkey}:${filePath}`;
134
return suffix ? `${base}:${suffix}` : base;
135
}
136
137
+
/**
138
+
* Invalidate site cache via tiered storage
139
+
* Also invalidates locally cached rewritten HTML
140
+
*/
141
+
export async function invalidateSiteCache(did: string, rkey: string): Promise<void> {
142
+
// Invalidate in tiered storage
143
+
const prefix = `${did}/${rkey}/`;
144
+
const deleted = await storage.invalidate(prefix);
145
146
+
// Invalidate rewritten HTML cache for this site
147
+
const sitePrefix = `${did}:${rkey}:`;
148
+
let htmlCount = 0;
149
+
const cacheKeys = Array.from((rewrittenHtmlCache as any).cache?.keys() || []) as string[];
150
+
for (const key of cacheKeys) {
151
+
if (key.startsWith(sitePrefix)) {
152
+
rewrittenHtmlCache.delete(key);
153
+
htmlCount++;
154
+
}
155
+
}
156
+
157
+
console.log(`[Cache] Invalidated site ${did}:${rkey} - ${deleted} files in tiered storage, ${htmlCount} rewritten HTML`);
158
}
159
160
// Track sites currently being cached (to prevent serving stale cache during updates)
···
176
}
177
178
// Get overall cache statistics
179
+
export async function getCacheStats() {
180
+
const tieredStats = await storage.getStats();
181
+
182
return {
183
+
tieredStorage: tieredStats,
184
rewrittenHtml: rewrittenHtmlCache.getStats(),
185
rewrittenHtmlHitRate: rewrittenHtmlCache.getHitRate(),
186
sitesBeingCached: sitesBeingCached.size,
+32
-1
apps/hosting-service/src/lib/db.ts
+32
-1
apps/hosting-service/src/lib/db.ts
···
183
return hashNum & 0x7FFFFFFFFFFFFFFFn;
184
}
185
186
/**
187
* Acquire a distributed lock using PostgreSQL advisory locks
188
* Returns true if lock was acquired, false if already held by another instance
···
193
194
try {
195
const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
196
-
return result[0]?.acquired === true;
197
} catch (err) {
198
console.error('Failed to acquire lock', { key, error: err });
199
return false;
···
208
209
try {
210
await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
211
} catch (err) {
212
console.error('Failed to release lock', { key, error: err });
213
}
214
}
215
···
183
return hashNum & 0x7FFFFFFFFFFFFFFFn;
184
}
185
186
+
// Track active locks for cleanup on shutdown
187
+
const activeLocks = new Set<string>();
188
+
189
/**
190
* Acquire a distributed lock using PostgreSQL advisory locks
191
* Returns true if lock was acquired, false if already held by another instance
···
196
197
try {
198
const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
199
+
const acquired = result[0]?.acquired === true;
200
+
if (acquired) {
201
+
activeLocks.add(key);
202
+
}
203
+
return acquired;
204
} catch (err) {
205
console.error('Failed to acquire lock', { key, error: err });
206
return false;
···
215
216
try {
217
await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
218
+
activeLocks.delete(key);
219
} catch (err) {
220
console.error('Failed to release lock', { key, error: err });
221
+
// Still remove from tracking even if unlock fails
222
+
activeLocks.delete(key);
223
+
}
224
+
}
225
+
226
+
/**
227
+
* Close all database connections
228
+
* Call this during graceful shutdown
229
+
*/
230
+
export async function closeDatabase(): Promise<void> {
231
+
try {
232
+
// Release all active advisory locks before closing connections
233
+
if (activeLocks.size > 0) {
234
+
console.log(`[DB] Releasing ${activeLocks.size} active advisory locks before shutdown`);
235
+
for (const key of activeLocks) {
236
+
await releaseLock(key);
237
+
}
238
+
}
239
+
240
+
await sql.end({ timeout: 5 });
241
+
console.log('[DB] Database connections closed');
242
+
} catch (err) {
243
+
console.error('[DB] Error closing database connections:', err);
244
}
245
}
246
+64
-76
apps/hosting-service/src/lib/file-serving.ts
+64
-76
apps/hosting-service/src/lib/file-serving.ts
···
7
import { lookup } from 'mime-types';
8
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
9
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
10
-
import { fileCache, metadataCache, rewrittenHtmlCache, getCacheKey, isSiteBeingCached } from './cache';
11
import { getCachedFilePath, getCachedSettings } from './utils';
12
import { loadRedirectRules, matchRedirectRule, parseCookies, parseQueryString } from './redirects';
13
import { rewriteHtmlPaths, isHtmlContent } from './html-rewriter';
14
import { generate404Page, generateDirectoryListing, siteUpdatingResponse } from './page-generators';
15
import { getIndexFiles, applyCustomHeaders, fileExists } from './request-utils';
16
import { getRedirectRulesFromCache, setRedirectRulesInCache } from './site-cache';
17
18
/**
19
* Helper to serve files from cache (for custom domains and subdomains)
···
176
177
// Not a directory, try to serve as a file
178
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
179
-
const cacheKey = getCacheKey(did, rkey, fileRequestPath);
180
-
const cachedFile = getCachedFilePath(did, rkey, fileRequestPath);
181
182
-
// Check in-memory cache first
183
-
let content = fileCache.get(cacheKey);
184
-
let meta = metadataCache.get(cacheKey);
185
186
-
if (!content && await fileExists(cachedFile)) {
187
-
// Read from disk and cache
188
-
content = await readFile(cachedFile);
189
-
fileCache.set(cacheKey, content, content.length);
190
191
-
const metaFile = `${cachedFile}.meta`;
192
-
if (await fileExists(metaFile)) {
193
-
const metaJson = await readFile(metaFile, 'utf-8');
194
-
meta = JSON.parse(metaJson);
195
-
metadataCache.set(cacheKey, meta!, JSON.stringify(meta).length);
196
-
}
197
-
}
198
-
199
-
if (content) {
200
// Build headers with caching
201
-
const headers: Record<string, string> = {};
202
203
-
if (meta && meta.encoding === 'gzip' && meta.mimeType) {
204
const shouldServeCompressed = shouldCompressMimeType(meta.mimeType);
205
206
if (!shouldServeCompressed) {
···
233
}
234
235
// Non-compressed files
236
-
const mimeType = lookup(cachedFile) || 'application/octet-stream';
237
headers['Content-Type'] = mimeType;
238
headers['Cache-Control'] = mimeType.startsWith('text/html')
239
? 'public, max-age=300'
···
246
if (!fileRequestPath.includes('.')) {
247
for (const indexFileName of indexFiles) {
248
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
249
-
const indexCacheKey = getCacheKey(did, rkey, indexPath);
250
-
const indexFile = getCachedFilePath(did, rkey, indexPath);
251
252
-
let indexContent = fileCache.get(indexCacheKey);
253
-
let indexMeta = metadataCache.get(indexCacheKey);
254
255
-
if (!indexContent && await fileExists(indexFile)) {
256
-
indexContent = await readFile(indexFile);
257
-
fileCache.set(indexCacheKey, indexContent, indexContent.length);
258
259
-
const indexMetaFile = `${indexFile}.meta`;
260
-
if (await fileExists(indexMetaFile)) {
261
-
const metaJson = await readFile(indexMetaFile, 'utf-8');
262
-
indexMeta = JSON.parse(metaJson);
263
-
metadataCache.set(indexCacheKey, indexMeta!, JSON.stringify(indexMeta).length);
264
-
}
265
-
}
266
-
267
-
if (indexContent) {
268
const headers: Record<string, string> = {
269
'Content-Type': 'text/html; charset=utf-8',
270
'Cache-Control': 'public, max-age=300',
271
};
272
273
-
if (indexMeta && indexMeta.encoding === 'gzip') {
274
headers['Content-Encoding'] = 'gzip';
275
}
276
···
556
557
// Not a directory, try to serve as a file
558
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
559
-
const cacheKey = getCacheKey(did, rkey, fileRequestPath);
560
-
const cachedFile = getCachedFilePath(did, rkey, fileRequestPath);
561
562
// Check for rewritten HTML in cache first (if it's HTML)
563
const mimeTypeGuess = lookup(fileRequestPath) || 'application/octet-stream';
···
565
const rewrittenKey = getCacheKey(did, rkey, fileRequestPath, `rewritten:${basePath}`);
566
const rewrittenContent = rewrittenHtmlCache.get(rewrittenKey);
567
if (rewrittenContent) {
568
const headers: Record<string, string> = {
569
'Content-Type': 'text/html; charset=utf-8',
570
'Content-Encoding': 'gzip',
571
'Cache-Control': 'public, max-age=300',
572
};
573
applyCustomHeaders(headers, fileRequestPath, settings);
574
return new Response(rewrittenContent, { headers });
575
}
576
}
577
578
-
// Check in-memory file cache
579
-
let content = fileCache.get(cacheKey);
580
-
let meta = metadataCache.get(cacheKey);
581
582
-
if (!content && await fileExists(cachedFile)) {
583
-
// Read from disk and cache
584
-
content = await readFile(cachedFile);
585
-
fileCache.set(cacheKey, content, content.length);
586
587
-
const metaFile = `${cachedFile}.meta`;
588
-
if (await fileExists(metaFile)) {
589
-
const metaJson = await readFile(metaFile, 'utf-8');
590
-
meta = JSON.parse(metaJson);
591
-
metadataCache.set(cacheKey, meta!, JSON.stringify(meta).length);
592
-
}
593
-
}
594
-
595
-
if (content) {
596
-
const mimeType = meta?.mimeType || lookup(cachedFile) || 'application/octet-stream';
597
-
const isGzipped = meta?.encoding === 'gzip';
598
599
// Check if this is HTML content that needs rewriting
600
if (isHtmlContent(fileRequestPath, mimeType)) {
601
let htmlContent: string;
602
if (isGzipped) {
603
// Verify content is actually gzipped
···
612
} else {
613
htmlContent = content.toString('utf-8');
614
}
615
const rewritten = rewriteHtmlPaths(htmlContent, basePath, fileRequestPath);
616
617
// Recompress and cache the rewritten HTML
618
const { gzipSync } = await import('zlib');
619
const recompressed = gzipSync(Buffer.from(rewritten, 'utf-8'));
···
625
'Content-Type': 'text/html; charset=utf-8',
626
'Content-Encoding': 'gzip',
627
'Cache-Control': 'public, max-age=300',
628
};
629
applyCustomHeaders(htmlHeaders, fileRequestPath, settings);
630
return new Response(recompressed, { headers: htmlHeaders });
···
634
const headers: Record<string, string> = {
635
'Content-Type': mimeType,
636
'Cache-Control': 'public, max-age=31536000, immutable',
637
};
638
639
if (isGzipped) {
···
663
if (!fileRequestPath.includes('.')) {
664
for (const indexFileName of indexFiles) {
665
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
666
-
const indexCacheKey = getCacheKey(did, rkey, indexPath);
667
-
const indexFile = getCachedFilePath(did, rkey, indexPath);
668
669
// Check for rewritten index file in cache
670
const rewrittenKey = getCacheKey(did, rkey, indexPath, `rewritten:${basePath}`);
···
674
'Content-Type': 'text/html; charset=utf-8',
675
'Content-Encoding': 'gzip',
676
'Cache-Control': 'public, max-age=300',
677
};
678
applyCustomHeaders(headers, indexPath, settings);
679
return new Response(rewrittenContent, { headers });
680
}
681
682
-
let indexContent = fileCache.get(indexCacheKey);
683
-
let indexMeta = metadataCache.get(indexCacheKey);
684
685
-
if (!indexContent && await fileExists(indexFile)) {
686
-
indexContent = await readFile(indexFile);
687
-
fileCache.set(indexCacheKey, indexContent, indexContent.length);
688
-
689
-
const indexMetaFile = `${indexFile}.meta`;
690
-
if (await fileExists(indexMetaFile)) {
691
-
const metaJson = await readFile(indexMetaFile, 'utf-8');
692
-
indexMeta = JSON.parse(metaJson);
693
-
metadataCache.set(indexCacheKey, indexMeta!, JSON.stringify(indexMeta).length);
694
-
}
695
-
}
696
-
697
-
if (indexContent) {
698
const isGzipped = indexMeta?.encoding === 'gzip';
699
700
let htmlContent: string;
···
722
'Content-Type': 'text/html; charset=utf-8',
723
'Content-Encoding': 'gzip',
724
'Cache-Control': 'public, max-age=300',
725
};
726
applyCustomHeaders(headers, indexPath, settings);
727
return new Response(recompressed, { headers });
···
7
import { lookup } from 'mime-types';
8
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
9
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
10
+
import { rewrittenHtmlCache, getCacheKey, isSiteBeingCached } from './cache';
11
import { getCachedFilePath, getCachedSettings } from './utils';
12
import { loadRedirectRules, matchRedirectRule, parseCookies, parseQueryString } from './redirects';
13
import { rewriteHtmlPaths, isHtmlContent } from './html-rewriter';
14
import { generate404Page, generateDirectoryListing, siteUpdatingResponse } from './page-generators';
15
import { getIndexFiles, applyCustomHeaders, fileExists } from './request-utils';
16
import { getRedirectRulesFromCache, setRedirectRulesInCache } from './site-cache';
17
+
import { storage } from './storage';
18
+
19
+
/**
20
+
* Helper to retrieve a file with metadata from tiered storage
21
+
*/
22
+
async function getFileWithMetadata(did: string, rkey: string, filePath: string) {
23
+
const key = `${did}/${rkey}/${filePath}`;
24
+
return await storage.getWithMetadata(key);
25
+
}
26
27
/**
28
* Helper to serve files from cache (for custom domains and subdomains)
···
185
186
// Not a directory, try to serve as a file
187
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
188
189
+
// Retrieve from tiered storage
190
+
const result = await getFileWithMetadata(did, rkey, fileRequestPath);
191
192
+
if (result) {
193
+
const content = Buffer.from(result.data);
194
+
const meta = result.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
195
196
// Build headers with caching
197
+
const headers: Record<string, string> = {
198
+
'X-Cache-Tier': result.source,
199
+
};
200
201
+
if (meta?.encoding === 'gzip' && meta.mimeType) {
202
const shouldServeCompressed = shouldCompressMimeType(meta.mimeType);
203
204
if (!shouldServeCompressed) {
···
231
}
232
233
// Non-compressed files
234
+
const mimeType = meta?.mimeType || lookup(fileRequestPath) || 'application/octet-stream';
235
headers['Content-Type'] = mimeType;
236
headers['Cache-Control'] = mimeType.startsWith('text/html')
237
? 'public, max-age=300'
···
244
if (!fileRequestPath.includes('.')) {
245
for (const indexFileName of indexFiles) {
246
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
247
248
+
const indexResult = await getFileWithMetadata(did, rkey, indexPath);
249
250
+
if (indexResult) {
251
+
const indexContent = Buffer.from(indexResult.data);
252
+
const indexMeta = indexResult.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
253
254
const headers: Record<string, string> = {
255
'Content-Type': 'text/html; charset=utf-8',
256
'Cache-Control': 'public, max-age=300',
257
+
'X-Cache-Tier': indexResult.source,
258
};
259
260
+
if (indexMeta?.encoding === 'gzip') {
261
headers['Content-Encoding'] = 'gzip';
262
}
263
···
543
544
// Not a directory, try to serve as a file
545
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
546
547
// Check for rewritten HTML in cache first (if it's HTML)
548
const mimeTypeGuess = lookup(fileRequestPath) || 'application/octet-stream';
···
550
const rewrittenKey = getCacheKey(did, rkey, fileRequestPath, `rewritten:${basePath}`);
551
const rewrittenContent = rewrittenHtmlCache.get(rewrittenKey);
552
if (rewrittenContent) {
553
+
console.log(`[HTML Rewrite] Serving from rewritten cache: ${rewrittenKey}`);
554
const headers: Record<string, string> = {
555
'Content-Type': 'text/html; charset=utf-8',
556
'Content-Encoding': 'gzip',
557
'Cache-Control': 'public, max-age=300',
558
+
'X-Cache-Tier': 'local', // Rewritten HTML is stored locally
559
};
560
applyCustomHeaders(headers, fileRequestPath, settings);
561
return new Response(rewrittenContent, { headers });
562
}
563
}
564
565
+
// Retrieve from tiered storage
566
+
const result = await getFileWithMetadata(did, rkey, fileRequestPath);
567
568
+
if (result) {
569
+
const content = Buffer.from(result.data);
570
+
const meta = result.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
571
+
const mimeType = meta?.mimeType || lookup(fileRequestPath) || 'application/octet-stream';
572
+
const isGzipped = meta?.encoding === 'gzip';
573
574
+
console.log(`[File Serve] Serving ${fileRequestPath}, mimeType: ${mimeType}, isHTML: ${isHtmlContent(fileRequestPath, mimeType)}, basePath: ${basePath}`);
575
576
// Check if this is HTML content that needs rewriting
577
if (isHtmlContent(fileRequestPath, mimeType)) {
578
+
console.log(`[HTML Rewrite] Processing ${fileRequestPath}, basePath: ${basePath}, mimeType: ${mimeType}, isGzipped: ${isGzipped}`);
579
let htmlContent: string;
580
if (isGzipped) {
581
// Verify content is actually gzipped
···
590
} else {
591
htmlContent = content.toString('utf-8');
592
}
593
+
// Check for <base> tag which can override paths
594
+
const baseTagMatch = htmlContent.match(/<base\s+[^>]*href=["'][^"']+["'][^>]*>/i);
595
+
if (baseTagMatch) {
596
+
console.warn(`[HTML Rewrite] WARNING: <base> tag found: ${baseTagMatch[0]} - this may override path rewrites`);
597
+
}
598
+
599
+
// Find src/href attributes (quoted and unquoted) to debug
600
+
const allMatches = htmlContent.match(/(?:src|href)\s*=\s*["']?\/[^"'\s>]+/g);
601
+
console.log(`[HTML Rewrite] Found ${allMatches ? allMatches.length : 0} local path attrs`);
602
+
if (allMatches && allMatches.length > 0) {
603
+
console.log(`[HTML Rewrite] Sample paths: ${allMatches.slice(0, 5).join(', ')}`);
604
+
}
605
+
606
const rewritten = rewriteHtmlPaths(htmlContent, basePath, fileRequestPath);
607
608
+
const rewrittenMatches = rewritten.match(/(?:src|href)\s*=\s*["']?\/[^"'\s>]+/g);
609
+
console.log(`[HTML Rewrite] After rewrite, found ${rewrittenMatches ? rewrittenMatches.length : 0} local paths`);
610
+
if (rewrittenMatches && rewrittenMatches.length > 0) {
611
+
console.log(`[HTML Rewrite] Sample rewritten: ${rewrittenMatches.slice(0, 5).join(', ')}`);
612
+
}
613
+
614
// Recompress and cache the rewritten HTML
615
const { gzipSync } = await import('zlib');
616
const recompressed = gzipSync(Buffer.from(rewritten, 'utf-8'));
···
622
'Content-Type': 'text/html; charset=utf-8',
623
'Content-Encoding': 'gzip',
624
'Cache-Control': 'public, max-age=300',
625
+
'X-Cache-Tier': result.source,
626
};
627
applyCustomHeaders(htmlHeaders, fileRequestPath, settings);
628
return new Response(recompressed, { headers: htmlHeaders });
···
632
const headers: Record<string, string> = {
633
'Content-Type': mimeType,
634
'Cache-Control': 'public, max-age=31536000, immutable',
635
+
'X-Cache-Tier': result.source,
636
};
637
638
if (isGzipped) {
···
662
if (!fileRequestPath.includes('.')) {
663
for (const indexFileName of indexFiles) {
664
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
665
666
// Check for rewritten index file in cache
667
const rewrittenKey = getCacheKey(did, rkey, indexPath, `rewritten:${basePath}`);
···
671
'Content-Type': 'text/html; charset=utf-8',
672
'Content-Encoding': 'gzip',
673
'Cache-Control': 'public, max-age=300',
674
+
'X-Cache-Tier': 'local', // Rewritten HTML is stored locally
675
};
676
applyCustomHeaders(headers, indexPath, settings);
677
return new Response(rewrittenContent, { headers });
678
}
679
680
+
const indexResult = await getFileWithMetadata(did, rkey, indexPath);
681
682
+
if (indexResult) {
683
+
const indexContent = Buffer.from(indexResult.data);
684
+
const indexMeta = indexResult.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
685
const isGzipped = indexMeta?.encoding === 'gzip';
686
687
let htmlContent: string;
···
709
'Content-Type': 'text/html; charset=utf-8',
710
'Content-Encoding': 'gzip',
711
'Cache-Control': 'public, max-age=300',
712
+
'X-Cache-Tier': indexResult.source,
713
};
714
applyCustomHeaders(headers, indexPath, settings);
715
return new Response(recompressed, { headers });
+46
-35
apps/hosting-service/src/lib/firehose.ts
+46
-35
apps/hosting-service/src/lib/firehose.ts
···
1
-
import { existsSync, rmSync } from 'fs'
2
import {
3
getPdsForDid,
4
downloadAndCacheSite,
···
13
import { invalidateSiteCache, markSiteAsBeingCached, unmarkSiteAsBeingCached } from './cache'
14
import { clearRedirectRulesCache } from './site-cache'
15
16
-
const CACHE_DIR = './cache/sites'
17
18
export class FirehoseWorker {
19
private firehose: Firehose | null = null
20
private idResolver: IdResolver
21
private isShuttingDown = false
22
private lastEventTime = Date.now()
23
private cacheCleanupInterval: NodeJS.Timeout | null = null
24
25
constructor(
26
private logger?: (msg: string, data?: Record<string, unknown>) => void
···
47
48
this.log('IdResolver cache cleared')
49
}, 60 * 60 * 1000) // Every hour
50
}
51
52
start() {
···
61
if (this.cacheCleanupInterval) {
62
clearInterval(this.cacheCleanupInterval)
63
this.cacheCleanupInterval = null
64
}
65
66
if (this.firehose) {
···
80
filterCollections: ['place.wisp.fs', 'place.wisp.settings'],
81
handleEvent: async (evt: any) => {
82
this.lastEventTime = Date.now()
83
84
// Watch for write events
85
if (evt.event === 'create' || evt.event === 'update') {
···
189
}
190
})
191
192
-
this.firehose.start()
193
-
this.log('Firehose started')
194
}
195
196
private async handleCreateOrUpdate(
···
250
}
251
252
// Invalidate in-memory caches before updating
253
-
invalidateSiteCache(did, site)
254
255
// Mark site as being cached to prevent serving stale content during update
256
markSiteAsBeingCached(did, site)
···
340
})
341
}
342
343
-
// Invalidate in-memory caches
344
-
invalidateSiteCache(did, site)
345
-
346
-
// Delete disk cache
347
-
this.deleteCache(did, site)
348
349
this.log('Successfully processed delete', { did, site })
350
}
···
353
this.log('Processing settings change', { did, rkey })
354
355
// Invalidate in-memory caches (includes metadata which stores settings)
356
-
invalidateSiteCache(did, rkey)
357
358
// Check if site is already cached
359
const cacheDir = `${CACHE_DIR}/${did}/${rkey}`
···
413
}
414
415
this.log('Successfully processed settings change', { did, rkey })
416
-
}
417
-
418
-
private deleteCache(did: string, site: string) {
419
-
const cacheDir = `${CACHE_DIR}/${did}/${site}`
420
-
421
-
if (!existsSync(cacheDir)) {
422
-
this.log('Cache directory does not exist, nothing to delete', {
423
-
did,
424
-
site
425
-
})
426
-
return
427
-
}
428
-
429
-
try {
430
-
rmSync(cacheDir, { recursive: true, force: true })
431
-
this.log('Cache deleted', { did, site, path: cacheDir })
432
-
} catch (err) {
433
-
this.log('Failed to delete cache', {
434
-
did,
435
-
site,
436
-
path: cacheDir,
437
-
error: err instanceof Error ? err.message : String(err)
438
-
})
439
-
}
440
}
441
442
getHealth() {
···
1
+
import { existsSync } from 'fs'
2
import {
3
getPdsForDid,
4
downloadAndCacheSite,
···
13
import { invalidateSiteCache, markSiteAsBeingCached, unmarkSiteAsBeingCached } from './cache'
14
import { clearRedirectRulesCache } from './site-cache'
15
16
+
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites'
17
18
export class FirehoseWorker {
19
private firehose: Firehose | null = null
20
private idResolver: IdResolver
21
private isShuttingDown = false
22
private lastEventTime = Date.now()
23
+
private eventCount = 0
24
private cacheCleanupInterval: NodeJS.Timeout | null = null
25
+
private healthCheckInterval: NodeJS.Timeout | null = null
26
27
constructor(
28
private logger?: (msg: string, data?: Record<string, unknown>) => void
···
49
50
this.log('IdResolver cache cleared')
51
}, 60 * 60 * 1000) // Every hour
52
+
53
+
// Health check: log if no events received for 30 seconds
54
+
this.healthCheckInterval = setInterval(() => {
55
+
if (this.isShuttingDown) return
56
+
57
+
const timeSinceLastEvent = Date.now() - this.lastEventTime
58
+
if (timeSinceLastEvent > 30000 && this.eventCount === 0) {
59
+
this.log('Warning: No firehose events received in the last 30 seconds', {
60
+
timeSinceLastEvent,
61
+
eventsReceived: this.eventCount
62
+
})
63
+
} else if (timeSinceLastEvent > 60000) {
64
+
this.log('Firehose status check', {
65
+
timeSinceLastEvent,
66
+
eventsReceived: this.eventCount
67
+
})
68
+
}
69
+
}, 30000) // Every 30 seconds
70
}
71
72
start() {
···
81
if (this.cacheCleanupInterval) {
82
clearInterval(this.cacheCleanupInterval)
83
this.cacheCleanupInterval = null
84
+
}
85
+
86
+
if (this.healthCheckInterval) {
87
+
clearInterval(this.healthCheckInterval)
88
+
this.healthCheckInterval = null
89
}
90
91
if (this.firehose) {
···
105
filterCollections: ['place.wisp.fs', 'place.wisp.settings'],
106
handleEvent: async (evt: any) => {
107
this.lastEventTime = Date.now()
108
+
this.eventCount++
109
+
110
+
if (this.eventCount === 1) {
111
+
this.log('First firehose event received - connection established', {
112
+
eventType: evt.event,
113
+
collection: evt.collection
114
+
})
115
+
}
116
117
// Watch for write events
118
if (evt.event === 'create' || evt.event === 'update') {
···
222
}
223
})
224
225
+
this.firehose.start().catch((err: unknown) => {
226
+
this.log('Fatal firehose error', {
227
+
error: err instanceof Error ? err.message : String(err)
228
+
})
229
+
console.error('Fatal firehose error:', err)
230
+
})
231
+
this.log('Firehose starting')
232
}
233
234
private async handleCreateOrUpdate(
···
288
}
289
290
// Invalidate in-memory caches before updating
291
+
await invalidateSiteCache(did, site)
292
293
// Mark site as being cached to prevent serving stale content during update
294
markSiteAsBeingCached(did, site)
···
378
})
379
}
380
381
+
// Invalidate all caches (tiered storage invalidation is handled by invalidateSiteCache)
382
+
await invalidateSiteCache(did, site)
383
384
this.log('Successfully processed delete', { did, site })
385
}
···
388
this.log('Processing settings change', { did, rkey })
389
390
// Invalidate in-memory caches (includes metadata which stores settings)
391
+
await invalidateSiteCache(did, rkey)
392
393
// Check if site is already cached
394
const cacheDir = `${CACHE_DIR}/${did}/${rkey}`
···
448
}
449
450
this.log('Successfully processed settings change', { did, rkey })
451
}
452
453
getHealth() {
+16
apps/hosting-service/src/lib/html-rewriter.ts
+16
apps/hosting-service/src/lib/html-rewriter.ts
···
189
`\\b${attr}[ \\t]{0,5}=[ \\t]{0,5}'([^']*)'`,
190
'gi'
191
)
192
+
// Unquoted attributes (valid in HTML5 for values without spaces/special chars)
193
+
// Match: attr=value where value starts immediately (no quotes) and continues until space or >
194
+
// Use negative lookahead to ensure we don't match quoted attributes
195
+
const unquotedRegex = new RegExp(
196
+
`\\b${attr}[ \\t]{0,5}=[ \\t]{0,5}(?!["'])([^\\s>]+)`,
197
+
'gi'
198
+
)
199
200
rewritten = rewritten.replace(doubleQuoteRegex, (match, value) => {
201
const rewrittenValue = rewritePath(
···
213
documentPath
214
)
215
return `${attr}='${rewrittenValue}'`
216
+
})
217
+
218
+
rewritten = rewritten.replace(unquotedRegex, (match, value) => {
219
+
const rewrittenValue = rewritePath(
220
+
value,
221
+
normalizedBase,
222
+
documentPath
223
+
)
224
+
return `${attr}=${rewrittenValue}`
225
})
226
}
227
}
+1
-1
apps/hosting-service/src/lib/site-cache.ts
+1
-1
apps/hosting-service/src/lib/site-cache.ts
+270
apps/hosting-service/src/lib/storage.ts
+270
apps/hosting-service/src/lib/storage.ts
···
···
1
+
/**
2
+
* Tiered storage configuration for wisp-hosting-service
3
+
*
4
+
* Implements a three-tier caching strategy:
5
+
* - Hot (Memory): Instant access for frequently used files (index.html, CSS, JS)
6
+
* - Warm (Disk): Local cache with eviction policy
7
+
* - Cold (S3/R2): Object storage as source of truth (optional)
8
+
*
9
+
* When S3 is not configured, falls back to disk-only mode (warm tier acts as source of truth).
10
+
* In cache-only mode (non-master nodes), S3 writes are skipped even if configured.
11
+
*/
12
+
13
+
import {
14
+
TieredStorage,
15
+
MemoryStorageTier,
16
+
DiskStorageTier,
17
+
S3StorageTier,
18
+
type StorageTier,
19
+
type StorageMetadata,
20
+
} from 'tiered-storage';
21
+
22
+
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
23
+
const HOT_CACHE_SIZE = parseInt(process.env.HOT_CACHE_SIZE || '104857600', 10); // 100MB default
24
+
const HOT_CACHE_COUNT = parseInt(process.env.HOT_CACHE_COUNT || '500', 10);
25
+
const WARM_CACHE_SIZE = parseInt(process.env.WARM_CACHE_SIZE || '10737418240', 10); // 10GB default
26
+
const WARM_EVICTION_POLICY = (process.env.WARM_EVICTION_POLICY || 'lru') as 'lru' | 'fifo' | 'size';
27
+
28
+
// Cache-only mode: skip S3 writes (non-master nodes)
29
+
// This is the same flag used to skip database writes
30
+
const CACHE_ONLY_MODE = process.env.CACHE_ONLY_MODE === 'true';
31
+
32
+
// S3/Cold tier configuration (optional)
33
+
const S3_BUCKET = process.env.S3_BUCKET || '';
34
+
const S3_METADATA_BUCKET = process.env.S3_METADATA_BUCKET;
35
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
36
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
37
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false';
38
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
39
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
40
+
const S3_PREFIX = process.env.S3_PREFIX || 'sites/';
41
+
42
+
// Identity serializers for raw binary data (no JSON transformation)
43
+
// Files are stored as-is without any encoding/decoding
44
+
const identitySerialize = async (data: unknown): Promise<Uint8Array> => {
45
+
if (data instanceof Uint8Array) return data;
46
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
47
+
if (Buffer.isBuffer(data)) return new Uint8Array(data);
48
+
// For other types, fall back to JSON (shouldn't happen with file storage)
49
+
return new TextEncoder().encode(JSON.stringify(data));
50
+
};
51
+
52
+
const identityDeserialize = async (data: Uint8Array): Promise<unknown> => {
53
+
// Return as-is for binary file storage
54
+
return data;
55
+
};
56
+
57
+
/**
58
+
* Read-only wrapper for S3 tier in cache-only mode.
59
+
* Allows reads from S3 but skips all writes (for non-master nodes).
60
+
*/
61
+
class ReadOnlyS3Tier implements StorageTier {
62
+
private static hasLoggedWriteSkip = false;
63
+
64
+
constructor(private tier: StorageTier) {}
65
+
66
+
// Read operations - pass through to underlying tier
67
+
async get(key: string) {
68
+
return this.tier.get(key);
69
+
}
70
+
71
+
async getWithMetadata(key: string) {
72
+
return this.tier.getWithMetadata?.(key) ?? null;
73
+
}
74
+
75
+
async getStream(key: string) {
76
+
return this.tier.getStream?.(key) ?? null;
77
+
}
78
+
79
+
async exists(key: string) {
80
+
return this.tier.exists(key);
81
+
}
82
+
83
+
async getMetadata(key: string) {
84
+
return this.tier.getMetadata(key);
85
+
}
86
+
87
+
async *listKeys(prefix?: string) {
88
+
yield* this.tier.listKeys(prefix);
89
+
}
90
+
91
+
async getStats() {
92
+
return this.tier.getStats();
93
+
}
94
+
95
+
// Write operations - no-op in cache-only mode
96
+
async set(key: string, _data: Uint8Array, _metadata: StorageMetadata) {
97
+
this.logWriteSkip('set', key);
98
+
}
99
+
100
+
async setStream(key: string, _stream: NodeJS.ReadableStream, _metadata: StorageMetadata) {
101
+
this.logWriteSkip('setStream', key);
102
+
}
103
+
104
+
async setMetadata(key: string, _metadata: StorageMetadata) {
105
+
this.logWriteSkip('setMetadata', key);
106
+
}
107
+
108
+
async delete(key: string) {
109
+
this.logWriteSkip('delete', key);
110
+
}
111
+
112
+
async deleteMany(keys: string[]) {
113
+
this.logWriteSkip('deleteMany', `${keys.length} keys`);
114
+
}
115
+
116
+
async clear() {
117
+
this.logWriteSkip('clear', 'all keys');
118
+
}
119
+
120
+
private logWriteSkip(operation: string, key: string) {
121
+
// Only log once to avoid spam
122
+
if (!ReadOnlyS3Tier.hasLoggedWriteSkip) {
123
+
console.log(`[Storage] Cache-only mode: skipping S3 writes (operation: ${operation})`);
124
+
ReadOnlyS3Tier.hasLoggedWriteSkip = true;
125
+
}
126
+
}
127
+
}
128
+
129
+
/**
130
+
* Initialize tiered storage
131
+
* Must be called before serving requests
132
+
*/
133
+
function initializeStorage(): TieredStorage<Uint8Array> {
134
+
// Determine cold tier: S3 if configured, otherwise disk acts as cold
135
+
let coldTier: StorageTier;
136
+
let warmTier: StorageTier | undefined;
137
+
138
+
const diskTier = new DiskStorageTier({
139
+
directory: CACHE_DIR,
140
+
maxSizeBytes: WARM_CACHE_SIZE,
141
+
evictionPolicy: WARM_EVICTION_POLICY,
142
+
encodeColons: false, // Preserve colons for readable DID paths on Unix/macOS
143
+
});
144
+
145
+
if (S3_BUCKET) {
146
+
// Full three-tier setup with S3 as cold storage
147
+
const s3Tier = new S3StorageTier({
148
+
bucket: S3_BUCKET,
149
+
metadataBucket: S3_METADATA_BUCKET,
150
+
region: S3_REGION,
151
+
endpoint: S3_ENDPOINT,
152
+
forcePathStyle: S3_FORCE_PATH_STYLE,
153
+
credentials:
154
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
155
+
? { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY }
156
+
: undefined,
157
+
prefix: S3_PREFIX,
158
+
});
159
+
160
+
// In cache-only mode, wrap S3 tier to make it read-only
161
+
coldTier = CACHE_ONLY_MODE ? new ReadOnlyS3Tier(s3Tier) : s3Tier;
162
+
warmTier = diskTier;
163
+
164
+
if (CACHE_ONLY_MODE) {
165
+
console.log('[Storage] Cache-only mode: S3 as read-only cold tier (no writes), disk as warm tier');
166
+
} else {
167
+
console.log('[Storage] Using S3 as cold tier, disk as warm tier');
168
+
}
169
+
} else {
170
+
// Disk-only mode: disk tier acts as source of truth (cold)
171
+
coldTier = diskTier;
172
+
warmTier = undefined;
173
+
console.log('[Storage] S3 not configured - using disk-only mode (disk as cold tier)');
174
+
}
175
+
176
+
const storage = new TieredStorage<Uint8Array>({
177
+
tiers: {
178
+
// Hot tier: In-memory LRU for instant serving
179
+
hot: new MemoryStorageTier({
180
+
maxSizeBytes: HOT_CACHE_SIZE,
181
+
maxItems: HOT_CACHE_COUNT,
182
+
}),
183
+
184
+
// Warm tier: Disk-based cache (only when S3 is configured)
185
+
warm: warmTier,
186
+
187
+
// Cold tier: S3/R2 as source of truth, or disk in disk-only mode
188
+
cold: coldTier,
189
+
},
190
+
191
+
// Placement rules: determine which tiers each file goes to
192
+
placementRules: [
193
+
// Metadata is critical: frequently accessed for cache validity checks
194
+
{
195
+
pattern: '**/.metadata.json',
196
+
tiers: ['hot', 'warm', 'cold'],
197
+
},
198
+
199
+
// index.html is critical: write to all tiers for instant serving
200
+
{
201
+
pattern: '**/index.html',
202
+
tiers: ['hot', 'warm', 'cold'],
203
+
},
204
+
{
205
+
pattern: 'index.html',
206
+
tiers: ['hot', 'warm', 'cold'],
207
+
},
208
+
209
+
// CSS and JS: eligible for hot tier if accessed frequently
210
+
{
211
+
pattern: '**/*.{css,js}',
212
+
tiers: ['hot', 'warm', 'cold'],
213
+
},
214
+
215
+
// Media files: never needed in memory, skip hot tier
216
+
{
217
+
pattern: '**/*.{jpg,jpeg,png,gif,webp,svg,ico,mp4,webm,mp3,woff,woff2,ttf,eot}',
218
+
tiers: ['warm', 'cold'],
219
+
},
220
+
221
+
// Default: everything else goes to warm and cold
222
+
{
223
+
pattern: '**',
224
+
tiers: ['warm', 'cold'],
225
+
},
226
+
],
227
+
228
+
// IMPORTANT: Compression is disabled at the tiered-storage level
229
+
// Text files (HTML, CSS, JS, JSON) are pre-compressed with gzip at the app level
230
+
// Binary files (images, video) are stored uncompressed as they're already compressed
231
+
// The file's compression state is tracked in customMetadata.encoding
232
+
compression: false,
233
+
234
+
// TTL for cache entries (14 days)
235
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
236
+
237
+
// Eager promotion: promote data to upper tiers on read
238
+
// This ensures frequently accessed files end up in hot tier
239
+
promotionStrategy: 'eager',
240
+
241
+
// Identity serialization: store raw binary without JSON transformation
242
+
serialization: {
243
+
serialize: identitySerialize,
244
+
deserialize: identityDeserialize,
245
+
},
246
+
});
247
+
248
+
return storage;
249
+
}
250
+
251
+
// Export singleton instance
252
+
export const storage = initializeStorage();
253
+
254
+
/**
255
+
* Get storage configuration summary for logging
256
+
*/
257
+
export function getStorageConfig() {
258
+
return {
259
+
cacheDir: CACHE_DIR,
260
+
hotCacheSize: `${(HOT_CACHE_SIZE / 1024 / 1024).toFixed(0)}MB`,
261
+
hotCacheCount: HOT_CACHE_COUNT,
262
+
warmCacheSize: `${(WARM_CACHE_SIZE / 1024 / 1024 / 1024).toFixed(1)}GB`,
263
+
warmEvictionPolicy: WARM_EVICTION_POLICY,
264
+
s3Bucket: S3_BUCKET,
265
+
s3Region: S3_REGION,
266
+
s3Endpoint: S3_ENDPOINT || '(default AWS S3)',
267
+
s3Prefix: S3_PREFIX,
268
+
metadataBucket: S3_METADATA_BUCKET || '(embedded in data bucket)',
269
+
};
270
+
}
+95
-154
apps/hosting-service/src/lib/utils.ts
+95
-154
apps/hosting-service/src/lib/utils.ts
···
4
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
5
import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
6
import { writeFile, readFile, rename } from 'fs/promises';
7
import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch';
8
import { CID } from 'multiformats';
9
import { extractBlobCid } from '@wisp/atproto-utils';
10
import { sanitizePath, collectFileCidsFromEntries, countFilesInDirectory } from '@wisp/fs-utils';
11
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
12
import { MAX_BLOB_SIZE, MAX_FILE_COUNT, MAX_SITE_SIZE } from '@wisp/constants';
13
14
// Re-export shared utilities for local usage and tests
15
export { extractBlobCid, sanitizePath };
···
90
export async function fetchSiteRecord(did: string, rkey: string): Promise<{ record: WispFsRecord; cid: string } | null> {
91
try {
92
const pdsEndpoint = await getPdsForDid(did);
93
-
if (!pdsEndpoint) return null;
94
95
const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
96
const data = await safeFetchJson(url);
···
100
cid: data.cid || ''
101
};
102
} catch (err) {
103
-
console.error('Failed to fetch site record', did, rkey, err);
104
return null;
105
}
106
}
···
371
const existingMetadata = await getCacheMetadata(did, rkey);
372
const existingFileCids = existingMetadata?.fileCids || {};
373
374
-
// Use a temporary directory with timestamp to avoid collisions
375
-
const tempSuffix = `.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
376
-
const tempDir = `${CACHE_DIR}/${did}/${rkey}${tempSuffix}`;
377
-
const finalDir = `${CACHE_DIR}/${did}/${rkey}`;
378
-
379
-
try {
380
-
// Collect file CIDs from the new record (using expanded root)
381
-
const newFileCids: Record<string, string> = {};
382
-
collectFileCidsFromEntries(expandedRoot.entries, '', newFileCids);
383
-
384
-
// Fetch site settings (optional)
385
-
const settings = await fetchSiteSettings(did, rkey);
386
-
387
-
// Download/copy files to temporary directory (with incremental logic, using expanded root)
388
-
await cacheFiles(did, rkey, expandedRoot.entries, pdsEndpoint, '', tempSuffix, existingFileCids, finalDir);
389
-
await saveCacheMetadata(did, rkey, recordCid, tempSuffix, newFileCids, settings);
390
-
391
-
// Atomically replace old cache with new cache
392
-
// On POSIX systems (Linux/macOS), rename is atomic
393
-
if (existsSync(finalDir)) {
394
-
// Rename old directory to backup
395
-
const backupDir = `${finalDir}.old-${Date.now()}`;
396
-
await rename(finalDir, backupDir);
397
398
-
try {
399
-
// Rename new directory to final location
400
-
await rename(tempDir, finalDir);
401
402
-
// Clean up old backup
403
-
rmSync(backupDir, { recursive: true, force: true });
404
-
} catch (err) {
405
-
// If rename failed, restore backup
406
-
if (existsSync(backupDir) && !existsSync(finalDir)) {
407
-
await rename(backupDir, finalDir);
408
-
}
409
-
throw err;
410
-
}
411
-
} else {
412
-
// No existing cache, just rename temp to final
413
-
await rename(tempDir, finalDir);
414
-
}
415
416
-
console.log('Successfully cached site atomically', did, rkey);
417
-
} catch (err) {
418
-
// Clean up temp directory on failure
419
-
if (existsSync(tempDir)) {
420
-
rmSync(tempDir, { recursive: true, force: true });
421
-
}
422
-
throw err;
423
-
}
424
}
425
426
···
430
entries: Entry[],
431
pdsEndpoint: string,
432
pathPrefix: string,
433
-
dirSuffix: string = '',
434
-
existingFileCids: Record<string, string> = {},
435
-
existingCacheDir?: string
436
): Promise<void> {
437
-
// Collect file tasks, separating unchanged files from new/changed files
438
const downloadTasks: Array<() => Promise<void>> = [];
439
-
const copyTasks: Array<() => Promise<void>> = [];
440
441
function collectFileTasks(
442
entries: Entry[],
···
453
const cid = extractBlobCid(fileNode.blob);
454
455
// Check if file is unchanged (same CID as existing cache)
456
-
if (cid && existingFileCids[currentPath] === cid && existingCacheDir) {
457
-
// File unchanged - copy from existing cache instead of downloading
458
-
copyTasks.push(() => copyExistingFile(
459
-
did,
460
-
site,
461
-
currentPath,
462
-
dirSuffix,
463
-
existingCacheDir
464
-
));
465
} else {
466
// File new or changed - download it
467
downloadTasks.push(() => cacheFileBlob(
···
472
pdsEndpoint,
473
fileNode.encoding,
474
fileNode.mimeType,
475
-
fileNode.base64,
476
-
dirSuffix
477
));
478
}
479
}
···
482
483
collectFileTasks(entries, pathPrefix);
484
485
-
console.log(`[Incremental Update] Files to copy: ${copyTasks.length}, Files to download: ${downloadTasks.length}`);
486
487
-
// Copy unchanged files in parallel (fast local operations) - increased limit for better performance
488
-
const copyLimit = 50;
489
-
for (let i = 0; i < copyTasks.length; i += copyLimit) {
490
-
const batch = copyTasks.slice(i, i + copyLimit);
491
-
await Promise.all(batch.map(task => task()));
492
-
if (copyTasks.length > copyLimit) {
493
-
console.log(`[Cache Progress] Copied ${Math.min(i + copyLimit, copyTasks.length)}/${copyTasks.length} unchanged files`);
494
-
}
495
-
}
496
-
497
-
// Download new/changed files concurrently - increased from 3 to 20 for much better performance
498
const downloadLimit = 20;
499
let successCount = 0;
500
let failureCount = 0;
···
523
}
524
}
525
526
-
/**
527
-
* Copy an unchanged file from existing cache to new cache location
528
-
*/
529
-
async function copyExistingFile(
530
-
did: string,
531
-
site: string,
532
-
filePath: string,
533
-
dirSuffix: string,
534
-
existingCacheDir: string
535
-
): Promise<void> {
536
-
const { copyFile } = await import('fs/promises');
537
-
538
-
const sourceFile = `${existingCacheDir}/${filePath}`;
539
-
const destFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`;
540
-
const destDir = destFile.substring(0, destFile.lastIndexOf('/'));
541
-
542
-
// Create destination directory if needed
543
-
if (destDir && !existsSync(destDir)) {
544
-
mkdirSync(destDir, { recursive: true });
545
-
}
546
-
547
-
try {
548
-
// Copy the file
549
-
await copyFile(sourceFile, destFile);
550
-
551
-
// Copy metadata file if it exists
552
-
const sourceMetaFile = `${sourceFile}.meta`;
553
-
const destMetaFile = `${destFile}.meta`;
554
-
if (existsSync(sourceMetaFile)) {
555
-
await copyFile(sourceMetaFile, destMetaFile);
556
-
}
557
-
} catch (err) {
558
-
console.error(`Failed to copy cached file ${filePath}, will attempt download:`, err);
559
-
throw err;
560
-
}
561
-
}
562
-
563
async function cacheFileBlob(
564
did: string,
565
site: string,
···
568
pdsEndpoint: string,
569
encoding?: 'gzip',
570
mimeType?: string,
571
-
base64?: boolean,
572
-
dirSuffix: string = ''
573
): Promise<void> {
574
const cid = extractBlobCid(blobRef);
575
if (!cid) {
···
592
content = Buffer.from(base64String, 'base64');
593
}
594
595
-
const cacheFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`;
596
-
const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/'));
597
-
598
-
if (fileDir && !existsSync(fileDir)) {
599
-
mkdirSync(fileDir, { recursive: true });
600
-
}
601
-
602
// Use the shared function to determine if this should remain compressed
603
const shouldStayCompressed = shouldCompressMimeType(mimeType);
604
···
616
}
617
}
618
619
-
await writeFile(cacheFile, content);
620
621
-
// Store metadata only if file is still compressed
622
if (encoding === 'gzip' && mimeType) {
623
-
const metaFile = `${cacheFile}.meta`;
624
-
await writeFile(metaFile, JSON.stringify({ encoding, mimeType }));
625
console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')');
626
} else {
627
console.log('Cached file', filePath, content.length, 'bytes');
···
634
return `${CACHE_DIR}/${did}/${site}/${sanitizedPath}`;
635
}
636
637
-
export function isCached(did: string, site: string): boolean {
638
-
return existsSync(`${CACHE_DIR}/${did}/${site}`);
639
}
640
641
-
async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = '', fileCids?: Record<string, string>, settings?: WispSettings | null): Promise<void> {
642
const metadata: CacheMetadata = {
643
recordCid,
644
cachedAt: Date.now(),
···
648
settings: settings || undefined
649
};
650
651
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`;
652
-
const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/'));
653
-
654
-
if (!existsSync(metadataDir)) {
655
-
mkdirSync(metadataDir, { recursive: true });
656
-
}
657
-
658
-
await writeFile(metadataPath, JSON.stringify(metadata, null, 2));
659
}
660
661
async function getCacheMetadata(did: string, rkey: string): Promise<CacheMetadata | null> {
662
try {
663
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`;
664
-
if (!existsSync(metadataPath)) return null;
665
666
-
const content = await readFile(metadataPath, 'utf-8');
667
-
return JSON.parse(content) as CacheMetadata;
668
} catch (err) {
669
console.error('Failed to read cache metadata', err);
670
return null;
···
698
}
699
700
export async function updateCacheMetadataSettings(did: string, rkey: string, settings: WispSettings | null): Promise<void> {
701
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`;
702
-
703
-
if (!existsSync(metadataPath)) {
704
-
console.warn('Metadata file does not exist, cannot update settings', { did, rkey });
705
-
return;
706
-
}
707
-
708
try {
709
-
// Read existing metadata
710
-
const content = await readFile(metadataPath, 'utf-8');
711
-
const metadata = JSON.parse(content) as CacheMetadata;
712
713
// Update settings field
714
// Store null explicitly to cache "no settings" state and avoid repeated fetches
715
metadata.settings = settings ?? null;
716
717
-
// Write back to disk
718
-
await writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
719
console.log('Updated metadata settings', { did, rkey, hasSettings: !!settings });
720
} catch (err) {
721
console.error('Failed to update metadata settings', err);
···
4
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
5
import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
6
import { writeFile, readFile, rename } from 'fs/promises';
7
+
import { Readable } from 'stream';
8
import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch';
9
import { CID } from 'multiformats';
10
import { extractBlobCid } from '@wisp/atproto-utils';
11
import { sanitizePath, collectFileCidsFromEntries, countFilesInDirectory } from '@wisp/fs-utils';
12
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
13
import { MAX_BLOB_SIZE, MAX_FILE_COUNT, MAX_SITE_SIZE } from '@wisp/constants';
14
+
import { storage } from './storage';
15
16
// Re-export shared utilities for local usage and tests
17
export { extractBlobCid, sanitizePath };
···
92
export async function fetchSiteRecord(did: string, rkey: string): Promise<{ record: WispFsRecord; cid: string } | null> {
93
try {
94
const pdsEndpoint = await getPdsForDid(did);
95
+
if (!pdsEndpoint) {
96
+
console.error('[hosting-service] Failed to get PDS endpoint for DID', { did, rkey });
97
+
return null;
98
+
}
99
100
const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
101
const data = await safeFetchJson(url);
···
105
cid: data.cid || ''
106
};
107
} catch (err) {
108
+
const errorCode = (err as any)?.code;
109
+
const errorMsg = err instanceof Error ? err.message : String(err);
110
+
111
+
// Better error logging to distinguish between network errors and 404s
112
+
if (errorMsg.includes('HTTP 404') || errorMsg.includes('Not Found')) {
113
+
console.log('[hosting-service] Site record not found', { did, rkey });
114
+
} else if (errorCode && ['ECONNRESET', 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', 'ETIMEDOUT'].includes(errorCode)) {
115
+
console.error('[hosting-service] Network/SSL error fetching site record (after retries)', {
116
+
did,
117
+
rkey,
118
+
error: errorMsg,
119
+
code: errorCode
120
+
});
121
+
} else {
122
+
console.error('[hosting-service] Failed to fetch site record', {
123
+
did,
124
+
rkey,
125
+
error: errorMsg,
126
+
code: errorCode
127
+
});
128
+
}
129
+
130
return null;
131
}
132
}
···
397
const existingMetadata = await getCacheMetadata(did, rkey);
398
const existingFileCids = existingMetadata?.fileCids || {};
399
400
+
// Collect file CIDs from the new record (using expanded root)
401
+
const newFileCids: Record<string, string> = {};
402
+
collectFileCidsFromEntries(expandedRoot.entries, '', newFileCids);
403
404
+
// Fetch site settings (optional)
405
+
const settings = await fetchSiteSettings(did, rkey);
406
407
+
// Download files directly to tiered storage (with incremental logic)
408
+
await cacheFiles(did, rkey, expandedRoot.entries, pdsEndpoint, '', existingFileCids);
409
+
await saveCacheMetadata(did, rkey, recordCid, newFileCids, settings);
410
411
+
console.log('Successfully cached site', did, rkey);
412
}
413
414
···
418
entries: Entry[],
419
pdsEndpoint: string,
420
pathPrefix: string,
421
+
existingFileCids: Record<string, string> = {}
422
): Promise<void> {
423
+
// Collect file download tasks (skip unchanged files)
424
const downloadTasks: Array<() => Promise<void>> = [];
425
426
function collectFileTasks(
427
entries: Entry[],
···
438
const cid = extractBlobCid(fileNode.blob);
439
440
// Check if file is unchanged (same CID as existing cache)
441
+
if (cid && existingFileCids[currentPath] === cid) {
442
+
// File unchanged - skip download (already in tiered storage)
443
+
console.log(`Skipping unchanged file: ${currentPath}`);
444
} else {
445
// File new or changed - download it
446
downloadTasks.push(() => cacheFileBlob(
···
451
pdsEndpoint,
452
fileNode.encoding,
453
fileNode.mimeType,
454
+
fileNode.base64
455
));
456
}
457
}
···
460
461
collectFileTasks(entries, pathPrefix);
462
463
+
console.log(`[Incremental Update] Files to download: ${downloadTasks.length}`);
464
465
+
// Download new/changed files concurrently
466
const downloadLimit = 20;
467
let successCount = 0;
468
let failureCount = 0;
···
491
}
492
}
493
494
async function cacheFileBlob(
495
did: string,
496
site: string,
···
499
pdsEndpoint: string,
500
encoding?: 'gzip',
501
mimeType?: string,
502
+
base64?: boolean
503
): Promise<void> {
504
const cid = extractBlobCid(blobRef);
505
if (!cid) {
···
522
content = Buffer.from(base64String, 'base64');
523
}
524
525
// Use the shared function to determine if this should remain compressed
526
const shouldStayCompressed = shouldCompressMimeType(mimeType);
527
···
539
}
540
}
541
542
+
// Write to tiered storage with metadata
543
+
const stream = Readable.from([content]);
544
+
const key = `${did}/${site}/${filePath}`;
545
+
546
+
// Build metadata object, only including defined values
547
+
const customMetadata: Record<string, string> = {};
548
+
if (encoding) customMetadata.encoding = encoding;
549
+
if (mimeType) customMetadata.mimeType = mimeType;
550
+
551
+
await storage.setStream(key, stream, {
552
+
size: content.length,
553
+
skipTiers: ['hot'], // Don't put in memory on ingest, only on access
554
+
metadata: customMetadata,
555
+
});
556
557
+
// Log completion
558
if (encoding === 'gzip' && mimeType) {
559
console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')');
560
} else {
561
console.log('Cached file', filePath, content.length, 'bytes');
···
568
return `${CACHE_DIR}/${did}/${site}/${sanitizedPath}`;
569
}
570
571
+
/**
572
+
* Check if a site exists in any tier of the cache (without checking metadata)
573
+
* This is a quick existence check - for actual retrieval, use storage.get()
574
+
*/
575
+
export async function isCached(did: string, site: string): Promise<boolean> {
576
+
// Check if any file exists for this site by checking for the index.html
577
+
// If index.html exists, the site is cached
578
+
const indexKey = `${did}/${site}/index.html`;
579
+
return await storage.exists(indexKey);
580
}
581
582
+
async function saveCacheMetadata(did: string, rkey: string, recordCid: string, fileCids?: Record<string, string>, settings?: WispSettings | null): Promise<void> {
583
const metadata: CacheMetadata = {
584
recordCid,
585
cachedAt: Date.now(),
···
589
settings: settings || undefined
590
};
591
592
+
// Store through tiered storage for persistence to S3/cold tier
593
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
594
+
const metadataBytes = new TextEncoder().encode(JSON.stringify(metadata, null, 2));
595
+
await storage.set(metadataKey, metadataBytes);
596
}
597
598
async function getCacheMetadata(did: string, rkey: string): Promise<CacheMetadata | null> {
599
try {
600
+
// Retrieve metadata from tiered storage
601
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
602
+
const data = await storage.get(metadataKey);
603
604
+
if (!data) return null;
605
+
606
+
// Deserialize from Uint8Array to JSON (storage uses identity serialization)
607
+
const jsonString = new TextDecoder().decode(data as Uint8Array);
608
+
return JSON.parse(jsonString) as CacheMetadata;
609
} catch (err) {
610
console.error('Failed to read cache metadata', err);
611
return null;
···
639
}
640
641
export async function updateCacheMetadataSettings(did: string, rkey: string, settings: WispSettings | null): Promise<void> {
642
try {
643
+
// Read existing metadata from tiered storage
644
+
const metadata = await getCacheMetadata(did, rkey);
645
+
646
+
if (!metadata) {
647
+
console.warn('Metadata does not exist, cannot update settings', { did, rkey });
648
+
return;
649
+
}
650
651
// Update settings field
652
// Store null explicitly to cache "no settings" state and avoid repeated fetches
653
metadata.settings = settings ?? null;
654
655
+
// Write back through tiered storage
656
+
// Convert to Uint8Array since storage is typed for binary data
657
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
658
+
const metadataBytes = new TextEncoder().encode(JSON.stringify(metadata, null, 2));
659
+
await storage.set(metadataKey, metadataBytes);
660
console.log('Updated metadata settings', { did, rkey, hasSettings: !!settings });
661
} catch (err) {
662
console.error('Failed to update metadata settings', err);
+4
-1
apps/hosting-service/src/server.ts
+4
-1
apps/hosting-service/src/server.ts
···
80
return c.text('Invalid identifier', 400);
81
}
82
83
// Check if site is currently being cached - return updating response early
84
if (isSiteBeingCached(did, site)) {
85
return siteUpdatingResponse();
···
93
94
// Serve with HTML path rewriting to handle absolute paths
95
const basePath = `/${identifier}/${site}/`;
96
const headers = extractHeaders(c.req.raw.headers);
97
return serveFromCacheWithRewrite(did, site, filePath, basePath, c.req.url, headers);
98
}
···
227
228
app.get('/__internal__/observability/cache', async (c) => {
229
const { getCacheStats } = await import('./lib/cache');
230
-
const stats = getCacheStats();
231
return c.json({ cache: stats });
232
});
233
···
80
return c.text('Invalid identifier', 400);
81
}
82
83
+
console.log(`[Server] sites.wisp.place request: identifier=${identifier}, site=${site}, filePath=${filePath}`);
84
+
85
// Check if site is currently being cached - return updating response early
86
if (isSiteBeingCached(did, site)) {
87
return siteUpdatingResponse();
···
95
96
// Serve with HTML path rewriting to handle absolute paths
97
const basePath = `/${identifier}/${site}/`;
98
+
console.log(`[Server] Serving with basePath: ${basePath}`);
99
const headers = extractHeaders(c.req.raw.headers);
100
return serveFromCacheWithRewrite(did, site, filePath, basePath, c.req.url, headers);
101
}
···
230
231
app.get('/__internal__/observability/cache', async (c) => {
232
const { getCacheStats } = await import('./lib/cache');
233
+
const stats = await getCacheStats();
234
return c.json({ cache: stats });
235
});
236
+1
-1
apps/main-app/package.json
+1
-1
apps/main-app/package.json
+4
-4
apps/main-app/public/acceptable-use/acceptable-use.tsx
+4
-4
apps/main-app/public/acceptable-use/acceptable-use.tsx
···
6
7
function AcceptableUsePage() {
8
return (
9
-
<div className="min-h-screen bg-background">
10
{/* Header */}
11
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
12
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
13
<div className="flex items-center gap-2">
14
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
15
<span className="text-xl font-semibold text-foreground">
···
326
</div>
327
328
{/* Footer */}
329
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
330
<div className="container mx-auto px-4 py-8">
331
<div className="text-center text-sm text-muted-foreground">
332
<p>
···
6
7
function AcceptableUsePage() {
8
return (
9
+
<div className="w-full min-h-screen bg-background flex flex-col">
10
{/* Header */}
11
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
12
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
13
<div className="flex items-center gap-2">
14
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
15
<span className="text-xl font-semibold text-foreground">
···
326
</div>
327
328
{/* Footer */}
329
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
330
<div className="container mx-auto px-4 py-8">
331
<div className="text-center text-sm text-muted-foreground">
332
<p>
+1
-1
apps/main-app/public/components/ui/checkbox.tsx
+1
-1
apps/main-app/public/components/ui/checkbox.tsx
···
12
<CheckboxPrimitive.Root
13
data-slot="checkbox"
14
className={cn(
15
-
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
16
className
17
)}
18
{...props}
···
12
<CheckboxPrimitive.Root
13
data-slot="checkbox"
14
className={cn(
15
+
"peer border-border bg-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
16
className
17
)}
18
{...props}
+6
-6
apps/main-app/public/editor/editor.tsx
+6
-6
apps/main-app/public/editor/editor.tsx
···
302
return (
303
<div className="w-full min-h-screen bg-background">
304
{/* Header Skeleton */}
305
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
306
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
307
<div className="flex items-center gap-2">
308
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
309
<span className="text-xl font-semibold text-foreground">
···
366
}
367
368
return (
369
-
<div className="w-full min-h-screen bg-background">
370
{/* Header */}
371
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
372
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
373
<div className="flex items-center gap-2">
374
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
375
<span className="text-xl font-semibold text-foreground">
···
454
</div>
455
456
{/* Footer */}
457
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
458
<div className="container mx-auto px-4 py-8">
459
<div className="text-center text-sm text-muted-foreground">
460
<p>
···
302
return (
303
<div className="w-full min-h-screen bg-background">
304
{/* Header Skeleton */}
305
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
306
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
307
<div className="flex items-center gap-2">
308
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
309
<span className="text-xl font-semibold text-foreground">
···
366
}
367
368
return (
369
+
<div className="w-full min-h-screen bg-background flex flex-col">
370
{/* Header */}
371
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
372
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
373
<div className="flex items-center gap-2">
374
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
375
<span className="text-xl font-semibold text-foreground">
···
454
</div>
455
456
{/* Footer */}
457
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
458
<div className="container mx-auto px-4 py-8">
459
<div className="text-center text-sm text-muted-foreground">
460
<p>
+74
-66
apps/main-app/public/index.tsx
+74
-66
apps/main-app/public/index.tsx
···
88
89
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
90
const navigationKeys = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Enter', 'Escape']
91
-
92
// Mark that we should preserve the index for navigation keys
93
if (navigationKeys.includes(e.key)) {
94
preserveIndexRef.current = true
···
142
setIndex(-1)
143
setIsOpen(false)
144
onSelect?.(handle)
145
-
146
// Auto-submit the form if enabled
147
if (autoSubmit && inputRef.current) {
148
const form = inputRef.current.closest('form')
···
236
height: 'calc(1.5rem + 12px)',
237
borderRadius: '4px',
238
cursor: 'pointer',
239
-
backgroundColor: i === index ? 'hsl(var(--accent) / 0.5)' : 'transparent',
240
transition: 'background-color 0.1s'
241
}}
242
onMouseEnter={() => setIndex(i)}
···
246
width: '1.5rem',
247
height: '1.5rem',
248
borderRadius: '50%',
249
-
backgroundColor: 'hsl(var(--muted))',
250
overflow: 'hidden',
251
flexShrink: 0
252
}}
···
255
<img
256
src={actor.avatar}
257
alt=""
258
style={{
259
display: 'block',
260
width: '100%',
···
359
360
return (
361
<>
362
-
<div className="min-h-screen">
363
{/* Header */}
364
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
365
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
366
<div className="flex items-center gap-2">
367
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
368
-
<span className="text-xl font-semibold text-foreground">
369
wisp.place
370
</span>
371
</div>
372
-
<div className="flex items-center gap-3">
373
<Button
374
-
variant="ghost"
375
size="sm"
376
onClick={() => setShowForm(true)}
377
>
378
Sign In
379
</Button>
380
-
<Button
381
-
size="sm"
382
-
className="bg-accent text-accent-foreground hover:bg-accent/90"
383
-
asChild
384
-
>
385
-
<a href="https://docs.wisp.place" target="_blank" rel="noopener noreferrer">
386
-
Read the Docs
387
-
</a>
388
-
</Button>
389
</div>
390
</div>
391
</header>
392
393
{/* Hero Section */}
394
-
<section className="container mx-auto px-4 py-20 md:py-32">
395
<div className="max-w-4xl mx-auto text-center">
396
-
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 mb-8">
397
-
<span className="w-2 h-2 bg-accent rounded-full animate-pulse"></span>
398
-
<span className="text-sm text-foreground">
399
-
Built on AT Protocol
400
-
</span>
401
-
</div>
402
-
403
-
<h1 className="text-5xl md:text-7xl font-bold text-balance mb-6 leading-tight">
404
-
Your Website.Your Control. Lightning Fast.
405
</h1>
406
407
-
<p className="text-xl md:text-2xl text-muted-foreground text-balance mb-10 leading-relaxed max-w-3xl mx-auto">
408
-
Host static sites in your AT Protocol account. You
409
-
keep ownership and control. We just serve them fast
410
-
through our CDN.
411
</p>
412
413
-
<div className="max-w-md mx-auto relative">
414
<div
415
-
className={`transition-all duration-500 ease-in-out ${
416
-
showForm
417
-
? 'opacity-0 -translate-y-5 pointer-events-none'
418
-
: 'opacity-100 translate-y-0'
419
-
}`}
420
>
421
-
<Button
422
-
size="lg"
423
-
className="bg-primary text-primary-foreground hover:bg-primary/90 text-lg px-8 py-6 w-full"
424
-
onClick={() => setShowForm(true)}
425
-
>
426
-
Log in with AT Proto
427
-
<ArrowRight className="ml-2 w-5 h-5" />
428
-
</Button>
429
</div>
430
431
<div
432
-
className={`transition-all duration-500 ease-in-out absolute inset-0 ${
433
-
showForm
434
-
? 'opacity-100 translate-y-0'
435
-
: 'opacity-0 translate-y-5 pointer-events-none'
436
-
}`}
437
>
438
<form
439
onSubmit={async (e) => {
···
494
</ActorTypeahead>
495
<button
496
type="submit"
497
-
className="w-full bg-accent hover:bg-accent/90 text-accent-foreground font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors"
498
>
499
Continue
500
<ArrowRight className="ml-2 w-5 h-5" />
···
518
</div>
519
<div>
520
<h3 className="text-xl font-semibold mb-2">
521
-
Upload your static site
522
</h3>
523
<p className="text-muted-foreground">
524
-
Your HTML, CSS, and JavaScript files are
525
-
stored in your AT Protocol account as
526
-
gzipped blobs and a manifest record.
527
</p>
528
</div>
529
</div>
···
533
</div>
534
<div>
535
<h3 className="text-xl font-semibold mb-2">
536
-
We serve it globally
537
</h3>
538
<p className="text-muted-foreground">
539
-
Wisp.place reads your site from your
540
-
account and delivers it through our CDN
541
-
for fast loading anywhere.
542
</p>
543
</div>
544
</div>
···
548
</div>
549
<div>
550
<h3 className="text-xl font-semibold mb-2">
551
-
You stay in control
552
</h3>
553
<p className="text-muted-foreground">
554
-
Update or remove your site anytime
555
-
through your AT Protocol account. No
556
-
lock-in, no middleman ownership.
557
</p>
558
</div>
559
</div>
···
686
</section>
687
688
{/* Footer */}
689
-
<footer className="border-t border-border/40 bg-muted/20">
690
<div className="container mx-auto px-4 py-8">
691
<div className="text-center text-sm text-muted-foreground">
692
<p>
···
88
89
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
90
const navigationKeys = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Enter', 'Escape']
91
+
92
// Mark that we should preserve the index for navigation keys
93
if (navigationKeys.includes(e.key)) {
94
preserveIndexRef.current = true
···
142
setIndex(-1)
143
setIsOpen(false)
144
onSelect?.(handle)
145
+
146
// Auto-submit the form if enabled
147
if (autoSubmit && inputRef.current) {
148
const form = inputRef.current.closest('form')
···
236
height: 'calc(1.5rem + 12px)',
237
borderRadius: '4px',
238
cursor: 'pointer',
239
+
backgroundColor: i === index ? 'color-mix(in oklch, var(--accent) 50%, transparent)' : 'transparent',
240
transition: 'background-color 0.1s'
241
}}
242
onMouseEnter={() => setIndex(i)}
···
246
width: '1.5rem',
247
height: '1.5rem',
248
borderRadius: '50%',
249
+
backgroundColor: 'var(--muted)',
250
overflow: 'hidden',
251
flexShrink: 0
252
}}
···
255
<img
256
src={actor.avatar}
257
alt=""
258
+
loading="lazy"
259
style={{
260
display: 'block',
261
width: '100%',
···
360
361
return (
362
<>
363
+
<div className="w-full min-h-screen flex flex-col">
364
{/* Header */}
365
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
366
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
367
<div className="flex items-center gap-2">
368
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
369
+
<span className="text-lg font-semibold text-foreground">
370
wisp.place
371
</span>
372
</div>
373
+
<div className="flex items-center gap-4">
374
+
<a
375
+
href="https://docs.wisp.place"
376
+
target="_blank"
377
+
rel="noopener noreferrer"
378
+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
379
+
>
380
+
Read the Docs
381
+
</a>
382
<Button
383
+
variant="outline"
384
size="sm"
385
+
className="btn-hover-lift"
386
onClick={() => setShowForm(true)}
387
>
388
Sign In
389
</Button>
390
</div>
391
</div>
392
</header>
393
394
{/* Hero Section */}
395
+
<section className="container mx-auto px-4 py-24 md:py-36">
396
<div className="max-w-4xl mx-auto text-center">
397
+
{/* Main Headline */}
398
+
<h1 className="animate-fade-in-up animate-delay-100 text-5xl md:text-7xl font-bold mb-2 leading-tight tracking-tight">
399
+
Deploy Anywhere.
400
+
</h1>
401
+
<h1 className="animate-fade-in-up animate-delay-200 text-5xl md:text-7xl font-bold mb-8 leading-tight tracking-tight text-gradient-animate">
402
+
For Free. Forever.
403
</h1>
404
405
+
{/* Subheadline */}
406
+
<p className="animate-fade-in-up animate-delay-300 text-lg md:text-xl text-muted-foreground mb-12 leading-relaxed max-w-2xl mx-auto">
407
+
The easiest way to deploy and orchestrate static sites.
408
+
Push updates instantly. Host on our infrastructure or yours.
409
+
All powered by AT Protocol.
410
</p>
411
412
+
{/* CTA Buttons */}
413
+
<div className="animate-fade-in-up animate-delay-400 max-w-lg mx-auto relative">
414
<div
415
+
className={`transition-all duration-500 ease-in-out ${showForm
416
+
? 'opacity-0 -translate-y-5 pointer-events-none absolute inset-0'
417
+
: 'opacity-100 translate-y-0'
418
+
}`}
419
>
420
+
<div className="flex flex-col sm:flex-row gap-3 justify-center">
421
+
<Button
422
+
size="lg"
423
+
className="bg-foreground text-background hover:bg-foreground/90 text-base px-6 py-5 btn-hover-lift"
424
+
onClick={() => setShowForm(true)}
425
+
>
426
+
<span className="mr-2 font-bold">@</span>
427
+
Deploy with AT
428
+
</Button>
429
+
<Button
430
+
variant="outline"
431
+
size="lg"
432
+
className="text-base px-6 py-5 btn-hover-lift"
433
+
asChild
434
+
>
435
+
<a href="https://docs.wisp.place/cli/" target="_blank" rel="noopener noreferrer">
436
+
<span className="font-mono mr-2 text-muted-foreground">>_</span>
437
+
Install wisp-cli
438
+
</a>
439
+
</Button>
440
+
</div>
441
</div>
442
443
<div
444
+
className={`transition-all duration-500 ease-in-out ${showForm
445
+
? 'opacity-100 translate-y-0'
446
+
: 'opacity-0 translate-y-5 pointer-events-none absolute inset-0'
447
+
}`}
448
>
449
<form
450
onSubmit={async (e) => {
···
505
</ActorTypeahead>
506
<button
507
type="submit"
508
+
className="w-full bg-foreground text-background hover:bg-foreground/90 font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors btn-hover-lift"
509
>
510
Continue
511
<ArrowRight className="ml-2 w-5 h-5" />
···
529
</div>
530
<div>
531
<h3 className="text-xl font-semibold mb-2">
532
+
Drop in your files
533
</h3>
534
<p className="text-muted-foreground">
535
+
Upload your site through our dashboard or push with the CLI.
536
+
Everything gets stored directly in your AT Protocol account.
537
</p>
538
</div>
539
</div>
···
543
</div>
544
<div>
545
<h3 className="text-xl font-semibold mb-2">
546
+
We handle the rest
547
</h3>
548
<p className="text-muted-foreground">
549
+
Your site goes live instantly on our global CDN.
550
+
Custom domains, HTTPS, cachingโall automatic.
551
</p>
552
</div>
553
</div>
···
557
</div>
558
<div>
559
<h3 className="text-xl font-semibold mb-2">
560
+
Push updates instantly
561
</h3>
562
<p className="text-muted-foreground">
563
+
Ship changes in seconds. Update through the dashboard,
564
+
run wisp-cli deploy, or wire up your CI/CD pipeline.
565
</p>
566
</div>
567
</div>
···
694
</section>
695
696
{/* Footer */}
697
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
698
<div className="container mx-auto px-4 py-8">
699
<div className="text-center text-sm text-muted-foreground">
700
<p>
+16
-19
apps/main-app/public/onboarding/onboarding.tsx
+16
-19
apps/main-app/public/onboarding/onboarding.tsx
···
161
return (
162
<div className="w-full min-h-screen bg-background">
163
{/* Header */}
164
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
165
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
166
<div className="flex items-center gap-2">
167
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
168
<Globe className="w-5 h-5 text-primary-foreground" />
···
179
<div className="mb-8">
180
<div className="flex items-center justify-center gap-2 mb-4">
181
<div
182
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
183
-
step === 'domain'
184
-
? 'bg-primary text-primary-foreground'
185
-
: 'bg-green-500 text-white'
186
-
}`}
187
>
188
{step === 'domain' ? (
189
'1'
···
193
</div>
194
<div className="w-16 h-0.5 bg-border"></div>
195
<div
196
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
197
-
step === 'upload'
198
-
? 'bg-primary text-primary-foreground'
199
-
: step === 'domain'
200
-
? 'bg-muted text-muted-foreground'
201
-
: 'bg-green-500 text-white'
202
-
}`}
203
>
204
{step === 'complete' ? (
205
<CheckCircle2 className="w-5 h-5" />
···
258
{!isCheckingAvailability &&
259
isAvailable !== null && (
260
<div
261
-
className={`absolute right-3 top-1/2 -translate-y-1/2 ${
262
-
isAvailable
263
-
? 'text-green-500'
264
-
: 'text-red-500'
265
-
}`}
266
>
267
{isAvailable ? 'โ' : 'โ'}
268
</div>
···
161
return (
162
<div className="w-full min-h-screen bg-background">
163
{/* Header */}
164
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
165
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
166
<div className="flex items-center gap-2">
167
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
168
<Globe className="w-5 h-5 text-primary-foreground" />
···
179
<div className="mb-8">
180
<div className="flex items-center justify-center gap-2 mb-4">
181
<div
182
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'domain'
183
+
? 'bg-primary text-primary-foreground'
184
+
: 'bg-green-500 text-white'
185
+
}`}
186
>
187
{step === 'domain' ? (
188
'1'
···
192
</div>
193
<div className="w-16 h-0.5 bg-border"></div>
194
<div
195
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'upload'
196
+
? 'bg-primary text-primary-foreground'
197
+
: step === 'domain'
198
+
? 'bg-muted text-muted-foreground'
199
+
: 'bg-green-500 text-white'
200
+
}`}
201
>
202
{step === 'complete' ? (
203
<CheckCircle2 className="w-5 h-5" />
···
256
{!isCheckingAvailability &&
257
isAvailable !== null && (
258
<div
259
+
className={`absolute right-3 top-1/2 -translate-y-1/2 ${isAvailable
260
+
? 'text-green-500'
261
+
: 'text-red-500'
262
+
}`}
263
>
264
{isAvailable ? 'โ' : 'โ'}
265
</div>
+212
-39
apps/main-app/public/styles/global.css
+212
-39
apps/main-app/public/styles/global.css
···
6
:root {
7
color-scheme: light;
8
9
-
/* Warm beige background inspired by Sunset design #E9DDD8 */
10
-
--background: oklch(0.90 0.012 35);
11
-
/* Very dark brown text for strong contrast #2A2420 */
12
-
--foreground: oklch(0.18 0.01 30);
13
14
-
/* Slightly lighter card background */
15
-
--card: oklch(0.93 0.01 35);
16
-
--card-foreground: oklch(0.18 0.01 30);
17
18
-
--popover: oklch(0.93 0.01 35);
19
-
--popover-foreground: oklch(0.18 0.01 30);
20
21
-
/* Dark brown primary inspired by #645343 */
22
-
--primary: oklch(0.35 0.02 35);
23
-
--primary-foreground: oklch(0.95 0.01 35);
24
25
-
/* Bright pink accent for links #FFAAD2 */
26
-
--accent: oklch(0.78 0.15 345);
27
-
--accent-foreground: oklch(0.18 0.01 30);
28
29
-
/* Medium taupe secondary inspired by #867D76 */
30
-
--secondary: oklch(0.52 0.015 30);
31
-
--secondary-foreground: oklch(0.95 0.01 35);
32
33
-
/* Light warm muted background */
34
--muted: oklch(0.88 0.01 35);
35
-
--muted-foreground: oklch(0.42 0.015 30);
36
37
-
--border: oklch(0.75 0.015 30);
38
-
--input: oklch(0.92 0.01 35);
39
-
--ring: oklch(0.72 0.08 15);
40
41
-
--destructive: oklch(0.577 0.245 27.325);
42
-
--destructive-foreground: oklch(0.985 0 0);
43
44
-
--chart-1: oklch(0.78 0.15 345);
45
--chart-2: oklch(0.32 0.04 285);
46
-
--chart-3: oklch(0.56 0.08 220);
47
-
--chart-4: oklch(0.85 0.02 130);
48
-
--chart-5: oklch(0.93 0.03 85);
49
50
--radius: 0.75rem;
51
-
--sidebar: oklch(0.985 0 0);
52
-
--sidebar-foreground: oklch(0.145 0 0);
53
-
--sidebar-primary: oklch(0.205 0 0);
54
-
--sidebar-primary-foreground: oklch(0.985 0 0);
55
-
--sidebar-accent: oklch(0.97 0 0);
56
-
--sidebar-accent-foreground: oklch(0.205 0 0);
57
-
--sidebar-border: oklch(0.922 0 0);
58
-
--sidebar-ring: oklch(0.708 0 0);
59
}
60
61
.dark {
···
160
* {
161
@apply border-border outline-ring/50;
162
}
163
body {
164
@apply bg-background text-foreground;
165
}
166
}
167
168
@keyframes arrow-bounce {
169
-
0%, 100% {
170
transform: translateX(0);
171
}
172
50% {
173
transform: translateX(4px);
174
}
···
189
border-radius: 0.5rem;
190
padding: 1rem;
191
overflow-x: auto;
192
-
border: 1px solid hsl(var(--border));
193
}
194
195
.shiki-wrapper pre {
196
margin: 0 !important;
197
padding: 0 !important;
198
}
···
6
:root {
7
color-scheme: light;
8
9
+
/* Warm beige background inspired by Sunset design */
10
+
--background: oklch(0.92 0.012 35);
11
+
/* Very dark brown text for strong contrast */
12
+
--foreground: oklch(0.15 0.015 30);
13
14
+
/* Slightly lighter card background for elevation */
15
+
--card: oklch(0.95 0.008 35);
16
+
--card-foreground: oklch(0.15 0.015 30);
17
18
+
--popover: oklch(0.96 0.006 35);
19
+
--popover-foreground: oklch(0.15 0.015 30);
20
21
+
/* Dark brown primary - darker for better contrast */
22
+
--primary: oklch(0.30 0.025 35);
23
+
--primary-foreground: oklch(0.96 0.008 35);
24
25
+
/* Deeper pink accent for better visibility */
26
+
--accent: oklch(0.65 0.18 345);
27
+
--accent-foreground: oklch(0.15 0.015 30);
28
29
+
/* Darker taupe secondary for better contrast */
30
+
--secondary: oklch(0.85 0.012 30);
31
+
--secondary-foreground: oklch(0.25 0.02 30);
32
33
+
/* Muted areas with better distinction */
34
--muted: oklch(0.88 0.01 35);
35
+
--muted-foreground: oklch(0.35 0.02 30);
36
37
+
/* Significantly darker border for visibility */
38
+
--border: oklch(0.65 0.02 30);
39
+
/* Input backgrounds lighter than cards */
40
+
--input: oklch(0.97 0.005 35);
41
+
--ring: oklch(0.55 0.12 345);
42
43
+
--destructive: oklch(0.50 0.20 25);
44
+
--destructive-foreground: oklch(0.98 0 0);
45
46
+
--chart-1: oklch(0.65 0.18 345);
47
--chart-2: oklch(0.32 0.04 285);
48
+
--chart-3: oklch(0.50 0.10 220);
49
+
--chart-4: oklch(0.70 0.08 130);
50
+
--chart-5: oklch(0.75 0.06 85);
51
52
--radius: 0.75rem;
53
+
--sidebar: oklch(0.94 0.008 35);
54
+
--sidebar-foreground: oklch(0.15 0.015 30);
55
+
--sidebar-primary: oklch(0.30 0.025 35);
56
+
--sidebar-primary-foreground: oklch(0.96 0.008 35);
57
+
--sidebar-accent: oklch(0.90 0.01 35);
58
+
--sidebar-accent-foreground: oklch(0.20 0.02 30);
59
+
--sidebar-border: oklch(0.65 0.02 30);
60
+
--sidebar-ring: oklch(0.55 0.12 345);
61
}
62
63
.dark {
···
162
* {
163
@apply border-border outline-ring/50;
164
}
165
+
166
+
html {
167
+
scrollbar-gutter: stable;
168
+
}
169
+
170
body {
171
@apply bg-background text-foreground;
172
}
173
}
174
175
@keyframes arrow-bounce {
176
+
177
+
0%,
178
+
100% {
179
transform: translateX(0);
180
}
181
+
182
50% {
183
transform: translateX(4px);
184
}
···
199
border-radius: 0.5rem;
200
padding: 1rem;
201
overflow-x: auto;
202
+
border: 1px solid var(--border);
203
}
204
205
.shiki-wrapper pre {
206
margin: 0 !important;
207
padding: 0 !important;
208
}
209
+
210
+
/* ========== Landing Page Animations ========== */
211
+
212
+
/* Animated gradient for headline text */
213
+
@keyframes gradient-shift {
214
+
215
+
0%,
216
+
100% {
217
+
background-position: 0% 50%;
218
+
}
219
+
220
+
50% {
221
+
background-position: 100% 50%;
222
+
}
223
+
}
224
+
225
+
.text-gradient-animate {
226
+
background: linear-gradient(90deg,
227
+
oklch(0.55 0.22 350),
228
+
oklch(0.60 0.24 10),
229
+
oklch(0.55 0.22 350));
230
+
background-size: 200% auto;
231
+
-webkit-background-clip: text;
232
+
background-clip: text;
233
+
-webkit-text-fill-color: transparent;
234
+
animation: gradient-shift 4s ease-in-out infinite;
235
+
}
236
+
237
+
.dark .text-gradient-animate {
238
+
background: linear-gradient(90deg,
239
+
oklch(0.75 0.12 295),
240
+
oklch(0.85 0.10 5),
241
+
oklch(0.75 0.12 295));
242
+
background-size: 200% auto;
243
+
-webkit-background-clip: text;
244
+
background-clip: text;
245
+
-webkit-text-fill-color: transparent;
246
+
}
247
+
248
+
/* Floating/breathing animation for hero elements */
249
+
@keyframes float {
250
+
251
+
0%,
252
+
100% {
253
+
transform: translateY(0);
254
+
}
255
+
256
+
50% {
257
+
transform: translateY(-8px);
258
+
}
259
+
}
260
+
261
+
.animate-float {
262
+
animation: float 3s ease-in-out infinite;
263
+
}
264
+
265
+
.animate-float-delayed {
266
+
animation: float 3s ease-in-out infinite;
267
+
animation-delay: 0.5s;
268
+
}
269
+
270
+
/* Staggered fade-in animation */
271
+
@keyframes fade-in-up {
272
+
from {
273
+
opacity: 0;
274
+
transform: translateY(20px);
275
+
}
276
+
277
+
to {
278
+
opacity: 1;
279
+
transform: translateY(0);
280
+
}
281
+
}
282
+
283
+
.animate-fade-in-up {
284
+
animation: fade-in-up 0.6s ease-out forwards;
285
+
opacity: 0;
286
+
}
287
+
288
+
.animate-delay-100 {
289
+
animation-delay: 0.1s;
290
+
}
291
+
292
+
.animate-delay-200 {
293
+
animation-delay: 0.2s;
294
+
}
295
+
296
+
.animate-delay-300 {
297
+
animation-delay: 0.3s;
298
+
}
299
+
300
+
.animate-delay-400 {
301
+
animation-delay: 0.4s;
302
+
}
303
+
304
+
.animate-delay-500 {
305
+
animation-delay: 0.5s;
306
+
}
307
+
308
+
.animate-delay-600 {
309
+
animation-delay: 0.6s;
310
+
}
311
+
312
+
/* Terminal cursor blink */
313
+
@keyframes cursor-blink {
314
+
315
+
0%,
316
+
50% {
317
+
opacity: 1;
318
+
}
319
+
320
+
51%,
321
+
100% {
322
+
opacity: 0;
323
+
}
324
+
}
325
+
326
+
.animate-cursor-blink {
327
+
animation: cursor-blink 1s step-end infinite;
328
+
}
329
+
330
+
/* Button hover scale effect */
331
+
.btn-hover-lift {
332
+
transition: all 0.2s ease-out;
333
+
}
334
+
335
+
.btn-hover-lift:hover {
336
+
transform: translateY(-2px);
337
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
338
+
}
339
+
340
+
.btn-hover-lift:active {
341
+
transform: translateY(0);
342
+
}
343
+
344
+
/* Subtle pulse for feature dots */
345
+
@keyframes dot-pulse {
346
+
347
+
0%,
348
+
100% {
349
+
transform: scale(1);
350
+
opacity: 1;
351
+
}
352
+
353
+
50% {
354
+
transform: scale(1.2);
355
+
opacity: 0.8;
356
+
}
357
+
}
358
+
359
+
.animate-dot-pulse {
360
+
animation: dot-pulse 2s ease-in-out infinite;
361
+
}
362
+
363
+
.animate-dot-pulse-delayed-1 {
364
+
animation: dot-pulse 2s ease-in-out infinite;
365
+
animation-delay: 0.3s;
366
+
}
367
+
368
+
.animate-dot-pulse-delayed-2 {
369
+
animation: dot-pulse 2s ease-in-out infinite;
370
+
animation-delay: 0.6s;
371
+
}
+23
-3
apps/main-app/src/index.ts
+23
-3
apps/main-app/src/index.ts
···
12
cleanupExpiredSessions,
13
rotateKeysIfNeeded
14
} from './lib/oauth-client'
15
-
import { getCookieSecret } from './lib/db'
16
import { authRoutes } from './routes/auth'
17
import { wispRoutes } from './routes/wisp'
18
import { domainRoutes } from './routes/domain'
···
61
setInterval(runMaintenance, 60 * 60 * 1000)
62
63
// Start DNS verification worker (runs every 10 minutes)
64
const dnsVerifier = new DNSVerificationWorker(
65
10 * 60 * 1000, // 10 minutes
66
(msg, data) => {
···
68
}
69
)
70
71
-
dnsVerifier.start()
72
-
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
73
74
export const app = new Elysia({
75
serve: {
···
200
console.log(
201
`๐ฆ Elysia is running at ${app.server?.hostname}:${app.server?.port}`
202
)
···
12
cleanupExpiredSessions,
13
rotateKeysIfNeeded
14
} from './lib/oauth-client'
15
+
import { getCookieSecret, closeDatabase } from './lib/db'
16
import { authRoutes } from './routes/auth'
17
import { wispRoutes } from './routes/wisp'
18
import { domainRoutes } from './routes/domain'
···
61
setInterval(runMaintenance, 60 * 60 * 1000)
62
63
// Start DNS verification worker (runs every 10 minutes)
64
+
// Can be disabled via DISABLE_DNS_WORKER=true environment variable
65
const dnsVerifier = new DNSVerificationWorker(
66
10 * 60 * 1000, // 10 minutes
67
(msg, data) => {
···
69
}
70
)
71
72
+
if (Bun.env.DISABLE_DNS_WORKER !== 'true') {
73
+
dnsVerifier.start()
74
+
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
75
+
} else {
76
+
logger.info('DNS Verifier disabled via DISABLE_DNS_WORKER environment variable')
77
+
}
78
79
export const app = new Elysia({
80
serve: {
···
205
console.log(
206
`๐ฆ Elysia is running at ${app.server?.hostname}:${app.server?.port}`
207
)
208
+
209
+
// Graceful shutdown
210
+
process.on('SIGINT', async () => {
211
+
console.log('\n๐ Shutting down...')
212
+
dnsVerifier.stop()
213
+
await closeDatabase()
214
+
process.exit(0)
215
+
})
216
+
217
+
process.on('SIGTERM', async () => {
218
+
console.log('\n๐ Shutting down...')
219
+
dnsVerifier.stop()
220
+
await closeDatabase()
221
+
process.exit(0)
222
+
})
+13
apps/main-app/src/lib/db.ts
+13
apps/main-app/src/lib/db.ts
···
526
console.log('[CookieSecret] Generated new cookie signing secret');
527
return secret;
528
};
529
+
530
+
/**
531
+
* Close database connection
532
+
* Call this during graceful shutdown
533
+
*/
534
+
export const closeDatabase = async (): Promise<void> => {
535
+
try {
536
+
await db.end();
537
+
console.log('[DB] Database connection closed');
538
+
} catch (err) {
539
+
console.error('[DB] Error closing database connection:', err);
540
+
}
541
+
};
+10
-16
apps/main-app/src/routes/wisp.ts
+10
-16
apps/main-app/src/routes/wisp.ts
···
39
40
const logger = createLogger('main-app')
41
42
-
function isValidSiteName(siteName: string): boolean {
43
if (!siteName || typeof siteName !== 'string') return false;
44
45
// Length check (AT Protocol rkey limit)
···
183
continue;
184
}
185
186
-
console.log(`Processing file ${i + 1}/${fileArray.length}:`, file.name, file.size, 'bytes');
187
updateJobProgress(jobId, {
188
filesProcessed: i + 1,
189
-
currentFile: file.name
190
});
191
192
// Skip files that match ignore patterns
193
-
const normalizedPath = file.name.replace(/^[^\/]*\//, '');
194
195
if (shouldIgnore(ignoreMatcher, normalizedPath)) {
196
-
console.log(`Skipping ignored file: ${file.name}`);
197
skippedFiles.push({
198
-
name: file.name,
199
reason: 'matched ignore pattern'
200
});
201
continue;
···
205
const maxSize = MAX_FILE_SIZE;
206
if (file.size > maxSize) {
207
skippedFiles.push({
208
-
name: file.name,
209
reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)`
210
});
211
continue;
···
238
// Text files: compress AND base64 encode
239
finalContent = Buffer.from(compressedContent.toString('base64'), 'binary');
240
base64Encoded = true;
241
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
242
-
console.log(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
243
-
logger.info(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
244
} else {
245
// Audio files: just compress, no base64
246
finalContent = compressedContent;
247
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
248
-
console.log(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
249
-
logger.info(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
250
}
251
} else {
252
// Binary files: upload directly
253
finalContent = originalContent;
254
-
console.log(`Uploading ${file.name} directly: ${originalContent.length} bytes (no compression)`);
255
-
logger.info(`Uploading ${file.name} directly: ${originalContent.length} bytes (binary)`);
256
}
257
258
uploadedFiles.push({
259
-
name: file.name,
260
content: finalContent,
261
mimeType: originalMimeType,
262
size: finalContent.length,
···
39
40
const logger = createLogger('main-app')
41
42
+
export function isValidSiteName(siteName: string): boolean {
43
if (!siteName || typeof siteName !== 'string') return false;
44
45
// Length check (AT Protocol rkey limit)
···
183
continue;
184
}
185
186
+
// Use webkitRelativePath when available (directory uploads), fallback to name for regular file uploads
187
+
const webkitPath = 'webkitRelativePath' in file ? String(file.webkitRelativePath) : '';
188
+
const filePath = webkitPath || file.name;
189
+
190
updateJobProgress(jobId, {
191
filesProcessed: i + 1,
192
+
currentFile: filePath
193
});
194
195
// Skip files that match ignore patterns
196
+
const normalizedPath = filePath.replace(/^[^\/]*\//, '');
197
198
if (shouldIgnore(ignoreMatcher, normalizedPath)) {
199
skippedFiles.push({
200
+
name: filePath,
201
reason: 'matched ignore pattern'
202
});
203
continue;
···
207
const maxSize = MAX_FILE_SIZE;
208
if (file.size > maxSize) {
209
skippedFiles.push({
210
+
name: filePath,
211
reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)`
212
});
213
continue;
···
240
// Text files: compress AND base64 encode
241
finalContent = Buffer.from(compressedContent.toString('base64'), 'binary');
242
base64Encoded = true;
243
} else {
244
// Audio files: just compress, no base64
245
finalContent = compressedContent;
246
}
247
} else {
248
// Binary files: upload directly
249
finalContent = originalContent;
250
}
251
252
uploadedFiles.push({
253
+
name: filePath,
254
content: finalContent,
255
mimeType: originalMimeType,
256
size: finalContent.length,
+59
backup.nix
+59
backup.nix
···
···
1
+
{
2
+
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
3
+
inputs.nci.url = "github:90-008/nix-cargo-integration";
4
+
inputs.nci.inputs.nixpkgs.follows = "nixpkgs";
5
+
inputs.parts.url = "github:hercules-ci/flake-parts";
6
+
inputs.parts.inputs.nixpkgs-lib.follows = "nixpkgs";
7
+
inputs.fenix = {
8
+
url = "github:nix-community/fenix";
9
+
inputs.nixpkgs.follows = "nixpkgs";
10
+
};
11
+
12
+
outputs = inputs @ {
13
+
parts,
14
+
nci,
15
+
...
16
+
}:
17
+
parts.lib.mkFlake {inherit inputs;} {
18
+
systems = ["x86_64-linux" "aarch64-darwin"];
19
+
imports = [
20
+
nci.flakeModule
21
+
./crates.nix
22
+
];
23
+
perSystem = {
24
+
pkgs,
25
+
config,
26
+
...
27
+
}: let
28
+
crateOutputs = config.nci.outputs."wisp-cli";
29
+
mkRenamedPackage = name: pkg: isWindows: pkgs.runCommand name {} ''
30
+
mkdir -p $out/bin
31
+
if [ -f ${pkg}/bin/wisp-cli.exe ]; then
32
+
cp ${pkg}/bin/wisp-cli.exe $out/bin/${name}
33
+
elif [ -f ${pkg}/bin/wisp-cli ]; then
34
+
cp ${pkg}/bin/wisp-cli $out/bin/${name}
35
+
else
36
+
echo "Error: Could not find wisp-cli binary in ${pkg}/bin/"
37
+
ls -la ${pkg}/bin/ || true
38
+
exit 1
39
+
fi
40
+
'';
41
+
in {
42
+
devShells.default = crateOutputs.devShell;
43
+
packages.default = crateOutputs.packages.release;
44
+
packages.wisp-cli-x86_64-linux = mkRenamedPackage "wisp-cli-x86_64-linux" crateOutputs.packages.release false;
45
+
packages.wisp-cli-aarch64-linux = mkRenamedPackage "wisp-cli-aarch64-linux" crateOutputs.allTargets."aarch64-unknown-linux-gnu".packages.release false;
46
+
packages.wisp-cli-x86_64-windows = mkRenamedPackage "wisp-cli-x86_64-windows.exe" crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release true;
47
+
packages.wisp-cli-aarch64-darwin = mkRenamedPackage "wisp-cli-aarch64-darwin" crateOutputs.allTargets."aarch64-apple-darwin".packages.release false;
48
+
packages.all = pkgs.symlinkJoin {
49
+
name = "wisp-cli-all";
50
+
paths = [
51
+
config.packages.wisp-cli-x86_64-linux
52
+
config.packages.wisp-cli-aarch64-linux
53
+
config.packages.wisp-cli-x86_64-windows
54
+
config.packages.wisp-cli-aarch64-darwin
55
+
];
56
+
};
57
+
};
58
+
};
59
+
}
+241
-6
bun.lock
+241
-6
bun.lock
···
8
"@tailwindcss/cli": "^4.1.17",
9
"atproto-ui": "^0.12.0",
10
"bun-plugin-tailwind": "^0.1.2",
11
"tailwindcss": "^4.1.17",
12
},
13
},
14
"apps/hosting-service": {
···
32
"mime-types": "^2.1.35",
33
"multiformats": "^13.4.1",
34
"postgres": "^3.4.5",
35
},
36
"devDependencies": {
37
"@types/bun": "^1.3.1",
···
75
"bun-plugin-tailwind": "^0.1.2",
76
"class-variance-authority": "^0.7.1",
77
"clsx": "^2.1.1",
78
-
"elysia": "latest",
79
"ignore": "^7.0.5",
80
"iron-session": "^8.0.4",
81
"lucide-react": "^0.546.0",
···
158
"typescript": "^5.9.3",
159
},
160
"peerDependencies": {
161
-
"hono": "",
162
},
163
"optionalPeers": [
164
"hono",
···
258
259
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.6", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/ws-client": "^0.0.2", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-N/wPK0VEk8lZLkVsfG1wlkINQnBLO2fzWT+xclOjYl5lJwDi5xgiiyEQJAyZN49d6cmbsONu0SuOVw9pa5xLCw=="],
260
261
"@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="],
262
263
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
···
540
541
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
542
543
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
544
545
"@tailwindcss/cli": ["@tailwindcss/cli@4.1.17", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "enhanced-resolve": "^5.18.3", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.17" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg=="],
···
582
583
"@ts-morph/common": ["@ts-morph/common@0.25.0", "", { "dependencies": { "minimatch": "^9.0.4", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.9" } }, "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg=="],
584
585
-
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
586
587
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
588
···
640
641
"body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="],
642
643
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
644
645
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
646
647
-
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
648
649
"bun": ["bun@1.3.3", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.3", "@oven/bun-darwin-x64": "1.3.3", "@oven/bun-darwin-x64-baseline": "1.3.3", "@oven/bun-linux-aarch64": "1.3.3", "@oven/bun-linux-aarch64-musl": "1.3.3", "@oven/bun-linux-x64": "1.3.3", "@oven/bun-linux-x64-baseline": "1.3.3", "@oven/bun-linux-x64-musl": "1.3.3", "@oven/bun-linux-x64-musl-baseline": "1.3.3", "@oven/bun-windows-x64": "1.3.3", "@oven/bun-windows-x64-baseline": "1.3.3" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw=="],
650
651
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
652
653
-
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
654
655
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
656
···
745
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
746
747
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
748
749
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
750
···
956
957
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
958
959
-
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
960
961
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
962
···
1000
1001
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
1002
1003
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
1004
1005
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
1006
1007
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1008
1009
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
1010
···
1020
1021
"thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="],
1022
1023
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
1024
1025
"tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="],
···
1059
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
1060
1061
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
1062
1063
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
1064
···
1114
1115
"@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1116
1117
"@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1118
1119
"@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
···
1266
1267
"@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1268
1269
"@wisp/main-app/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw=="],
1270
1271
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
1272
···
1276
1277
"node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
1278
1279
"require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1280
1281
"send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
···
1285
"send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1286
1287
"serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
1288
1289
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1290
···
1298
1299
"@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
1300
1301
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1302
1303
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
···
1334
1335
"@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1336
1337
"require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1338
1339
"serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
···
1344
1345
"serve-static/send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1346
1347
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="],
1348
1349
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="],
···
1397
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
1398
1399
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1400
}
1401
}
···
8
"@tailwindcss/cli": "^4.1.17",
9
"atproto-ui": "^0.12.0",
10
"bun-plugin-tailwind": "^0.1.2",
11
+
"elysia": "^1.4.18",
12
"tailwindcss": "^4.1.17",
13
+
},
14
+
"devDependencies": {
15
+
"@types/bun": "^1.3.5",
16
},
17
},
18
"apps/hosting-service": {
···
36
"mime-types": "^2.1.35",
37
"multiformats": "^13.4.1",
38
"postgres": "^3.4.5",
39
+
"tiered-storage": "1.0.3",
40
},
41
"devDependencies": {
42
"@types/bun": "^1.3.1",
···
80
"bun-plugin-tailwind": "^0.1.2",
81
"class-variance-authority": "^0.7.1",
82
"clsx": "^2.1.1",
83
+
"elysia": "^1.4.18",
84
"ignore": "^7.0.5",
85
"iron-session": "^8.0.4",
86
"lucide-react": "^0.546.0",
···
163
"typescript": "^5.9.3",
164
},
165
"peerDependencies": {
166
+
"hono": "^4.10.7",
167
},
168
"optionalPeers": [
169
"hono",
···
263
264
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.6", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/ws-client": "^0.0.2", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-N/wPK0VEk8lZLkVsfG1wlkINQnBLO2fzWT+xclOjYl5lJwDi5xgiiyEQJAyZN49d6cmbsONu0SuOVw9pa5xLCw=="],
265
266
+
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
267
+
268
+
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
269
+
270
+
"@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
271
+
272
+
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
273
+
274
+
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
275
+
276
+
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
277
+
278
+
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
279
+
280
+
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.962.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-node": "3.962.0", "@aws-sdk/middleware-bucket-endpoint": "3.957.0", "@aws-sdk/middleware-expect-continue": "3.957.0", "@aws-sdk/middleware-flexible-checksums": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-location-constraint": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-sdk-s3": "3.957.0", "@aws-sdk/middleware-ssec": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/signature-v4-multi-region": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/eventstream-serde-browser": "^4.2.7", "@smithy/eventstream-serde-config-resolver": "^4.3.7", "@smithy/eventstream-serde-node": "^4.2.7", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-blob-browser": "^4.2.8", "@smithy/hash-node": "^4.2.7", "@smithy/hash-stream-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/md5-js": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-I2/1McBZCcM3PfM4ck8D6gnZR3K7+yl1fGkwTq/3ThEn9tdLjNwcdgTbPfxfX6LoecLrH9Ekoo+D9nmQ0T261w=="],
281
+
282
+
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.958.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg=="],
283
+
284
+
"@aws-sdk/core": ["@aws-sdk/core@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws-sdk/xml-builder": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw=="],
285
+
286
+
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA=="],
287
+
288
+
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog=="],
289
+
290
+
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw=="],
291
+
292
+
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.962.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-login": "3.962.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-h0kVnXLW2d3nxbcrR/Pfg3W/+YoCguasWz7/3nYzVqmdKarGrpJzaFdoZtLgvDSZ8VgWUC4lWOTcsDMV0UNqUQ=="],
293
+
294
+
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.962.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-kHYH6Av2UifG3mPkpPUNRh/PuX6adaAcpmsclJdHdxlixMCRdh8GNeEihq480DC0GmfqdpoSf1w2CLmLLPIS6w=="],
295
+
296
+
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.962.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-ini": "3.962.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-CS78NsWRxLa+nWqeWBEYMZTLacMFIXs1C5WJuM9kD05LLiWL32ksljoPsvNN24Bc7rCSQIIMx/U3KGvkDVZMVg=="],
297
+
298
+
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg=="],
299
+
300
+
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.958.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.958.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/token-providers": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg=="],
301
+
302
+
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.958.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA=="],
303
+
304
+
"@aws-sdk/lib-storage": ["@aws-sdk/lib-storage@3.962.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/smithy-client": "^4.10.2", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-s3": "^3.962.0" } }, "sha512-Ai5gWRQkzsUMQ6NPoZZoiLXoQ6/yPRcR4oracIVjyWcu48TfBpsRgbqY/5zNOM55ag1wPX9TtJJGOhK3TNk45g=="],
305
+
306
+
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws-sdk/util-arn-parser": "3.957.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA=="],
307
+
308
+
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg=="],
309
+
310
+
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.957.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/crc64-nvme": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA=="],
311
+
312
+
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA=="],
313
+
314
+
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ=="],
315
+
316
+
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ=="],
317
+
318
+
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA=="],
319
+
320
+
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-arn-parser": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg=="],
321
+
322
+
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ=="],
323
+
324
+
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ=="],
325
+
326
+
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.958.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw=="],
327
+
328
+
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A=="],
329
+
330
+
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.957.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg=="],
331
+
332
+
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.958.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q=="],
333
+
334
+
"@aws-sdk/types": ["@aws-sdk/types@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg=="],
335
+
336
+
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.957.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g=="],
337
+
338
+
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" } }, "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw=="],
339
+
340
+
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.957.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw=="],
341
+
342
+
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw=="],
343
+
344
+
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.957.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q=="],
345
+
346
+
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA=="],
347
+
348
+
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="],
349
+
350
"@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="],
351
352
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
···
629
630
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
631
632
+
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw=="],
633
+
634
+
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="],
635
+
636
+
"@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="],
637
+
638
+
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg=="],
639
+
640
+
"@smithy/core": ["@smithy/core@3.20.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.8", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ=="],
641
+
642
+
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA=="],
643
+
644
+
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
645
+
646
+
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.7", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g=="],
647
+
648
+
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ=="],
649
+
650
+
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.7", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A=="],
651
+
652
+
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.7", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g=="],
653
+
654
+
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg=="],
655
+
656
+
"@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.8", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw=="],
657
+
658
+
"@smithy/hash-node": ["@smithy/hash-node@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw=="],
659
+
660
+
"@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ=="],
661
+
662
+
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ=="],
663
+
664
+
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
665
+
666
+
"@smithy/md5-js": ["@smithy/md5-js@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw=="],
667
+
668
+
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg=="],
669
+
670
+
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.1", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-serde": "^4.2.8", "@smithy/node-config-provider": "^4.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg=="],
671
+
672
+
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/service-error-classification": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg=="],
673
+
674
+
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w=="],
675
+
676
+
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw=="],
677
+
678
+
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.7", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw=="],
679
+
680
+
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.7", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ=="],
681
+
682
+
"@smithy/property-provider": ["@smithy/property-provider@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="],
683
+
684
+
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA=="],
685
+
686
+
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg=="],
687
+
688
+
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w=="],
689
+
690
+
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0" } }, "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA=="],
691
+
692
+
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.2", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg=="],
693
+
694
+
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.7", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg=="],
695
+
696
+
"@smithy/smithy-client": ["@smithy/smithy-client@4.10.2", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-stack": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g=="],
697
+
698
+
"@smithy/types": ["@smithy/types@4.11.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="],
699
+
700
+
"@smithy/url-parser": ["@smithy/url-parser@4.2.7", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg=="],
701
+
702
+
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
703
+
704
+
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
705
+
706
+
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
707
+
708
+
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
709
+
710
+
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
711
+
712
+
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.16", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ=="],
713
+
714
+
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.19", "", { "dependencies": { "@smithy/config-resolver": "^4.4.5", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA=="],
715
+
716
+
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg=="],
717
+
718
+
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
719
+
720
+
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w=="],
721
+
722
+
"@smithy/util-retry": ["@smithy/util-retry@4.2.7", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg=="],
723
+
724
+
"@smithy/util-stream": ["@smithy/util-stream@4.5.8", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="],
725
+
726
+
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
727
+
728
+
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
729
+
730
+
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.7", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw=="],
731
+
732
+
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
733
+
734
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
735
736
"@tailwindcss/cli": ["@tailwindcss/cli@4.1.17", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "enhanced-resolve": "^5.18.3", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.17" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg=="],
···
773
774
"@ts-morph/common": ["@ts-morph/common@0.25.0", "", { "dependencies": { "minimatch": "^9.0.4", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.9" } }, "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg=="],
775
776
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
777
778
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
779
···
831
832
"body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="],
833
834
+
"bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
835
+
836
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
837
838
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
839
840
+
"buffer": ["buffer@5.6.0", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw=="],
841
842
"bun": ["bun@1.3.3", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.3", "@oven/bun-darwin-x64": "1.3.3", "@oven/bun-darwin-x64-baseline": "1.3.3", "@oven/bun-linux-aarch64": "1.3.3", "@oven/bun-linux-aarch64-musl": "1.3.3", "@oven/bun-linux-x64": "1.3.3", "@oven/bun-linux-x64-baseline": "1.3.3", "@oven/bun-linux-x64-musl": "1.3.3", "@oven/bun-linux-x64-musl-baseline": "1.3.3", "@oven/bun-windows-x64": "1.3.3", "@oven/bun-windows-x64-baseline": "1.3.3" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw=="],
843
844
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
845
846
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
847
848
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
849
···
938
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
939
940
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
941
+
942
+
"fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
943
944
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
945
···
1151
1152
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
1153
1154
+
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
1155
1156
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
1157
···
1195
1196
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
1197
1198
+
"stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="],
1199
+
1200
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
1201
1202
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
1203
1204
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1205
+
1206
+
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
1207
1208
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
1209
···
1219
1220
"thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="],
1221
1222
+
"tiered-storage": ["tiered-storage@1.0.3", "", { "dependencies": { "@aws-sdk/client-s3": "^3.500.0", "@aws-sdk/lib-storage": "^3.500.0", "hono": "^4.10.7", "mime-types": "^3.0.2", "tiny-lru": "^11.0.0" } }, "sha512-ntJCsWWYHSQWIDbObMfnJ8xw7cRSJCPzfbrjoY4hu0YIRizCthRWg8kCvJOgsgC+Upt259YHmfNwEr1wLv6Rxw=="],
1223
+
1224
+
"tiny-lru": ["tiny-lru@11.4.5", "", {}, "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw=="],
1225
+
1226
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
1227
1228
"tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="],
···
1262
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
1263
1264
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
1265
+
1266
+
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
1267
1268
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
1269
···
1319
1320
"@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1321
1322
+
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1323
+
1324
+
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1325
+
1326
+
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1327
+
1328
"@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1329
1330
"@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
···
1477
1478
"@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1479
1480
+
"@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
1481
+
1482
"@wisp/main-app/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw=="],
1483
+
1484
+
"@wisp/observability/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
1485
1486
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
1487
···
1491
1492
"node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
1493
1494
+
"pino-abstract-transport/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
1495
+
1496
"require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1497
1498
"send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
···
1502
"send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1503
1504
"serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
1505
+
1506
+
"tiered-storage/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
1507
1508
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1509
···
1517
1518
"@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
1519
1520
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1521
+
1522
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1523
+
1524
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1525
+
1526
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1527
1528
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
···
1559
1560
"@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1561
1562
+
"pino-abstract-transport/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
1563
+
1564
"require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1565
1566
"serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
···
1571
1572
"serve-static/send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1573
1574
+
"tiered-storage/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1575
+
1576
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="],
1577
1578
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="],
···
1626
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
1627
1628
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1629
+
1630
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1631
+
1632
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1633
+
1634
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1635
}
1636
}
+370
-503
cli/Cargo.lock
+370
-503
cli/Cargo.lock
···
3
version = 4
4
5
[[package]]
6
-
name = "abnf"
7
-
version = "0.13.0"
8
-
source = "registry+https://github.com/rust-lang/crates.io-index"
9
-
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
-
dependencies = [
11
-
"abnf-core",
12
-
"nom",
13
-
]
14
-
15
-
[[package]]
16
-
name = "abnf-core"
17
-
version = "0.5.0"
18
-
source = "registry+https://github.com/rust-lang/crates.io-index"
19
-
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
20
-
dependencies = [
21
-
"nom",
22
-
]
23
-
24
-
[[package]]
25
name = "addr2line"
26
version = "0.25.1"
27
source = "registry+https://github.com/rust-lang/crates.io-index"
···
71
dependencies = [
72
"alloc-no-stdlib",
73
]
74
75
[[package]]
76
name = "android_system_properties"
···
139
140
[[package]]
141
name = "async-compression"
142
-
version = "0.4.34"
143
source = "registry+https://github.com/rust-lang/crates.io-index"
144
-
checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
145
dependencies = [
146
"compression-codecs",
147
"compression-core",
···
158
dependencies = [
159
"proc-macro2",
160
"quote",
161
-
"syn 2.0.111",
162
]
163
164
[[package]]
···
175
176
[[package]]
177
name = "axum"
178
-
version = "0.8.7"
179
source = "registry+https://github.com/rust-lang/crates.io-index"
180
-
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
181
dependencies = [
182
"axum-core",
183
"bytes",
···
208
209
[[package]]
210
name = "axum-core"
211
-
version = "0.5.5"
212
source = "registry+https://github.com/rust-lang/crates.io-index"
213
-
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
214
dependencies = [
215
"bytes",
216
"futures-core",
···
237
"miniz_oxide",
238
"object",
239
"rustc-demangle",
240
-
"windows-link 0.2.1",
241
]
242
243
[[package]]
···
285
286
[[package]]
287
name = "base64ct"
288
-
version = "1.8.0"
289
source = "registry+https://github.com/rust-lang/crates.io-index"
290
-
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
291
292
[[package]]
293
name = "bitflags"
···
326
"proc-macro2",
327
"quote",
328
"rustversion",
329
-
"syn 2.0.111",
330
]
331
332
[[package]]
333
name = "borsh"
334
-
version = "1.5.7"
335
source = "registry+https://github.com/rust-lang/crates.io-index"
336
-
checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
337
dependencies = [
338
"cfg_aliases",
339
]
···
370
]
371
372
[[package]]
373
-
name = "btree-range-map"
374
-
version = "0.7.2"
375
-
source = "registry+https://github.com/rust-lang/crates.io-index"
376
-
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
377
-
dependencies = [
378
-
"btree-slab",
379
-
"cc-traits",
380
-
"range-traits",
381
-
"serde",
382
-
"slab",
383
-
]
384
-
385
-
[[package]]
386
-
name = "btree-slab"
387
-
version = "0.6.1"
388
-
source = "registry+https://github.com/rust-lang/crates.io-index"
389
-
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
390
-
dependencies = [
391
-
"cc-traits",
392
-
"slab",
393
-
"smallvec",
394
-
]
395
-
396
-
[[package]]
397
name = "buf_redux"
398
version = "0.8.4"
399
source = "registry+https://github.com/rust-lang/crates.io-index"
···
405
406
[[package]]
407
name = "bumpalo"
408
-
version = "3.19.0"
409
source = "registry+https://github.com/rust-lang/crates.io-index"
410
-
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
411
412
[[package]]
413
name = "byteorder"
···
435
436
[[package]]
437
name = "cc"
438
-
version = "1.2.47"
439
source = "registry+https://github.com/rust-lang/crates.io-index"
440
-
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
441
dependencies = [
442
"find-msvc-tools",
443
"shlex",
444
]
445
446
[[package]]
447
-
name = "cc-traits"
448
-
version = "2.0.0"
449
-
source = "registry+https://github.com/rust-lang/crates.io-index"
450
-
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
451
-
dependencies = [
452
-
"slab",
453
-
]
454
-
455
-
[[package]]
456
name = "cesu8"
457
version = "1.1.0"
458
source = "registry+https://github.com/rust-lang/crates.io-index"
···
481
"num-traits",
482
"serde",
483
"wasm-bindgen",
484
-
"windows-link 0.2.1",
485
]
486
487
[[package]]
···
533
534
[[package]]
535
name = "clap"
536
-
version = "4.5.53"
537
source = "registry+https://github.com/rust-lang/crates.io-index"
538
-
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
539
dependencies = [
540
"clap_builder",
541
"clap_derive",
···
543
544
[[package]]
545
name = "clap_builder"
546
-
version = "4.5.53"
547
source = "registry+https://github.com/rust-lang/crates.io-index"
548
-
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
549
dependencies = [
550
"anstream",
551
"anstyle",
···
562
"heck 0.5.0",
563
"proc-macro2",
564
"quote",
565
-
"syn 2.0.111",
566
]
567
568
[[package]]
···
572
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
573
574
[[package]]
575
name = "colorchoice"
576
version = "1.0.4"
577
source = "registry+https://github.com/rust-lang/crates.io-index"
···
589
590
[[package]]
591
name = "compression-codecs"
592
-
version = "0.4.33"
593
source = "registry+https://github.com/rust-lang/crates.io-index"
594
-
checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad"
595
dependencies = [
596
"compression-core",
597
"flate2",
···
693
]
694
695
[[package]]
696
name = "crossbeam-channel"
697
version = "0.5.15"
698
source = "registry+https://github.com/rust-lang/crates.io-index"
···
775
"proc-macro2",
776
"quote",
777
"strsim",
778
-
"syn 2.0.111",
779
]
780
781
[[package]]
···
786
dependencies = [
787
"darling_core",
788
"quote",
789
-
"syn 2.0.111",
790
]
791
792
[[package]]
···
826
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
827
dependencies = [
828
"data-encoding",
829
-
"syn 2.0.111",
830
]
831
832
[[package]]
···
857
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
858
dependencies = [
859
"powerfmt",
860
-
"serde_core",
861
]
862
863
[[package]]
···
877
dependencies = [
878
"proc-macro2",
879
"quote",
880
-
"syn 2.0.111",
881
"unicode-xid",
882
]
883
···
928
dependencies = [
929
"proc-macro2",
930
"quote",
931
-
"syn 2.0.111",
932
]
933
934
[[package]]
935
-
name = "dyn-clone"
936
-
version = "1.0.20"
937
-
source = "registry+https://github.com/rust-lang/crates.io-index"
938
-
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
939
-
940
-
[[package]]
941
name = "ecdsa"
942
version = "0.16.9"
943
source = "registry+https://github.com/rust-lang/crates.io-index"
···
972
]
973
974
[[package]]
975
name = "encode_unicode"
976
version = "1.0.0"
977
source = "registry+https://github.com/rust-lang/crates.io-index"
···
995
"heck 0.5.0",
996
"proc-macro2",
997
"quote",
998
-
"syn 2.0.111",
999
]
1000
1001
[[package]]
···
1044
1045
[[package]]
1046
name = "find-msvc-tools"
1047
-
version = "0.1.5"
1048
source = "registry+https://github.com/rust-lang/crates.io-index"
1049
-
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
1050
1051
[[package]]
1052
name = "flate2"
···
1063
version = "1.0.7"
1064
source = "registry+https://github.com/rust-lang/crates.io-index"
1065
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1066
1067
[[package]]
1068
name = "form_urlencoded"
···
1165
dependencies = [
1166
"proc-macro2",
1167
"quote",
1168
-
"syn 2.0.111",
1169
]
1170
1171
[[package]]
···
1200
1201
[[package]]
1202
name = "generator"
1203
-
version = "0.8.7"
1204
source = "registry+https://github.com/rust-lang/crates.io-index"
1205
-
checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2"
1206
dependencies = [
1207
"cc",
1208
"cfg-if",
1209
"libc",
1210
"log",
1211
"rustversion",
1212
-
"windows",
1213
]
1214
1215
[[package]]
···
1319
1320
[[package]]
1321
name = "h2"
1322
-
version = "0.4.12"
1323
source = "registry+https://github.com/rust-lang/crates.io-index"
1324
-
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
1325
dependencies = [
1326
"atomic-waker",
1327
"bytes",
···
1329
"futures-core",
1330
"futures-sink",
1331
"http",
1332
-
"indexmap 2.12.1",
1333
"slab",
1334
"tokio",
1335
"tokio-util",
···
1348
]
1349
1350
[[package]]
1351
-
name = "hashbrown"
1352
-
version = "0.12.3"
1353
source = "registry+https://github.com/rust-lang/crates.io-index"
1354
-
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1355
1356
[[package]]
1357
name = "hashbrown"
···
1361
1362
[[package]]
1363
name = "hashbrown"
1364
version = "0.16.1"
1365
source = "registry+https://github.com/rust-lang/crates.io-index"
1366
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1367
1368
[[package]]
1369
name = "heck"
1370
version = "0.4.1"
1371
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1390
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1391
1392
[[package]]
1393
-
name = "hex_fmt"
1394
-
version = "0.3.0"
1395
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1396
-
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
1397
-
1398
-
[[package]]
1399
name = "hickory-proto"
1400
version = "0.24.4"
1401
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1460
"markup5ever",
1461
"proc-macro2",
1462
"quote",
1463
-
"syn 2.0.111",
1464
]
1465
1466
[[package]]
···
1556
1557
[[package]]
1558
name = "hyper-util"
1559
-
version = "0.1.18"
1560
source = "registry+https://github.com/rust-lang/crates.io-index"
1561
-
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
1562
dependencies = [
1563
"base64 0.22.1",
1564
"bytes",
···
1592
"js-sys",
1593
"log",
1594
"wasm-bindgen",
1595
-
"windows-core 0.62.2",
1596
]
1597
1598
[[package]]
···
1652
1653
[[package]]
1654
name = "icu_properties"
1655
-
version = "2.1.1"
1656
source = "registry+https://github.com/rust-lang/crates.io-index"
1657
-
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
1658
dependencies = [
1659
"icu_collections",
1660
"icu_locale_core",
···
1666
1667
[[package]]
1668
name = "icu_properties_data"
1669
-
version = "2.1.1"
1670
source = "registry+https://github.com/rust-lang/crates.io-index"
1671
-
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
1672
1673
[[package]]
1674
name = "icu_provider"
···
1730
1731
[[package]]
1732
name = "indexmap"
1733
-
version = "1.9.3"
1734
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1735
-
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1736
-
dependencies = [
1737
-
"autocfg",
1738
-
"hashbrown 0.12.3",
1739
-
"serde",
1740
-
]
1741
-
1742
-
[[package]]
1743
-
name = "indexmap"
1744
version = "2.12.1"
1745
source = "registry+https://github.com/rust-lang/crates.io-index"
1746
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
1747
dependencies = [
1748
"equivalent",
1749
"hashbrown 0.16.1",
1750
-
"serde",
1751
-
"serde_core",
1752
]
1753
1754
[[package]]
···
1765
]
1766
1767
[[package]]
1768
-
name = "indoc"
1769
-
version = "2.0.7"
1770
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1771
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1772
-
dependencies = [
1773
-
"rustversion",
1774
-
]
1775
-
1776
-
[[package]]
1777
name = "inventory"
1778
version = "0.3.21"
1779
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1813
1814
[[package]]
1815
name = "iri-string"
1816
-
version = "0.7.9"
1817
source = "registry+https://github.com/rust-lang/crates.io-index"
1818
-
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
1819
dependencies = [
1820
"memchr",
1821
"serde",
···
1835
1836
[[package]]
1837
name = "itoa"
1838
-
version = "1.0.15"
1839
source = "registry+https://github.com/rust-lang/crates.io-index"
1840
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1841
1842
[[package]]
1843
name = "jacquard"
1844
-
version = "0.9.3"
1845
dependencies = [
1846
"bytes",
1847
"getrandom 0.2.16",
···
1870
1871
[[package]]
1872
name = "jacquard-api"
1873
-
version = "0.9.2"
1874
dependencies = [
1875
"bon",
1876
"bytes",
···
1880
"miette",
1881
"rustversion",
1882
"serde",
1883
"serde_ipld_dagcbor",
1884
"thiserror 2.0.17",
1885
"unicode-segmentation",
···
1887
1888
[[package]]
1889
name = "jacquard-common"
1890
-
version = "0.9.2"
1891
dependencies = [
1892
"base64 0.22.1",
1893
"bon",
1894
"bytes",
1895
"chrono",
1896
"ciborium",
1897
"cid",
1898
"futures",
1899
"getrandom 0.2.16",
1900
"getrandom 0.3.4",
1901
"http",
1902
"ipld-core",
1903
"k256",
1904
-
"langtag",
1905
"miette",
1906
"multibase",
1907
"multihash",
1908
"n0-future 0.1.3",
1909
"ouroboros",
1910
"p256",
1911
"rand 0.9.2",
1912
"regex",
1913
"regex-lite",
1914
"reqwest",
1915
"serde",
1916
"serde_html_form",
1917
"serde_ipld_dagcbor",
1918
"serde_json",
1919
"signature",
1920
"smol_str",
1921
"thiserror 2.0.17",
1922
"tokio",
1923
"tokio-tungstenite-wasm",
···
1928
1929
[[package]]
1930
name = "jacquard-derive"
1931
-
version = "0.9.3"
1932
dependencies = [
1933
"heck 0.5.0",
1934
"jacquard-lexicon",
1935
"proc-macro2",
1936
"quote",
1937
-
"syn 2.0.111",
1938
]
1939
1940
[[package]]
1941
name = "jacquard-identity"
1942
-
version = "0.9.2"
1943
dependencies = [
1944
"bon",
1945
"bytes",
···
1949
"jacquard-common",
1950
"jacquard-lexicon",
1951
"miette",
1952
-
"mini-moka",
1953
"percent-encoding",
1954
"reqwest",
1955
"serde",
···
1964
1965
[[package]]
1966
name = "jacquard-lexicon"
1967
-
version = "0.9.2"
1968
dependencies = [
1969
"cid",
1970
"dashmap",
···
1982
"serde_repr",
1983
"serde_with",
1984
"sha2",
1985
-
"syn 2.0.111",
1986
"thiserror 2.0.17",
1987
"unicode-segmentation",
1988
]
1989
1990
[[package]]
1991
name = "jacquard-oauth"
1992
-
version = "0.9.2"
1993
dependencies = [
1994
"base64 0.22.1",
1995
"bytes",
···
2077
2078
[[package]]
2079
name = "js-sys"
2080
-
version = "0.3.82"
2081
source = "registry+https://github.com/rust-lang/crates.io-index"
2082
-
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
2083
dependencies = [
2084
"once_cell",
2085
"wasm-bindgen",
···
2098
]
2099
2100
[[package]]
2101
-
name = "langtag"
2102
-
version = "0.4.0"
2103
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2104
-
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
2105
-
dependencies = [
2106
-
"serde",
2107
-
"static-regular-grammar",
2108
-
"thiserror 1.0.69",
2109
-
]
2110
-
2111
-
[[package]]
2112
name = "lazy_static"
2113
version = "1.5.0"
2114
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2119
2120
[[package]]
2121
name = "libc"
2122
-
version = "0.2.177"
2123
source = "registry+https://github.com/rust-lang/crates.io-index"
2124
-
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
2125
2126
[[package]]
2127
name = "libm"
···
2131
2132
[[package]]
2133
name = "libredox"
2134
-
version = "0.1.10"
2135
source = "registry+https://github.com/rust-lang/crates.io-index"
2136
-
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
2137
dependencies = [
2138
"bitflags",
2139
"libc",
2140
-
"redox_syscall",
2141
]
2142
2143
[[package]]
···
2169
2170
[[package]]
2171
name = "log"
2172
-
version = "0.4.28"
2173
source = "registry+https://github.com/rust-lang/crates.io-index"
2174
-
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
2175
2176
[[package]]
2177
name = "loom"
···
2208
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
2209
2210
[[package]]
2211
name = "markup5ever"
2212
version = "0.12.1"
2213
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2292
dependencies = [
2293
"proc-macro2",
2294
"quote",
2295
-
"syn 2.0.111",
2296
]
2297
2298
[[package]]
···
2312
]
2313
2314
[[package]]
2315
-
name = "mini-moka"
2316
version = "0.10.99"
2317
dependencies = [
2318
"crossbeam-channel",
2319
"crossbeam-utils",
···
2325
]
2326
2327
[[package]]
2328
-
name = "minimal-lexical"
2329
-
version = "0.2.1"
2330
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2331
-
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2332
-
2333
-
[[package]]
2334
name = "miniz_oxide"
2335
version = "0.8.9"
2336
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2342
2343
[[package]]
2344
name = "mio"
2345
-
version = "1.1.0"
2346
source = "registry+https://github.com/rust-lang/crates.io-index"
2347
-
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
2348
dependencies = [
2349
"libc",
2350
"wasi",
···
2393
]
2394
2395
[[package]]
2396
name = "n0-future"
2397
version = "0.1.3"
2398
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2447
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2448
2449
[[package]]
2450
-
name = "nom"
2451
-
version = "7.1.3"
2452
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2453
-
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2454
-
dependencies = [
2455
-
"memchr",
2456
-
"minimal-lexical",
2457
-
]
2458
-
2459
-
[[package]]
2460
name = "nu-ansi-term"
2461
version = "0.50.3"
2462
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2590
2591
[[package]]
2592
name = "openssl-probe"
2593
-
version = "0.1.6"
2594
source = "registry+https://github.com/rust-lang/crates.io-index"
2595
-
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
2596
2597
[[package]]
2598
name = "option-ext"
···
2621
"proc-macro2",
2622
"proc-macro2-diagnostics",
2623
"quote",
2624
-
"syn 2.0.111",
2625
]
2626
2627
[[package]]
···
2631
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
2632
2633
[[package]]
2634
name = "p256"
2635
version = "0.13.2"
2636
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2676
dependencies = [
2677
"cfg-if",
2678
"libc",
2679
-
"redox_syscall",
2680
"smallvec",
2681
-
"windows-link 0.2.1",
2682
]
2683
2684
[[package]]
···
2751
dependencies = [
2752
"proc-macro2",
2753
"quote",
2754
-
"syn 2.0.111",
2755
]
2756
2757
[[package]]
···
2789
2790
[[package]]
2791
name = "portable-atomic"
2792
-
version = "1.11.1"
2793
source = "registry+https://github.com/rust-lang/crates.io-index"
2794
-
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2795
2796
[[package]]
2797
name = "potential_utf"
···
2830
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2831
dependencies = [
2832
"proc-macro2",
2833
-
"syn 2.0.111",
2834
]
2835
2836
[[package]]
···
2843
]
2844
2845
[[package]]
2846
-
name = "proc-macro-error"
2847
-
version = "1.0.4"
2848
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2849
-
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2850
-
dependencies = [
2851
-
"proc-macro-error-attr",
2852
-
"proc-macro2",
2853
-
"quote",
2854
-
"syn 1.0.109",
2855
-
"version_check",
2856
-
]
2857
-
2858
-
[[package]]
2859
-
name = "proc-macro-error-attr"
2860
-
version = "1.0.4"
2861
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2862
-
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2863
-
dependencies = [
2864
-
"proc-macro2",
2865
-
"quote",
2866
-
"version_check",
2867
-
]
2868
-
2869
-
[[package]]
2870
name = "proc-macro2"
2871
-
version = "1.0.103"
2872
source = "registry+https://github.com/rust-lang/crates.io-index"
2873
-
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
2874
dependencies = [
2875
"unicode-ident",
2876
]
···
2883
dependencies = [
2884
"proc-macro2",
2885
"quote",
2886
-
"syn 2.0.111",
2887
"version_check",
2888
"yansi",
2889
]
···
2951
2952
[[package]]
2953
name = "quote"
2954
-
version = "1.0.42"
2955
source = "registry+https://github.com/rust-lang/crates.io-index"
2956
-
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
2957
dependencies = [
2958
"proc-macro2",
2959
]
···
3024
]
3025
3026
[[package]]
3027
-
name = "range-traits"
3028
-
version = "0.3.2"
3029
source = "registry+https://github.com/rust-lang/crates.io-index"
3030
-
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
3031
3032
[[package]]
3033
name = "redox_syscall"
3034
-
version = "0.5.18"
3035
source = "registry+https://github.com/rust-lang/crates.io-index"
3036
-
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3037
dependencies = [
3038
"bitflags",
3039
]
···
3047
"getrandom 0.2.16",
3048
"libredox",
3049
"thiserror 2.0.17",
3050
-
]
3051
-
3052
-
[[package]]
3053
-
name = "ref-cast"
3054
-
version = "1.0.25"
3055
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3056
-
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
3057
-
dependencies = [
3058
-
"ref-cast-impl",
3059
-
]
3060
-
3061
-
[[package]]
3062
-
name = "ref-cast-impl"
3063
-
version = "1.0.25"
3064
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3065
-
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
3066
-
dependencies = [
3067
-
"proc-macro2",
3068
-
"quote",
3069
-
"syn 2.0.111",
3070
]
3071
3072
[[package]]
···
3106
3107
[[package]]
3108
name = "reqwest"
3109
-
version = "0.12.24"
3110
source = "registry+https://github.com/rust-lang/crates.io-index"
3111
-
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
3112
dependencies = [
3113
-
"async-compression",
3114
"base64 0.22.1",
3115
"bytes",
3116
"encoding_rs",
···
3205
3206
[[package]]
3207
name = "rsa"
3208
-
version = "0.9.9"
3209
source = "registry+https://github.com/rust-lang/crates.io-index"
3210
-
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
3211
dependencies = [
3212
"const-oid",
3213
"digest",
···
3236
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3237
3238
[[package]]
3239
name = "rustix"
3240
-
version = "1.1.2"
3241
source = "registry+https://github.com/rust-lang/crates.io-index"
3242
-
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
3243
dependencies = [
3244
"bitflags",
3245
"errno",
···
3250
3251
[[package]]
3252
name = "rustls"
3253
-
version = "0.23.35"
3254
source = "registry+https://github.com/rust-lang/crates.io-index"
3255
-
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
3256
dependencies = [
3257
"once_cell",
3258
"ring",
···
3264
3265
[[package]]
3266
name = "rustls-native-certs"
3267
-
version = "0.8.2"
3268
source = "registry+https://github.com/rust-lang/crates.io-index"
3269
-
checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923"
3270
dependencies = [
3271
"openssl-probe",
3272
"rustls-pki-types",
···
3276
3277
[[package]]
3278
name = "rustls-pki-types"
3279
-
version = "1.13.0"
3280
source = "registry+https://github.com/rust-lang/crates.io-index"
3281
-
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
3282
dependencies = [
3283
"web-time",
3284
"zeroize",
···
3303
3304
[[package]]
3305
name = "ryu"
3306
-
version = "1.0.20"
3307
source = "registry+https://github.com/rust-lang/crates.io-index"
3308
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
3309
3310
[[package]]
3311
name = "safemem"
···
3332
]
3333
3334
[[package]]
3335
-
name = "schemars"
3336
-
version = "0.9.0"
3337
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3338
-
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
3339
-
dependencies = [
3340
-
"dyn-clone",
3341
-
"ref-cast",
3342
-
"serde",
3343
-
"serde_json",
3344
-
]
3345
-
3346
-
[[package]]
3347
-
name = "schemars"
3348
-
version = "1.1.0"
3349
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3350
-
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
3351
-
dependencies = [
3352
-
"dyn-clone",
3353
-
"ref-cast",
3354
-
"serde",
3355
-
"serde_json",
3356
-
]
3357
-
3358
-
[[package]]
3359
name = "scoped-tls"
3360
version = "1.0.1"
3361
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3403
"core-foundation-sys",
3404
"libc",
3405
]
3406
3407
[[package]]
3408
name = "send_wrapper"
···
3447
dependencies = [
3448
"proc-macro2",
3449
"quote",
3450
-
"syn 2.0.111",
3451
]
3452
3453
[[package]]
3454
name = "serde_html_form"
3455
-
version = "0.2.8"
3456
source = "registry+https://github.com/rust-lang/crates.io-index"
3457
-
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
3458
dependencies = [
3459
"form_urlencoded",
3460
-
"indexmap 2.12.1",
3461
"itoa",
3462
-
"ryu",
3463
"serde_core",
3464
]
3465
···
3477
3478
[[package]]
3479
name = "serde_json"
3480
-
version = "1.0.145"
3481
source = "registry+https://github.com/rust-lang/crates.io-index"
3482
-
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
3483
dependencies = [
3484
"itoa",
3485
"memchr",
3486
-
"ryu",
3487
"serde",
3488
"serde_core",
3489
]
3490
3491
[[package]]
···
3507
dependencies = [
3508
"proc-macro2",
3509
"quote",
3510
-
"syn 2.0.111",
3511
]
3512
3513
[[package]]
···
3524
3525
[[package]]
3526
name = "serde_with"
3527
-
version = "3.16.0"
3528
source = "registry+https://github.com/rust-lang/crates.io-index"
3529
-
checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1"
3530
dependencies = [
3531
"base64 0.22.1",
3532
"chrono",
3533
"hex",
3534
-
"indexmap 1.9.3",
3535
-
"indexmap 2.12.1",
3536
-
"schemars 0.9.0",
3537
-
"schemars 1.1.0",
3538
"serde_core",
3539
"serde_json",
3540
"serde_with_macros",
···
3543
3544
[[package]]
3545
name = "serde_with_macros"
3546
-
version = "3.16.0"
3547
source = "registry+https://github.com/rust-lang/crates.io-index"
3548
-
checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b"
3549
dependencies = [
3550
"darling",
3551
"proc-macro2",
3552
"quote",
3553
-
"syn 2.0.111",
3554
]
3555
3556
[[package]]
···
3607
3608
[[package]]
3609
name = "signal-hook-registry"
3610
-
version = "1.4.7"
3611
source = "registry+https://github.com/rust-lang/crates.io-index"
3612
-
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
3613
dependencies = [
3614
"libc",
3615
]
3616
···
3626
3627
[[package]]
3628
name = "simd-adler32"
3629
-
version = "0.3.7"
3630
source = "registry+https://github.com/rust-lang/crates.io-index"
3631
-
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3632
3633
[[package]]
3634
name = "siphasher"
···
3683
version = "0.9.8"
3684
source = "registry+https://github.com/rust-lang/crates.io-index"
3685
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
3686
3687
[[package]]
3688
name = "spin"
···
3707
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3708
3709
[[package]]
3710
-
name = "static-regular-grammar"
3711
-
version = "2.0.2"
3712
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3713
-
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
3714
-
dependencies = [
3715
-
"abnf",
3716
-
"btree-range-map",
3717
-
"ciborium",
3718
-
"hex_fmt",
3719
-
"indoc",
3720
-
"proc-macro-error",
3721
-
"proc-macro2",
3722
-
"quote",
3723
-
"serde",
3724
-
"sha2",
3725
-
"syn 2.0.111",
3726
-
"thiserror 1.0.69",
3727
-
]
3728
-
3729
-
[[package]]
3730
name = "static_assertions"
3731
version = "1.1.0"
3732
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3780
3781
[[package]]
3782
name = "supports-hyperlinks"
3783
-
version = "3.1.0"
3784
source = "registry+https://github.com/rust-lang/crates.io-index"
3785
-
checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
3786
3787
[[package]]
3788
name = "supports-unicode"
···
3803
3804
[[package]]
3805
name = "syn"
3806
-
version = "2.0.111"
3807
source = "registry+https://github.com/rust-lang/crates.io-index"
3808
-
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
3809
dependencies = [
3810
"proc-macro2",
3811
"quote",
···
3829
dependencies = [
3830
"proc-macro2",
3831
"quote",
3832
-
"syn 2.0.111",
3833
]
3834
3835
[[package]]
···
3861
3862
[[package]]
3863
name = "tempfile"
3864
-
version = "3.23.0"
3865
source = "registry+https://github.com/rust-lang/crates.io-index"
3866
-
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
3867
dependencies = [
3868
"fastrand",
3869
"getrandom 0.3.4",
···
3929
dependencies = [
3930
"proc-macro2",
3931
"quote",
3932
-
"syn 2.0.111",
3933
]
3934
3935
[[package]]
···
3940
dependencies = [
3941
"proc-macro2",
3942
"quote",
3943
-
"syn 2.0.111",
3944
]
3945
3946
[[package]]
···
3968
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
3969
dependencies = [
3970
"deranged",
3971
-
"itoa",
3972
"libc",
3973
"num-conv",
3974
"num_threads",
3975
"powerfmt",
3976
"serde",
3977
"time-core",
3978
-
"time-macros",
3979
]
3980
3981
[[package]]
···
3983
version = "0.1.6"
3984
source = "registry+https://github.com/rust-lang/crates.io-index"
3985
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
3986
-
3987
-
[[package]]
3988
-
name = "time-macros"
3989
-
version = "0.2.24"
3990
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3991
-
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
3992
-
dependencies = [
3993
-
"num-conv",
3994
-
"time-core",
3995
-
]
3996
3997
[[package]]
3998
name = "tiny_http"
···
4033
4034
[[package]]
4035
name = "tokio"
4036
-
version = "1.48.0"
4037
source = "registry+https://github.com/rust-lang/crates.io-index"
4038
-
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
4039
dependencies = [
4040
"bytes",
4041
"libc",
···
4056
dependencies = [
4057
"proc-macro2",
4058
"quote",
4059
-
"syn 2.0.111",
4060
]
4061
4062
[[package]]
···
4106
4107
[[package]]
4108
name = "tokio-util"
4109
-
version = "0.7.17"
4110
source = "registry+https://github.com/rust-lang/crates.io-index"
4111
-
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
4112
dependencies = [
4113
"bytes",
4114
"futures-core",
···
4136
4137
[[package]]
4138
name = "tower-http"
4139
-
version = "0.6.7"
4140
source = "registry+https://github.com/rust-lang/crates.io-index"
4141
-
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
4142
dependencies = [
4143
"async-compression",
4144
"bitflags",
···
4177
4178
[[package]]
4179
name = "tracing"
4180
-
version = "0.1.41"
4181
source = "registry+https://github.com/rust-lang/crates.io-index"
4182
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
4183
dependencies = [
4184
"log",
4185
"pin-project-lite",
···
4189
4190
[[package]]
4191
name = "tracing-attributes"
4192
-
version = "0.1.30"
4193
source = "registry+https://github.com/rust-lang/crates.io-index"
4194
-
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
4195
dependencies = [
4196
"proc-macro2",
4197
"quote",
4198
-
"syn 2.0.111",
4199
]
4200
4201
[[package]]
4202
name = "tracing-core"
4203
-
version = "0.1.34"
4204
source = "registry+https://github.com/rust-lang/crates.io-index"
4205
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
4206
dependencies = [
4207
"once_cell",
4208
"valuable",
···
4221
4222
[[package]]
4223
name = "tracing-subscriber"
4224
-
version = "0.3.20"
4225
source = "registry+https://github.com/rust-lang/crates.io-index"
4226
-
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
4227
dependencies = [
4228
"matchers",
4229
"nu-ansi-term",
···
4245
dependencies = [
4246
"proc-macro2",
4247
"quote",
4248
-
"syn 2.0.111",
4249
]
4250
4251
[[package]]
···
4297
4298
[[package]]
4299
name = "unicase"
4300
-
version = "2.8.1"
4301
source = "registry+https://github.com/rust-lang/crates.io-index"
4302
-
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
4303
4304
[[package]]
4305
name = "unicode-ident"
···
4351
4352
[[package]]
4353
name = "url"
4354
-
version = "2.5.7"
4355
source = "registry+https://github.com/rust-lang/crates.io-index"
4356
-
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
4357
dependencies = [
4358
"form_urlencoded",
4359
"idna",
4360
"percent-encoding",
4361
"serde",
4362
]
4363
4364
[[package]]
···
4433
4434
[[package]]
4435
name = "wasm-bindgen"
4436
-
version = "0.2.105"
4437
source = "registry+https://github.com/rust-lang/crates.io-index"
4438
-
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
4439
dependencies = [
4440
"cfg-if",
4441
"once_cell",
···
4446
4447
[[package]]
4448
name = "wasm-bindgen-futures"
4449
-
version = "0.4.55"
4450
source = "registry+https://github.com/rust-lang/crates.io-index"
4451
-
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
4452
dependencies = [
4453
"cfg-if",
4454
"js-sys",
···
4459
4460
[[package]]
4461
name = "wasm-bindgen-macro"
4462
-
version = "0.2.105"
4463
source = "registry+https://github.com/rust-lang/crates.io-index"
4464
-
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
4465
dependencies = [
4466
"quote",
4467
"wasm-bindgen-macro-support",
···
4469
4470
[[package]]
4471
name = "wasm-bindgen-macro-support"
4472
-
version = "0.2.105"
4473
source = "registry+https://github.com/rust-lang/crates.io-index"
4474
-
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
4475
dependencies = [
4476
"bumpalo",
4477
"proc-macro2",
4478
"quote",
4479
-
"syn 2.0.111",
4480
"wasm-bindgen-shared",
4481
]
4482
4483
[[package]]
4484
name = "wasm-bindgen-shared"
4485
-
version = "0.2.105"
4486
source = "registry+https://github.com/rust-lang/crates.io-index"
4487
-
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
4488
dependencies = [
4489
"unicode-ident",
4490
]
···
4504
4505
[[package]]
4506
name = "web-sys"
4507
-
version = "0.3.82"
4508
source = "registry+https://github.com/rust-lang/crates.io-index"
4509
-
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
4510
dependencies = [
4511
"js-sys",
4512
"wasm-bindgen",
···
4552
4553
[[package]]
4554
name = "webpki-roots"
4555
-
version = "1.0.4"
4556
source = "registry+https://github.com/rust-lang/crates.io-index"
4557
-
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
4558
dependencies = [
4559
"rustls-pki-types",
4560
]
···
4575
]
4576
4577
[[package]]
4578
-
name = "windows"
4579
-
version = "0.61.3"
4580
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4581
-
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
4582
-
dependencies = [
4583
-
"windows-collections",
4584
-
"windows-core 0.61.2",
4585
-
"windows-future",
4586
-
"windows-link 0.1.3",
4587
-
"windows-numerics",
4588
-
]
4589
-
4590
-
[[package]]
4591
-
name = "windows-collections"
4592
-
version = "0.2.0"
4593
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4594
-
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
4595
-
dependencies = [
4596
-
"windows-core 0.61.2",
4597
-
]
4598
-
4599
-
[[package]]
4600
-
name = "windows-core"
4601
-
version = "0.61.2"
4602
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4603
-
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
4604
-
dependencies = [
4605
-
"windows-implement",
4606
-
"windows-interface",
4607
-
"windows-link 0.1.3",
4608
-
"windows-result 0.3.4",
4609
-
"windows-strings 0.4.2",
4610
-
]
4611
-
4612
-
[[package]]
4613
name = "windows-core"
4614
version = "0.62.2"
4615
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4617
dependencies = [
4618
"windows-implement",
4619
"windows-interface",
4620
-
"windows-link 0.2.1",
4621
-
"windows-result 0.4.1",
4622
-
"windows-strings 0.5.1",
4623
-
]
4624
-
4625
-
[[package]]
4626
-
name = "windows-future"
4627
-
version = "0.2.1"
4628
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4629
-
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
4630
-
dependencies = [
4631
-
"windows-core 0.61.2",
4632
-
"windows-link 0.1.3",
4633
-
"windows-threading",
4634
]
4635
4636
[[package]]
···
4641
dependencies = [
4642
"proc-macro2",
4643
"quote",
4644
-
"syn 2.0.111",
4645
]
4646
4647
[[package]]
···
4652
dependencies = [
4653
"proc-macro2",
4654
"quote",
4655
-
"syn 2.0.111",
4656
]
4657
4658
[[package]]
4659
name = "windows-link"
4660
-
version = "0.1.3"
4661
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4662
-
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
4663
-
4664
-
[[package]]
4665
-
name = "windows-link"
4666
version = "0.2.1"
4667
source = "registry+https://github.com/rust-lang/crates.io-index"
4668
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4669
4670
[[package]]
4671
-
name = "windows-numerics"
4672
-
version = "0.2.0"
4673
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4674
-
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
4675
-
dependencies = [
4676
-
"windows-core 0.61.2",
4677
-
"windows-link 0.1.3",
4678
-
]
4679
-
4680
-
[[package]]
4681
name = "windows-registry"
4682
version = "0.6.1"
4683
source = "registry+https://github.com/rust-lang/crates.io-index"
4684
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
4685
dependencies = [
4686
-
"windows-link 0.2.1",
4687
-
"windows-result 0.4.1",
4688
-
"windows-strings 0.5.1",
4689
-
]
4690
-
4691
-
[[package]]
4692
-
name = "windows-result"
4693
-
version = "0.3.4"
4694
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4695
-
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
4696
-
dependencies = [
4697
-
"windows-link 0.1.3",
4698
]
4699
4700
[[package]]
···
4703
source = "registry+https://github.com/rust-lang/crates.io-index"
4704
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
4705
dependencies = [
4706
-
"windows-link 0.2.1",
4707
-
]
4708
-
4709
-
[[package]]
4710
-
name = "windows-strings"
4711
-
version = "0.4.2"
4712
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4713
-
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
4714
-
dependencies = [
4715
-
"windows-link 0.1.3",
4716
]
4717
4718
[[package]]
···
4721
source = "registry+https://github.com/rust-lang/crates.io-index"
4722
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
4723
dependencies = [
4724
-
"windows-link 0.2.1",
4725
]
4726
4727
[[package]]
···
4775
source = "registry+https://github.com/rust-lang/crates.io-index"
4776
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
4777
dependencies = [
4778
-
"windows-link 0.2.1",
4779
]
4780
4781
[[package]]
···
4830
source = "registry+https://github.com/rust-lang/crates.io-index"
4831
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
4832
dependencies = [
4833
-
"windows-link 0.2.1",
4834
"windows_aarch64_gnullvm 0.53.1",
4835
"windows_aarch64_msvc 0.53.1",
4836
"windows_i686_gnu 0.53.1",
···
4839
"windows_x86_64_gnu 0.53.1",
4840
"windows_x86_64_gnullvm 0.53.1",
4841
"windows_x86_64_msvc 0.53.1",
4842
-
]
4843
-
4844
-
[[package]]
4845
-
name = "windows-threading"
4846
-
version = "0.1.0"
4847
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4848
-
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
4849
-
dependencies = [
4850
-
"windows-link 0.1.3",
4851
]
4852
4853
[[package]]
···
5078
"tower-http",
5079
"url",
5080
"walkdir",
5081
]
5082
5083
[[package]]
···
5128
dependencies = [
5129
"proc-macro2",
5130
"quote",
5131
-
"syn 2.0.111",
5132
"synstructure",
5133
]
5134
5135
[[package]]
5136
name = "zerocopy"
5137
-
version = "0.8.30"
5138
source = "registry+https://github.com/rust-lang/crates.io-index"
5139
-
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
5140
dependencies = [
5141
"zerocopy-derive",
5142
]
5143
5144
[[package]]
5145
name = "zerocopy-derive"
5146
-
version = "0.8.30"
5147
source = "registry+https://github.com/rust-lang/crates.io-index"
5148
-
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
5149
dependencies = [
5150
"proc-macro2",
5151
"quote",
5152
-
"syn 2.0.111",
5153
]
5154
5155
[[package]]
···
5169
dependencies = [
5170
"proc-macro2",
5171
"quote",
5172
-
"syn 2.0.111",
5173
"synstructure",
5174
]
5175
···
5212
dependencies = [
5213
"proc-macro2",
5214
"quote",
5215
-
"syn 2.0.111",
5216
]
···
3
version = 4
4
5
[[package]]
6
name = "addr2line"
7
version = "0.25.1"
8
source = "registry+https://github.com/rust-lang/crates.io-index"
···
52
dependencies = [
53
"alloc-no-stdlib",
54
]
55
+
56
+
[[package]]
57
+
name = "allocator-api2"
58
+
version = "0.2.21"
59
+
source = "registry+https://github.com/rust-lang/crates.io-index"
60
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
61
62
[[package]]
63
name = "android_system_properties"
···
126
127
[[package]]
128
name = "async-compression"
129
+
version = "0.4.36"
130
source = "registry+https://github.com/rust-lang/crates.io-index"
131
+
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
132
dependencies = [
133
"compression-codecs",
134
"compression-core",
···
145
dependencies = [
146
"proc-macro2",
147
"quote",
148
+
"syn 2.0.113",
149
+
]
150
+
151
+
[[package]]
152
+
name = "atomic-polyfill"
153
+
version = "1.0.3"
154
+
source = "registry+https://github.com/rust-lang/crates.io-index"
155
+
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
156
+
dependencies = [
157
+
"critical-section",
158
]
159
160
[[package]]
···
171
172
[[package]]
173
name = "axum"
174
+
version = "0.8.8"
175
source = "registry+https://github.com/rust-lang/crates.io-index"
176
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
177
dependencies = [
178
"axum-core",
179
"bytes",
···
204
205
[[package]]
206
name = "axum-core"
207
+
version = "0.5.6"
208
source = "registry+https://github.com/rust-lang/crates.io-index"
209
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
210
dependencies = [
211
"bytes",
212
"futures-core",
···
233
"miniz_oxide",
234
"object",
235
"rustc-demangle",
236
+
"windows-link",
237
]
238
239
[[package]]
···
281
282
[[package]]
283
name = "base64ct"
284
+
version = "1.8.2"
285
source = "registry+https://github.com/rust-lang/crates.io-index"
286
+
checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82"
287
288
[[package]]
289
name = "bitflags"
···
322
"proc-macro2",
323
"quote",
324
"rustversion",
325
+
"syn 2.0.113",
326
]
327
328
[[package]]
329
name = "borsh"
330
+
version = "1.6.0"
331
source = "registry+https://github.com/rust-lang/crates.io-index"
332
+
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
333
dependencies = [
334
"cfg_aliases",
335
]
···
366
]
367
368
[[package]]
369
name = "buf_redux"
370
version = "0.8.4"
371
source = "registry+https://github.com/rust-lang/crates.io-index"
···
377
378
[[package]]
379
name = "bumpalo"
380
+
version = "3.19.1"
381
source = "registry+https://github.com/rust-lang/crates.io-index"
382
+
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
383
384
[[package]]
385
name = "byteorder"
···
407
408
[[package]]
409
name = "cc"
410
+
version = "1.2.51"
411
source = "registry+https://github.com/rust-lang/crates.io-index"
412
+
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
413
dependencies = [
414
"find-msvc-tools",
415
"shlex",
416
]
417
418
[[package]]
419
name = "cesu8"
420
version = "1.1.0"
421
source = "registry+https://github.com/rust-lang/crates.io-index"
···
444
"num-traits",
445
"serde",
446
"wasm-bindgen",
447
+
"windows-link",
448
]
449
450
[[package]]
···
496
497
[[package]]
498
name = "clap"
499
+
version = "4.5.54"
500
source = "registry+https://github.com/rust-lang/crates.io-index"
501
+
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
502
dependencies = [
503
"clap_builder",
504
"clap_derive",
···
506
507
[[package]]
508
name = "clap_builder"
509
+
version = "4.5.54"
510
source = "registry+https://github.com/rust-lang/crates.io-index"
511
+
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
512
dependencies = [
513
"anstream",
514
"anstyle",
···
525
"heck 0.5.0",
526
"proc-macro2",
527
"quote",
528
+
"syn 2.0.113",
529
]
530
531
[[package]]
···
535
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
536
537
[[package]]
538
+
name = "cobs"
539
+
version = "0.3.0"
540
+
source = "registry+https://github.com/rust-lang/crates.io-index"
541
+
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
542
+
dependencies = [
543
+
"thiserror 2.0.17",
544
+
]
545
+
546
+
[[package]]
547
name = "colorchoice"
548
version = "1.0.4"
549
source = "registry+https://github.com/rust-lang/crates.io-index"
···
561
562
[[package]]
563
name = "compression-codecs"
564
+
version = "0.4.35"
565
source = "registry+https://github.com/rust-lang/crates.io-index"
566
+
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
567
dependencies = [
568
"compression-core",
569
"flate2",
···
665
]
666
667
[[package]]
668
+
name = "critical-section"
669
+
version = "1.2.0"
670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
671
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
672
+
673
+
[[package]]
674
name = "crossbeam-channel"
675
version = "0.5.15"
676
source = "registry+https://github.com/rust-lang/crates.io-index"
···
753
"proc-macro2",
754
"quote",
755
"strsim",
756
+
"syn 2.0.113",
757
]
758
759
[[package]]
···
764
dependencies = [
765
"darling_core",
766
"quote",
767
+
"syn 2.0.113",
768
]
769
770
[[package]]
···
804
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
805
dependencies = [
806
"data-encoding",
807
+
"syn 2.0.113",
808
]
809
810
[[package]]
···
835
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
836
dependencies = [
837
"powerfmt",
838
]
839
840
[[package]]
···
854
dependencies = [
855
"proc-macro2",
856
"quote",
857
+
"syn 2.0.113",
858
"unicode-xid",
859
]
860
···
905
dependencies = [
906
"proc-macro2",
907
"quote",
908
+
"syn 2.0.113",
909
]
910
911
[[package]]
912
name = "ecdsa"
913
version = "0.16.9"
914
source = "registry+https://github.com/rust-lang/crates.io-index"
···
943
]
944
945
[[package]]
946
+
name = "embedded-io"
947
+
version = "0.4.0"
948
+
source = "registry+https://github.com/rust-lang/crates.io-index"
949
+
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
950
+
951
+
[[package]]
952
+
name = "embedded-io"
953
+
version = "0.6.1"
954
+
source = "registry+https://github.com/rust-lang/crates.io-index"
955
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
956
+
957
+
[[package]]
958
name = "encode_unicode"
959
version = "1.0.0"
960
source = "registry+https://github.com/rust-lang/crates.io-index"
···
978
"heck 0.5.0",
979
"proc-macro2",
980
"quote",
981
+
"syn 2.0.113",
982
]
983
984
[[package]]
···
1027
1028
[[package]]
1029
name = "find-msvc-tools"
1030
+
version = "0.1.6"
1031
source = "registry+https://github.com/rust-lang/crates.io-index"
1032
+
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
1033
1034
[[package]]
1035
name = "flate2"
···
1046
version = "1.0.7"
1047
source = "registry+https://github.com/rust-lang/crates.io-index"
1048
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1049
+
1050
+
[[package]]
1051
+
name = "foldhash"
1052
+
version = "0.1.5"
1053
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1054
+
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1055
1056
[[package]]
1057
name = "form_urlencoded"
···
1154
dependencies = [
1155
"proc-macro2",
1156
"quote",
1157
+
"syn 2.0.113",
1158
]
1159
1160
[[package]]
···
1189
1190
[[package]]
1191
name = "generator"
1192
+
version = "0.8.8"
1193
source = "registry+https://github.com/rust-lang/crates.io-index"
1194
+
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
1195
dependencies = [
1196
"cc",
1197
"cfg-if",
1198
"libc",
1199
"log",
1200
"rustversion",
1201
+
"windows-link",
1202
+
"windows-result",
1203
]
1204
1205
[[package]]
···
1309
1310
[[package]]
1311
name = "h2"
1312
+
version = "0.4.13"
1313
source = "registry+https://github.com/rust-lang/crates.io-index"
1314
+
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
1315
dependencies = [
1316
"atomic-waker",
1317
"bytes",
···
1319
"futures-core",
1320
"futures-sink",
1321
"http",
1322
+
"indexmap",
1323
"slab",
1324
"tokio",
1325
"tokio-util",
···
1338
]
1339
1340
[[package]]
1341
+
name = "hash32"
1342
+
version = "0.2.1"
1343
source = "registry+https://github.com/rust-lang/crates.io-index"
1344
+
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
1345
+
dependencies = [
1346
+
"byteorder",
1347
+
]
1348
1349
[[package]]
1350
name = "hashbrown"
···
1354
1355
[[package]]
1356
name = "hashbrown"
1357
+
version = "0.15.5"
1358
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1359
+
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
1360
+
dependencies = [
1361
+
"allocator-api2",
1362
+
"equivalent",
1363
+
"foldhash",
1364
+
]
1365
+
1366
+
[[package]]
1367
+
name = "hashbrown"
1368
version = "0.16.1"
1369
source = "registry+https://github.com/rust-lang/crates.io-index"
1370
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1371
1372
[[package]]
1373
+
name = "heapless"
1374
+
version = "0.7.17"
1375
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1376
+
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
1377
+
dependencies = [
1378
+
"atomic-polyfill",
1379
+
"hash32",
1380
+
"rustc_version",
1381
+
"serde",
1382
+
"spin 0.9.8",
1383
+
"stable_deref_trait",
1384
+
]
1385
+
1386
+
[[package]]
1387
name = "heck"
1388
version = "0.4.1"
1389
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1408
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1409
1410
[[package]]
1411
name = "hickory-proto"
1412
version = "0.24.4"
1413
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1472
"markup5ever",
1473
"proc-macro2",
1474
"quote",
1475
+
"syn 2.0.113",
1476
]
1477
1478
[[package]]
···
1568
1569
[[package]]
1570
name = "hyper-util"
1571
+
version = "0.1.19"
1572
source = "registry+https://github.com/rust-lang/crates.io-index"
1573
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
1574
dependencies = [
1575
"base64 0.22.1",
1576
"bytes",
···
1604
"js-sys",
1605
"log",
1606
"wasm-bindgen",
1607
+
"windows-core",
1608
]
1609
1610
[[package]]
···
1664
1665
[[package]]
1666
name = "icu_properties"
1667
+
version = "2.1.2"
1668
source = "registry+https://github.com/rust-lang/crates.io-index"
1669
+
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1670
dependencies = [
1671
"icu_collections",
1672
"icu_locale_core",
···
1678
1679
[[package]]
1680
name = "icu_properties_data"
1681
+
version = "2.1.2"
1682
source = "registry+https://github.com/rust-lang/crates.io-index"
1683
+
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1684
1685
[[package]]
1686
name = "icu_provider"
···
1742
1743
[[package]]
1744
name = "indexmap"
1745
version = "2.12.1"
1746
source = "registry+https://github.com/rust-lang/crates.io-index"
1747
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
1748
dependencies = [
1749
"equivalent",
1750
"hashbrown 0.16.1",
1751
]
1752
1753
[[package]]
···
1764
]
1765
1766
[[package]]
1767
name = "inventory"
1768
version = "0.3.21"
1769
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1803
1804
[[package]]
1805
name = "iri-string"
1806
+
version = "0.7.10"
1807
source = "registry+https://github.com/rust-lang/crates.io-index"
1808
+
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
1809
dependencies = [
1810
"memchr",
1811
"serde",
···
1825
1826
[[package]]
1827
name = "itoa"
1828
+
version = "1.0.17"
1829
source = "registry+https://github.com/rust-lang/crates.io-index"
1830
+
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1831
1832
[[package]]
1833
name = "jacquard"
1834
+
version = "0.9.5"
1835
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1836
dependencies = [
1837
"bytes",
1838
"getrandom 0.2.16",
···
1861
1862
[[package]]
1863
name = "jacquard-api"
1864
+
version = "0.9.5"
1865
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1866
dependencies = [
1867
"bon",
1868
"bytes",
···
1872
"miette",
1873
"rustversion",
1874
"serde",
1875
+
"serde_bytes",
1876
"serde_ipld_dagcbor",
1877
"thiserror 2.0.17",
1878
"unicode-segmentation",
···
1880
1881
[[package]]
1882
name = "jacquard-common"
1883
+
version = "0.9.5"
1884
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1885
dependencies = [
1886
"base64 0.22.1",
1887
"bon",
1888
"bytes",
1889
"chrono",
1890
"ciborium",
1891
+
"ciborium-io",
1892
"cid",
1893
"futures",
1894
"getrandom 0.2.16",
1895
"getrandom 0.3.4",
1896
+
"hashbrown 0.15.5",
1897
"http",
1898
"ipld-core",
1899
"k256",
1900
+
"maitake-sync",
1901
"miette",
1902
"multibase",
1903
"multihash",
1904
"n0-future 0.1.3",
1905
"ouroboros",
1906
+
"oxilangtag",
1907
"p256",
1908
+
"postcard",
1909
"rand 0.9.2",
1910
"regex",
1911
+
"regex-automata",
1912
"regex-lite",
1913
"reqwest",
1914
"serde",
1915
+
"serde_bytes",
1916
"serde_html_form",
1917
"serde_ipld_dagcbor",
1918
"serde_json",
1919
"signature",
1920
"smol_str",
1921
+
"spin 0.10.0",
1922
"thiserror 2.0.17",
1923
"tokio",
1924
"tokio-tungstenite-wasm",
···
1929
1930
[[package]]
1931
name = "jacquard-derive"
1932
+
version = "0.9.5"
1933
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1934
dependencies = [
1935
"heck 0.5.0",
1936
"jacquard-lexicon",
1937
"proc-macro2",
1938
"quote",
1939
+
"syn 2.0.113",
1940
]
1941
1942
[[package]]
1943
name = "jacquard-identity"
1944
+
version = "0.9.5"
1945
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1946
dependencies = [
1947
"bon",
1948
"bytes",
···
1952
"jacquard-common",
1953
"jacquard-lexicon",
1954
"miette",
1955
+
"mini-moka-wasm",
1956
+
"n0-future 0.1.3",
1957
"percent-encoding",
1958
"reqwest",
1959
"serde",
···
1968
1969
[[package]]
1970
name = "jacquard-lexicon"
1971
+
version = "0.9.5"
1972
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1973
dependencies = [
1974
"cid",
1975
"dashmap",
···
1987
"serde_repr",
1988
"serde_with",
1989
"sha2",
1990
+
"syn 2.0.113",
1991
"thiserror 2.0.17",
1992
"unicode-segmentation",
1993
]
1994
1995
[[package]]
1996
name = "jacquard-oauth"
1997
+
version = "0.9.6"
1998
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1999
dependencies = [
2000
"base64 0.22.1",
2001
"bytes",
···
2083
2084
[[package]]
2085
name = "js-sys"
2086
+
version = "0.3.83"
2087
source = "registry+https://github.com/rust-lang/crates.io-index"
2088
+
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
2089
dependencies = [
2090
"once_cell",
2091
"wasm-bindgen",
···
2104
]
2105
2106
[[package]]
2107
name = "lazy_static"
2108
version = "1.5.0"
2109
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2114
2115
[[package]]
2116
name = "libc"
2117
+
version = "0.2.179"
2118
source = "registry+https://github.com/rust-lang/crates.io-index"
2119
+
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
2120
2121
[[package]]
2122
name = "libm"
···
2126
2127
[[package]]
2128
name = "libredox"
2129
+
version = "0.1.12"
2130
source = "registry+https://github.com/rust-lang/crates.io-index"
2131
+
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
2132
dependencies = [
2133
"bitflags",
2134
"libc",
2135
+
"redox_syscall 0.7.0",
2136
]
2137
2138
[[package]]
···
2164
2165
[[package]]
2166
name = "log"
2167
+
version = "0.4.29"
2168
source = "registry+https://github.com/rust-lang/crates.io-index"
2169
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
2170
2171
[[package]]
2172
name = "loom"
···
2203
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
2204
2205
[[package]]
2206
+
name = "maitake-sync"
2207
+
version = "0.1.2"
2208
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2209
+
checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5"
2210
+
dependencies = [
2211
+
"cordyceps",
2212
+
"loom",
2213
+
"mycelium-bitfield",
2214
+
"pin-project",
2215
+
"portable-atomic",
2216
+
]
2217
+
2218
+
[[package]]
2219
name = "markup5ever"
2220
version = "0.12.1"
2221
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2300
dependencies = [
2301
"proc-macro2",
2302
"quote",
2303
+
"syn 2.0.113",
2304
]
2305
2306
[[package]]
···
2320
]
2321
2322
[[package]]
2323
+
name = "mini-moka-wasm"
2324
version = "0.10.99"
2325
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
2326
dependencies = [
2327
"crossbeam-channel",
2328
"crossbeam-utils",
···
2334
]
2335
2336
[[package]]
2337
name = "miniz_oxide"
2338
version = "0.8.9"
2339
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2345
2346
[[package]]
2347
name = "mio"
2348
+
version = "1.1.1"
2349
source = "registry+https://github.com/rust-lang/crates.io-index"
2350
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
2351
dependencies = [
2352
"libc",
2353
"wasi",
···
2396
]
2397
2398
[[package]]
2399
+
name = "mycelium-bitfield"
2400
+
version = "0.1.5"
2401
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2402
+
checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc"
2403
+
2404
+
[[package]]
2405
name = "n0-future"
2406
version = "0.1.3"
2407
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2456
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2457
2458
[[package]]
2459
name = "nu-ansi-term"
2460
version = "0.50.3"
2461
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2589
2590
[[package]]
2591
name = "openssl-probe"
2592
+
version = "0.2.0"
2593
source = "registry+https://github.com/rust-lang/crates.io-index"
2594
+
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
2595
2596
[[package]]
2597
name = "option-ext"
···
2620
"proc-macro2",
2621
"proc-macro2-diagnostics",
2622
"quote",
2623
+
"syn 2.0.113",
2624
]
2625
2626
[[package]]
···
2630
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
2631
2632
[[package]]
2633
+
name = "oxilangtag"
2634
+
version = "0.1.5"
2635
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2636
+
checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb"
2637
+
dependencies = [
2638
+
"serde",
2639
+
]
2640
+
2641
+
[[package]]
2642
name = "p256"
2643
version = "0.13.2"
2644
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2684
dependencies = [
2685
"cfg-if",
2686
"libc",
2687
+
"redox_syscall 0.5.18",
2688
"smallvec",
2689
+
"windows-link",
2690
]
2691
2692
[[package]]
···
2759
dependencies = [
2760
"proc-macro2",
2761
"quote",
2762
+
"syn 2.0.113",
2763
]
2764
2765
[[package]]
···
2797
2798
[[package]]
2799
name = "portable-atomic"
2800
+
version = "1.13.0"
2801
source = "registry+https://github.com/rust-lang/crates.io-index"
2802
+
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
2803
+
2804
+
[[package]]
2805
+
name = "postcard"
2806
+
version = "1.1.3"
2807
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2808
+
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
2809
+
dependencies = [
2810
+
"cobs",
2811
+
"embedded-io 0.4.0",
2812
+
"embedded-io 0.6.1",
2813
+
"heapless",
2814
+
"serde",
2815
+
]
2816
2817
[[package]]
2818
name = "potential_utf"
···
2851
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2852
dependencies = [
2853
"proc-macro2",
2854
+
"syn 2.0.113",
2855
]
2856
2857
[[package]]
···
2864
]
2865
2866
[[package]]
2867
name = "proc-macro2"
2868
+
version = "1.0.105"
2869
source = "registry+https://github.com/rust-lang/crates.io-index"
2870
+
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
2871
dependencies = [
2872
"unicode-ident",
2873
]
···
2880
dependencies = [
2881
"proc-macro2",
2882
"quote",
2883
+
"syn 2.0.113",
2884
"version_check",
2885
"yansi",
2886
]
···
2948
2949
[[package]]
2950
name = "quote"
2951
+
version = "1.0.43"
2952
source = "registry+https://github.com/rust-lang/crates.io-index"
2953
+
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
2954
dependencies = [
2955
"proc-macro2",
2956
]
···
3021
]
3022
3023
[[package]]
3024
+
name = "redox_syscall"
3025
+
version = "0.5.18"
3026
source = "registry+https://github.com/rust-lang/crates.io-index"
3027
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3028
+
dependencies = [
3029
+
"bitflags",
3030
+
]
3031
3032
[[package]]
3033
name = "redox_syscall"
3034
+
version = "0.7.0"
3035
source = "registry+https://github.com/rust-lang/crates.io-index"
3036
+
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
3037
dependencies = [
3038
"bitflags",
3039
]
···
3047
"getrandom 0.2.16",
3048
"libredox",
3049
"thiserror 2.0.17",
3050
]
3051
3052
[[package]]
···
3086
3087
[[package]]
3088
name = "reqwest"
3089
+
version = "0.12.28"
3090
source = "registry+https://github.com/rust-lang/crates.io-index"
3091
+
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
3092
dependencies = [
3093
"base64 0.22.1",
3094
"bytes",
3095
"encoding_rs",
···
3184
3185
[[package]]
3186
name = "rsa"
3187
+
version = "0.9.10"
3188
source = "registry+https://github.com/rust-lang/crates.io-index"
3189
+
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
3190
dependencies = [
3191
"const-oid",
3192
"digest",
···
3215
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3216
3217
[[package]]
3218
+
name = "rustc_version"
3219
+
version = "0.4.1"
3220
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3221
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
3222
+
dependencies = [
3223
+
"semver",
3224
+
]
3225
+
3226
+
[[package]]
3227
name = "rustix"
3228
+
version = "1.1.3"
3229
source = "registry+https://github.com/rust-lang/crates.io-index"
3230
+
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
3231
dependencies = [
3232
"bitflags",
3233
"errno",
···
3238
3239
[[package]]
3240
name = "rustls"
3241
+
version = "0.23.36"
3242
source = "registry+https://github.com/rust-lang/crates.io-index"
3243
+
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
3244
dependencies = [
3245
"once_cell",
3246
"ring",
···
3252
3253
[[package]]
3254
name = "rustls-native-certs"
3255
+
version = "0.8.3"
3256
source = "registry+https://github.com/rust-lang/crates.io-index"
3257
+
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
3258
dependencies = [
3259
"openssl-probe",
3260
"rustls-pki-types",
···
3264
3265
[[package]]
3266
name = "rustls-pki-types"
3267
+
version = "1.13.2"
3268
source = "registry+https://github.com/rust-lang/crates.io-index"
3269
+
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
3270
dependencies = [
3271
"web-time",
3272
"zeroize",
···
3291
3292
[[package]]
3293
name = "ryu"
3294
+
version = "1.0.22"
3295
source = "registry+https://github.com/rust-lang/crates.io-index"
3296
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
3297
3298
[[package]]
3299
name = "safemem"
···
3320
]
3321
3322
[[package]]
3323
name = "scoped-tls"
3324
version = "1.0.1"
3325
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3367
"core-foundation-sys",
3368
"libc",
3369
]
3370
+
3371
+
[[package]]
3372
+
name = "semver"
3373
+
version = "1.0.27"
3374
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3375
+
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
3376
3377
[[package]]
3378
name = "send_wrapper"
···
3417
dependencies = [
3418
"proc-macro2",
3419
"quote",
3420
+
"syn 2.0.113",
3421
]
3422
3423
[[package]]
3424
name = "serde_html_form"
3425
+
version = "0.3.2"
3426
source = "registry+https://github.com/rust-lang/crates.io-index"
3427
+
checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94"
3428
dependencies = [
3429
"form_urlencoded",
3430
+
"indexmap",
3431
"itoa",
3432
"serde_core",
3433
]
3434
···
3446
3447
[[package]]
3448
name = "serde_json"
3449
+
version = "1.0.149"
3450
source = "registry+https://github.com/rust-lang/crates.io-index"
3451
+
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
3452
dependencies = [
3453
"itoa",
3454
"memchr",
3455
"serde",
3456
"serde_core",
3457
+
"zmij",
3458
]
3459
3460
[[package]]
···
3476
dependencies = [
3477
"proc-macro2",
3478
"quote",
3479
+
"syn 2.0.113",
3480
]
3481
3482
[[package]]
···
3493
3494
[[package]]
3495
name = "serde_with"
3496
+
version = "3.16.1"
3497
source = "registry+https://github.com/rust-lang/crates.io-index"
3498
+
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
3499
dependencies = [
3500
"base64 0.22.1",
3501
"chrono",
3502
"hex",
3503
"serde_core",
3504
"serde_json",
3505
"serde_with_macros",
···
3508
3509
[[package]]
3510
name = "serde_with_macros"
3511
+
version = "3.16.1"
3512
source = "registry+https://github.com/rust-lang/crates.io-index"
3513
+
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
3514
dependencies = [
3515
"darling",
3516
"proc-macro2",
3517
"quote",
3518
+
"syn 2.0.113",
3519
]
3520
3521
[[package]]
···
3572
3573
[[package]]
3574
name = "signal-hook-registry"
3575
+
version = "1.4.8"
3576
source = "registry+https://github.com/rust-lang/crates.io-index"
3577
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
3578
dependencies = [
3579
+
"errno",
3580
"libc",
3581
]
3582
···
3592
3593
[[package]]
3594
name = "simd-adler32"
3595
+
version = "0.3.8"
3596
source = "registry+https://github.com/rust-lang/crates.io-index"
3597
+
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
3598
3599
[[package]]
3600
name = "siphasher"
···
3649
version = "0.9.8"
3650
source = "registry+https://github.com/rust-lang/crates.io-index"
3651
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
3652
+
dependencies = [
3653
+
"lock_api",
3654
+
]
3655
3656
[[package]]
3657
name = "spin"
···
3676
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3677
3678
[[package]]
3679
name = "static_assertions"
3680
version = "1.1.0"
3681
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3729
3730
[[package]]
3731
name = "supports-hyperlinks"
3732
+
version = "3.2.0"
3733
source = "registry+https://github.com/rust-lang/crates.io-index"
3734
+
checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
3735
3736
[[package]]
3737
name = "supports-unicode"
···
3752
3753
[[package]]
3754
name = "syn"
3755
+
version = "2.0.113"
3756
source = "registry+https://github.com/rust-lang/crates.io-index"
3757
+
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
3758
dependencies = [
3759
"proc-macro2",
3760
"quote",
···
3778
dependencies = [
3779
"proc-macro2",
3780
"quote",
3781
+
"syn 2.0.113",
3782
]
3783
3784
[[package]]
···
3810
3811
[[package]]
3812
name = "tempfile"
3813
+
version = "3.24.0"
3814
source = "registry+https://github.com/rust-lang/crates.io-index"
3815
+
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
3816
dependencies = [
3817
"fastrand",
3818
"getrandom 0.3.4",
···
3878
dependencies = [
3879
"proc-macro2",
3880
"quote",
3881
+
"syn 2.0.113",
3882
]
3883
3884
[[package]]
···
3889
dependencies = [
3890
"proc-macro2",
3891
"quote",
3892
+
"syn 2.0.113",
3893
]
3894
3895
[[package]]
···
3917
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
3918
dependencies = [
3919
"deranged",
3920
"libc",
3921
"num-conv",
3922
"num_threads",
3923
"powerfmt",
3924
"serde",
3925
"time-core",
3926
]
3927
3928
[[package]]
···
3930
version = "0.1.6"
3931
source = "registry+https://github.com/rust-lang/crates.io-index"
3932
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
3933
3934
[[package]]
3935
name = "tiny_http"
···
3970
3971
[[package]]
3972
name = "tokio"
3973
+
version = "1.49.0"
3974
source = "registry+https://github.com/rust-lang/crates.io-index"
3975
+
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
3976
dependencies = [
3977
"bytes",
3978
"libc",
···
3993
dependencies = [
3994
"proc-macro2",
3995
"quote",
3996
+
"syn 2.0.113",
3997
]
3998
3999
[[package]]
···
4043
4044
[[package]]
4045
name = "tokio-util"
4046
+
version = "0.7.18"
4047
source = "registry+https://github.com/rust-lang/crates.io-index"
4048
+
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
4049
dependencies = [
4050
"bytes",
4051
"futures-core",
···
4073
4074
[[package]]
4075
name = "tower-http"
4076
+
version = "0.6.8"
4077
source = "registry+https://github.com/rust-lang/crates.io-index"
4078
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
4079
dependencies = [
4080
"async-compression",
4081
"bitflags",
···
4114
4115
[[package]]
4116
name = "tracing"
4117
+
version = "0.1.44"
4118
source = "registry+https://github.com/rust-lang/crates.io-index"
4119
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
4120
dependencies = [
4121
"log",
4122
"pin-project-lite",
···
4126
4127
[[package]]
4128
name = "tracing-attributes"
4129
+
version = "0.1.31"
4130
source = "registry+https://github.com/rust-lang/crates.io-index"
4131
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
4132
dependencies = [
4133
"proc-macro2",
4134
"quote",
4135
+
"syn 2.0.113",
4136
]
4137
4138
[[package]]
4139
name = "tracing-core"
4140
+
version = "0.1.36"
4141
source = "registry+https://github.com/rust-lang/crates.io-index"
4142
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
4143
dependencies = [
4144
"once_cell",
4145
"valuable",
···
4158
4159
[[package]]
4160
name = "tracing-subscriber"
4161
+
version = "0.3.22"
4162
source = "registry+https://github.com/rust-lang/crates.io-index"
4163
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
4164
dependencies = [
4165
"matchers",
4166
"nu-ansi-term",
···
4182
dependencies = [
4183
"proc-macro2",
4184
"quote",
4185
+
"syn 2.0.113",
4186
]
4187
4188
[[package]]
···
4234
4235
[[package]]
4236
name = "unicase"
4237
+
version = "2.9.0"
4238
source = "registry+https://github.com/rust-lang/crates.io-index"
4239
+
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
4240
4241
[[package]]
4242
name = "unicode-ident"
···
4288
4289
[[package]]
4290
name = "url"
4291
+
version = "2.5.8"
4292
source = "registry+https://github.com/rust-lang/crates.io-index"
4293
+
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
4294
dependencies = [
4295
"form_urlencoded",
4296
"idna",
4297
"percent-encoding",
4298
"serde",
4299
+
"serde_derive",
4300
]
4301
4302
[[package]]
···
4371
4372
[[package]]
4373
name = "wasm-bindgen"
4374
+
version = "0.2.106"
4375
source = "registry+https://github.com/rust-lang/crates.io-index"
4376
+
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
4377
dependencies = [
4378
"cfg-if",
4379
"once_cell",
···
4384
4385
[[package]]
4386
name = "wasm-bindgen-futures"
4387
+
version = "0.4.56"
4388
source = "registry+https://github.com/rust-lang/crates.io-index"
4389
+
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
4390
dependencies = [
4391
"cfg-if",
4392
"js-sys",
···
4397
4398
[[package]]
4399
name = "wasm-bindgen-macro"
4400
+
version = "0.2.106"
4401
source = "registry+https://github.com/rust-lang/crates.io-index"
4402
+
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
4403
dependencies = [
4404
"quote",
4405
"wasm-bindgen-macro-support",
···
4407
4408
[[package]]
4409
name = "wasm-bindgen-macro-support"
4410
+
version = "0.2.106"
4411
source = "registry+https://github.com/rust-lang/crates.io-index"
4412
+
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
4413
dependencies = [
4414
"bumpalo",
4415
"proc-macro2",
4416
"quote",
4417
+
"syn 2.0.113",
4418
"wasm-bindgen-shared",
4419
]
4420
4421
[[package]]
4422
name = "wasm-bindgen-shared"
4423
+
version = "0.2.106"
4424
source = "registry+https://github.com/rust-lang/crates.io-index"
4425
+
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
4426
dependencies = [
4427
"unicode-ident",
4428
]
···
4442
4443
[[package]]
4444
name = "web-sys"
4445
+
version = "0.3.83"
4446
source = "registry+https://github.com/rust-lang/crates.io-index"
4447
+
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
4448
dependencies = [
4449
"js-sys",
4450
"wasm-bindgen",
···
4490
4491
[[package]]
4492
name = "webpki-roots"
4493
+
version = "1.0.5"
4494
source = "registry+https://github.com/rust-lang/crates.io-index"
4495
+
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
4496
dependencies = [
4497
"rustls-pki-types",
4498
]
···
4513
]
4514
4515
[[package]]
4516
name = "windows-core"
4517
version = "0.62.2"
4518
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4520
dependencies = [
4521
"windows-implement",
4522
"windows-interface",
4523
+
"windows-link",
4524
+
"windows-result",
4525
+
"windows-strings",
4526
]
4527
4528
[[package]]
···
4533
dependencies = [
4534
"proc-macro2",
4535
"quote",
4536
+
"syn 2.0.113",
4537
]
4538
4539
[[package]]
···
4544
dependencies = [
4545
"proc-macro2",
4546
"quote",
4547
+
"syn 2.0.113",
4548
]
4549
4550
[[package]]
4551
name = "windows-link"
4552
version = "0.2.1"
4553
source = "registry+https://github.com/rust-lang/crates.io-index"
4554
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4555
4556
[[package]]
4557
name = "windows-registry"
4558
version = "0.6.1"
4559
source = "registry+https://github.com/rust-lang/crates.io-index"
4560
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
4561
dependencies = [
4562
+
"windows-link",
4563
+
"windows-result",
4564
+
"windows-strings",
4565
]
4566
4567
[[package]]
···
4570
source = "registry+https://github.com/rust-lang/crates.io-index"
4571
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
4572
dependencies = [
4573
+
"windows-link",
4574
]
4575
4576
[[package]]
···
4579
source = "registry+https://github.com/rust-lang/crates.io-index"
4580
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
4581
dependencies = [
4582
+
"windows-link",
4583
]
4584
4585
[[package]]
···
4633
source = "registry+https://github.com/rust-lang/crates.io-index"
4634
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
4635
dependencies = [
4636
+
"windows-link",
4637
]
4638
4639
[[package]]
···
4688
source = "registry+https://github.com/rust-lang/crates.io-index"
4689
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
4690
dependencies = [
4691
+
"windows-link",
4692
"windows_aarch64_gnullvm 0.53.1",
4693
"windows_aarch64_msvc 0.53.1",
4694
"windows_i686_gnu 0.53.1",
···
4697
"windows_x86_64_gnu 0.53.1",
4698
"windows_x86_64_gnullvm 0.53.1",
4699
"windows_x86_64_msvc 0.53.1",
4700
]
4701
4702
[[package]]
···
4927
"tower-http",
4928
"url",
4929
"walkdir",
4930
+
"wisp-lexicons",
4931
+
]
4932
+
4933
+
[[package]]
4934
+
name = "wisp-lexicons"
4935
+
version = "0.1.0"
4936
+
dependencies = [
4937
+
"jacquard-common",
4938
+
"jacquard-derive",
4939
+
"jacquard-lexicon",
4940
+
"rustversion",
4941
+
"serde",
4942
]
4943
4944
[[package]]
···
4989
dependencies = [
4990
"proc-macro2",
4991
"quote",
4992
+
"syn 2.0.113",
4993
"synstructure",
4994
]
4995
4996
[[package]]
4997
name = "zerocopy"
4998
+
version = "0.8.32"
4999
source = "registry+https://github.com/rust-lang/crates.io-index"
5000
+
checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56"
5001
dependencies = [
5002
"zerocopy-derive",
5003
]
5004
5005
[[package]]
5006
name = "zerocopy-derive"
5007
+
version = "0.8.32"
5008
source = "registry+https://github.com/rust-lang/crates.io-index"
5009
+
checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90"
5010
dependencies = [
5011
"proc-macro2",
5012
"quote",
5013
+
"syn 2.0.113",
5014
]
5015
5016
[[package]]
···
5030
dependencies = [
5031
"proc-macro2",
5032
"quote",
5033
+
"syn 2.0.113",
5034
"synstructure",
5035
]
5036
···
5073
dependencies = [
5074
"proc-macro2",
5075
"quote",
5076
+
"syn 2.0.113",
5077
]
5078
+
5079
+
[[package]]
5080
+
name = "zmij"
5081
+
version = "1.0.12"
5082
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5083
+
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
+8
-7
cli/Cargo.toml
+8
-7
cli/Cargo.toml
···
8
place_wisp = []
9
10
[dependencies]
11
-
jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] }
12
-
jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" }
13
-
jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] }
14
-
jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] }
15
-
jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] }
16
-
jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" }
17
-
jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" }
18
#jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
19
#jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
20
#jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
···
8
place_wisp = []
9
10
[dependencies]
11
+
jacquard = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["loopback"] }
12
+
jacquard-oauth = { git = "https://tangled.org/nonbinary.computer/jacquard" }
13
+
jacquard-api = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["streaming"] }
14
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["websocket"] }
15
+
jacquard-identity = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["dns"] }
16
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
17
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
18
+
wisp-lexicons = { path = "crates/lexicons" }
19
#jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
20
#jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
21
#jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
+72
-2
cli/README.md
+72
-2
cli/README.md
···
32
33
## Usage
34
35
### Basic Deployment
36
37
Deploy the current directory:
38
39
```bash
40
-
wisp-cli nekomimi.ppet --path . --site my-site
41
```
42
43
Deploy a specific directory:
···
46
wisp-cli alice.bsky.social --path ./dist/ --site my-site
47
```
48
49
### Authentication Methods
50
51
#### OAuth (Recommended)
···
79
80
## Command-Line Options
81
82
```
83
-
wisp-cli [OPTIONS] <INPUT>
84
85
Arguments:
86
<INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
···
90
-s, --site <SITE> Site name (defaults to directory name)
91
--store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
92
--password <PASSWORD> App Password for authentication (alternative to OAuth)
93
-h, --help Print help
94
-V, --version Print version
95
```
96
97
## How It Works
···
32
33
## Usage
34
35
+
### Commands
36
+
37
+
The CLI supports three main commands:
38
+
- **deploy**: Upload a site to your PDS (default command)
39
+
- **pull**: Download a site from a PDS to a local directory
40
+
- **serve**: Serve a site locally with real-time firehose updates
41
+
42
### Basic Deployment
43
44
Deploy the current directory:
45
46
```bash
47
+
wisp-cli nekomimi.pet --path . --site my-site
48
```
49
50
Deploy a specific directory:
···
53
wisp-cli alice.bsky.social --path ./dist/ --site my-site
54
```
55
56
+
Or use the explicit `deploy` subcommand:
57
+
58
+
```bash
59
+
wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site
60
+
```
61
+
62
+
### Pull a Site
63
+
64
+
Download a site from a PDS to a local directory:
65
+
66
+
```bash
67
+
wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site
68
+
```
69
+
70
+
This will download all files from the site to the specified directory.
71
+
72
+
### Serve a Site Locally
73
+
74
+
Serve a site locally with real-time updates from the firehose:
75
+
76
+
```bash
77
+
wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080
78
+
```
79
+
80
+
This will:
81
+
1. Download the site to the specified path
82
+
2. Start a local server on the specified port (default: 8080)
83
+
3. Watch the firehose for updates and automatically reload files when changed
84
+
85
### Authentication Methods
86
87
#### OAuth (Recommended)
···
115
116
## Command-Line Options
117
118
+
### Deploy Command
119
+
120
```
121
+
wisp-cli [deploy] [OPTIONS] <INPUT>
122
123
Arguments:
124
<INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
···
128
-s, --site <SITE> Site name (defaults to directory name)
129
--store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
130
--password <PASSWORD> App Password for authentication (alternative to OAuth)
131
+
--directory Enable directory listing mode for paths without index files
132
+
--spa Enable SPA mode (serve index.html for all routes)
133
+
-y, --yes Skip confirmation prompts (automatically accept warnings)
134
-h, --help Print help
135
-V, --version Print version
136
+
```
137
+
138
+
### Pull Command
139
+
140
+
```
141
+
wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
142
+
143
+
Arguments:
144
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
145
+
146
+
Options:
147
+
-s, --site <SITE> Site name (record key)
148
+
-p, --path <PATH> Output directory for the downloaded site [default: .]
149
+
-h, --help Print help
150
+
```
151
+
152
+
### Serve Command
153
+
154
+
```
155
+
wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
156
+
157
+
Arguments:
158
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
159
+
160
+
Options:
161
+
-s, --site <SITE> Site name (record key)
162
+
-p, --path <PATH> Output directory for the site files [default: .]
163
+
-P, --port <PORT> Port to serve on [default: 8080]
164
+
-h, --help Print help
165
```
166
167
## How It Works
+15
cli/crates/lexicons/Cargo.toml
+15
cli/crates/lexicons/Cargo.toml
···
···
1
+
[package]
2
+
name = "wisp-lexicons"
3
+
version = "0.1.0"
4
+
edition = "2024"
5
+
6
+
[features]
7
+
default = ["place_wisp"]
8
+
place_wisp = []
9
+
10
+
[dependencies]
11
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard" }
12
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
13
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
14
+
serde = { version = "1.0", features = ["derive"] }
15
+
rustversion = "1.0"
+43
cli/crates/lexicons/src/builder_types.rs
+43
cli/crates/lexicons/src/builder_types.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
+
/// Marker type indicating a builder field has been set
7
+
pub struct Set<T>(pub T);
8
+
impl<T> Set<T> {
9
+
/// Extract the inner value
10
+
#[inline]
11
+
pub fn into_inner(self) -> T {
12
+
self.0
13
+
}
14
+
}
15
+
16
+
/// Marker type indicating a builder field has not been set
17
+
pub struct Unset;
18
+
/// Trait indicating a builder field is set (has a value)
19
+
#[rustversion::attr(
20
+
since(1.78.0),
21
+
diagnostic::on_unimplemented(
22
+
message = "the field `{Self}` was not set, but this method requires it to be set",
23
+
label = "the field `{Self}` was not set"
24
+
)
25
+
)]
26
+
pub trait IsSet: private::Sealed {}
27
+
/// Trait indicating a builder field is unset (no value yet)
28
+
#[rustversion::attr(
29
+
since(1.78.0),
30
+
diagnostic::on_unimplemented(
31
+
message = "the field `{Self}` was already set, but this method requires it to be unset",
32
+
label = "the field `{Self}` was already set"
33
+
)
34
+
)]
35
+
pub trait IsUnset: private::Sealed {}
36
+
impl<T> IsSet for Set<T> {}
37
+
impl IsUnset for Unset {}
38
+
mod private {
39
+
/// Sealed trait to prevent external implementations
40
+
pub trait Sealed {}
41
+
impl<T> Sealed for super::Set<T> {}
42
+
impl Sealed for super::Unset {}
43
+
}
+11
cli/crates/lexicons/src/lib.rs
+11
cli/crates/lexicons/src/lib.rs
···
···
1
+
extern crate alloc;
2
+
3
+
// @generated by jacquard-lexicon. DO NOT EDIT.
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
pub mod builder_types;
9
+
10
+
#[cfg(feature = "place_wisp")]
11
+
pub mod place_wisp;
+1490
cli/crates/lexicons/src/place_wisp/fs.rs
+1490
cli/crates/lexicons/src/place_wisp/fs.rs
···
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.fs
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
#[jacquard_derive::lexicon]
9
+
#[derive(
10
+
serde::Serialize,
11
+
serde::Deserialize,
12
+
Debug,
13
+
Clone,
14
+
PartialEq,
15
+
Eq,
16
+
jacquard_derive::IntoStatic
17
+
)]
18
+
#[serde(rename_all = "camelCase")]
19
+
pub struct Directory<'a> {
20
+
#[serde(borrow)]
21
+
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
22
+
#[serde(borrow)]
23
+
pub r#type: jacquard_common::CowStr<'a>,
24
+
}
25
+
26
+
pub mod directory_state {
27
+
28
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
+
#[allow(unused)]
30
+
use ::core::marker::PhantomData;
31
+
mod sealed {
32
+
pub trait Sealed {}
33
+
}
34
+
/// State trait tracking which required fields have been set
35
+
pub trait State: sealed::Sealed {
36
+
type Type;
37
+
type Entries;
38
+
}
39
+
/// Empty state - all required fields are unset
40
+
pub struct Empty(());
41
+
impl sealed::Sealed for Empty {}
42
+
impl State for Empty {
43
+
type Type = Unset;
44
+
type Entries = Unset;
45
+
}
46
+
///State transition - sets the `type` field to Set
47
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
+
impl<S: State> sealed::Sealed for SetType<S> {}
49
+
impl<S: State> State for SetType<S> {
50
+
type Type = Set<members::r#type>;
51
+
type Entries = S::Entries;
52
+
}
53
+
///State transition - sets the `entries` field to Set
54
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
+
impl<S: State> State for SetEntries<S> {
57
+
type Type = S::Type;
58
+
type Entries = Set<members::entries>;
59
+
}
60
+
/// Marker types for field names
61
+
#[allow(non_camel_case_types)]
62
+
pub mod members {
63
+
///Marker type for the `type` field
64
+
pub struct r#type(());
65
+
///Marker type for the `entries` field
66
+
pub struct entries(());
67
+
}
68
+
}
69
+
70
+
/// Builder for constructing an instance of this type
71
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
+
__unsafe_private_named: (
74
+
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
75
+
::core::option::Option<jacquard_common::CowStr<'a>>,
76
+
),
77
+
_phantom: ::core::marker::PhantomData<&'a ()>,
78
+
}
79
+
80
+
impl<'a> Directory<'a> {
81
+
/// Create a new builder for this type
82
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
+
DirectoryBuilder::new()
84
+
}
85
+
}
86
+
87
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
+
/// Create a new builder with all fields unset
89
+
pub fn new() -> Self {
90
+
DirectoryBuilder {
91
+
_phantom_state: ::core::marker::PhantomData,
92
+
__unsafe_private_named: (None, None),
93
+
_phantom: ::core::marker::PhantomData,
94
+
}
95
+
}
96
+
}
97
+
98
+
impl<'a, S> DirectoryBuilder<'a, S>
99
+
where
100
+
S: directory_state::State,
101
+
S::Entries: directory_state::IsUnset,
102
+
{
103
+
/// Set the `entries` field (required)
104
+
pub fn entries(
105
+
mut self,
106
+
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
107
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
+
DirectoryBuilder {
110
+
_phantom_state: ::core::marker::PhantomData,
111
+
__unsafe_private_named: self.__unsafe_private_named,
112
+
_phantom: ::core::marker::PhantomData,
113
+
}
114
+
}
115
+
}
116
+
117
+
impl<'a, S> DirectoryBuilder<'a, S>
118
+
where
119
+
S: directory_state::State,
120
+
S::Type: directory_state::IsUnset,
121
+
{
122
+
/// Set the `type` field (required)
123
+
pub fn r#type(
124
+
mut self,
125
+
value: impl Into<jacquard_common::CowStr<'a>>,
126
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
+
DirectoryBuilder {
129
+
_phantom_state: ::core::marker::PhantomData,
130
+
__unsafe_private_named: self.__unsafe_private_named,
131
+
_phantom: ::core::marker::PhantomData,
132
+
}
133
+
}
134
+
}
135
+
136
+
impl<'a, S> DirectoryBuilder<'a, S>
137
+
where
138
+
S: directory_state::State,
139
+
S::Type: directory_state::IsSet,
140
+
S::Entries: directory_state::IsSet,
141
+
{
142
+
/// Build the final struct
143
+
pub fn build(self) -> Directory<'a> {
144
+
Directory {
145
+
entries: self.__unsafe_private_named.0.unwrap(),
146
+
r#type: self.__unsafe_private_named.1.unwrap(),
147
+
extra_data: Default::default(),
148
+
}
149
+
}
150
+
/// Build the final struct with custom extra_data
151
+
pub fn build_with_data(
152
+
self,
153
+
extra_data: std::collections::BTreeMap<
154
+
jacquard_common::smol_str::SmolStr,
155
+
jacquard_common::types::value::Data<'a>,
156
+
>,
157
+
) -> Directory<'a> {
158
+
Directory {
159
+
entries: self.__unsafe_private_named.0.unwrap(),
160
+
r#type: self.__unsafe_private_named.1.unwrap(),
161
+
extra_data: Some(extra_data),
162
+
}
163
+
}
164
+
}
165
+
166
+
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
+
::jacquard_lexicon::lexicon::LexiconDoc {
168
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
+
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
170
+
revision: None,
171
+
description: None,
172
+
defs: {
173
+
let mut map = ::std::collections::BTreeMap::new();
174
+
map.insert(
175
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
+
description: None,
178
+
required: Some(
179
+
vec![
180
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
+
],
183
+
),
184
+
nullable: None,
185
+
properties: {
186
+
#[allow(unused_mut)]
187
+
let mut map = ::std::collections::BTreeMap::new();
188
+
map.insert(
189
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
+
description: None,
192
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
+
description: None,
194
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
+
}),
196
+
min_length: None,
197
+
max_length: Some(500usize),
198
+
}),
199
+
);
200
+
map.insert(
201
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
+
description: None,
204
+
format: None,
205
+
default: None,
206
+
min_length: None,
207
+
max_length: None,
208
+
min_graphemes: None,
209
+
max_graphemes: None,
210
+
r#enum: None,
211
+
r#const: None,
212
+
known_values: None,
213
+
}),
214
+
);
215
+
map
216
+
},
217
+
}),
218
+
);
219
+
map.insert(
220
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
+
description: None,
223
+
required: Some(
224
+
vec![
225
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
+
::jacquard_common::smol_str::SmolStr::new_static("node")
227
+
],
228
+
),
229
+
nullable: None,
230
+
properties: {
231
+
#[allow(unused_mut)]
232
+
let mut map = ::std::collections::BTreeMap::new();
233
+
map.insert(
234
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
+
description: None,
237
+
format: None,
238
+
default: None,
239
+
min_length: None,
240
+
max_length: Some(255usize),
241
+
min_graphemes: None,
242
+
max_graphemes: None,
243
+
r#enum: None,
244
+
r#const: None,
245
+
known_values: None,
246
+
}),
247
+
);
248
+
map.insert(
249
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
+
description: None,
252
+
refs: vec![
253
+
::jacquard_common::CowStr::new_static("#file"),
254
+
::jacquard_common::CowStr::new_static("#directory"),
255
+
::jacquard_common::CowStr::new_static("#subfs")
256
+
],
257
+
closed: None,
258
+
}),
259
+
);
260
+
map
261
+
},
262
+
}),
263
+
);
264
+
map.insert(
265
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
+
description: None,
268
+
required: Some(
269
+
vec![
270
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
+
],
273
+
),
274
+
nullable: None,
275
+
properties: {
276
+
#[allow(unused_mut)]
277
+
let mut map = ::std::collections::BTreeMap::new();
278
+
map.insert(
279
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
+
description: None,
282
+
default: None,
283
+
r#const: None,
284
+
}),
285
+
);
286
+
map.insert(
287
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
+
description: None,
290
+
accept: None,
291
+
max_size: None,
292
+
}),
293
+
);
294
+
map.insert(
295
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
+
description: Some(
298
+
::jacquard_common::CowStr::new_static(
299
+
"Content encoding (e.g., gzip for compressed files)",
300
+
),
301
+
),
302
+
format: None,
303
+
default: None,
304
+
min_length: None,
305
+
max_length: None,
306
+
min_graphemes: None,
307
+
max_graphemes: None,
308
+
r#enum: None,
309
+
r#const: None,
310
+
known_values: None,
311
+
}),
312
+
);
313
+
map.insert(
314
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
+
description: Some(
317
+
::jacquard_common::CowStr::new_static(
318
+
"Original MIME type before compression",
319
+
),
320
+
),
321
+
format: None,
322
+
default: None,
323
+
min_length: None,
324
+
max_length: None,
325
+
min_graphemes: None,
326
+
max_graphemes: None,
327
+
r#enum: None,
328
+
r#const: None,
329
+
known_values: None,
330
+
}),
331
+
);
332
+
map.insert(
333
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
+
description: None,
336
+
format: None,
337
+
default: None,
338
+
min_length: None,
339
+
max_length: None,
340
+
min_graphemes: None,
341
+
max_graphemes: None,
342
+
r#enum: None,
343
+
r#const: None,
344
+
known_values: None,
345
+
}),
346
+
);
347
+
map
348
+
},
349
+
}),
350
+
);
351
+
map.insert(
352
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
+
description: Some(
355
+
::jacquard_common::CowStr::new_static(
356
+
"Virtual filesystem manifest for a Wisp site",
357
+
),
358
+
),
359
+
key: None,
360
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
+
description: None,
362
+
required: Some(
363
+
vec![
364
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
365
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
366
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
367
+
],
368
+
),
369
+
nullable: None,
370
+
properties: {
371
+
#[allow(unused_mut)]
372
+
let mut map = ::std::collections::BTreeMap::new();
373
+
map.insert(
374
+
::jacquard_common::smol_str::SmolStr::new_static(
375
+
"createdAt",
376
+
),
377
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
378
+
description: None,
379
+
format: Some(
380
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
381
+
),
382
+
default: None,
383
+
min_length: None,
384
+
max_length: None,
385
+
min_graphemes: None,
386
+
max_graphemes: None,
387
+
r#enum: None,
388
+
r#const: None,
389
+
known_values: None,
390
+
}),
391
+
);
392
+
map.insert(
393
+
::jacquard_common::smol_str::SmolStr::new_static(
394
+
"fileCount",
395
+
),
396
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
397
+
description: None,
398
+
default: None,
399
+
minimum: Some(0i64),
400
+
maximum: Some(1000i64),
401
+
r#enum: None,
402
+
r#const: None,
403
+
}),
404
+
);
405
+
map.insert(
406
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
407
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
408
+
description: None,
409
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
410
+
}),
411
+
);
412
+
map.insert(
413
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
414
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
415
+
description: None,
416
+
format: None,
417
+
default: None,
418
+
min_length: None,
419
+
max_length: None,
420
+
min_graphemes: None,
421
+
max_graphemes: None,
422
+
r#enum: None,
423
+
r#const: None,
424
+
known_values: None,
425
+
}),
426
+
);
427
+
map
428
+
},
429
+
}),
430
+
}),
431
+
);
432
+
map.insert(
433
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
434
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
435
+
description: None,
436
+
required: Some(
437
+
vec![
438
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
439
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
440
+
],
441
+
),
442
+
nullable: None,
443
+
properties: {
444
+
#[allow(unused_mut)]
445
+
let mut map = ::std::collections::BTreeMap::new();
446
+
map.insert(
447
+
::jacquard_common::smol_str::SmolStr::new_static("flat"),
448
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
449
+
description: None,
450
+
default: None,
451
+
r#const: None,
452
+
}),
453
+
);
454
+
map.insert(
455
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
456
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
457
+
description: Some(
458
+
::jacquard_common::CowStr::new_static(
459
+
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
460
+
),
461
+
),
462
+
format: Some(
463
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
464
+
),
465
+
default: None,
466
+
min_length: None,
467
+
max_length: None,
468
+
min_graphemes: None,
469
+
max_graphemes: None,
470
+
r#enum: None,
471
+
r#const: None,
472
+
known_values: None,
473
+
}),
474
+
);
475
+
map.insert(
476
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
477
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
478
+
description: None,
479
+
format: None,
480
+
default: None,
481
+
min_length: None,
482
+
max_length: None,
483
+
min_graphemes: None,
484
+
max_graphemes: None,
485
+
r#enum: None,
486
+
r#const: None,
487
+
known_values: None,
488
+
}),
489
+
);
490
+
map
491
+
},
492
+
}),
493
+
);
494
+
map
495
+
},
496
+
}
497
+
}
498
+
499
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
500
+
fn nsid() -> &'static str {
501
+
"place.wisp.fs"
502
+
}
503
+
fn def_name() -> &'static str {
504
+
"directory"
505
+
}
506
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
507
+
lexicon_doc_place_wisp_fs()
508
+
}
509
+
fn validate(
510
+
&self,
511
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
512
+
{
513
+
let value = &self.entries;
514
+
#[allow(unused_comparisons)]
515
+
if value.len() > 500usize {
516
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
517
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
518
+
"entries",
519
+
),
520
+
max: 500usize,
521
+
actual: value.len(),
522
+
});
523
+
}
524
+
}
525
+
Ok(())
526
+
}
527
+
}
528
+
529
+
#[jacquard_derive::lexicon]
530
+
#[derive(
531
+
serde::Serialize,
532
+
serde::Deserialize,
533
+
Debug,
534
+
Clone,
535
+
PartialEq,
536
+
Eq,
537
+
jacquard_derive::IntoStatic
538
+
)]
539
+
#[serde(rename_all = "camelCase")]
540
+
pub struct Entry<'a> {
541
+
#[serde(borrow)]
542
+
pub name: jacquard_common::CowStr<'a>,
543
+
#[serde(borrow)]
544
+
pub node: EntryNode<'a>,
545
+
}
546
+
547
+
pub mod entry_state {
548
+
549
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
550
+
#[allow(unused)]
551
+
use ::core::marker::PhantomData;
552
+
mod sealed {
553
+
pub trait Sealed {}
554
+
}
555
+
/// State trait tracking which required fields have been set
556
+
pub trait State: sealed::Sealed {
557
+
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 {
564
+
type Node = Unset;
565
+
type Name = Unset;
566
+
}
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> {
571
+
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(());
588
+
}
589
+
}
590
+
591
+
/// Builder for constructing an instance of this type
592
+
pub struct EntryBuilder<'a, S: entry_state::State> {
593
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
594
+
__unsafe_private_named: (
595
+
::core::option::Option<jacquard_common::CowStr<'a>>,
596
+
::core::option::Option<EntryNode<'a>>,
597
+
),
598
+
_phantom: ::core::marker::PhantomData<&'a ()>,
599
+
}
600
+
601
+
impl<'a> Entry<'a> {
602
+
/// Create a new builder for this type
603
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
604
+
EntryBuilder::new()
605
+
}
606
+
}
607
+
608
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
609
+
/// Create a new builder with all fields unset
610
+
pub fn new() -> Self {
611
+
EntryBuilder {
612
+
_phantom_state: ::core::marker::PhantomData,
613
+
__unsafe_private_named: (None, None),
614
+
_phantom: ::core::marker::PhantomData,
615
+
}
616
+
}
617
+
}
618
+
619
+
impl<'a, S> EntryBuilder<'a, S>
620
+
where
621
+
S: entry_state::State,
622
+
S::Name: entry_state::IsUnset,
623
+
{
624
+
/// Set the `name` field (required)
625
+
pub fn name(
626
+
mut self,
627
+
value: impl Into<jacquard_common::CowStr<'a>>,
628
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
629
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
630
+
EntryBuilder {
631
+
_phantom_state: ::core::marker::PhantomData,
632
+
__unsafe_private_named: self.__unsafe_private_named,
633
+
_phantom: ::core::marker::PhantomData,
634
+
}
635
+
}
636
+
}
637
+
638
+
impl<'a, S> EntryBuilder<'a, S>
639
+
where
640
+
S: entry_state::State,
641
+
S::Node: entry_state::IsUnset,
642
+
{
643
+
/// Set the `node` field (required)
644
+
pub fn node(
645
+
mut self,
646
+
value: impl Into<EntryNode<'a>>,
647
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
648
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
649
+
EntryBuilder {
650
+
_phantom_state: ::core::marker::PhantomData,
651
+
__unsafe_private_named: self.__unsafe_private_named,
652
+
_phantom: ::core::marker::PhantomData,
653
+
}
654
+
}
655
+
}
656
+
657
+
impl<'a, S> EntryBuilder<'a, S>
658
+
where
659
+
S: entry_state::State,
660
+
S::Node: entry_state::IsSet,
661
+
S::Name: entry_state::IsSet,
662
+
{
663
+
/// Build the final struct
664
+
pub fn build(self) -> Entry<'a> {
665
+
Entry {
666
+
name: self.__unsafe_private_named.0.unwrap(),
667
+
node: self.__unsafe_private_named.1.unwrap(),
668
+
extra_data: Default::default(),
669
+
}
670
+
}
671
+
/// Build the final struct with custom extra_data
672
+
pub fn build_with_data(
673
+
self,
674
+
extra_data: std::collections::BTreeMap<
675
+
jacquard_common::smol_str::SmolStr,
676
+
jacquard_common::types::value::Data<'a>,
677
+
>,
678
+
) -> Entry<'a> {
679
+
Entry {
680
+
name: self.__unsafe_private_named.0.unwrap(),
681
+
node: self.__unsafe_private_named.1.unwrap(),
682
+
extra_data: Some(extra_data),
683
+
}
684
+
}
685
+
}
686
+
687
+
#[jacquard_derive::open_union]
688
+
#[derive(
689
+
serde::Serialize,
690
+
serde::Deserialize,
691
+
Debug,
692
+
Clone,
693
+
PartialEq,
694
+
Eq,
695
+
jacquard_derive::IntoStatic
696
+
)]
697
+
#[serde(tag = "$type")]
698
+
#[serde(bound(deserialize = "'de: 'a"))]
699
+
pub enum EntryNode<'a> {
700
+
#[serde(rename = "place.wisp.fs#file")]
701
+
File(Box<crate::place_wisp::fs::File<'a>>),
702
+
#[serde(rename = "place.wisp.fs#directory")]
703
+
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
704
+
#[serde(rename = "place.wisp.fs#subfs")]
705
+
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
706
+
}
707
+
708
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
709
+
fn nsid() -> &'static str {
710
+
"place.wisp.fs"
711
+
}
712
+
fn def_name() -> &'static str {
713
+
"entry"
714
+
}
715
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
716
+
lexicon_doc_place_wisp_fs()
717
+
}
718
+
fn validate(
719
+
&self,
720
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
721
+
{
722
+
let value = &self.name;
723
+
#[allow(unused_comparisons)]
724
+
if <str>::len(value.as_ref()) > 255usize {
725
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
726
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
727
+
"name",
728
+
),
729
+
max: 255usize,
730
+
actual: <str>::len(value.as_ref()),
731
+
});
732
+
}
733
+
}
734
+
Ok(())
735
+
}
736
+
}
737
+
738
+
#[jacquard_derive::lexicon]
739
+
#[derive(
740
+
serde::Serialize,
741
+
serde::Deserialize,
742
+
Debug,
743
+
Clone,
744
+
PartialEq,
745
+
Eq,
746
+
jacquard_derive::IntoStatic
747
+
)]
748
+
#[serde(rename_all = "camelCase")]
749
+
pub 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
+
}
767
+
768
+
pub mod file_state {
769
+
770
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
771
+
#[allow(unused)]
772
+
use ::core::marker::PhantomData;
773
+
mod sealed {
774
+
pub trait Sealed {}
775
+
}
776
+
/// State trait tracking which required fields have been set
777
+
pub trait State: sealed::Sealed {
778
+
type Type;
779
+
type Blob;
780
+
}
781
+
/// Empty state - all required fields are unset
782
+
pub struct Empty(());
783
+
impl sealed::Sealed for Empty {}
784
+
impl State for Empty {
785
+
type Type = Unset;
786
+
type Blob = Unset;
787
+
}
788
+
///State transition - sets the `type` field to Set
789
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
790
+
impl<S: State> sealed::Sealed for SetType<S> {}
791
+
impl<S: State> State for SetType<S> {
792
+
type Type = Set<members::r#type>;
793
+
type Blob = S::Blob;
794
+
}
795
+
///State transition - sets the `blob` field to Set
796
+
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
797
+
impl<S: State> sealed::Sealed for SetBlob<S> {}
798
+
impl<S: State> State for SetBlob<S> {
799
+
type Type = S::Type;
800
+
type Blob = Set<members::blob>;
801
+
}
802
+
/// Marker types for field names
803
+
#[allow(non_camel_case_types)]
804
+
pub mod members {
805
+
///Marker type for the `type` field
806
+
pub struct r#type(());
807
+
///Marker type for the `blob` field
808
+
pub struct blob(());
809
+
}
810
+
}
811
+
812
+
/// Builder for constructing an instance of this type
813
+
pub struct FileBuilder<'a, S: file_state::State> {
814
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
815
+
__unsafe_private_named: (
816
+
::core::option::Option<bool>,
817
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
818
+
::core::option::Option<jacquard_common::CowStr<'a>>,
819
+
::core::option::Option<jacquard_common::CowStr<'a>>,
820
+
::core::option::Option<jacquard_common::CowStr<'a>>,
821
+
),
822
+
_phantom: ::core::marker::PhantomData<&'a ()>,
823
+
}
824
+
825
+
impl<'a> File<'a> {
826
+
/// Create a new builder for this type
827
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
828
+
FileBuilder::new()
829
+
}
830
+
}
831
+
832
+
impl<'a> FileBuilder<'a, file_state::Empty> {
833
+
/// Create a new builder with all fields unset
834
+
pub fn new() -> Self {
835
+
FileBuilder {
836
+
_phantom_state: ::core::marker::PhantomData,
837
+
__unsafe_private_named: (None, None, None, None, None),
838
+
_phantom: ::core::marker::PhantomData,
839
+
}
840
+
}
841
+
}
842
+
843
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
844
+
/// Set the `base64` field (optional)
845
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
846
+
self.__unsafe_private_named.0 = value.into();
847
+
self
848
+
}
849
+
/// Set the `base64` field to an Option value (optional)
850
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
851
+
self.__unsafe_private_named.0 = value;
852
+
self
853
+
}
854
+
}
855
+
856
+
impl<'a, S> FileBuilder<'a, S>
857
+
where
858
+
S: file_state::State,
859
+
S::Blob: file_state::IsUnset,
860
+
{
861
+
/// Set the `blob` field (required)
862
+
pub fn blob(
863
+
mut self,
864
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
865
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
866
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
867
+
FileBuilder {
868
+
_phantom_state: ::core::marker::PhantomData,
869
+
__unsafe_private_named: self.__unsafe_private_named,
870
+
_phantom: ::core::marker::PhantomData,
871
+
}
872
+
}
873
+
}
874
+
875
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
876
+
/// Set the `encoding` field (optional)
877
+
pub fn encoding(
878
+
mut self,
879
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
880
+
) -> Self {
881
+
self.__unsafe_private_named.2 = value.into();
882
+
self
883
+
}
884
+
/// Set the `encoding` field to an Option value (optional)
885
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
886
+
self.__unsafe_private_named.2 = value;
887
+
self
888
+
}
889
+
}
890
+
891
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
892
+
/// Set the `mimeType` field (optional)
893
+
pub fn mime_type(
894
+
mut self,
895
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
896
+
) -> Self {
897
+
self.__unsafe_private_named.3 = value.into();
898
+
self
899
+
}
900
+
/// Set the `mimeType` field to an Option value (optional)
901
+
pub fn maybe_mime_type(
902
+
mut self,
903
+
value: Option<jacquard_common::CowStr<'a>>,
904
+
) -> Self {
905
+
self.__unsafe_private_named.3 = value;
906
+
self
907
+
}
908
+
}
909
+
910
+
impl<'a, S> FileBuilder<'a, S>
911
+
where
912
+
S: file_state::State,
913
+
S::Type: file_state::IsUnset,
914
+
{
915
+
/// Set the `type` field (required)
916
+
pub fn r#type(
917
+
mut self,
918
+
value: impl Into<jacquard_common::CowStr<'a>>,
919
+
) -> FileBuilder<'a, file_state::SetType<S>> {
920
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
921
+
FileBuilder {
922
+
_phantom_state: ::core::marker::PhantomData,
923
+
__unsafe_private_named: self.__unsafe_private_named,
924
+
_phantom: ::core::marker::PhantomData,
925
+
}
926
+
}
927
+
}
928
+
929
+
impl<'a, S> FileBuilder<'a, S>
930
+
where
931
+
S: file_state::State,
932
+
S::Type: file_state::IsSet,
933
+
S::Blob: file_state::IsSet,
934
+
{
935
+
/// Build the final struct
936
+
pub fn build(self) -> File<'a> {
937
+
File {
938
+
base64: self.__unsafe_private_named.0,
939
+
blob: self.__unsafe_private_named.1.unwrap(),
940
+
encoding: self.__unsafe_private_named.2,
941
+
mime_type: self.__unsafe_private_named.3,
942
+
r#type: self.__unsafe_private_named.4.unwrap(),
943
+
extra_data: Default::default(),
944
+
}
945
+
}
946
+
/// Build the final struct with custom extra_data
947
+
pub fn build_with_data(
948
+
self,
949
+
extra_data: std::collections::BTreeMap<
950
+
jacquard_common::smol_str::SmolStr,
951
+
jacquard_common::types::value::Data<'a>,
952
+
>,
953
+
) -> File<'a> {
954
+
File {
955
+
base64: self.__unsafe_private_named.0,
956
+
blob: self.__unsafe_private_named.1.unwrap(),
957
+
encoding: self.__unsafe_private_named.2,
958
+
mime_type: self.__unsafe_private_named.3,
959
+
r#type: self.__unsafe_private_named.4.unwrap(),
960
+
extra_data: Some(extra_data),
961
+
}
962
+
}
963
+
}
964
+
965
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
966
+
fn nsid() -> &'static str {
967
+
"place.wisp.fs"
968
+
}
969
+
fn def_name() -> &'static str {
970
+
"file"
971
+
}
972
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
973
+
lexicon_doc_place_wisp_fs()
974
+
}
975
+
fn validate(
976
+
&self,
977
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
978
+
Ok(())
979
+
}
980
+
}
981
+
982
+
/// Virtual filesystem manifest for a Wisp site
983
+
#[jacquard_derive::lexicon]
984
+
#[derive(
985
+
serde::Serialize,
986
+
serde::Deserialize,
987
+
Debug,
988
+
Clone,
989
+
PartialEq,
990
+
Eq,
991
+
jacquard_derive::IntoStatic
992
+
)]
993
+
#[serde(rename_all = "camelCase")]
994
+
pub 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)]
1001
+
pub site: jacquard_common::CowStr<'a>,
1002
+
}
1003
+
1004
+
pub mod fs_state {
1005
+
1006
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1007
+
#[allow(unused)]
1008
+
use ::core::marker::PhantomData;
1009
+
mod sealed {
1010
+
pub trait Sealed {}
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;
1017
+
}
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;
1041
+
}
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> {
1046
+
type CreatedAt = S::CreatedAt;
1047
+
type Site = S::Site;
1048
+
type Root = Set<members::root>;
1049
+
}
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(());
1059
+
}
1060
+
}
1061
+
1062
+
/// Builder for constructing an instance of this type
1063
+
pub struct FsBuilder<'a, S: fs_state::State> {
1064
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1065
+
__unsafe_private_named: (
1066
+
::core::option::Option<jacquard_common::types::string::Datetime>,
1067
+
::core::option::Option<i64>,
1068
+
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
1069
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1070
+
),
1071
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1072
+
}
1073
+
1074
+
impl<'a> Fs<'a> {
1075
+
/// Create a new builder for this type
1076
+
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
1077
+
FsBuilder::new()
1078
+
}
1079
+
}
1080
+
1081
+
impl<'a> FsBuilder<'a, fs_state::Empty> {
1082
+
/// Create a new builder with all fields unset
1083
+
pub fn new() -> Self {
1084
+
FsBuilder {
1085
+
_phantom_state: ::core::marker::PhantomData,
1086
+
__unsafe_private_named: (None, None, None, None),
1087
+
_phantom: ::core::marker::PhantomData,
1088
+
}
1089
+
}
1090
+
}
1091
+
1092
+
impl<'a, S> FsBuilder<'a, S>
1093
+
where
1094
+
S: fs_state::State,
1095
+
S::CreatedAt: fs_state::IsUnset,
1096
+
{
1097
+
/// Set the `createdAt` field (required)
1098
+
pub fn created_at(
1099
+
mut self,
1100
+
value: impl Into<jacquard_common::types::string::Datetime>,
1101
+
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
1102
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1103
+
FsBuilder {
1104
+
_phantom_state: ::core::marker::PhantomData,
1105
+
__unsafe_private_named: self.__unsafe_private_named,
1106
+
_phantom: ::core::marker::PhantomData,
1107
+
}
1108
+
}
1109
+
}
1110
+
1111
+
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
1112
+
/// Set the `fileCount` field (optional)
1113
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1114
+
self.__unsafe_private_named.1 = value.into();
1115
+
self
1116
+
}
1117
+
/// Set the `fileCount` field to an Option value (optional)
1118
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1119
+
self.__unsafe_private_named.1 = value;
1120
+
self
1121
+
}
1122
+
}
1123
+
1124
+
impl<'a, S> FsBuilder<'a, S>
1125
+
where
1126
+
S: fs_state::State,
1127
+
S::Root: fs_state::IsUnset,
1128
+
{
1129
+
/// Set the `root` field (required)
1130
+
pub fn root(
1131
+
mut self,
1132
+
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
1133
+
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
1134
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1135
+
FsBuilder {
1136
+
_phantom_state: ::core::marker::PhantomData,
1137
+
__unsafe_private_named: self.__unsafe_private_named,
1138
+
_phantom: ::core::marker::PhantomData,
1139
+
}
1140
+
}
1141
+
}
1142
+
1143
+
impl<'a, S> FsBuilder<'a, S>
1144
+
where
1145
+
S: fs_state::State,
1146
+
S::Site: fs_state::IsUnset,
1147
+
{
1148
+
/// Set the `site` field (required)
1149
+
pub fn site(
1150
+
mut self,
1151
+
value: impl Into<jacquard_common::CowStr<'a>>,
1152
+
) -> FsBuilder<'a, fs_state::SetSite<S>> {
1153
+
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
1154
+
FsBuilder {
1155
+
_phantom_state: ::core::marker::PhantomData,
1156
+
__unsafe_private_named: self.__unsafe_private_named,
1157
+
_phantom: ::core::marker::PhantomData,
1158
+
}
1159
+
}
1160
+
}
1161
+
1162
+
impl<'a, S> FsBuilder<'a, S>
1163
+
where
1164
+
S: fs_state::State,
1165
+
S::CreatedAt: fs_state::IsSet,
1166
+
S::Site: fs_state::IsSet,
1167
+
S::Root: fs_state::IsSet,
1168
+
{
1169
+
/// Build the final struct
1170
+
pub fn build(self) -> Fs<'a> {
1171
+
Fs {
1172
+
created_at: self.__unsafe_private_named.0.unwrap(),
1173
+
file_count: self.__unsafe_private_named.1,
1174
+
root: self.__unsafe_private_named.2.unwrap(),
1175
+
site: self.__unsafe_private_named.3.unwrap(),
1176
+
extra_data: Default::default(),
1177
+
}
1178
+
}
1179
+
/// Build the final struct with custom extra_data
1180
+
pub fn build_with_data(
1181
+
self,
1182
+
extra_data: std::collections::BTreeMap<
1183
+
jacquard_common::smol_str::SmolStr,
1184
+
jacquard_common::types::value::Data<'a>,
1185
+
>,
1186
+
) -> Fs<'a> {
1187
+
Fs {
1188
+
created_at: self.__unsafe_private_named.0.unwrap(),
1189
+
file_count: self.__unsafe_private_named.1,
1190
+
root: self.__unsafe_private_named.2.unwrap(),
1191
+
site: self.__unsafe_private_named.3.unwrap(),
1192
+
extra_data: Some(extra_data),
1193
+
}
1194
+
}
1195
+
}
1196
+
1197
+
impl<'a> Fs<'a> {
1198
+
pub fn uri(
1199
+
uri: impl Into<jacquard_common::CowStr<'a>>,
1200
+
) -> Result<
1201
+
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
1202
+
jacquard_common::types::uri::UriError,
1203
+
> {
1204
+
jacquard_common::types::uri::RecordUri::try_from_uri(
1205
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1206
+
)
1207
+
}
1208
+
}
1209
+
1210
+
/// Typed wrapper for GetRecord response with this collection's record type.
1211
+
#[derive(
1212
+
serde::Serialize,
1213
+
serde::Deserialize,
1214
+
Debug,
1215
+
Clone,
1216
+
PartialEq,
1217
+
Eq,
1218
+
jacquard_derive::IntoStatic
1219
+
)]
1220
+
#[serde(rename_all = "camelCase")]
1221
+
pub struct FsGetRecordOutput<'a> {
1222
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1223
+
#[serde(borrow)]
1224
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1225
+
#[serde(borrow)]
1226
+
pub uri: jacquard_common::types::string::AtUri<'a>,
1227
+
#[serde(borrow)]
1228
+
pub value: Fs<'a>,
1229
+
}
1230
+
1231
+
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
1232
+
fn from(output: FsGetRecordOutput<'_>) -> Self {
1233
+
use jacquard_common::IntoStatic;
1234
+
output.value.into_static()
1235
+
}
1236
+
}
1237
+
1238
+
impl jacquard_common::types::collection::Collection for Fs<'_> {
1239
+
const NSID: &'static str = "place.wisp.fs";
1240
+
type Record = FsRecord;
1241
+
}
1242
+
1243
+
/// Marker type for deserializing records from this collection.
1244
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1245
+
pub struct FsRecord;
1246
+
impl jacquard_common::xrpc::XrpcResp for FsRecord {
1247
+
const NSID: &'static str = "place.wisp.fs";
1248
+
const ENCODING: &'static str = "application/json";
1249
+
type Output<'de> = FsGetRecordOutput<'de>;
1250
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1251
+
}
1252
+
1253
+
impl jacquard_common::types::collection::Collection for FsRecord {
1254
+
const NSID: &'static str = "place.wisp.fs";
1255
+
type Record = FsRecord;
1256
+
}
1257
+
1258
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
1259
+
fn nsid() -> &'static str {
1260
+
"place.wisp.fs"
1261
+
}
1262
+
fn def_name() -> &'static str {
1263
+
"main"
1264
+
}
1265
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1266
+
lexicon_doc_place_wisp_fs()
1267
+
}
1268
+
fn validate(
1269
+
&self,
1270
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1271
+
if let Some(ref value) = self.file_count {
1272
+
if *value > 1000i64 {
1273
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1274
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1275
+
"file_count",
1276
+
),
1277
+
max: 1000i64,
1278
+
actual: *value,
1279
+
});
1280
+
}
1281
+
}
1282
+
if let Some(ref value) = self.file_count {
1283
+
if *value < 0i64 {
1284
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1285
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1286
+
"file_count",
1287
+
),
1288
+
min: 0i64,
1289
+
actual: *value,
1290
+
});
1291
+
}
1292
+
}
1293
+
Ok(())
1294
+
}
1295
+
}
1296
+
1297
+
#[jacquard_derive::lexicon]
1298
+
#[derive(
1299
+
serde::Serialize,
1300
+
serde::Deserialize,
1301
+
Debug,
1302
+
Clone,
1303
+
PartialEq,
1304
+
Eq,
1305
+
jacquard_derive::IntoStatic
1306
+
)]
1307
+
#[serde(rename_all = "camelCase")]
1308
+
pub 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>,
1315
+
#[serde(borrow)]
1316
+
pub r#type: jacquard_common::CowStr<'a>,
1317
+
}
1318
+
1319
+
pub mod subfs_state {
1320
+
1321
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1322
+
#[allow(unused)]
1323
+
use ::core::marker::PhantomData;
1324
+
mod sealed {
1325
+
pub trait Sealed {}
1326
+
}
1327
+
/// State trait tracking which required fields have been set
1328
+
pub trait State: sealed::Sealed {
1329
+
type Type;
1330
+
type Subject;
1331
+
}
1332
+
/// Empty state - all required fields are unset
1333
+
pub struct Empty(());
1334
+
impl sealed::Sealed for Empty {}
1335
+
impl State for Empty {
1336
+
type Type = Unset;
1337
+
type Subject = Unset;
1338
+
}
1339
+
///State transition - sets the `type` field to Set
1340
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1341
+
impl<S: State> sealed::Sealed for SetType<S> {}
1342
+
impl<S: State> State for SetType<S> {
1343
+
type Type = Set<members::r#type>;
1344
+
type Subject = S::Subject;
1345
+
}
1346
+
///State transition - sets the `subject` field to Set
1347
+
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1348
+
impl<S: State> sealed::Sealed for SetSubject<S> {}
1349
+
impl<S: State> State for SetSubject<S> {
1350
+
type Type = S::Type;
1351
+
type Subject = Set<members::subject>;
1352
+
}
1353
+
/// Marker types for field names
1354
+
#[allow(non_camel_case_types)]
1355
+
pub mod members {
1356
+
///Marker type for the `type` field
1357
+
pub struct r#type(());
1358
+
///Marker type for the `subject` field
1359
+
pub struct subject(());
1360
+
}
1361
+
}
1362
+
1363
+
/// Builder for constructing an instance of this type
1364
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1365
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1366
+
__unsafe_private_named: (
1367
+
::core::option::Option<bool>,
1368
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1369
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1370
+
),
1371
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1372
+
}
1373
+
1374
+
impl<'a> Subfs<'a> {
1375
+
/// Create a new builder for this type
1376
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1377
+
SubfsBuilder::new()
1378
+
}
1379
+
}
1380
+
1381
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1382
+
/// Create a new builder with all fields unset
1383
+
pub fn new() -> Self {
1384
+
SubfsBuilder {
1385
+
_phantom_state: ::core::marker::PhantomData,
1386
+
__unsafe_private_named: (None, None, None),
1387
+
_phantom: ::core::marker::PhantomData,
1388
+
}
1389
+
}
1390
+
}
1391
+
1392
+
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
1393
+
/// Set the `flat` field (optional)
1394
+
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
1395
+
self.__unsafe_private_named.0 = value.into();
1396
+
self
1397
+
}
1398
+
/// Set the `flat` field to an Option value (optional)
1399
+
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
1400
+
self.__unsafe_private_named.0 = value;
1401
+
self
1402
+
}
1403
+
}
1404
+
1405
+
impl<'a, S> SubfsBuilder<'a, S>
1406
+
where
1407
+
S: subfs_state::State,
1408
+
S::Subject: subfs_state::IsUnset,
1409
+
{
1410
+
/// Set the `subject` field (required)
1411
+
pub fn subject(
1412
+
mut self,
1413
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1414
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1415
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1416
+
SubfsBuilder {
1417
+
_phantom_state: ::core::marker::PhantomData,
1418
+
__unsafe_private_named: self.__unsafe_private_named,
1419
+
_phantom: ::core::marker::PhantomData,
1420
+
}
1421
+
}
1422
+
}
1423
+
1424
+
impl<'a, S> SubfsBuilder<'a, S>
1425
+
where
1426
+
S: subfs_state::State,
1427
+
S::Type: subfs_state::IsUnset,
1428
+
{
1429
+
/// Set the `type` field (required)
1430
+
pub fn r#type(
1431
+
mut self,
1432
+
value: impl Into<jacquard_common::CowStr<'a>>,
1433
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1434
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1435
+
SubfsBuilder {
1436
+
_phantom_state: ::core::marker::PhantomData,
1437
+
__unsafe_private_named: self.__unsafe_private_named,
1438
+
_phantom: ::core::marker::PhantomData,
1439
+
}
1440
+
}
1441
+
}
1442
+
1443
+
impl<'a, S> SubfsBuilder<'a, S>
1444
+
where
1445
+
S: subfs_state::State,
1446
+
S::Type: subfs_state::IsSet,
1447
+
S::Subject: subfs_state::IsSet,
1448
+
{
1449
+
/// Build the final struct
1450
+
pub fn build(self) -> Subfs<'a> {
1451
+
Subfs {
1452
+
flat: self.__unsafe_private_named.0,
1453
+
subject: self.__unsafe_private_named.1.unwrap(),
1454
+
r#type: self.__unsafe_private_named.2.unwrap(),
1455
+
extra_data: Default::default(),
1456
+
}
1457
+
}
1458
+
/// Build the final struct with custom extra_data
1459
+
pub fn build_with_data(
1460
+
self,
1461
+
extra_data: std::collections::BTreeMap<
1462
+
jacquard_common::smol_str::SmolStr,
1463
+
jacquard_common::types::value::Data<'a>,
1464
+
>,
1465
+
) -> Subfs<'a> {
1466
+
Subfs {
1467
+
flat: self.__unsafe_private_named.0,
1468
+
subject: self.__unsafe_private_named.1.unwrap(),
1469
+
r#type: self.__unsafe_private_named.2.unwrap(),
1470
+
extra_data: Some(extra_data),
1471
+
}
1472
+
}
1473
+
}
1474
+
1475
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1476
+
fn nsid() -> &'static str {
1477
+
"place.wisp.fs"
1478
+
}
1479
+
fn def_name() -> &'static str {
1480
+
"subfs"
1481
+
}
1482
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1483
+
lexicon_doc_place_wisp_fs()
1484
+
}
1485
+
fn validate(
1486
+
&self,
1487
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1488
+
Ok(())
1489
+
}
1490
+
}
+653
cli/crates/lexicons/src/place_wisp/settings.rs
+653
cli/crates/lexicons/src/place_wisp/settings.rs
···
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.settings
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
/// Custom HTTP header configuration
9
+
#[jacquard_derive::lexicon]
10
+
#[derive(
11
+
serde::Serialize,
12
+
serde::Deserialize,
13
+
Debug,
14
+
Clone,
15
+
PartialEq,
16
+
Eq,
17
+
jacquard_derive::IntoStatic,
18
+
Default
19
+
)]
20
+
#[serde(rename_all = "camelCase")]
21
+
pub struct CustomHeader<'a> {
22
+
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
23
+
#[serde(borrow)]
24
+
pub name: jacquard_common::CowStr<'a>,
25
+
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
26
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
27
+
#[serde(borrow)]
28
+
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
29
+
/// HTTP header value
30
+
#[serde(borrow)]
31
+
pub value: jacquard_common::CowStr<'a>,
32
+
}
33
+
34
+
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
35
+
'static,
36
+
> {
37
+
::jacquard_lexicon::lexicon::LexiconDoc {
38
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
39
+
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
40
+
revision: None,
41
+
description: None,
42
+
defs: {
43
+
let mut map = ::std::collections::BTreeMap::new();
44
+
map.insert(
45
+
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
46
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
47
+
description: Some(
48
+
::jacquard_common::CowStr::new_static(
49
+
"Custom HTTP header configuration",
50
+
),
51
+
),
52
+
required: Some(
53
+
vec![
54
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
55
+
::jacquard_common::smol_str::SmolStr::new_static("value")
56
+
],
57
+
),
58
+
nullable: None,
59
+
properties: {
60
+
#[allow(unused_mut)]
61
+
let mut map = ::std::collections::BTreeMap::new();
62
+
map.insert(
63
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
64
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
65
+
description: Some(
66
+
::jacquard_common::CowStr::new_static(
67
+
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
68
+
),
69
+
),
70
+
format: None,
71
+
default: None,
72
+
min_length: None,
73
+
max_length: Some(100usize),
74
+
min_graphemes: None,
75
+
max_graphemes: None,
76
+
r#enum: None,
77
+
r#const: None,
78
+
known_values: None,
79
+
}),
80
+
);
81
+
map.insert(
82
+
::jacquard_common::smol_str::SmolStr::new_static("path"),
83
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
84
+
description: Some(
85
+
::jacquard_common::CowStr::new_static(
86
+
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
87
+
),
88
+
),
89
+
format: None,
90
+
default: None,
91
+
min_length: None,
92
+
max_length: Some(500usize),
93
+
min_graphemes: None,
94
+
max_graphemes: None,
95
+
r#enum: None,
96
+
r#const: None,
97
+
known_values: None,
98
+
}),
99
+
);
100
+
map.insert(
101
+
::jacquard_common::smol_str::SmolStr::new_static("value"),
102
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
103
+
description: Some(
104
+
::jacquard_common::CowStr::new_static("HTTP header value"),
105
+
),
106
+
format: None,
107
+
default: None,
108
+
min_length: None,
109
+
max_length: Some(1000usize),
110
+
min_graphemes: None,
111
+
max_graphemes: None,
112
+
r#enum: None,
113
+
r#const: None,
114
+
known_values: None,
115
+
}),
116
+
);
117
+
map
118
+
},
119
+
}),
120
+
);
121
+
map.insert(
122
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
123
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
124
+
description: Some(
125
+
::jacquard_common::CowStr::new_static(
126
+
"Configuration settings for a static site hosted on wisp.place",
127
+
),
128
+
),
129
+
key: Some(::jacquard_common::CowStr::new_static("any")),
130
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
131
+
description: None,
132
+
required: None,
133
+
nullable: None,
134
+
properties: {
135
+
#[allow(unused_mut)]
136
+
let mut map = ::std::collections::BTreeMap::new();
137
+
map.insert(
138
+
::jacquard_common::smol_str::SmolStr::new_static(
139
+
"cleanUrls",
140
+
),
141
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
142
+
description: None,
143
+
default: None,
144
+
r#const: None,
145
+
}),
146
+
);
147
+
map.insert(
148
+
::jacquard_common::smol_str::SmolStr::new_static(
149
+
"custom404",
150
+
),
151
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
152
+
description: Some(
153
+
::jacquard_common::CowStr::new_static(
154
+
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
155
+
),
156
+
),
157
+
format: None,
158
+
default: None,
159
+
min_length: None,
160
+
max_length: Some(500usize),
161
+
min_graphemes: None,
162
+
max_graphemes: None,
163
+
r#enum: None,
164
+
r#const: None,
165
+
known_values: None,
166
+
}),
167
+
);
168
+
map.insert(
169
+
::jacquard_common::smol_str::SmolStr::new_static(
170
+
"directoryListing",
171
+
),
172
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
173
+
description: None,
174
+
default: None,
175
+
r#const: None,
176
+
}),
177
+
);
178
+
map.insert(
179
+
::jacquard_common::smol_str::SmolStr::new_static("headers"),
180
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
181
+
description: Some(
182
+
::jacquard_common::CowStr::new_static(
183
+
"Custom HTTP headers to set on responses",
184
+
),
185
+
),
186
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
187
+
description: None,
188
+
r#ref: ::jacquard_common::CowStr::new_static(
189
+
"#customHeader",
190
+
),
191
+
}),
192
+
min_length: None,
193
+
max_length: Some(50usize),
194
+
}),
195
+
);
196
+
map.insert(
197
+
::jacquard_common::smol_str::SmolStr::new_static(
198
+
"indexFiles",
199
+
),
200
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
201
+
description: Some(
202
+
::jacquard_common::CowStr::new_static(
203
+
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
204
+
),
205
+
),
206
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
207
+
description: None,
208
+
format: None,
209
+
default: None,
210
+
min_length: None,
211
+
max_length: Some(255usize),
212
+
min_graphemes: None,
213
+
max_graphemes: None,
214
+
r#enum: None,
215
+
r#const: None,
216
+
known_values: None,
217
+
}),
218
+
min_length: None,
219
+
max_length: Some(10usize),
220
+
}),
221
+
);
222
+
map.insert(
223
+
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
224
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
225
+
description: Some(
226
+
::jacquard_common::CowStr::new_static(
227
+
"File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
228
+
),
229
+
),
230
+
format: None,
231
+
default: None,
232
+
min_length: None,
233
+
max_length: Some(500usize),
234
+
min_graphemes: None,
235
+
max_graphemes: None,
236
+
r#enum: None,
237
+
r#const: None,
238
+
known_values: None,
239
+
}),
240
+
);
241
+
map
242
+
},
243
+
}),
244
+
}),
245
+
);
246
+
map
247
+
},
248
+
}
249
+
}
250
+
251
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
252
+
fn nsid() -> &'static str {
253
+
"place.wisp.settings"
254
+
}
255
+
fn def_name() -> &'static str {
256
+
"customHeader"
257
+
}
258
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
259
+
lexicon_doc_place_wisp_settings()
260
+
}
261
+
fn validate(
262
+
&self,
263
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
264
+
{
265
+
let value = &self.name;
266
+
#[allow(unused_comparisons)]
267
+
if <str>::len(value.as_ref()) > 100usize {
268
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
269
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
270
+
"name",
271
+
),
272
+
max: 100usize,
273
+
actual: <str>::len(value.as_ref()),
274
+
});
275
+
}
276
+
}
277
+
if let Some(ref value) = self.path {
278
+
#[allow(unused_comparisons)]
279
+
if <str>::len(value.as_ref()) > 500usize {
280
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
281
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
282
+
"path",
283
+
),
284
+
max: 500usize,
285
+
actual: <str>::len(value.as_ref()),
286
+
});
287
+
}
288
+
}
289
+
{
290
+
let value = &self.value;
291
+
#[allow(unused_comparisons)]
292
+
if <str>::len(value.as_ref()) > 1000usize {
293
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
294
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
295
+
"value",
296
+
),
297
+
max: 1000usize,
298
+
actual: <str>::len(value.as_ref()),
299
+
});
300
+
}
301
+
}
302
+
Ok(())
303
+
}
304
+
}
305
+
306
+
/// Configuration settings for a static site hosted on wisp.place
307
+
#[jacquard_derive::lexicon]
308
+
#[derive(
309
+
serde::Serialize,
310
+
serde::Deserialize,
311
+
Debug,
312
+
Clone,
313
+
PartialEq,
314
+
Eq,
315
+
jacquard_derive::IntoStatic
316
+
)]
317
+
#[serde(rename_all = "camelCase")]
318
+
pub struct Settings<'a> {
319
+
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
320
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
321
+
pub clean_urls: std::option::Option<bool>,
322
+
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
323
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
324
+
#[serde(borrow)]
325
+
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
326
+
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
327
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
328
+
pub directory_listing: std::option::Option<bool>,
329
+
/// Custom HTTP headers to set on responses
330
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
331
+
#[serde(borrow)]
332
+
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
333
+
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
334
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
335
+
#[serde(borrow)]
336
+
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
337
+
/// File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.
338
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
339
+
#[serde(borrow)]
340
+
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
341
+
}
342
+
343
+
pub mod settings_state {
344
+
345
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
346
+
#[allow(unused)]
347
+
use ::core::marker::PhantomData;
348
+
mod sealed {
349
+
pub trait Sealed {}
350
+
}
351
+
/// State trait tracking which required fields have been set
352
+
pub trait State: sealed::Sealed {}
353
+
/// Empty state - all required fields are unset
354
+
pub struct Empty(());
355
+
impl sealed::Sealed for Empty {}
356
+
impl State for Empty {}
357
+
/// Marker types for field names
358
+
#[allow(non_camel_case_types)]
359
+
pub mod members {}
360
+
}
361
+
362
+
/// Builder for constructing an instance of this type
363
+
pub struct SettingsBuilder<'a, S: settings_state::State> {
364
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
365
+
__unsafe_private_named: (
366
+
::core::option::Option<bool>,
367
+
::core::option::Option<jacquard_common::CowStr<'a>>,
368
+
::core::option::Option<bool>,
369
+
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
370
+
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
371
+
::core::option::Option<jacquard_common::CowStr<'a>>,
372
+
),
373
+
_phantom: ::core::marker::PhantomData<&'a ()>,
374
+
}
375
+
376
+
impl<'a> Settings<'a> {
377
+
/// Create a new builder for this type
378
+
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
379
+
SettingsBuilder::new()
380
+
}
381
+
}
382
+
383
+
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
384
+
/// Create a new builder with all fields unset
385
+
pub fn new() -> Self {
386
+
SettingsBuilder {
387
+
_phantom_state: ::core::marker::PhantomData,
388
+
__unsafe_private_named: (None, None, None, None, None, None),
389
+
_phantom: ::core::marker::PhantomData,
390
+
}
391
+
}
392
+
}
393
+
394
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
395
+
/// Set the `cleanUrls` field (optional)
396
+
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
397
+
self.__unsafe_private_named.0 = value.into();
398
+
self
399
+
}
400
+
/// Set the `cleanUrls` field to an Option value (optional)
401
+
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
402
+
self.__unsafe_private_named.0 = value;
403
+
self
404
+
}
405
+
}
406
+
407
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
408
+
/// Set the `custom404` field (optional)
409
+
pub fn custom404(
410
+
mut self,
411
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
412
+
) -> Self {
413
+
self.__unsafe_private_named.1 = value.into();
414
+
self
415
+
}
416
+
/// Set the `custom404` field to an Option value (optional)
417
+
pub fn maybe_custom404(
418
+
mut self,
419
+
value: Option<jacquard_common::CowStr<'a>>,
420
+
) -> Self {
421
+
self.__unsafe_private_named.1 = value;
422
+
self
423
+
}
424
+
}
425
+
426
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
427
+
/// Set the `directoryListing` field (optional)
428
+
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
429
+
self.__unsafe_private_named.2 = value.into();
430
+
self
431
+
}
432
+
/// Set the `directoryListing` field to an Option value (optional)
433
+
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
434
+
self.__unsafe_private_named.2 = value;
435
+
self
436
+
}
437
+
}
438
+
439
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
440
+
/// Set the `headers` field (optional)
441
+
pub fn headers(
442
+
mut self,
443
+
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
444
+
) -> Self {
445
+
self.__unsafe_private_named.3 = value.into();
446
+
self
447
+
}
448
+
/// Set the `headers` field to an Option value (optional)
449
+
pub fn maybe_headers(
450
+
mut self,
451
+
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
452
+
) -> Self {
453
+
self.__unsafe_private_named.3 = value;
454
+
self
455
+
}
456
+
}
457
+
458
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
459
+
/// Set the `indexFiles` field (optional)
460
+
pub fn index_files(
461
+
mut self,
462
+
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
463
+
) -> Self {
464
+
self.__unsafe_private_named.4 = value.into();
465
+
self
466
+
}
467
+
/// Set the `indexFiles` field to an Option value (optional)
468
+
pub fn maybe_index_files(
469
+
mut self,
470
+
value: Option<Vec<jacquard_common::CowStr<'a>>>,
471
+
) -> Self {
472
+
self.__unsafe_private_named.4 = value;
473
+
self
474
+
}
475
+
}
476
+
477
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
478
+
/// Set the `spaMode` field (optional)
479
+
pub fn spa_mode(
480
+
mut self,
481
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
482
+
) -> Self {
483
+
self.__unsafe_private_named.5 = value.into();
484
+
self
485
+
}
486
+
/// Set the `spaMode` field to an Option value (optional)
487
+
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
488
+
self.__unsafe_private_named.5 = value;
489
+
self
490
+
}
491
+
}
492
+
493
+
impl<'a, S> SettingsBuilder<'a, S>
494
+
where
495
+
S: settings_state::State,
496
+
{
497
+
/// Build the final struct
498
+
pub fn build(self) -> Settings<'a> {
499
+
Settings {
500
+
clean_urls: self.__unsafe_private_named.0,
501
+
custom404: self.__unsafe_private_named.1,
502
+
directory_listing: self.__unsafe_private_named.2,
503
+
headers: self.__unsafe_private_named.3,
504
+
index_files: self.__unsafe_private_named.4,
505
+
spa_mode: self.__unsafe_private_named.5,
506
+
extra_data: Default::default(),
507
+
}
508
+
}
509
+
/// Build the final struct with custom extra_data
510
+
pub fn build_with_data(
511
+
self,
512
+
extra_data: std::collections::BTreeMap<
513
+
jacquard_common::smol_str::SmolStr,
514
+
jacquard_common::types::value::Data<'a>,
515
+
>,
516
+
) -> Settings<'a> {
517
+
Settings {
518
+
clean_urls: self.__unsafe_private_named.0,
519
+
custom404: self.__unsafe_private_named.1,
520
+
directory_listing: self.__unsafe_private_named.2,
521
+
headers: self.__unsafe_private_named.3,
522
+
index_files: self.__unsafe_private_named.4,
523
+
spa_mode: self.__unsafe_private_named.5,
524
+
extra_data: Some(extra_data),
525
+
}
526
+
}
527
+
}
528
+
529
+
impl<'a> Settings<'a> {
530
+
pub fn uri(
531
+
uri: impl Into<jacquard_common::CowStr<'a>>,
532
+
) -> Result<
533
+
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
534
+
jacquard_common::types::uri::UriError,
535
+
> {
536
+
jacquard_common::types::uri::RecordUri::try_from_uri(
537
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
538
+
)
539
+
}
540
+
}
541
+
542
+
/// Typed wrapper for GetRecord response with this collection's record type.
543
+
#[derive(
544
+
serde::Serialize,
545
+
serde::Deserialize,
546
+
Debug,
547
+
Clone,
548
+
PartialEq,
549
+
Eq,
550
+
jacquard_derive::IntoStatic
551
+
)]
552
+
#[serde(rename_all = "camelCase")]
553
+
pub struct SettingsGetRecordOutput<'a> {
554
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
555
+
#[serde(borrow)]
556
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
557
+
#[serde(borrow)]
558
+
pub uri: jacquard_common::types::string::AtUri<'a>,
559
+
#[serde(borrow)]
560
+
pub value: Settings<'a>,
561
+
}
562
+
563
+
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
564
+
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
565
+
use jacquard_common::IntoStatic;
566
+
output.value.into_static()
567
+
}
568
+
}
569
+
570
+
impl jacquard_common::types::collection::Collection for Settings<'_> {
571
+
const NSID: &'static str = "place.wisp.settings";
572
+
type Record = SettingsRecord;
573
+
}
574
+
575
+
/// Marker type for deserializing records from this collection.
576
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
577
+
pub struct SettingsRecord;
578
+
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
579
+
const NSID: &'static str = "place.wisp.settings";
580
+
const ENCODING: &'static str = "application/json";
581
+
type Output<'de> = SettingsGetRecordOutput<'de>;
582
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
583
+
}
584
+
585
+
impl jacquard_common::types::collection::Collection for SettingsRecord {
586
+
const NSID: &'static str = "place.wisp.settings";
587
+
type Record = SettingsRecord;
588
+
}
589
+
590
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
591
+
fn nsid() -> &'static str {
592
+
"place.wisp.settings"
593
+
}
594
+
fn def_name() -> &'static str {
595
+
"main"
596
+
}
597
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
598
+
lexicon_doc_place_wisp_settings()
599
+
}
600
+
fn validate(
601
+
&self,
602
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
603
+
if let Some(ref value) = self.custom404 {
604
+
#[allow(unused_comparisons)]
605
+
if <str>::len(value.as_ref()) > 500usize {
606
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
607
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
608
+
"custom404",
609
+
),
610
+
max: 500usize,
611
+
actual: <str>::len(value.as_ref()),
612
+
});
613
+
}
614
+
}
615
+
if let Some(ref value) = self.headers {
616
+
#[allow(unused_comparisons)]
617
+
if value.len() > 50usize {
618
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
619
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
620
+
"headers",
621
+
),
622
+
max: 50usize,
623
+
actual: value.len(),
624
+
});
625
+
}
626
+
}
627
+
if let Some(ref value) = self.index_files {
628
+
#[allow(unused_comparisons)]
629
+
if value.len() > 10usize {
630
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
631
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
632
+
"index_files",
633
+
),
634
+
max: 10usize,
635
+
actual: value.len(),
636
+
});
637
+
}
638
+
}
639
+
if let Some(ref value) = self.spa_mode {
640
+
#[allow(unused_comparisons)]
641
+
if <str>::len(value.as_ref()) > 500usize {
642
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
643
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
644
+
"spa_mode",
645
+
),
646
+
max: 500usize,
647
+
actual: <str>::len(value.as_ref()),
648
+
});
649
+
}
650
+
}
651
+
Ok(())
652
+
}
653
+
}
+1408
cli/crates/lexicons/src/place_wisp/subfs.rs
+1408
cli/crates/lexicons/src/place_wisp/subfs.rs
···
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.subfs
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
#[jacquard_derive::lexicon]
9
+
#[derive(
10
+
serde::Serialize,
11
+
serde::Deserialize,
12
+
Debug,
13
+
Clone,
14
+
PartialEq,
15
+
Eq,
16
+
jacquard_derive::IntoStatic
17
+
)]
18
+
#[serde(rename_all = "camelCase")]
19
+
pub struct Directory<'a> {
20
+
#[serde(borrow)]
21
+
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
22
+
#[serde(borrow)]
23
+
pub r#type: jacquard_common::CowStr<'a>,
24
+
}
25
+
26
+
pub mod directory_state {
27
+
28
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
+
#[allow(unused)]
30
+
use ::core::marker::PhantomData;
31
+
mod sealed {
32
+
pub trait Sealed {}
33
+
}
34
+
/// State trait tracking which required fields have been set
35
+
pub trait State: sealed::Sealed {
36
+
type Type;
37
+
type Entries;
38
+
}
39
+
/// Empty state - all required fields are unset
40
+
pub struct Empty(());
41
+
impl sealed::Sealed for Empty {}
42
+
impl State for Empty {
43
+
type Type = Unset;
44
+
type Entries = Unset;
45
+
}
46
+
///State transition - sets the `type` field to Set
47
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
+
impl<S: State> sealed::Sealed for SetType<S> {}
49
+
impl<S: State> State for SetType<S> {
50
+
type Type = Set<members::r#type>;
51
+
type Entries = S::Entries;
52
+
}
53
+
///State transition - sets the `entries` field to Set
54
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
+
impl<S: State> State for SetEntries<S> {
57
+
type Type = S::Type;
58
+
type Entries = Set<members::entries>;
59
+
}
60
+
/// Marker types for field names
61
+
#[allow(non_camel_case_types)]
62
+
pub mod members {
63
+
///Marker type for the `type` field
64
+
pub struct r#type(());
65
+
///Marker type for the `entries` field
66
+
pub struct entries(());
67
+
}
68
+
}
69
+
70
+
/// Builder for constructing an instance of this type
71
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
+
__unsafe_private_named: (
74
+
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
75
+
::core::option::Option<jacquard_common::CowStr<'a>>,
76
+
),
77
+
_phantom: ::core::marker::PhantomData<&'a ()>,
78
+
}
79
+
80
+
impl<'a> Directory<'a> {
81
+
/// Create a new builder for this type
82
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
+
DirectoryBuilder::new()
84
+
}
85
+
}
86
+
87
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
+
/// Create a new builder with all fields unset
89
+
pub fn new() -> Self {
90
+
DirectoryBuilder {
91
+
_phantom_state: ::core::marker::PhantomData,
92
+
__unsafe_private_named: (None, None),
93
+
_phantom: ::core::marker::PhantomData,
94
+
}
95
+
}
96
+
}
97
+
98
+
impl<'a, S> DirectoryBuilder<'a, S>
99
+
where
100
+
S: directory_state::State,
101
+
S::Entries: directory_state::IsUnset,
102
+
{
103
+
/// Set the `entries` field (required)
104
+
pub fn entries(
105
+
mut self,
106
+
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
107
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
+
DirectoryBuilder {
110
+
_phantom_state: ::core::marker::PhantomData,
111
+
__unsafe_private_named: self.__unsafe_private_named,
112
+
_phantom: ::core::marker::PhantomData,
113
+
}
114
+
}
115
+
}
116
+
117
+
impl<'a, S> DirectoryBuilder<'a, S>
118
+
where
119
+
S: directory_state::State,
120
+
S::Type: directory_state::IsUnset,
121
+
{
122
+
/// Set the `type` field (required)
123
+
pub fn r#type(
124
+
mut self,
125
+
value: impl Into<jacquard_common::CowStr<'a>>,
126
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
+
DirectoryBuilder {
129
+
_phantom_state: ::core::marker::PhantomData,
130
+
__unsafe_private_named: self.__unsafe_private_named,
131
+
_phantom: ::core::marker::PhantomData,
132
+
}
133
+
}
134
+
}
135
+
136
+
impl<'a, S> DirectoryBuilder<'a, S>
137
+
where
138
+
S: directory_state::State,
139
+
S::Type: directory_state::IsSet,
140
+
S::Entries: directory_state::IsSet,
141
+
{
142
+
/// Build the final struct
143
+
pub fn build(self) -> Directory<'a> {
144
+
Directory {
145
+
entries: self.__unsafe_private_named.0.unwrap(),
146
+
r#type: self.__unsafe_private_named.1.unwrap(),
147
+
extra_data: Default::default(),
148
+
}
149
+
}
150
+
/// Build the final struct with custom extra_data
151
+
pub fn build_with_data(
152
+
self,
153
+
extra_data: std::collections::BTreeMap<
154
+
jacquard_common::smol_str::SmolStr,
155
+
jacquard_common::types::value::Data<'a>,
156
+
>,
157
+
) -> Directory<'a> {
158
+
Directory {
159
+
entries: self.__unsafe_private_named.0.unwrap(),
160
+
r#type: self.__unsafe_private_named.1.unwrap(),
161
+
extra_data: Some(extra_data),
162
+
}
163
+
}
164
+
}
165
+
166
+
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
+
::jacquard_lexicon::lexicon::LexiconDoc {
168
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
+
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
170
+
revision: None,
171
+
description: None,
172
+
defs: {
173
+
let mut map = ::std::collections::BTreeMap::new();
174
+
map.insert(
175
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
+
description: None,
178
+
required: Some(
179
+
vec![
180
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
+
],
183
+
),
184
+
nullable: None,
185
+
properties: {
186
+
#[allow(unused_mut)]
187
+
let mut map = ::std::collections::BTreeMap::new();
188
+
map.insert(
189
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
+
description: None,
192
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
+
description: None,
194
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
+
}),
196
+
min_length: None,
197
+
max_length: Some(500usize),
198
+
}),
199
+
);
200
+
map.insert(
201
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
+
description: None,
204
+
format: None,
205
+
default: None,
206
+
min_length: None,
207
+
max_length: None,
208
+
min_graphemes: None,
209
+
max_graphemes: None,
210
+
r#enum: None,
211
+
r#const: None,
212
+
known_values: None,
213
+
}),
214
+
);
215
+
map
216
+
},
217
+
}),
218
+
);
219
+
map.insert(
220
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
+
description: None,
223
+
required: Some(
224
+
vec![
225
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
+
::jacquard_common::smol_str::SmolStr::new_static("node")
227
+
],
228
+
),
229
+
nullable: None,
230
+
properties: {
231
+
#[allow(unused_mut)]
232
+
let mut map = ::std::collections::BTreeMap::new();
233
+
map.insert(
234
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
+
description: None,
237
+
format: None,
238
+
default: None,
239
+
min_length: None,
240
+
max_length: Some(255usize),
241
+
min_graphemes: None,
242
+
max_graphemes: None,
243
+
r#enum: None,
244
+
r#const: None,
245
+
known_values: None,
246
+
}),
247
+
);
248
+
map.insert(
249
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
+
description: None,
252
+
refs: vec![
253
+
::jacquard_common::CowStr::new_static("#file"),
254
+
::jacquard_common::CowStr::new_static("#directory"),
255
+
::jacquard_common::CowStr::new_static("#subfs")
256
+
],
257
+
closed: None,
258
+
}),
259
+
);
260
+
map
261
+
},
262
+
}),
263
+
);
264
+
map.insert(
265
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
+
description: None,
268
+
required: Some(
269
+
vec![
270
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
+
],
273
+
),
274
+
nullable: None,
275
+
properties: {
276
+
#[allow(unused_mut)]
277
+
let mut map = ::std::collections::BTreeMap::new();
278
+
map.insert(
279
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
+
description: None,
282
+
default: None,
283
+
r#const: None,
284
+
}),
285
+
);
286
+
map.insert(
287
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
+
description: None,
290
+
accept: None,
291
+
max_size: None,
292
+
}),
293
+
);
294
+
map.insert(
295
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
+
description: Some(
298
+
::jacquard_common::CowStr::new_static(
299
+
"Content encoding (e.g., gzip for compressed files)",
300
+
),
301
+
),
302
+
format: None,
303
+
default: None,
304
+
min_length: None,
305
+
max_length: None,
306
+
min_graphemes: None,
307
+
max_graphemes: None,
308
+
r#enum: None,
309
+
r#const: None,
310
+
known_values: None,
311
+
}),
312
+
);
313
+
map.insert(
314
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
+
description: Some(
317
+
::jacquard_common::CowStr::new_static(
318
+
"Original MIME type before compression",
319
+
),
320
+
),
321
+
format: None,
322
+
default: None,
323
+
min_length: None,
324
+
max_length: None,
325
+
min_graphemes: None,
326
+
max_graphemes: None,
327
+
r#enum: None,
328
+
r#const: None,
329
+
known_values: None,
330
+
}),
331
+
);
332
+
map.insert(
333
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
+
description: None,
336
+
format: None,
337
+
default: None,
338
+
min_length: None,
339
+
max_length: None,
340
+
min_graphemes: None,
341
+
max_graphemes: None,
342
+
r#enum: None,
343
+
r#const: None,
344
+
known_values: None,
345
+
}),
346
+
);
347
+
map
348
+
},
349
+
}),
350
+
);
351
+
map.insert(
352
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
+
description: Some(
355
+
::jacquard_common::CowStr::new_static(
356
+
"Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
357
+
),
358
+
),
359
+
key: None,
360
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
+
description: None,
362
+
required: Some(
363
+
vec![
364
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
365
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
366
+
],
367
+
),
368
+
nullable: None,
369
+
properties: {
370
+
#[allow(unused_mut)]
371
+
let mut map = ::std::collections::BTreeMap::new();
372
+
map.insert(
373
+
::jacquard_common::smol_str::SmolStr::new_static(
374
+
"createdAt",
375
+
),
376
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
377
+
description: None,
378
+
format: Some(
379
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
380
+
),
381
+
default: None,
382
+
min_length: None,
383
+
max_length: None,
384
+
min_graphemes: None,
385
+
max_graphemes: None,
386
+
r#enum: None,
387
+
r#const: None,
388
+
known_values: None,
389
+
}),
390
+
);
391
+
map.insert(
392
+
::jacquard_common::smol_str::SmolStr::new_static(
393
+
"fileCount",
394
+
),
395
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
396
+
description: None,
397
+
default: None,
398
+
minimum: Some(0i64),
399
+
maximum: Some(1000i64),
400
+
r#enum: None,
401
+
r#const: None,
402
+
}),
403
+
);
404
+
map.insert(
405
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
406
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
407
+
description: None,
408
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
409
+
}),
410
+
);
411
+
map
412
+
},
413
+
}),
414
+
}),
415
+
);
416
+
map.insert(
417
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
418
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
419
+
description: None,
420
+
required: Some(
421
+
vec![
422
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
423
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
424
+
],
425
+
),
426
+
nullable: None,
427
+
properties: {
428
+
#[allow(unused_mut)]
429
+
let mut map = ::std::collections::BTreeMap::new();
430
+
map.insert(
431
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
432
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
433
+
description: Some(
434
+
::jacquard_common::CowStr::new_static(
435
+
"AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.",
436
+
),
437
+
),
438
+
format: Some(
439
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
440
+
),
441
+
default: None,
442
+
min_length: None,
443
+
max_length: None,
444
+
min_graphemes: None,
445
+
max_graphemes: None,
446
+
r#enum: None,
447
+
r#const: None,
448
+
known_values: None,
449
+
}),
450
+
);
451
+
map.insert(
452
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
453
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
454
+
description: None,
455
+
format: None,
456
+
default: None,
457
+
min_length: None,
458
+
max_length: None,
459
+
min_graphemes: None,
460
+
max_graphemes: None,
461
+
r#enum: None,
462
+
r#const: None,
463
+
known_values: None,
464
+
}),
465
+
);
466
+
map
467
+
},
468
+
}),
469
+
);
470
+
map
471
+
},
472
+
}
473
+
}
474
+
475
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
476
+
fn nsid() -> &'static str {
477
+
"place.wisp.subfs"
478
+
}
479
+
fn def_name() -> &'static str {
480
+
"directory"
481
+
}
482
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
483
+
lexicon_doc_place_wisp_subfs()
484
+
}
485
+
fn validate(
486
+
&self,
487
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
488
+
{
489
+
let value = &self.entries;
490
+
#[allow(unused_comparisons)]
491
+
if value.len() > 500usize {
492
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
493
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
494
+
"entries",
495
+
),
496
+
max: 500usize,
497
+
actual: value.len(),
498
+
});
499
+
}
500
+
}
501
+
Ok(())
502
+
}
503
+
}
504
+
505
+
#[jacquard_derive::lexicon]
506
+
#[derive(
507
+
serde::Serialize,
508
+
serde::Deserialize,
509
+
Debug,
510
+
Clone,
511
+
PartialEq,
512
+
Eq,
513
+
jacquard_derive::IntoStatic
514
+
)]
515
+
#[serde(rename_all = "camelCase")]
516
+
pub struct Entry<'a> {
517
+
#[serde(borrow)]
518
+
pub name: jacquard_common::CowStr<'a>,
519
+
#[serde(borrow)]
520
+
pub node: EntryNode<'a>,
521
+
}
522
+
523
+
pub mod entry_state {
524
+
525
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
526
+
#[allow(unused)]
527
+
use ::core::marker::PhantomData;
528
+
mod sealed {
529
+
pub trait Sealed {}
530
+
}
531
+
/// State trait tracking which required fields have been set
532
+
pub trait State: sealed::Sealed {
533
+
type Name;
534
+
type Node;
535
+
}
536
+
/// Empty state - all required fields are unset
537
+
pub struct Empty(());
538
+
impl sealed::Sealed for Empty {}
539
+
impl State for Empty {
540
+
type Name = Unset;
541
+
type Node = Unset;
542
+
}
543
+
///State transition - sets the `name` field to Set
544
+
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
545
+
impl<S: State> sealed::Sealed for SetName<S> {}
546
+
impl<S: State> State for SetName<S> {
547
+
type Name = Set<members::name>;
548
+
type Node = S::Node;
549
+
}
550
+
///State transition - sets the `node` field to Set
551
+
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
552
+
impl<S: State> sealed::Sealed for SetNode<S> {}
553
+
impl<S: State> State for SetNode<S> {
554
+
type Name = S::Name;
555
+
type Node = Set<members::node>;
556
+
}
557
+
/// Marker types for field names
558
+
#[allow(non_camel_case_types)]
559
+
pub mod members {
560
+
///Marker type for the `name` field
561
+
pub struct name(());
562
+
///Marker type for the `node` field
563
+
pub struct node(());
564
+
}
565
+
}
566
+
567
+
/// Builder for constructing an instance of this type
568
+
pub struct EntryBuilder<'a, S: entry_state::State> {
569
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
570
+
__unsafe_private_named: (
571
+
::core::option::Option<jacquard_common::CowStr<'a>>,
572
+
::core::option::Option<EntryNode<'a>>,
573
+
),
574
+
_phantom: ::core::marker::PhantomData<&'a ()>,
575
+
}
576
+
577
+
impl<'a> Entry<'a> {
578
+
/// Create a new builder for this type
579
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
580
+
EntryBuilder::new()
581
+
}
582
+
}
583
+
584
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
585
+
/// Create a new builder with all fields unset
586
+
pub fn new() -> Self {
587
+
EntryBuilder {
588
+
_phantom_state: ::core::marker::PhantomData,
589
+
__unsafe_private_named: (None, None),
590
+
_phantom: ::core::marker::PhantomData,
591
+
}
592
+
}
593
+
}
594
+
595
+
impl<'a, S> EntryBuilder<'a, S>
596
+
where
597
+
S: entry_state::State,
598
+
S::Name: entry_state::IsUnset,
599
+
{
600
+
/// Set the `name` field (required)
601
+
pub fn name(
602
+
mut self,
603
+
value: impl Into<jacquard_common::CowStr<'a>>,
604
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
605
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
606
+
EntryBuilder {
607
+
_phantom_state: ::core::marker::PhantomData,
608
+
__unsafe_private_named: self.__unsafe_private_named,
609
+
_phantom: ::core::marker::PhantomData,
610
+
}
611
+
}
612
+
}
613
+
614
+
impl<'a, S> EntryBuilder<'a, S>
615
+
where
616
+
S: entry_state::State,
617
+
S::Node: entry_state::IsUnset,
618
+
{
619
+
/// Set the `node` field (required)
620
+
pub fn node(
621
+
mut self,
622
+
value: impl Into<EntryNode<'a>>,
623
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
624
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
625
+
EntryBuilder {
626
+
_phantom_state: ::core::marker::PhantomData,
627
+
__unsafe_private_named: self.__unsafe_private_named,
628
+
_phantom: ::core::marker::PhantomData,
629
+
}
630
+
}
631
+
}
632
+
633
+
impl<'a, S> EntryBuilder<'a, S>
634
+
where
635
+
S: entry_state::State,
636
+
S::Name: entry_state::IsSet,
637
+
S::Node: entry_state::IsSet,
638
+
{
639
+
/// Build the final struct
640
+
pub fn build(self) -> Entry<'a> {
641
+
Entry {
642
+
name: self.__unsafe_private_named.0.unwrap(),
643
+
node: self.__unsafe_private_named.1.unwrap(),
644
+
extra_data: Default::default(),
645
+
}
646
+
}
647
+
/// Build the final struct with custom extra_data
648
+
pub fn build_with_data(
649
+
self,
650
+
extra_data: std::collections::BTreeMap<
651
+
jacquard_common::smol_str::SmolStr,
652
+
jacquard_common::types::value::Data<'a>,
653
+
>,
654
+
) -> Entry<'a> {
655
+
Entry {
656
+
name: self.__unsafe_private_named.0.unwrap(),
657
+
node: self.__unsafe_private_named.1.unwrap(),
658
+
extra_data: Some(extra_data),
659
+
}
660
+
}
661
+
}
662
+
663
+
#[jacquard_derive::open_union]
664
+
#[derive(
665
+
serde::Serialize,
666
+
serde::Deserialize,
667
+
Debug,
668
+
Clone,
669
+
PartialEq,
670
+
Eq,
671
+
jacquard_derive::IntoStatic
672
+
)]
673
+
#[serde(tag = "$type")]
674
+
#[serde(bound(deserialize = "'de: 'a"))]
675
+
pub enum EntryNode<'a> {
676
+
#[serde(rename = "place.wisp.subfs#file")]
677
+
File(Box<crate::place_wisp::subfs::File<'a>>),
678
+
#[serde(rename = "place.wisp.subfs#directory")]
679
+
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
680
+
#[serde(rename = "place.wisp.subfs#subfs")]
681
+
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
682
+
}
683
+
684
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
685
+
fn nsid() -> &'static str {
686
+
"place.wisp.subfs"
687
+
}
688
+
fn def_name() -> &'static str {
689
+
"entry"
690
+
}
691
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
692
+
lexicon_doc_place_wisp_subfs()
693
+
}
694
+
fn validate(
695
+
&self,
696
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
697
+
{
698
+
let value = &self.name;
699
+
#[allow(unused_comparisons)]
700
+
if <str>::len(value.as_ref()) > 255usize {
701
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
702
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
703
+
"name",
704
+
),
705
+
max: 255usize,
706
+
actual: <str>::len(value.as_ref()),
707
+
});
708
+
}
709
+
}
710
+
Ok(())
711
+
}
712
+
}
713
+
714
+
#[jacquard_derive::lexicon]
715
+
#[derive(
716
+
serde::Serialize,
717
+
serde::Deserialize,
718
+
Debug,
719
+
Clone,
720
+
PartialEq,
721
+
Eq,
722
+
jacquard_derive::IntoStatic
723
+
)]
724
+
#[serde(rename_all = "camelCase")]
725
+
pub 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
+
}
743
+
744
+
pub mod file_state {
745
+
746
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
747
+
#[allow(unused)]
748
+
use ::core::marker::PhantomData;
749
+
mod sealed {
750
+
pub trait Sealed {}
751
+
}
752
+
/// State trait tracking which required fields have been set
753
+
pub trait State: sealed::Sealed {
754
+
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 {
761
+
type Blob = Unset;
762
+
type Type = Unset;
763
+
}
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(());
785
+
}
786
+
}
787
+
788
+
/// Builder for constructing an instance of this type
789
+
pub struct FileBuilder<'a, S: file_state::State> {
790
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
791
+
__unsafe_private_named: (
792
+
::core::option::Option<bool>,
793
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
794
+
::core::option::Option<jacquard_common::CowStr<'a>>,
795
+
::core::option::Option<jacquard_common::CowStr<'a>>,
796
+
::core::option::Option<jacquard_common::CowStr<'a>>,
797
+
),
798
+
_phantom: ::core::marker::PhantomData<&'a ()>,
799
+
}
800
+
801
+
impl<'a> File<'a> {
802
+
/// Create a new builder for this type
803
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
804
+
FileBuilder::new()
805
+
}
806
+
}
807
+
808
+
impl<'a> FileBuilder<'a, file_state::Empty> {
809
+
/// Create a new builder with all fields unset
810
+
pub fn new() -> Self {
811
+
FileBuilder {
812
+
_phantom_state: ::core::marker::PhantomData,
813
+
__unsafe_private_named: (None, None, None, None, None),
814
+
_phantom: ::core::marker::PhantomData,
815
+
}
816
+
}
817
+
}
818
+
819
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
820
+
/// Set the `base64` field (optional)
821
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
822
+
self.__unsafe_private_named.0 = value.into();
823
+
self
824
+
}
825
+
/// Set the `base64` field to an Option value (optional)
826
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
827
+
self.__unsafe_private_named.0 = value;
828
+
self
829
+
}
830
+
}
831
+
832
+
impl<'a, S> FileBuilder<'a, S>
833
+
where
834
+
S: file_state::State,
835
+
S::Blob: file_state::IsUnset,
836
+
{
837
+
/// Set the `blob` field (required)
838
+
pub fn blob(
839
+
mut self,
840
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
841
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
842
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
843
+
FileBuilder {
844
+
_phantom_state: ::core::marker::PhantomData,
845
+
__unsafe_private_named: self.__unsafe_private_named,
846
+
_phantom: ::core::marker::PhantomData,
847
+
}
848
+
}
849
+
}
850
+
851
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
852
+
/// Set the `encoding` field (optional)
853
+
pub fn encoding(
854
+
mut self,
855
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
856
+
) -> Self {
857
+
self.__unsafe_private_named.2 = value.into();
858
+
self
859
+
}
860
+
/// Set the `encoding` field to an Option value (optional)
861
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
862
+
self.__unsafe_private_named.2 = value;
863
+
self
864
+
}
865
+
}
866
+
867
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
868
+
/// Set the `mimeType` field (optional)
869
+
pub fn mime_type(
870
+
mut self,
871
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
872
+
) -> Self {
873
+
self.__unsafe_private_named.3 = value.into();
874
+
self
875
+
}
876
+
/// Set the `mimeType` field to an Option value (optional)
877
+
pub fn maybe_mime_type(
878
+
mut self,
879
+
value: Option<jacquard_common::CowStr<'a>>,
880
+
) -> Self {
881
+
self.__unsafe_private_named.3 = value;
882
+
self
883
+
}
884
+
}
885
+
886
+
impl<'a, S> FileBuilder<'a, S>
887
+
where
888
+
S: file_state::State,
889
+
S::Type: file_state::IsUnset,
890
+
{
891
+
/// Set the `type` field (required)
892
+
pub fn r#type(
893
+
mut self,
894
+
value: impl Into<jacquard_common::CowStr<'a>>,
895
+
) -> FileBuilder<'a, file_state::SetType<S>> {
896
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
897
+
FileBuilder {
898
+
_phantom_state: ::core::marker::PhantomData,
899
+
__unsafe_private_named: self.__unsafe_private_named,
900
+
_phantom: ::core::marker::PhantomData,
901
+
}
902
+
}
903
+
}
904
+
905
+
impl<'a, S> FileBuilder<'a, S>
906
+
where
907
+
S: file_state::State,
908
+
S::Blob: file_state::IsSet,
909
+
S::Type: file_state::IsSet,
910
+
{
911
+
/// Build the final struct
912
+
pub fn build(self) -> File<'a> {
913
+
File {
914
+
base64: self.__unsafe_private_named.0,
915
+
blob: self.__unsafe_private_named.1.unwrap(),
916
+
encoding: self.__unsafe_private_named.2,
917
+
mime_type: self.__unsafe_private_named.3,
918
+
r#type: self.__unsafe_private_named.4.unwrap(),
919
+
extra_data: Default::default(),
920
+
}
921
+
}
922
+
/// Build the final struct with custom extra_data
923
+
pub fn build_with_data(
924
+
self,
925
+
extra_data: std::collections::BTreeMap<
926
+
jacquard_common::smol_str::SmolStr,
927
+
jacquard_common::types::value::Data<'a>,
928
+
>,
929
+
) -> File<'a> {
930
+
File {
931
+
base64: self.__unsafe_private_named.0,
932
+
blob: self.__unsafe_private_named.1.unwrap(),
933
+
encoding: self.__unsafe_private_named.2,
934
+
mime_type: self.__unsafe_private_named.3,
935
+
r#type: self.__unsafe_private_named.4.unwrap(),
936
+
extra_data: Some(extra_data),
937
+
}
938
+
}
939
+
}
940
+
941
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
942
+
fn nsid() -> &'static str {
943
+
"place.wisp.subfs"
944
+
}
945
+
fn def_name() -> &'static str {
946
+
"file"
947
+
}
948
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
949
+
lexicon_doc_place_wisp_subfs()
950
+
}
951
+
fn validate(
952
+
&self,
953
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
954
+
Ok(())
955
+
}
956
+
}
957
+
958
+
/// Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.
959
+
#[jacquard_derive::lexicon]
960
+
#[derive(
961
+
serde::Serialize,
962
+
serde::Deserialize,
963
+
Debug,
964
+
Clone,
965
+
PartialEq,
966
+
Eq,
967
+
jacquard_derive::IntoStatic
968
+
)]
969
+
#[serde(rename_all = "camelCase")]
970
+
pub 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
+
}
977
+
978
+
pub mod subfs_record_state {
979
+
980
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
981
+
#[allow(unused)]
982
+
use ::core::marker::PhantomData;
983
+
mod sealed {
984
+
pub trait Sealed {}
985
+
}
986
+
/// State trait tracking which required fields have been set
987
+
pub trait State: sealed::Sealed {
988
+
type Root;
989
+
type CreatedAt;
990
+
}
991
+
/// Empty state - all required fields are unset
992
+
pub struct Empty(());
993
+
impl sealed::Sealed for Empty {}
994
+
impl State for Empty {
995
+
type Root = Unset;
996
+
type CreatedAt = Unset;
997
+
}
998
+
///State transition - sets the `root` field to Set
999
+
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1000
+
impl<S: State> sealed::Sealed for SetRoot<S> {}
1001
+
impl<S: State> State for SetRoot<S> {
1002
+
type Root = Set<members::root>;
1003
+
type CreatedAt = S::CreatedAt;
1004
+
}
1005
+
///State transition - sets the `created_at` field to Set
1006
+
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1007
+
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1008
+
impl<S: State> State for SetCreatedAt<S> {
1009
+
type Root = S::Root;
1010
+
type CreatedAt = Set<members::created_at>;
1011
+
}
1012
+
/// Marker types for field names
1013
+
#[allow(non_camel_case_types)]
1014
+
pub mod members {
1015
+
///Marker type for the `root` field
1016
+
pub struct root(());
1017
+
///Marker type for the `created_at` field
1018
+
pub struct created_at(());
1019
+
}
1020
+
}
1021
+
1022
+
/// Builder for constructing an instance of this type
1023
+
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
1024
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1025
+
__unsafe_private_named: (
1026
+
::core::option::Option<jacquard_common::types::string::Datetime>,
1027
+
::core::option::Option<i64>,
1028
+
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
1029
+
),
1030
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1031
+
}
1032
+
1033
+
impl<'a> SubfsRecord<'a> {
1034
+
/// Create a new builder for this type
1035
+
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1036
+
SubfsRecordBuilder::new()
1037
+
}
1038
+
}
1039
+
1040
+
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1041
+
/// Create a new builder with all fields unset
1042
+
pub fn new() -> Self {
1043
+
SubfsRecordBuilder {
1044
+
_phantom_state: ::core::marker::PhantomData,
1045
+
__unsafe_private_named: (None, None, None),
1046
+
_phantom: ::core::marker::PhantomData,
1047
+
}
1048
+
}
1049
+
}
1050
+
1051
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1052
+
where
1053
+
S: subfs_record_state::State,
1054
+
S::CreatedAt: subfs_record_state::IsUnset,
1055
+
{
1056
+
/// Set the `createdAt` field (required)
1057
+
pub fn created_at(
1058
+
mut self,
1059
+
value: impl Into<jacquard_common::types::string::Datetime>,
1060
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
1061
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1062
+
SubfsRecordBuilder {
1063
+
_phantom_state: ::core::marker::PhantomData,
1064
+
__unsafe_private_named: self.__unsafe_private_named,
1065
+
_phantom: ::core::marker::PhantomData,
1066
+
}
1067
+
}
1068
+
}
1069
+
1070
+
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
1071
+
/// Set the `fileCount` field (optional)
1072
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1073
+
self.__unsafe_private_named.1 = value.into();
1074
+
self
1075
+
}
1076
+
/// Set the `fileCount` field to an Option value (optional)
1077
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1078
+
self.__unsafe_private_named.1 = value;
1079
+
self
1080
+
}
1081
+
}
1082
+
1083
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1084
+
where
1085
+
S: subfs_record_state::State,
1086
+
S::Root: subfs_record_state::IsUnset,
1087
+
{
1088
+
/// Set the `root` field (required)
1089
+
pub fn root(
1090
+
mut self,
1091
+
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
1092
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
1093
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1094
+
SubfsRecordBuilder {
1095
+
_phantom_state: ::core::marker::PhantomData,
1096
+
__unsafe_private_named: self.__unsafe_private_named,
1097
+
_phantom: ::core::marker::PhantomData,
1098
+
}
1099
+
}
1100
+
}
1101
+
1102
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1103
+
where
1104
+
S: subfs_record_state::State,
1105
+
S::Root: subfs_record_state::IsSet,
1106
+
S::CreatedAt: subfs_record_state::IsSet,
1107
+
{
1108
+
/// Build the final struct
1109
+
pub fn build(self) -> SubfsRecord<'a> {
1110
+
SubfsRecord {
1111
+
created_at: self.__unsafe_private_named.0.unwrap(),
1112
+
file_count: self.__unsafe_private_named.1,
1113
+
root: self.__unsafe_private_named.2.unwrap(),
1114
+
extra_data: Default::default(),
1115
+
}
1116
+
}
1117
+
/// Build the final struct with custom extra_data
1118
+
pub fn build_with_data(
1119
+
self,
1120
+
extra_data: std::collections::BTreeMap<
1121
+
jacquard_common::smol_str::SmolStr,
1122
+
jacquard_common::types::value::Data<'a>,
1123
+
>,
1124
+
) -> SubfsRecord<'a> {
1125
+
SubfsRecord {
1126
+
created_at: self.__unsafe_private_named.0.unwrap(),
1127
+
file_count: self.__unsafe_private_named.1,
1128
+
root: self.__unsafe_private_named.2.unwrap(),
1129
+
extra_data: Some(extra_data),
1130
+
}
1131
+
}
1132
+
}
1133
+
1134
+
impl<'a> SubfsRecord<'a> {
1135
+
pub fn uri(
1136
+
uri: impl Into<jacquard_common::CowStr<'a>>,
1137
+
) -> Result<
1138
+
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
1139
+
jacquard_common::types::uri::UriError,
1140
+
> {
1141
+
jacquard_common::types::uri::RecordUri::try_from_uri(
1142
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1143
+
)
1144
+
}
1145
+
}
1146
+
1147
+
/// Typed wrapper for GetRecord response with this collection's record type.
1148
+
#[derive(
1149
+
serde::Serialize,
1150
+
serde::Deserialize,
1151
+
Debug,
1152
+
Clone,
1153
+
PartialEq,
1154
+
Eq,
1155
+
jacquard_derive::IntoStatic
1156
+
)]
1157
+
#[serde(rename_all = "camelCase")]
1158
+
pub struct SubfsRecordGetRecordOutput<'a> {
1159
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1160
+
#[serde(borrow)]
1161
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1162
+
#[serde(borrow)]
1163
+
pub uri: jacquard_common::types::string::AtUri<'a>,
1164
+
#[serde(borrow)]
1165
+
pub value: SubfsRecord<'a>,
1166
+
}
1167
+
1168
+
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
1169
+
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
1170
+
use jacquard_common::IntoStatic;
1171
+
output.value.into_static()
1172
+
}
1173
+
}
1174
+
1175
+
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
1176
+
const NSID: &'static str = "place.wisp.subfs";
1177
+
type Record = SubfsRecordRecord;
1178
+
}
1179
+
1180
+
/// Marker type for deserializing records from this collection.
1181
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1182
+
pub struct SubfsRecordRecord;
1183
+
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
1184
+
const NSID: &'static str = "place.wisp.subfs";
1185
+
const ENCODING: &'static str = "application/json";
1186
+
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
1187
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1188
+
}
1189
+
1190
+
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
1191
+
const NSID: &'static str = "place.wisp.subfs";
1192
+
type Record = SubfsRecordRecord;
1193
+
}
1194
+
1195
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
1196
+
fn nsid() -> &'static str {
1197
+
"place.wisp.subfs"
1198
+
}
1199
+
fn def_name() -> &'static str {
1200
+
"main"
1201
+
}
1202
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1203
+
lexicon_doc_place_wisp_subfs()
1204
+
}
1205
+
fn validate(
1206
+
&self,
1207
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1208
+
if let Some(ref value) = self.file_count {
1209
+
if *value > 1000i64 {
1210
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1211
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1212
+
"file_count",
1213
+
),
1214
+
max: 1000i64,
1215
+
actual: *value,
1216
+
});
1217
+
}
1218
+
}
1219
+
if let Some(ref value) = self.file_count {
1220
+
if *value < 0i64 {
1221
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1222
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1223
+
"file_count",
1224
+
),
1225
+
min: 0i64,
1226
+
actual: *value,
1227
+
});
1228
+
}
1229
+
}
1230
+
Ok(())
1231
+
}
1232
+
}
1233
+
1234
+
#[jacquard_derive::lexicon]
1235
+
#[derive(
1236
+
serde::Serialize,
1237
+
serde::Deserialize,
1238
+
Debug,
1239
+
Clone,
1240
+
PartialEq,
1241
+
Eq,
1242
+
jacquard_derive::IntoStatic
1243
+
)]
1244
+
#[serde(rename_all = "camelCase")]
1245
+
pub struct Subfs<'a> {
1246
+
/// AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.
1247
+
#[serde(borrow)]
1248
+
pub subject: jacquard_common::types::string::AtUri<'a>,
1249
+
#[serde(borrow)]
1250
+
pub r#type: jacquard_common::CowStr<'a>,
1251
+
}
1252
+
1253
+
pub mod subfs_state {
1254
+
1255
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1256
+
#[allow(unused)]
1257
+
use ::core::marker::PhantomData;
1258
+
mod sealed {
1259
+
pub trait Sealed {}
1260
+
}
1261
+
/// State trait tracking which required fields have been set
1262
+
pub trait State: sealed::Sealed {
1263
+
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 {
1270
+
type Subject = Unset;
1271
+
type Type = Unset;
1272
+
}
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> {
1277
+
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 {
1290
+
///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
+
1297
+
/// Builder for constructing an instance of this type
1298
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1299
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1300
+
__unsafe_private_named: (
1301
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1302
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1303
+
),
1304
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1305
+
}
1306
+
1307
+
impl<'a> Subfs<'a> {
1308
+
/// Create a new builder for this type
1309
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1310
+
SubfsBuilder::new()
1311
+
}
1312
+
}
1313
+
1314
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1315
+
/// Create a new builder with all fields unset
1316
+
pub fn new() -> Self {
1317
+
SubfsBuilder {
1318
+
_phantom_state: ::core::marker::PhantomData,
1319
+
__unsafe_private_named: (None, None),
1320
+
_phantom: ::core::marker::PhantomData,
1321
+
}
1322
+
}
1323
+
}
1324
+
1325
+
impl<'a, S> SubfsBuilder<'a, S>
1326
+
where
1327
+
S: subfs_state::State,
1328
+
S::Subject: subfs_state::IsUnset,
1329
+
{
1330
+
/// Set the `subject` field (required)
1331
+
pub fn subject(
1332
+
mut self,
1333
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1334
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1335
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1336
+
SubfsBuilder {
1337
+
_phantom_state: ::core::marker::PhantomData,
1338
+
__unsafe_private_named: self.__unsafe_private_named,
1339
+
_phantom: ::core::marker::PhantomData,
1340
+
}
1341
+
}
1342
+
}
1343
+
1344
+
impl<'a, S> SubfsBuilder<'a, S>
1345
+
where
1346
+
S: subfs_state::State,
1347
+
S::Type: subfs_state::IsUnset,
1348
+
{
1349
+
/// Set the `type` field (required)
1350
+
pub fn r#type(
1351
+
mut self,
1352
+
value: impl Into<jacquard_common::CowStr<'a>>,
1353
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1354
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1355
+
SubfsBuilder {
1356
+
_phantom_state: ::core::marker::PhantomData,
1357
+
__unsafe_private_named: self.__unsafe_private_named,
1358
+
_phantom: ::core::marker::PhantomData,
1359
+
}
1360
+
}
1361
+
}
1362
+
1363
+
impl<'a, S> SubfsBuilder<'a, S>
1364
+
where
1365
+
S: subfs_state::State,
1366
+
S::Subject: subfs_state::IsSet,
1367
+
S::Type: subfs_state::IsSet,
1368
+
{
1369
+
/// Build the final struct
1370
+
pub fn build(self) -> Subfs<'a> {
1371
+
Subfs {
1372
+
subject: self.__unsafe_private_named.0.unwrap(),
1373
+
r#type: self.__unsafe_private_named.1.unwrap(),
1374
+
extra_data: Default::default(),
1375
+
}
1376
+
}
1377
+
/// Build the final struct with custom extra_data
1378
+
pub fn build_with_data(
1379
+
self,
1380
+
extra_data: std::collections::BTreeMap<
1381
+
jacquard_common::smol_str::SmolStr,
1382
+
jacquard_common::types::value::Data<'a>,
1383
+
>,
1384
+
) -> Subfs<'a> {
1385
+
Subfs {
1386
+
subject: self.__unsafe_private_named.0.unwrap(),
1387
+
r#type: self.__unsafe_private_named.1.unwrap(),
1388
+
extra_data: Some(extra_data),
1389
+
}
1390
+
}
1391
+
}
1392
+
1393
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1394
+
fn nsid() -> &'static str {
1395
+
"place.wisp.subfs"
1396
+
}
1397
+
fn def_name() -> &'static str {
1398
+
"subfs"
1399
+
}
1400
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1401
+
lexicon_doc_place_wisp_subfs()
1402
+
}
1403
+
fn validate(
1404
+
&self,
1405
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1406
+
Ok(())
1407
+
}
1408
+
}
+8
cli/crates/lexicons/src/place_wisp.rs
+8
cli/crates/lexicons/src/place_wisp.rs
+16
cli/default.nix
+16
cli/default.nix
···
···
1
+
{
2
+
rustPlatform,
3
+
glibc,
4
+
}:
5
+
rustPlatform.buildRustPackage {
6
+
name = "wisp-cli";
7
+
src = ./.;
8
+
cargoLock = {
9
+
lockFile = ./Cargo.lock;
10
+
outputHashes = {
11
+
"jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA=";
12
+
};
13
+
};
14
+
buildInputs = [glibc.static];
15
+
RUSTFLAGS = ["-C" "target-feature=+crt-static"];
16
+
}
+96
cli/flake.lock
+96
cli/flake.lock
···
···
1
+
{
2
+
"nodes": {
3
+
"flake-utils": {
4
+
"inputs": {
5
+
"systems": "systems"
6
+
},
7
+
"locked": {
8
+
"lastModified": 1731533236,
9
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10
+
"owner": "numtide",
11
+
"repo": "flake-utils",
12
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13
+
"type": "github"
14
+
},
15
+
"original": {
16
+
"owner": "numtide",
17
+
"repo": "flake-utils",
18
+
"type": "github"
19
+
}
20
+
},
21
+
"nixpkgs": {
22
+
"locked": {
23
+
"lastModified": 1767640445,
24
+
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
25
+
"owner": "NixOS",
26
+
"repo": "nixpkgs",
27
+
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
28
+
"type": "github"
29
+
},
30
+
"original": {
31
+
"owner": "NixOS",
32
+
"ref": "nixos-unstable",
33
+
"repo": "nixpkgs",
34
+
"type": "github"
35
+
}
36
+
},
37
+
"nixpkgs_2": {
38
+
"locked": {
39
+
"lastModified": 1744536153,
40
+
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
41
+
"owner": "NixOS",
42
+
"repo": "nixpkgs",
43
+
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
44
+
"type": "github"
45
+
},
46
+
"original": {
47
+
"owner": "NixOS",
48
+
"ref": "nixpkgs-unstable",
49
+
"repo": "nixpkgs",
50
+
"type": "github"
51
+
}
52
+
},
53
+
"root": {
54
+
"inputs": {
55
+
"flake-utils": "flake-utils",
56
+
"nixpkgs": "nixpkgs",
57
+
"rust-overlay": "rust-overlay"
58
+
}
59
+
},
60
+
"rust-overlay": {
61
+
"inputs": {
62
+
"nixpkgs": "nixpkgs_2"
63
+
},
64
+
"locked": {
65
+
"lastModified": 1767667566,
66
+
"narHash": "sha256-COy+yxZGuhQRVD1r4bWVgeFt1GB+IB1k5WRpDKbLfI8=",
67
+
"owner": "oxalica",
68
+
"repo": "rust-overlay",
69
+
"rev": "056ce5b125ab32ffe78c7d3e394d9da44733c95e",
70
+
"type": "github"
71
+
},
72
+
"original": {
73
+
"owner": "oxalica",
74
+
"repo": "rust-overlay",
75
+
"type": "github"
76
+
}
77
+
},
78
+
"systems": {
79
+
"locked": {
80
+
"lastModified": 1681028828,
81
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
82
+
"owner": "nix-systems",
83
+
"repo": "default",
84
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
85
+
"type": "github"
86
+
},
87
+
"original": {
88
+
"owner": "nix-systems",
89
+
"repo": "default",
90
+
"type": "github"
91
+
}
92
+
}
93
+
},
94
+
"root": "root",
95
+
"version": 7
96
+
}
+136
cli/flake.nix
+136
cli/flake.nix
···
···
1
+
{
2
+
inputs = {
3
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4
+
rust-overlay.url = "github:oxalica/rust-overlay";
5
+
flake-utils.url = "github:numtide/flake-utils";
6
+
};
7
+
8
+
outputs = { self, nixpkgs, rust-overlay, flake-utils }:
9
+
flake-utils.lib.eachDefaultSystem (system:
10
+
let
11
+
overlays = [ (import rust-overlay) ];
12
+
pkgs = import nixpkgs { inherit system overlays; };
13
+
14
+
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
15
+
targets = [
16
+
"x86_64-unknown-linux-musl"
17
+
"aarch64-unknown-linux-musl"
18
+
"x86_64-apple-darwin"
19
+
"aarch64-apple-darwin"
20
+
];
21
+
};
22
+
23
+
cargoLockConfig = {
24
+
lockFile = ./Cargo.lock;
25
+
outputHashes = {
26
+
"jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA=";
27
+
};
28
+
};
29
+
30
+
# Native build for current system
31
+
native = pkgs.rustPlatform.buildRustPackage {
32
+
pname = "wisp-cli";
33
+
version = "0.4.2";
34
+
src = ./.;
35
+
cargoLock = cargoLockConfig;
36
+
nativeBuildInputs = [ rustToolchain ];
37
+
};
38
+
39
+
# Cross-compilation targets (Linux from macOS needs zigbuild)
40
+
linuxTargets = let
41
+
zigbuildPkgs = pkgs;
42
+
in {
43
+
x86_64-linux = pkgs.stdenv.mkDerivation {
44
+
pname = "wisp-cli-x86_64-linux";
45
+
version = "0.4.2";
46
+
src = ./.;
47
+
48
+
nativeBuildInputs = [
49
+
rustToolchain
50
+
pkgs.cargo-zigbuild
51
+
pkgs.zig
52
+
];
53
+
54
+
buildPhase = ''
55
+
export HOME=$(mktemp -d)
56
+
cargo zigbuild --release --target x86_64-unknown-linux-musl
57
+
'';
58
+
59
+
installPhase = ''
60
+
mkdir -p $out/bin
61
+
cp target/x86_64-unknown-linux-musl/release/wisp-cli $out/bin/
62
+
'';
63
+
64
+
# Skip Nix's cargo vendor - we use network
65
+
dontConfigure = true;
66
+
dontFixup = true;
67
+
};
68
+
69
+
aarch64-linux = pkgs.stdenv.mkDerivation {
70
+
pname = "wisp-cli-aarch64-linux";
71
+
version = "0.4.2";
72
+
src = ./.;
73
+
74
+
nativeBuildInputs = [
75
+
rustToolchain
76
+
pkgs.cargo-zigbuild
77
+
pkgs.zig
78
+
];
79
+
80
+
buildPhase = ''
81
+
export HOME=$(mktemp -d)
82
+
cargo zigbuild --release --target aarch64-unknown-linux-musl
83
+
'';
84
+
85
+
installPhase = ''
86
+
mkdir -p $out/bin
87
+
cp target/aarch64-unknown-linux-musl/release/wisp-cli $out/bin/
88
+
'';
89
+
90
+
dontConfigure = true;
91
+
dontFixup = true;
92
+
};
93
+
};
94
+
95
+
in {
96
+
packages = {
97
+
default = native;
98
+
inherit native;
99
+
100
+
# macOS universal binary
101
+
macos-universal = pkgs.stdenv.mkDerivation {
102
+
pname = "wisp-cli-macos-universal";
103
+
version = "0.4.2";
104
+
src = ./.;
105
+
106
+
nativeBuildInputs = [ rustToolchain pkgs.darwin.lipo ];
107
+
108
+
buildPhase = ''
109
+
export HOME=$(mktemp -d)
110
+
cargo build --release --target aarch64-apple-darwin
111
+
cargo build --release --target x86_64-apple-darwin
112
+
'';
113
+
114
+
installPhase = ''
115
+
mkdir -p $out/bin
116
+
lipo -create \
117
+
target/aarch64-apple-darwin/release/wisp-cli \
118
+
target/x86_64-apple-darwin/release/wisp-cli \
119
+
-output $out/bin/wisp-cli
120
+
'';
121
+
122
+
dontConfigure = true;
123
+
dontFixup = true;
124
+
};
125
+
} // (if pkgs.stdenv.isDarwin then linuxTargets else {});
126
+
127
+
devShells.default = pkgs.mkShell {
128
+
buildInputs = [
129
+
rustToolchain
130
+
pkgs.cargo-zigbuild
131
+
pkgs.zig
132
+
];
133
+
};
134
+
}
135
+
);
136
+
}
+1
-1
cli/src/blob_map.rs
+1
-1
cli/src/blob_map.rs
-43
cli/src/builder_types.rs
-43
cli/src/builder_types.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
-
/// Marker type indicating a builder field has been set
7
-
pub struct Set<T>(pub T);
8
-
impl<T> Set<T> {
9
-
/// Extract the inner value
10
-
#[inline]
11
-
pub fn into_inner(self) -> T {
12
-
self.0
13
-
}
14
-
}
15
-
16
-
/// Marker type indicating a builder field has not been set
17
-
pub struct Unset;
18
-
/// Trait indicating a builder field is set (has a value)
19
-
#[rustversion::attr(
20
-
since(1.78.0),
21
-
diagnostic::on_unimplemented(
22
-
message = "the field `{Self}` was not set, but this method requires it to be set",
23
-
label = "the field `{Self}` was not set"
24
-
)
25
-
)]
26
-
pub trait IsSet: private::Sealed {}
27
-
/// Trait indicating a builder field is unset (no value yet)
28
-
#[rustversion::attr(
29
-
since(1.78.0),
30
-
diagnostic::on_unimplemented(
31
-
message = "the field `{Self}` was already set, but this method requires it to be unset",
32
-
label = "the field `{Self}` was already set"
33
-
)
34
-
)]
35
-
pub trait IsUnset: private::Sealed {}
36
-
impl<T> IsSet for Set<T> {}
37
-
impl IsUnset for Unset {}
38
-
mod private {
39
-
/// Sealed trait to prevent external implementations
40
-
pub trait Sealed {}
41
-
impl<T> Sealed for super::Set<T> {}
42
-
impl Sealed for super::Unset {}
43
-
}
···
-9
cli/src/lib.rs
-9
cli/src/lib.rs
+28
-31
cli/src/main.rs
+28
-31
cli/src/main.rs
···
1
-
mod builder_types;
2
-
mod place_wisp;
3
mod cid;
4
mod blob_map;
5
mod metadata;
···
28
use futures::stream::{self, StreamExt};
29
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
30
31
-
use place_wisp::fs::*;
32
-
use place_wisp::settings::*;
33
34
/// Maximum number of concurrent file uploads to the PDS
35
const MAX_CONCURRENT_UPLOADS: usize = 2;
···
43
struct Args {
44
#[command(subcommand)]
45
command: Option<Commands>,
46
-
47
// Deploy arguments (when no subcommand is specified)
48
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
49
-
#[arg(global = true, conflicts_with = "command")]
50
input: Option<CowStr<'static>>,
51
52
/// Path to the directory containing your static site
53
-
#[arg(short, long, global = true, conflicts_with = "command")]
54
path: Option<PathBuf>,
55
56
/// Site name (defaults to directory name)
57
-
#[arg(short, long, global = true, conflicts_with = "command")]
58
site: Option<String>,
59
60
/// Path to auth store file
61
-
#[arg(long, global = true, conflicts_with = "command")]
62
store: Option<String>,
63
64
/// App Password for authentication
65
-
#[arg(long, global = true, conflicts_with = "command")]
66
password: Option<CowStr<'static>>,
67
68
/// Enable directory listing mode for paths without index files
69
-
#[arg(long, global = true, conflicts_with = "command")]
70
directory: bool,
71
72
/// Enable SPA mode (serve index.html for all routes)
73
-
#[arg(long, global = true, conflicts_with = "command")]
74
spa: bool,
75
76
/// Skip confirmation prompts (automatically accept warnings)
77
-
#[arg(short = 'y', long, global = true, conflicts_with = "command")]
78
yes: bool,
79
}
80
···
124
125
/// Output directory for the downloaded site
126
#[arg(short, long, default_value = ".")]
127
-
output: PathBuf,
128
},
129
/// Serve a site locally with real-time firehose updates
130
Serve {
···
137
138
/// Output directory for the site files
139
#[arg(short, long, default_value = ".")]
140
-
output: PathBuf,
141
142
/// Port to serve on
143
-
#[arg(short, long, default_value = "8080")]
144
port: u16,
145
},
146
}
···
158
run_with_oauth(input, store, path, site, directory, spa, yes).await
159
}
160
}
161
-
Some(Commands::Pull { input, site, output }) => {
162
-
pull::pull_site(input, CowStr::from(site), output).await
163
}
164
-
Some(Commands::Serve { input, site, output, port }) => {
165
-
serve::serve_site(input, CowStr::from(site), output, port).await
166
}
167
None => {
168
// Legacy mode: if input is provided, assume deploy command
···
512
let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
513
let chunk_size = subfs_utils::estimate_directory_size(chunk);
514
515
-
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};
539
540
// 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();
567
568
-
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();
586
587
-
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())
···
952
953
/// 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};
957
958
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
};
···
1
mod cid;
2
mod blob_map;
3
mod metadata;
···
26
use futures::stream::{self, StreamExt};
27
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
28
29
+
use wisp_lexicons::place_wisp::fs::*;
30
+
use wisp_lexicons::place_wisp::settings::*;
31
32
/// Maximum number of concurrent file uploads to the PDS
33
const MAX_CONCURRENT_UPLOADS: usize = 2;
···
41
struct Args {
42
#[command(subcommand)]
43
command: Option<Commands>,
44
+
45
// Deploy arguments (when no subcommand is specified)
46
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
47
input: Option<CowStr<'static>>,
48
49
/// Path to the directory containing your static site
50
+
#[arg(short, long)]
51
path: Option<PathBuf>,
52
53
/// Site name (defaults to directory name)
54
+
#[arg(short, long)]
55
site: Option<String>,
56
57
/// Path to auth store file
58
+
#[arg(long)]
59
store: Option<String>,
60
61
/// App Password for authentication
62
+
#[arg(long)]
63
password: Option<CowStr<'static>>,
64
65
/// Enable directory listing mode for paths without index files
66
+
#[arg(long)]
67
directory: bool,
68
69
/// Enable SPA mode (serve index.html for all routes)
70
+
#[arg(long)]
71
spa: bool,
72
73
/// Skip confirmation prompts (automatically accept warnings)
74
+
#[arg(short = 'y', long)]
75
yes: bool,
76
}
77
···
121
122
/// Output directory for the downloaded site
123
#[arg(short, long, default_value = ".")]
124
+
path: PathBuf,
125
},
126
/// Serve a site locally with real-time firehose updates
127
Serve {
···
134
135
/// Output directory for the site files
136
#[arg(short, long, default_value = ".")]
137
+
path: PathBuf,
138
139
/// Port to serve on
140
+
#[arg(short = 'P', long, default_value = "8080")]
141
port: u16,
142
},
143
}
···
155
run_with_oauth(input, store, path, site, directory, spa, yes).await
156
}
157
}
158
+
Some(Commands::Pull { input, site, path }) => {
159
+
pull::pull_site(input, CowStr::from(site), path).await
160
}
161
+
Some(Commands::Serve { input, site, path, port }) => {
162
+
serve::serve_site(input, CowStr::from(site), path, port).await
163
}
164
None => {
165
// Legacy mode: if input is provided, assume deploy command
···
509
let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
510
let chunk_size = subfs_utils::estimate_directory_size(chunk);
511
512
+
let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
513
.root(convert_fs_dir_to_subfs_dir(chunk.clone()))
514
.file_count(Some(chunk_file_count as i64))
515
.created_at(Datetime::now())
···
532
// Each chunk reference MUST have flat: true to merge chunk contents
533
println!(" โ Creating parent subfs with {} chunk references...", chunk_uris.len());
534
use jacquard_common::CowStr;
535
+
use wisp_lexicons::place_wisp::fs::{Subfs};
536
537
// Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
538
let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···
562
let parent_tid = Tid::now_0();
563
let parent_rkey = parent_tid.to_string();
564
565
+
let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
566
.root(parent_root_subfs)
567
.file_count(Some(largest_dir.file_count as i64))
568
.created_at(Datetime::now())
···
581
let subfs_tid = Tid::now_0();
582
let subfs_rkey = subfs_tid.to_string();
583
584
+
let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
585
.root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
586
.file_count(Some(largest_dir.file_count as i64))
587
.created_at(Datetime::now())
···
949
950
/// Convert fs::Directory to subfs::Directory
951
/// They have the same structure, but different types
952
+
fn convert_fs_dir_to_subfs_dir(fs_dir: wisp_lexicons::place_wisp::fs::Directory<'static>) -> wisp_lexicons::place_wisp::subfs::Directory<'static> {
953
+
use wisp_lexicons::place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
954
955
let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
956
let node = match entry.node {
957
+
wisp_lexicons::place_wisp::fs::EntryNode::File(file) => {
958
SubfsEntryNode::File(Box::new(SubfsFile::new()
959
.r#type(file.r#type)
960
.blob(file.blob)
···
963
.base64(file.base64)
964
.build()))
965
}
966
+
wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => {
967
SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
968
}
969
+
wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => {
970
// Nested subfs in the directory we're converting
971
// Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
972
+
SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new()
973
.r#type(subfs.r#type)
974
.subject(subfs.subject)
975
.build()))
976
}
977
+
wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => {
978
SubfsEntryNode::Unknown(unknown)
979
}
980
};
-9
cli/src/mod.rs
-9
cli/src/mod.rs
-1490
cli/src/place_wisp/fs.rs
-1490
cli/src/place_wisp/fs.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.fs
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
#[jacquard_derive::lexicon]
9
-
#[derive(
10
-
serde::Serialize,
11
-
serde::Deserialize,
12
-
Debug,
13
-
Clone,
14
-
PartialEq,
15
-
Eq,
16
-
jacquard_derive::IntoStatic
17
-
)]
18
-
#[serde(rename_all = "camelCase")]
19
-
pub struct Directory<'a> {
20
-
#[serde(borrow)]
21
-
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
22
-
#[serde(borrow)]
23
-
pub r#type: jacquard_common::CowStr<'a>,
24
-
}
25
-
26
-
pub mod directory_state {
27
-
28
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
-
#[allow(unused)]
30
-
use ::core::marker::PhantomData;
31
-
mod sealed {
32
-
pub trait Sealed {}
33
-
}
34
-
/// State trait tracking which required fields have been set
35
-
pub trait State: sealed::Sealed {
36
-
type Type;
37
-
type Entries;
38
-
}
39
-
/// Empty state - all required fields are unset
40
-
pub struct Empty(());
41
-
impl sealed::Sealed for Empty {}
42
-
impl State for Empty {
43
-
type Type = Unset;
44
-
type Entries = Unset;
45
-
}
46
-
///State transition - sets the `type` field to Set
47
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
-
impl<S: State> sealed::Sealed for SetType<S> {}
49
-
impl<S: State> State for SetType<S> {
50
-
type Type = Set<members::r#type>;
51
-
type Entries = S::Entries;
52
-
}
53
-
///State transition - sets the `entries` field to Set
54
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
-
impl<S: State> State for SetEntries<S> {
57
-
type Type = S::Type;
58
-
type Entries = Set<members::entries>;
59
-
}
60
-
/// Marker types for field names
61
-
#[allow(non_camel_case_types)]
62
-
pub mod members {
63
-
///Marker type for the `type` field
64
-
pub struct r#type(());
65
-
///Marker type for the `entries` field
66
-
pub struct entries(());
67
-
}
68
-
}
69
-
70
-
/// Builder for constructing an instance of this type
71
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
-
__unsafe_private_named: (
74
-
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
75
-
::core::option::Option<jacquard_common::CowStr<'a>>,
76
-
),
77
-
_phantom: ::core::marker::PhantomData<&'a ()>,
78
-
}
79
-
80
-
impl<'a> Directory<'a> {
81
-
/// Create a new builder for this type
82
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
-
DirectoryBuilder::new()
84
-
}
85
-
}
86
-
87
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
-
/// Create a new builder with all fields unset
89
-
pub fn new() -> Self {
90
-
DirectoryBuilder {
91
-
_phantom_state: ::core::marker::PhantomData,
92
-
__unsafe_private_named: (None, None),
93
-
_phantom: ::core::marker::PhantomData,
94
-
}
95
-
}
96
-
}
97
-
98
-
impl<'a, S> DirectoryBuilder<'a, S>
99
-
where
100
-
S: directory_state::State,
101
-
S::Entries: directory_state::IsUnset,
102
-
{
103
-
/// Set the `entries` field (required)
104
-
pub fn entries(
105
-
mut self,
106
-
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
107
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
-
DirectoryBuilder {
110
-
_phantom_state: ::core::marker::PhantomData,
111
-
__unsafe_private_named: self.__unsafe_private_named,
112
-
_phantom: ::core::marker::PhantomData,
113
-
}
114
-
}
115
-
}
116
-
117
-
impl<'a, S> DirectoryBuilder<'a, S>
118
-
where
119
-
S: directory_state::State,
120
-
S::Type: directory_state::IsUnset,
121
-
{
122
-
/// Set the `type` field (required)
123
-
pub fn r#type(
124
-
mut self,
125
-
value: impl Into<jacquard_common::CowStr<'a>>,
126
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
-
DirectoryBuilder {
129
-
_phantom_state: ::core::marker::PhantomData,
130
-
__unsafe_private_named: self.__unsafe_private_named,
131
-
_phantom: ::core::marker::PhantomData,
132
-
}
133
-
}
134
-
}
135
-
136
-
impl<'a, S> DirectoryBuilder<'a, S>
137
-
where
138
-
S: directory_state::State,
139
-
S::Type: directory_state::IsSet,
140
-
S::Entries: directory_state::IsSet,
141
-
{
142
-
/// Build the final struct
143
-
pub fn build(self) -> Directory<'a> {
144
-
Directory {
145
-
entries: self.__unsafe_private_named.0.unwrap(),
146
-
r#type: self.__unsafe_private_named.1.unwrap(),
147
-
extra_data: Default::default(),
148
-
}
149
-
}
150
-
/// Build the final struct with custom extra_data
151
-
pub fn build_with_data(
152
-
self,
153
-
extra_data: std::collections::BTreeMap<
154
-
jacquard_common::smol_str::SmolStr,
155
-
jacquard_common::types::value::Data<'a>,
156
-
>,
157
-
) -> Directory<'a> {
158
-
Directory {
159
-
entries: self.__unsafe_private_named.0.unwrap(),
160
-
r#type: self.__unsafe_private_named.1.unwrap(),
161
-
extra_data: Some(extra_data),
162
-
}
163
-
}
164
-
}
165
-
166
-
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
-
::jacquard_lexicon::lexicon::LexiconDoc {
168
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
-
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
170
-
revision: None,
171
-
description: None,
172
-
defs: {
173
-
let mut map = ::std::collections::BTreeMap::new();
174
-
map.insert(
175
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
-
description: None,
178
-
required: Some(
179
-
vec![
180
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
-
],
183
-
),
184
-
nullable: None,
185
-
properties: {
186
-
#[allow(unused_mut)]
187
-
let mut map = ::std::collections::BTreeMap::new();
188
-
map.insert(
189
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
-
description: None,
192
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
-
description: None,
194
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
-
}),
196
-
min_length: None,
197
-
max_length: Some(500usize),
198
-
}),
199
-
);
200
-
map.insert(
201
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
-
description: None,
204
-
format: None,
205
-
default: None,
206
-
min_length: None,
207
-
max_length: None,
208
-
min_graphemes: None,
209
-
max_graphemes: None,
210
-
r#enum: None,
211
-
r#const: None,
212
-
known_values: None,
213
-
}),
214
-
);
215
-
map
216
-
},
217
-
}),
218
-
);
219
-
map.insert(
220
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
-
description: None,
223
-
required: Some(
224
-
vec![
225
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
-
::jacquard_common::smol_str::SmolStr::new_static("node")
227
-
],
228
-
),
229
-
nullable: None,
230
-
properties: {
231
-
#[allow(unused_mut)]
232
-
let mut map = ::std::collections::BTreeMap::new();
233
-
map.insert(
234
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
-
description: None,
237
-
format: None,
238
-
default: None,
239
-
min_length: None,
240
-
max_length: Some(255usize),
241
-
min_graphemes: None,
242
-
max_graphemes: None,
243
-
r#enum: None,
244
-
r#const: None,
245
-
known_values: None,
246
-
}),
247
-
);
248
-
map.insert(
249
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
-
description: None,
252
-
refs: vec![
253
-
::jacquard_common::CowStr::new_static("#file"),
254
-
::jacquard_common::CowStr::new_static("#directory"),
255
-
::jacquard_common::CowStr::new_static("#subfs")
256
-
],
257
-
closed: None,
258
-
}),
259
-
);
260
-
map
261
-
},
262
-
}),
263
-
);
264
-
map.insert(
265
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
-
description: None,
268
-
required: Some(
269
-
vec![
270
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
-
],
273
-
),
274
-
nullable: None,
275
-
properties: {
276
-
#[allow(unused_mut)]
277
-
let mut map = ::std::collections::BTreeMap::new();
278
-
map.insert(
279
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
-
description: None,
282
-
default: None,
283
-
r#const: None,
284
-
}),
285
-
);
286
-
map.insert(
287
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
-
description: None,
290
-
accept: None,
291
-
max_size: None,
292
-
}),
293
-
);
294
-
map.insert(
295
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
-
description: Some(
298
-
::jacquard_common::CowStr::new_static(
299
-
"Content encoding (e.g., gzip for compressed files)",
300
-
),
301
-
),
302
-
format: None,
303
-
default: None,
304
-
min_length: None,
305
-
max_length: None,
306
-
min_graphemes: None,
307
-
max_graphemes: None,
308
-
r#enum: None,
309
-
r#const: None,
310
-
known_values: None,
311
-
}),
312
-
);
313
-
map.insert(
314
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
-
description: Some(
317
-
::jacquard_common::CowStr::new_static(
318
-
"Original MIME type before compression",
319
-
),
320
-
),
321
-
format: None,
322
-
default: None,
323
-
min_length: None,
324
-
max_length: None,
325
-
min_graphemes: None,
326
-
max_graphemes: None,
327
-
r#enum: None,
328
-
r#const: None,
329
-
known_values: None,
330
-
}),
331
-
);
332
-
map.insert(
333
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
-
description: None,
336
-
format: None,
337
-
default: None,
338
-
min_length: None,
339
-
max_length: None,
340
-
min_graphemes: None,
341
-
max_graphemes: None,
342
-
r#enum: None,
343
-
r#const: None,
344
-
known_values: None,
345
-
}),
346
-
);
347
-
map
348
-
},
349
-
}),
350
-
);
351
-
map.insert(
352
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
-
description: Some(
355
-
::jacquard_common::CowStr::new_static(
356
-
"Virtual filesystem manifest for a Wisp site",
357
-
),
358
-
),
359
-
key: None,
360
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
-
description: None,
362
-
required: Some(
363
-
vec![
364
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
365
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
366
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
367
-
],
368
-
),
369
-
nullable: None,
370
-
properties: {
371
-
#[allow(unused_mut)]
372
-
let mut map = ::std::collections::BTreeMap::new();
373
-
map.insert(
374
-
::jacquard_common::smol_str::SmolStr::new_static(
375
-
"createdAt",
376
-
),
377
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
378
-
description: None,
379
-
format: Some(
380
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
381
-
),
382
-
default: None,
383
-
min_length: None,
384
-
max_length: None,
385
-
min_graphemes: None,
386
-
max_graphemes: None,
387
-
r#enum: None,
388
-
r#const: None,
389
-
known_values: None,
390
-
}),
391
-
);
392
-
map.insert(
393
-
::jacquard_common::smol_str::SmolStr::new_static(
394
-
"fileCount",
395
-
),
396
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
397
-
description: None,
398
-
default: None,
399
-
minimum: Some(0i64),
400
-
maximum: Some(1000i64),
401
-
r#enum: None,
402
-
r#const: None,
403
-
}),
404
-
);
405
-
map.insert(
406
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
407
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
408
-
description: None,
409
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
410
-
}),
411
-
);
412
-
map.insert(
413
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
414
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
415
-
description: None,
416
-
format: None,
417
-
default: None,
418
-
min_length: None,
419
-
max_length: None,
420
-
min_graphemes: None,
421
-
max_graphemes: None,
422
-
r#enum: None,
423
-
r#const: None,
424
-
known_values: None,
425
-
}),
426
-
);
427
-
map
428
-
},
429
-
}),
430
-
}),
431
-
);
432
-
map.insert(
433
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
434
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
435
-
description: None,
436
-
required: Some(
437
-
vec![
438
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
439
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
440
-
],
441
-
),
442
-
nullable: None,
443
-
properties: {
444
-
#[allow(unused_mut)]
445
-
let mut map = ::std::collections::BTreeMap::new();
446
-
map.insert(
447
-
::jacquard_common::smol_str::SmolStr::new_static("flat"),
448
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
449
-
description: None,
450
-
default: None,
451
-
r#const: None,
452
-
}),
453
-
);
454
-
map.insert(
455
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
456
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
457
-
description: Some(
458
-
::jacquard_common::CowStr::new_static(
459
-
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
460
-
),
461
-
),
462
-
format: Some(
463
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
464
-
),
465
-
default: None,
466
-
min_length: None,
467
-
max_length: None,
468
-
min_graphemes: None,
469
-
max_graphemes: None,
470
-
r#enum: None,
471
-
r#const: None,
472
-
known_values: None,
473
-
}),
474
-
);
475
-
map.insert(
476
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
477
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
478
-
description: None,
479
-
format: None,
480
-
default: None,
481
-
min_length: None,
482
-
max_length: None,
483
-
min_graphemes: None,
484
-
max_graphemes: None,
485
-
r#enum: None,
486
-
r#const: None,
487
-
known_values: None,
488
-
}),
489
-
);
490
-
map
491
-
},
492
-
}),
493
-
);
494
-
map
495
-
},
496
-
}
497
-
}
498
-
499
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
500
-
fn nsid() -> &'static str {
501
-
"place.wisp.fs"
502
-
}
503
-
fn def_name() -> &'static str {
504
-
"directory"
505
-
}
506
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
507
-
lexicon_doc_place_wisp_fs()
508
-
}
509
-
fn validate(
510
-
&self,
511
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
512
-
{
513
-
let value = &self.entries;
514
-
#[allow(unused_comparisons)]
515
-
if value.len() > 500usize {
516
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
517
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
518
-
"entries",
519
-
),
520
-
max: 500usize,
521
-
actual: value.len(),
522
-
});
523
-
}
524
-
}
525
-
Ok(())
526
-
}
527
-
}
528
-
529
-
#[jacquard_derive::lexicon]
530
-
#[derive(
531
-
serde::Serialize,
532
-
serde::Deserialize,
533
-
Debug,
534
-
Clone,
535
-
PartialEq,
536
-
Eq,
537
-
jacquard_derive::IntoStatic
538
-
)]
539
-
#[serde(rename_all = "camelCase")]
540
-
pub struct Entry<'a> {
541
-
#[serde(borrow)]
542
-
pub name: jacquard_common::CowStr<'a>,
543
-
#[serde(borrow)]
544
-
pub node: EntryNode<'a>,
545
-
}
546
-
547
-
pub mod entry_state {
548
-
549
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
550
-
#[allow(unused)]
551
-
use ::core::marker::PhantomData;
552
-
mod sealed {
553
-
pub trait Sealed {}
554
-
}
555
-
/// State trait tracking which required fields have been set
556
-
pub trait State: sealed::Sealed {
557
-
type Name;
558
-
type Node;
559
-
}
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>;
580
-
}
581
-
/// Marker types for field names
582
-
#[allow(non_camel_case_types)]
583
-
pub mod members {
584
-
///Marker type for the `name` field
585
-
pub struct name(());
586
-
///Marker type for the `node` field
587
-
pub struct node(());
588
-
}
589
-
}
590
-
591
-
/// Builder for constructing an instance of this type
592
-
pub struct EntryBuilder<'a, S: entry_state::State> {
593
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
594
-
__unsafe_private_named: (
595
-
::core::option::Option<jacquard_common::CowStr<'a>>,
596
-
::core::option::Option<EntryNode<'a>>,
597
-
),
598
-
_phantom: ::core::marker::PhantomData<&'a ()>,
599
-
}
600
-
601
-
impl<'a> Entry<'a> {
602
-
/// Create a new builder for this type
603
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
604
-
EntryBuilder::new()
605
-
}
606
-
}
607
-
608
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
609
-
/// Create a new builder with all fields unset
610
-
pub fn new() -> Self {
611
-
EntryBuilder {
612
-
_phantom_state: ::core::marker::PhantomData,
613
-
__unsafe_private_named: (None, None),
614
-
_phantom: ::core::marker::PhantomData,
615
-
}
616
-
}
617
-
}
618
-
619
-
impl<'a, S> EntryBuilder<'a, S>
620
-
where
621
-
S: entry_state::State,
622
-
S::Name: entry_state::IsUnset,
623
-
{
624
-
/// Set the `name` field (required)
625
-
pub fn name(
626
-
mut self,
627
-
value: impl Into<jacquard_common::CowStr<'a>>,
628
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
629
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
630
-
EntryBuilder {
631
-
_phantom_state: ::core::marker::PhantomData,
632
-
__unsafe_private_named: self.__unsafe_private_named,
633
-
_phantom: ::core::marker::PhantomData,
634
-
}
635
-
}
636
-
}
637
-
638
-
impl<'a, S> EntryBuilder<'a, S>
639
-
where
640
-
S: entry_state::State,
641
-
S::Node: entry_state::IsUnset,
642
-
{
643
-
/// Set the `node` field (required)
644
-
pub fn node(
645
-
mut self,
646
-
value: impl Into<EntryNode<'a>>,
647
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
648
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
649
-
EntryBuilder {
650
-
_phantom_state: ::core::marker::PhantomData,
651
-
__unsafe_private_named: self.__unsafe_private_named,
652
-
_phantom: ::core::marker::PhantomData,
653
-
}
654
-
}
655
-
}
656
-
657
-
impl<'a, S> EntryBuilder<'a, S>
658
-
where
659
-
S: entry_state::State,
660
-
S::Name: entry_state::IsSet,
661
-
S::Node: entry_state::IsSet,
662
-
{
663
-
/// Build the final struct
664
-
pub fn build(self) -> Entry<'a> {
665
-
Entry {
666
-
name: self.__unsafe_private_named.0.unwrap(),
667
-
node: self.__unsafe_private_named.1.unwrap(),
668
-
extra_data: Default::default(),
669
-
}
670
-
}
671
-
/// Build the final struct with custom extra_data
672
-
pub fn build_with_data(
673
-
self,
674
-
extra_data: std::collections::BTreeMap<
675
-
jacquard_common::smol_str::SmolStr,
676
-
jacquard_common::types::value::Data<'a>,
677
-
>,
678
-
) -> Entry<'a> {
679
-
Entry {
680
-
name: self.__unsafe_private_named.0.unwrap(),
681
-
node: self.__unsafe_private_named.1.unwrap(),
682
-
extra_data: Some(extra_data),
683
-
}
684
-
}
685
-
}
686
-
687
-
#[jacquard_derive::open_union]
688
-
#[derive(
689
-
serde::Serialize,
690
-
serde::Deserialize,
691
-
Debug,
692
-
Clone,
693
-
PartialEq,
694
-
Eq,
695
-
jacquard_derive::IntoStatic
696
-
)]
697
-
#[serde(tag = "$type")]
698
-
#[serde(bound(deserialize = "'de: 'a"))]
699
-
pub enum EntryNode<'a> {
700
-
#[serde(rename = "place.wisp.fs#file")]
701
-
File(Box<crate::place_wisp::fs::File<'a>>),
702
-
#[serde(rename = "place.wisp.fs#directory")]
703
-
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
704
-
#[serde(rename = "place.wisp.fs#subfs")]
705
-
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
706
-
}
707
-
708
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
709
-
fn nsid() -> &'static str {
710
-
"place.wisp.fs"
711
-
}
712
-
fn def_name() -> &'static str {
713
-
"entry"
714
-
}
715
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
716
-
lexicon_doc_place_wisp_fs()
717
-
}
718
-
fn validate(
719
-
&self,
720
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
721
-
{
722
-
let value = &self.name;
723
-
#[allow(unused_comparisons)]
724
-
if <str>::len(value.as_ref()) > 255usize {
725
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
726
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
727
-
"name",
728
-
),
729
-
max: 255usize,
730
-
actual: <str>::len(value.as_ref()),
731
-
});
732
-
}
733
-
}
734
-
Ok(())
735
-
}
736
-
}
737
-
738
-
#[jacquard_derive::lexicon]
739
-
#[derive(
740
-
serde::Serialize,
741
-
serde::Deserialize,
742
-
Debug,
743
-
Clone,
744
-
PartialEq,
745
-
Eq,
746
-
jacquard_derive::IntoStatic
747
-
)]
748
-
#[serde(rename_all = "camelCase")]
749
-
pub 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
-
}
767
-
768
-
pub mod file_state {
769
-
770
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
771
-
#[allow(unused)]
772
-
use ::core::marker::PhantomData;
773
-
mod sealed {
774
-
pub trait Sealed {}
775
-
}
776
-
/// State trait tracking which required fields have been set
777
-
pub trait State: sealed::Sealed {
778
-
type Type;
779
-
type Blob;
780
-
}
781
-
/// Empty state - all required fields are unset
782
-
pub struct Empty(());
783
-
impl sealed::Sealed for Empty {}
784
-
impl State for Empty {
785
-
type Type = Unset;
786
-
type Blob = Unset;
787
-
}
788
-
///State transition - sets the `type` field to Set
789
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
790
-
impl<S: State> sealed::Sealed for SetType<S> {}
791
-
impl<S: State> State for SetType<S> {
792
-
type Type = Set<members::r#type>;
793
-
type Blob = S::Blob;
794
-
}
795
-
///State transition - sets the `blob` field to Set
796
-
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
797
-
impl<S: State> sealed::Sealed for SetBlob<S> {}
798
-
impl<S: State> State for SetBlob<S> {
799
-
type Type = S::Type;
800
-
type Blob = Set<members::blob>;
801
-
}
802
-
/// Marker types for field names
803
-
#[allow(non_camel_case_types)]
804
-
pub mod members {
805
-
///Marker type for the `type` field
806
-
pub struct r#type(());
807
-
///Marker type for the `blob` field
808
-
pub struct blob(());
809
-
}
810
-
}
811
-
812
-
/// Builder for constructing an instance of this type
813
-
pub struct FileBuilder<'a, S: file_state::State> {
814
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
815
-
__unsafe_private_named: (
816
-
::core::option::Option<bool>,
817
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
818
-
::core::option::Option<jacquard_common::CowStr<'a>>,
819
-
::core::option::Option<jacquard_common::CowStr<'a>>,
820
-
::core::option::Option<jacquard_common::CowStr<'a>>,
821
-
),
822
-
_phantom: ::core::marker::PhantomData<&'a ()>,
823
-
}
824
-
825
-
impl<'a> File<'a> {
826
-
/// Create a new builder for this type
827
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
828
-
FileBuilder::new()
829
-
}
830
-
}
831
-
832
-
impl<'a> FileBuilder<'a, file_state::Empty> {
833
-
/// Create a new builder with all fields unset
834
-
pub fn new() -> Self {
835
-
FileBuilder {
836
-
_phantom_state: ::core::marker::PhantomData,
837
-
__unsafe_private_named: (None, None, None, None, None),
838
-
_phantom: ::core::marker::PhantomData,
839
-
}
840
-
}
841
-
}
842
-
843
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
844
-
/// Set the `base64` field (optional)
845
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
846
-
self.__unsafe_private_named.0 = value.into();
847
-
self
848
-
}
849
-
/// Set the `base64` field to an Option value (optional)
850
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
851
-
self.__unsafe_private_named.0 = value;
852
-
self
853
-
}
854
-
}
855
-
856
-
impl<'a, S> FileBuilder<'a, S>
857
-
where
858
-
S: file_state::State,
859
-
S::Blob: file_state::IsUnset,
860
-
{
861
-
/// Set the `blob` field (required)
862
-
pub fn blob(
863
-
mut self,
864
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
865
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
866
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
867
-
FileBuilder {
868
-
_phantom_state: ::core::marker::PhantomData,
869
-
__unsafe_private_named: self.__unsafe_private_named,
870
-
_phantom: ::core::marker::PhantomData,
871
-
}
872
-
}
873
-
}
874
-
875
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
876
-
/// Set the `encoding` field (optional)
877
-
pub fn encoding(
878
-
mut self,
879
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
880
-
) -> Self {
881
-
self.__unsafe_private_named.2 = value.into();
882
-
self
883
-
}
884
-
/// Set the `encoding` field to an Option value (optional)
885
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
886
-
self.__unsafe_private_named.2 = value;
887
-
self
888
-
}
889
-
}
890
-
891
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
892
-
/// Set the `mimeType` field (optional)
893
-
pub fn mime_type(
894
-
mut self,
895
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
896
-
) -> Self {
897
-
self.__unsafe_private_named.3 = value.into();
898
-
self
899
-
}
900
-
/// Set the `mimeType` field to an Option value (optional)
901
-
pub fn maybe_mime_type(
902
-
mut self,
903
-
value: Option<jacquard_common::CowStr<'a>>,
904
-
) -> Self {
905
-
self.__unsafe_private_named.3 = value;
906
-
self
907
-
}
908
-
}
909
-
910
-
impl<'a, S> FileBuilder<'a, S>
911
-
where
912
-
S: file_state::State,
913
-
S::Type: file_state::IsUnset,
914
-
{
915
-
/// Set the `type` field (required)
916
-
pub fn r#type(
917
-
mut self,
918
-
value: impl Into<jacquard_common::CowStr<'a>>,
919
-
) -> FileBuilder<'a, file_state::SetType<S>> {
920
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
921
-
FileBuilder {
922
-
_phantom_state: ::core::marker::PhantomData,
923
-
__unsafe_private_named: self.__unsafe_private_named,
924
-
_phantom: ::core::marker::PhantomData,
925
-
}
926
-
}
927
-
}
928
-
929
-
impl<'a, S> FileBuilder<'a, S>
930
-
where
931
-
S: file_state::State,
932
-
S::Type: file_state::IsSet,
933
-
S::Blob: file_state::IsSet,
934
-
{
935
-
/// Build the final struct
936
-
pub fn build(self) -> File<'a> {
937
-
File {
938
-
base64: self.__unsafe_private_named.0,
939
-
blob: self.__unsafe_private_named.1.unwrap(),
940
-
encoding: self.__unsafe_private_named.2,
941
-
mime_type: self.__unsafe_private_named.3,
942
-
r#type: self.__unsafe_private_named.4.unwrap(),
943
-
extra_data: Default::default(),
944
-
}
945
-
}
946
-
/// Build the final struct with custom extra_data
947
-
pub fn build_with_data(
948
-
self,
949
-
extra_data: std::collections::BTreeMap<
950
-
jacquard_common::smol_str::SmolStr,
951
-
jacquard_common::types::value::Data<'a>,
952
-
>,
953
-
) -> File<'a> {
954
-
File {
955
-
base64: self.__unsafe_private_named.0,
956
-
blob: self.__unsafe_private_named.1.unwrap(),
957
-
encoding: self.__unsafe_private_named.2,
958
-
mime_type: self.__unsafe_private_named.3,
959
-
r#type: self.__unsafe_private_named.4.unwrap(),
960
-
extra_data: Some(extra_data),
961
-
}
962
-
}
963
-
}
964
-
965
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
966
-
fn nsid() -> &'static str {
967
-
"place.wisp.fs"
968
-
}
969
-
fn def_name() -> &'static str {
970
-
"file"
971
-
}
972
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
973
-
lexicon_doc_place_wisp_fs()
974
-
}
975
-
fn validate(
976
-
&self,
977
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
978
-
Ok(())
979
-
}
980
-
}
981
-
982
-
/// Virtual filesystem manifest for a Wisp site
983
-
#[jacquard_derive::lexicon]
984
-
#[derive(
985
-
serde::Serialize,
986
-
serde::Deserialize,
987
-
Debug,
988
-
Clone,
989
-
PartialEq,
990
-
Eq,
991
-
jacquard_derive::IntoStatic
992
-
)]
993
-
#[serde(rename_all = "camelCase")]
994
-
pub 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)]
1001
-
pub site: jacquard_common::CowStr<'a>,
1002
-
}
1003
-
1004
-
pub mod fs_state {
1005
-
1006
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1007
-
#[allow(unused)]
1008
-
use ::core::marker::PhantomData;
1009
-
mod sealed {
1010
-
pub trait Sealed {}
1011
-
}
1012
-
/// State trait tracking which required fields have been set
1013
-
pub trait State: sealed::Sealed {
1014
-
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 {
1022
-
type Site = Unset;
1023
-
type Root = Unset;
1024
-
type CreatedAt = Unset;
1025
-
}
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> {
1030
-
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 {
1053
-
///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
-
1062
-
/// Builder for constructing an instance of this type
1063
-
pub struct FsBuilder<'a, S: fs_state::State> {
1064
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1065
-
__unsafe_private_named: (
1066
-
::core::option::Option<jacquard_common::types::string::Datetime>,
1067
-
::core::option::Option<i64>,
1068
-
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
1069
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1070
-
),
1071
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1072
-
}
1073
-
1074
-
impl<'a> Fs<'a> {
1075
-
/// Create a new builder for this type
1076
-
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
1077
-
FsBuilder::new()
1078
-
}
1079
-
}
1080
-
1081
-
impl<'a> FsBuilder<'a, fs_state::Empty> {
1082
-
/// Create a new builder with all fields unset
1083
-
pub fn new() -> Self {
1084
-
FsBuilder {
1085
-
_phantom_state: ::core::marker::PhantomData,
1086
-
__unsafe_private_named: (None, None, None, None),
1087
-
_phantom: ::core::marker::PhantomData,
1088
-
}
1089
-
}
1090
-
}
1091
-
1092
-
impl<'a, S> FsBuilder<'a, S>
1093
-
where
1094
-
S: fs_state::State,
1095
-
S::CreatedAt: fs_state::IsUnset,
1096
-
{
1097
-
/// Set the `createdAt` field (required)
1098
-
pub fn created_at(
1099
-
mut self,
1100
-
value: impl Into<jacquard_common::types::string::Datetime>,
1101
-
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
1102
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1103
-
FsBuilder {
1104
-
_phantom_state: ::core::marker::PhantomData,
1105
-
__unsafe_private_named: self.__unsafe_private_named,
1106
-
_phantom: ::core::marker::PhantomData,
1107
-
}
1108
-
}
1109
-
}
1110
-
1111
-
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
1112
-
/// Set the `fileCount` field (optional)
1113
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1114
-
self.__unsafe_private_named.1 = value.into();
1115
-
self
1116
-
}
1117
-
/// Set the `fileCount` field to an Option value (optional)
1118
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1119
-
self.__unsafe_private_named.1 = value;
1120
-
self
1121
-
}
1122
-
}
1123
-
1124
-
impl<'a, S> FsBuilder<'a, S>
1125
-
where
1126
-
S: fs_state::State,
1127
-
S::Root: fs_state::IsUnset,
1128
-
{
1129
-
/// Set the `root` field (required)
1130
-
pub fn root(
1131
-
mut self,
1132
-
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
1133
-
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
1134
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1135
-
FsBuilder {
1136
-
_phantom_state: ::core::marker::PhantomData,
1137
-
__unsafe_private_named: self.__unsafe_private_named,
1138
-
_phantom: ::core::marker::PhantomData,
1139
-
}
1140
-
}
1141
-
}
1142
-
1143
-
impl<'a, S> FsBuilder<'a, S>
1144
-
where
1145
-
S: fs_state::State,
1146
-
S::Site: fs_state::IsUnset,
1147
-
{
1148
-
/// Set the `site` field (required)
1149
-
pub fn site(
1150
-
mut self,
1151
-
value: impl Into<jacquard_common::CowStr<'a>>,
1152
-
) -> FsBuilder<'a, fs_state::SetSite<S>> {
1153
-
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
1154
-
FsBuilder {
1155
-
_phantom_state: ::core::marker::PhantomData,
1156
-
__unsafe_private_named: self.__unsafe_private_named,
1157
-
_phantom: ::core::marker::PhantomData,
1158
-
}
1159
-
}
1160
-
}
1161
-
1162
-
impl<'a, S> FsBuilder<'a, S>
1163
-
where
1164
-
S: fs_state::State,
1165
-
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> {
1171
-
Fs {
1172
-
created_at: self.__unsafe_private_named.0.unwrap(),
1173
-
file_count: self.__unsafe_private_named.1,
1174
-
root: self.__unsafe_private_named.2.unwrap(),
1175
-
site: self.__unsafe_private_named.3.unwrap(),
1176
-
extra_data: Default::default(),
1177
-
}
1178
-
}
1179
-
/// Build the final struct with custom extra_data
1180
-
pub fn build_with_data(
1181
-
self,
1182
-
extra_data: std::collections::BTreeMap<
1183
-
jacquard_common::smol_str::SmolStr,
1184
-
jacquard_common::types::value::Data<'a>,
1185
-
>,
1186
-
) -> Fs<'a> {
1187
-
Fs {
1188
-
created_at: self.__unsafe_private_named.0.unwrap(),
1189
-
file_count: self.__unsafe_private_named.1,
1190
-
root: self.__unsafe_private_named.2.unwrap(),
1191
-
site: self.__unsafe_private_named.3.unwrap(),
1192
-
extra_data: Some(extra_data),
1193
-
}
1194
-
}
1195
-
}
1196
-
1197
-
impl<'a> Fs<'a> {
1198
-
pub fn uri(
1199
-
uri: impl Into<jacquard_common::CowStr<'a>>,
1200
-
) -> Result<
1201
-
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
1202
-
jacquard_common::types::uri::UriError,
1203
-
> {
1204
-
jacquard_common::types::uri::RecordUri::try_from_uri(
1205
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1206
-
)
1207
-
}
1208
-
}
1209
-
1210
-
/// Typed wrapper for GetRecord response with this collection's record type.
1211
-
#[derive(
1212
-
serde::Serialize,
1213
-
serde::Deserialize,
1214
-
Debug,
1215
-
Clone,
1216
-
PartialEq,
1217
-
Eq,
1218
-
jacquard_derive::IntoStatic
1219
-
)]
1220
-
#[serde(rename_all = "camelCase")]
1221
-
pub struct FsGetRecordOutput<'a> {
1222
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1223
-
#[serde(borrow)]
1224
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1225
-
#[serde(borrow)]
1226
-
pub uri: jacquard_common::types::string::AtUri<'a>,
1227
-
#[serde(borrow)]
1228
-
pub value: Fs<'a>,
1229
-
}
1230
-
1231
-
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
1232
-
fn from(output: FsGetRecordOutput<'_>) -> Self {
1233
-
use jacquard_common::IntoStatic;
1234
-
output.value.into_static()
1235
-
}
1236
-
}
1237
-
1238
-
impl jacquard_common::types::collection::Collection for Fs<'_> {
1239
-
const NSID: &'static str = "place.wisp.fs";
1240
-
type Record = FsRecord;
1241
-
}
1242
-
1243
-
/// Marker type for deserializing records from this collection.
1244
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1245
-
pub struct FsRecord;
1246
-
impl jacquard_common::xrpc::XrpcResp for FsRecord {
1247
-
const NSID: &'static str = "place.wisp.fs";
1248
-
const ENCODING: &'static str = "application/json";
1249
-
type Output<'de> = FsGetRecordOutput<'de>;
1250
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1251
-
}
1252
-
1253
-
impl jacquard_common::types::collection::Collection for FsRecord {
1254
-
const NSID: &'static str = "place.wisp.fs";
1255
-
type Record = FsRecord;
1256
-
}
1257
-
1258
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
1259
-
fn nsid() -> &'static str {
1260
-
"place.wisp.fs"
1261
-
}
1262
-
fn def_name() -> &'static str {
1263
-
"main"
1264
-
}
1265
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1266
-
lexicon_doc_place_wisp_fs()
1267
-
}
1268
-
fn validate(
1269
-
&self,
1270
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1271
-
if let Some(ref value) = self.file_count {
1272
-
if *value > 1000i64 {
1273
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1274
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1275
-
"file_count",
1276
-
),
1277
-
max: 1000i64,
1278
-
actual: *value,
1279
-
});
1280
-
}
1281
-
}
1282
-
if let Some(ref value) = self.file_count {
1283
-
if *value < 0i64 {
1284
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1285
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1286
-
"file_count",
1287
-
),
1288
-
min: 0i64,
1289
-
actual: *value,
1290
-
});
1291
-
}
1292
-
}
1293
-
Ok(())
1294
-
}
1295
-
}
1296
-
1297
-
#[jacquard_derive::lexicon]
1298
-
#[derive(
1299
-
serde::Serialize,
1300
-
serde::Deserialize,
1301
-
Debug,
1302
-
Clone,
1303
-
PartialEq,
1304
-
Eq,
1305
-
jacquard_derive::IntoStatic
1306
-
)]
1307
-
#[serde(rename_all = "camelCase")]
1308
-
pub 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>,
1315
-
#[serde(borrow)]
1316
-
pub r#type: jacquard_common::CowStr<'a>,
1317
-
}
1318
-
1319
-
pub mod subfs_state {
1320
-
1321
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1322
-
#[allow(unused)]
1323
-
use ::core::marker::PhantomData;
1324
-
mod sealed {
1325
-
pub trait Sealed {}
1326
-
}
1327
-
/// State trait tracking which required fields have been set
1328
-
pub trait State: sealed::Sealed {
1329
-
type Type;
1330
-
type Subject;
1331
-
}
1332
-
/// Empty state - all required fields are unset
1333
-
pub struct Empty(());
1334
-
impl sealed::Sealed for Empty {}
1335
-
impl State for Empty {
1336
-
type Type = Unset;
1337
-
type Subject = Unset;
1338
-
}
1339
-
///State transition - sets the `type` field to Set
1340
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1341
-
impl<S: State> sealed::Sealed for SetType<S> {}
1342
-
impl<S: State> State for SetType<S> {
1343
-
type Type = Set<members::r#type>;
1344
-
type Subject = S::Subject;
1345
-
}
1346
-
///State transition - sets the `subject` field to Set
1347
-
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1348
-
impl<S: State> sealed::Sealed for SetSubject<S> {}
1349
-
impl<S: State> State for SetSubject<S> {
1350
-
type Type = S::Type;
1351
-
type Subject = Set<members::subject>;
1352
-
}
1353
-
/// Marker types for field names
1354
-
#[allow(non_camel_case_types)]
1355
-
pub mod members {
1356
-
///Marker type for the `type` field
1357
-
pub struct r#type(());
1358
-
///Marker type for the `subject` field
1359
-
pub struct subject(());
1360
-
}
1361
-
}
1362
-
1363
-
/// Builder for constructing an instance of this type
1364
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1365
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1366
-
__unsafe_private_named: (
1367
-
::core::option::Option<bool>,
1368
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1369
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1370
-
),
1371
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1372
-
}
1373
-
1374
-
impl<'a> Subfs<'a> {
1375
-
/// Create a new builder for this type
1376
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1377
-
SubfsBuilder::new()
1378
-
}
1379
-
}
1380
-
1381
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1382
-
/// Create a new builder with all fields unset
1383
-
pub fn new() -> Self {
1384
-
SubfsBuilder {
1385
-
_phantom_state: ::core::marker::PhantomData,
1386
-
__unsafe_private_named: (None, None, None),
1387
-
_phantom: ::core::marker::PhantomData,
1388
-
}
1389
-
}
1390
-
}
1391
-
1392
-
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
1393
-
/// Set the `flat` field (optional)
1394
-
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
1395
-
self.__unsafe_private_named.0 = value.into();
1396
-
self
1397
-
}
1398
-
/// Set the `flat` field to an Option value (optional)
1399
-
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
1400
-
self.__unsafe_private_named.0 = value;
1401
-
self
1402
-
}
1403
-
}
1404
-
1405
-
impl<'a, S> SubfsBuilder<'a, S>
1406
-
where
1407
-
S: subfs_state::State,
1408
-
S::Subject: subfs_state::IsUnset,
1409
-
{
1410
-
/// Set the `subject` field (required)
1411
-
pub fn subject(
1412
-
mut self,
1413
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1414
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1415
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1416
-
SubfsBuilder {
1417
-
_phantom_state: ::core::marker::PhantomData,
1418
-
__unsafe_private_named: self.__unsafe_private_named,
1419
-
_phantom: ::core::marker::PhantomData,
1420
-
}
1421
-
}
1422
-
}
1423
-
1424
-
impl<'a, S> SubfsBuilder<'a, S>
1425
-
where
1426
-
S: subfs_state::State,
1427
-
S::Type: subfs_state::IsUnset,
1428
-
{
1429
-
/// Set the `type` field (required)
1430
-
pub fn r#type(
1431
-
mut self,
1432
-
value: impl Into<jacquard_common::CowStr<'a>>,
1433
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1434
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1435
-
SubfsBuilder {
1436
-
_phantom_state: ::core::marker::PhantomData,
1437
-
__unsafe_private_named: self.__unsafe_private_named,
1438
-
_phantom: ::core::marker::PhantomData,
1439
-
}
1440
-
}
1441
-
}
1442
-
1443
-
impl<'a, S> SubfsBuilder<'a, S>
1444
-
where
1445
-
S: subfs_state::State,
1446
-
S::Type: subfs_state::IsSet,
1447
-
S::Subject: subfs_state::IsSet,
1448
-
{
1449
-
/// Build the final struct
1450
-
pub fn build(self) -> Subfs<'a> {
1451
-
Subfs {
1452
-
flat: self.__unsafe_private_named.0,
1453
-
subject: self.__unsafe_private_named.1.unwrap(),
1454
-
r#type: self.__unsafe_private_named.2.unwrap(),
1455
-
extra_data: Default::default(),
1456
-
}
1457
-
}
1458
-
/// Build the final struct with custom extra_data
1459
-
pub fn build_with_data(
1460
-
self,
1461
-
extra_data: std::collections::BTreeMap<
1462
-
jacquard_common::smol_str::SmolStr,
1463
-
jacquard_common::types::value::Data<'a>,
1464
-
>,
1465
-
) -> Subfs<'a> {
1466
-
Subfs {
1467
-
flat: self.__unsafe_private_named.0,
1468
-
subject: self.__unsafe_private_named.1.unwrap(),
1469
-
r#type: self.__unsafe_private_named.2.unwrap(),
1470
-
extra_data: Some(extra_data),
1471
-
}
1472
-
}
1473
-
}
1474
-
1475
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1476
-
fn nsid() -> &'static str {
1477
-
"place.wisp.fs"
1478
-
}
1479
-
fn def_name() -> &'static str {
1480
-
"subfs"
1481
-
}
1482
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1483
-
lexicon_doc_place_wisp_fs()
1484
-
}
1485
-
fn validate(
1486
-
&self,
1487
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1488
-
Ok(())
1489
-
}
1490
-
}
···
-653
cli/src/place_wisp/settings.rs
-653
cli/src/place_wisp/settings.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.settings
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
/// Custom HTTP header configuration
9
-
#[jacquard_derive::lexicon]
10
-
#[derive(
11
-
serde::Serialize,
12
-
serde::Deserialize,
13
-
Debug,
14
-
Clone,
15
-
PartialEq,
16
-
Eq,
17
-
jacquard_derive::IntoStatic,
18
-
Default
19
-
)]
20
-
#[serde(rename_all = "camelCase")]
21
-
pub struct CustomHeader<'a> {
22
-
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
23
-
#[serde(borrow)]
24
-
pub name: jacquard_common::CowStr<'a>,
25
-
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
26
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
27
-
#[serde(borrow)]
28
-
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
29
-
/// HTTP header value
30
-
#[serde(borrow)]
31
-
pub value: jacquard_common::CowStr<'a>,
32
-
}
33
-
34
-
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
35
-
'static,
36
-
> {
37
-
::jacquard_lexicon::lexicon::LexiconDoc {
38
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
39
-
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
40
-
revision: None,
41
-
description: None,
42
-
defs: {
43
-
let mut map = ::std::collections::BTreeMap::new();
44
-
map.insert(
45
-
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
46
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
47
-
description: Some(
48
-
::jacquard_common::CowStr::new_static(
49
-
"Custom HTTP header configuration",
50
-
),
51
-
),
52
-
required: Some(
53
-
vec![
54
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
55
-
::jacquard_common::smol_str::SmolStr::new_static("value")
56
-
],
57
-
),
58
-
nullable: None,
59
-
properties: {
60
-
#[allow(unused_mut)]
61
-
let mut map = ::std::collections::BTreeMap::new();
62
-
map.insert(
63
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
64
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
65
-
description: Some(
66
-
::jacquard_common::CowStr::new_static(
67
-
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
68
-
),
69
-
),
70
-
format: None,
71
-
default: None,
72
-
min_length: None,
73
-
max_length: Some(100usize),
74
-
min_graphemes: None,
75
-
max_graphemes: None,
76
-
r#enum: None,
77
-
r#const: None,
78
-
known_values: None,
79
-
}),
80
-
);
81
-
map.insert(
82
-
::jacquard_common::smol_str::SmolStr::new_static("path"),
83
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
84
-
description: Some(
85
-
::jacquard_common::CowStr::new_static(
86
-
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
87
-
),
88
-
),
89
-
format: None,
90
-
default: None,
91
-
min_length: None,
92
-
max_length: Some(500usize),
93
-
min_graphemes: None,
94
-
max_graphemes: None,
95
-
r#enum: None,
96
-
r#const: None,
97
-
known_values: None,
98
-
}),
99
-
);
100
-
map.insert(
101
-
::jacquard_common::smol_str::SmolStr::new_static("value"),
102
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
103
-
description: Some(
104
-
::jacquard_common::CowStr::new_static("HTTP header value"),
105
-
),
106
-
format: None,
107
-
default: None,
108
-
min_length: None,
109
-
max_length: Some(1000usize),
110
-
min_graphemes: None,
111
-
max_graphemes: None,
112
-
r#enum: None,
113
-
r#const: None,
114
-
known_values: None,
115
-
}),
116
-
);
117
-
map
118
-
},
119
-
}),
120
-
);
121
-
map.insert(
122
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
123
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
124
-
description: Some(
125
-
::jacquard_common::CowStr::new_static(
126
-
"Configuration settings for a static site hosted on wisp.place",
127
-
),
128
-
),
129
-
key: Some(::jacquard_common::CowStr::new_static("any")),
130
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
131
-
description: None,
132
-
required: None,
133
-
nullable: None,
134
-
properties: {
135
-
#[allow(unused_mut)]
136
-
let mut map = ::std::collections::BTreeMap::new();
137
-
map.insert(
138
-
::jacquard_common::smol_str::SmolStr::new_static(
139
-
"cleanUrls",
140
-
),
141
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
142
-
description: None,
143
-
default: None,
144
-
r#const: None,
145
-
}),
146
-
);
147
-
map.insert(
148
-
::jacquard_common::smol_str::SmolStr::new_static(
149
-
"custom404",
150
-
),
151
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
152
-
description: Some(
153
-
::jacquard_common::CowStr::new_static(
154
-
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
155
-
),
156
-
),
157
-
format: None,
158
-
default: None,
159
-
min_length: None,
160
-
max_length: Some(500usize),
161
-
min_graphemes: None,
162
-
max_graphemes: None,
163
-
r#enum: None,
164
-
r#const: None,
165
-
known_values: None,
166
-
}),
167
-
);
168
-
map.insert(
169
-
::jacquard_common::smol_str::SmolStr::new_static(
170
-
"directoryListing",
171
-
),
172
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
173
-
description: None,
174
-
default: None,
175
-
r#const: None,
176
-
}),
177
-
);
178
-
map.insert(
179
-
::jacquard_common::smol_str::SmolStr::new_static("headers"),
180
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
181
-
description: Some(
182
-
::jacquard_common::CowStr::new_static(
183
-
"Custom HTTP headers to set on responses",
184
-
),
185
-
),
186
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
187
-
description: None,
188
-
r#ref: ::jacquard_common::CowStr::new_static(
189
-
"#customHeader",
190
-
),
191
-
}),
192
-
min_length: None,
193
-
max_length: Some(50usize),
194
-
}),
195
-
);
196
-
map.insert(
197
-
::jacquard_common::smol_str::SmolStr::new_static(
198
-
"indexFiles",
199
-
),
200
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
201
-
description: Some(
202
-
::jacquard_common::CowStr::new_static(
203
-
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
204
-
),
205
-
),
206
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
207
-
description: None,
208
-
format: None,
209
-
default: None,
210
-
min_length: None,
211
-
max_length: Some(255usize),
212
-
min_graphemes: None,
213
-
max_graphemes: None,
214
-
r#enum: None,
215
-
r#const: None,
216
-
known_values: None,
217
-
}),
218
-
min_length: None,
219
-
max_length: Some(10usize),
220
-
}),
221
-
);
222
-
map.insert(
223
-
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
224
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
225
-
description: Some(
226
-
::jacquard_common::CowStr::new_static(
227
-
"File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
228
-
),
229
-
),
230
-
format: None,
231
-
default: None,
232
-
min_length: None,
233
-
max_length: Some(500usize),
234
-
min_graphemes: None,
235
-
max_graphemes: None,
236
-
r#enum: None,
237
-
r#const: None,
238
-
known_values: None,
239
-
}),
240
-
);
241
-
map
242
-
},
243
-
}),
244
-
}),
245
-
);
246
-
map
247
-
},
248
-
}
249
-
}
250
-
251
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
252
-
fn nsid() -> &'static str {
253
-
"place.wisp.settings"
254
-
}
255
-
fn def_name() -> &'static str {
256
-
"customHeader"
257
-
}
258
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
259
-
lexicon_doc_place_wisp_settings()
260
-
}
261
-
fn validate(
262
-
&self,
263
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
264
-
{
265
-
let value = &self.name;
266
-
#[allow(unused_comparisons)]
267
-
if <str>::len(value.as_ref()) > 100usize {
268
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
269
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
270
-
"name",
271
-
),
272
-
max: 100usize,
273
-
actual: <str>::len(value.as_ref()),
274
-
});
275
-
}
276
-
}
277
-
if let Some(ref value) = self.path {
278
-
#[allow(unused_comparisons)]
279
-
if <str>::len(value.as_ref()) > 500usize {
280
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
281
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
282
-
"path",
283
-
),
284
-
max: 500usize,
285
-
actual: <str>::len(value.as_ref()),
286
-
});
287
-
}
288
-
}
289
-
{
290
-
let value = &self.value;
291
-
#[allow(unused_comparisons)]
292
-
if <str>::len(value.as_ref()) > 1000usize {
293
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
294
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
295
-
"value",
296
-
),
297
-
max: 1000usize,
298
-
actual: <str>::len(value.as_ref()),
299
-
});
300
-
}
301
-
}
302
-
Ok(())
303
-
}
304
-
}
305
-
306
-
/// Configuration settings for a static site hosted on wisp.place
307
-
#[jacquard_derive::lexicon]
308
-
#[derive(
309
-
serde::Serialize,
310
-
serde::Deserialize,
311
-
Debug,
312
-
Clone,
313
-
PartialEq,
314
-
Eq,
315
-
jacquard_derive::IntoStatic
316
-
)]
317
-
#[serde(rename_all = "camelCase")]
318
-
pub struct Settings<'a> {
319
-
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
320
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
321
-
pub clean_urls: std::option::Option<bool>,
322
-
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
323
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
324
-
#[serde(borrow)]
325
-
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
326
-
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
327
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
328
-
pub directory_listing: std::option::Option<bool>,
329
-
/// Custom HTTP headers to set on responses
330
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
331
-
#[serde(borrow)]
332
-
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
333
-
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
334
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
335
-
#[serde(borrow)]
336
-
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
337
-
/// File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.
338
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
339
-
#[serde(borrow)]
340
-
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
341
-
}
342
-
343
-
pub mod settings_state {
344
-
345
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
346
-
#[allow(unused)]
347
-
use ::core::marker::PhantomData;
348
-
mod sealed {
349
-
pub trait Sealed {}
350
-
}
351
-
/// State trait tracking which required fields have been set
352
-
pub trait State: sealed::Sealed {}
353
-
/// Empty state - all required fields are unset
354
-
pub struct Empty(());
355
-
impl sealed::Sealed for Empty {}
356
-
impl State for Empty {}
357
-
/// Marker types for field names
358
-
#[allow(non_camel_case_types)]
359
-
pub mod members {}
360
-
}
361
-
362
-
/// Builder for constructing an instance of this type
363
-
pub struct SettingsBuilder<'a, S: settings_state::State> {
364
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
365
-
__unsafe_private_named: (
366
-
::core::option::Option<bool>,
367
-
::core::option::Option<jacquard_common::CowStr<'a>>,
368
-
::core::option::Option<bool>,
369
-
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
370
-
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
371
-
::core::option::Option<jacquard_common::CowStr<'a>>,
372
-
),
373
-
_phantom: ::core::marker::PhantomData<&'a ()>,
374
-
}
375
-
376
-
impl<'a> Settings<'a> {
377
-
/// Create a new builder for this type
378
-
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
379
-
SettingsBuilder::new()
380
-
}
381
-
}
382
-
383
-
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
384
-
/// Create a new builder with all fields unset
385
-
pub fn new() -> Self {
386
-
SettingsBuilder {
387
-
_phantom_state: ::core::marker::PhantomData,
388
-
__unsafe_private_named: (None, None, None, None, None, None),
389
-
_phantom: ::core::marker::PhantomData,
390
-
}
391
-
}
392
-
}
393
-
394
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
395
-
/// Set the `cleanUrls` field (optional)
396
-
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
397
-
self.__unsafe_private_named.0 = value.into();
398
-
self
399
-
}
400
-
/// Set the `cleanUrls` field to an Option value (optional)
401
-
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
402
-
self.__unsafe_private_named.0 = value;
403
-
self
404
-
}
405
-
}
406
-
407
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
408
-
/// Set the `custom404` field (optional)
409
-
pub fn custom404(
410
-
mut self,
411
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
412
-
) -> Self {
413
-
self.__unsafe_private_named.1 = value.into();
414
-
self
415
-
}
416
-
/// Set the `custom404` field to an Option value (optional)
417
-
pub fn maybe_custom404(
418
-
mut self,
419
-
value: Option<jacquard_common::CowStr<'a>>,
420
-
) -> Self {
421
-
self.__unsafe_private_named.1 = value;
422
-
self
423
-
}
424
-
}
425
-
426
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
427
-
/// Set the `directoryListing` field (optional)
428
-
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
429
-
self.__unsafe_private_named.2 = value.into();
430
-
self
431
-
}
432
-
/// Set the `directoryListing` field to an Option value (optional)
433
-
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
434
-
self.__unsafe_private_named.2 = value;
435
-
self
436
-
}
437
-
}
438
-
439
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
440
-
/// Set the `headers` field (optional)
441
-
pub fn headers(
442
-
mut self,
443
-
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
444
-
) -> Self {
445
-
self.__unsafe_private_named.3 = value.into();
446
-
self
447
-
}
448
-
/// Set the `headers` field to an Option value (optional)
449
-
pub fn maybe_headers(
450
-
mut self,
451
-
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
452
-
) -> Self {
453
-
self.__unsafe_private_named.3 = value;
454
-
self
455
-
}
456
-
}
457
-
458
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
459
-
/// Set the `indexFiles` field (optional)
460
-
pub fn index_files(
461
-
mut self,
462
-
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
463
-
) -> Self {
464
-
self.__unsafe_private_named.4 = value.into();
465
-
self
466
-
}
467
-
/// Set the `indexFiles` field to an Option value (optional)
468
-
pub fn maybe_index_files(
469
-
mut self,
470
-
value: Option<Vec<jacquard_common::CowStr<'a>>>,
471
-
) -> Self {
472
-
self.__unsafe_private_named.4 = value;
473
-
self
474
-
}
475
-
}
476
-
477
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
478
-
/// Set the `spaMode` field (optional)
479
-
pub fn spa_mode(
480
-
mut self,
481
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
482
-
) -> Self {
483
-
self.__unsafe_private_named.5 = value.into();
484
-
self
485
-
}
486
-
/// Set the `spaMode` field to an Option value (optional)
487
-
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
488
-
self.__unsafe_private_named.5 = value;
489
-
self
490
-
}
491
-
}
492
-
493
-
impl<'a, S> SettingsBuilder<'a, S>
494
-
where
495
-
S: settings_state::State,
496
-
{
497
-
/// Build the final struct
498
-
pub fn build(self) -> Settings<'a> {
499
-
Settings {
500
-
clean_urls: self.__unsafe_private_named.0,
501
-
custom404: self.__unsafe_private_named.1,
502
-
directory_listing: self.__unsafe_private_named.2,
503
-
headers: self.__unsafe_private_named.3,
504
-
index_files: self.__unsafe_private_named.4,
505
-
spa_mode: self.__unsafe_private_named.5,
506
-
extra_data: Default::default(),
507
-
}
508
-
}
509
-
/// Build the final struct with custom extra_data
510
-
pub fn build_with_data(
511
-
self,
512
-
extra_data: std::collections::BTreeMap<
513
-
jacquard_common::smol_str::SmolStr,
514
-
jacquard_common::types::value::Data<'a>,
515
-
>,
516
-
) -> Settings<'a> {
517
-
Settings {
518
-
clean_urls: self.__unsafe_private_named.0,
519
-
custom404: self.__unsafe_private_named.1,
520
-
directory_listing: self.__unsafe_private_named.2,
521
-
headers: self.__unsafe_private_named.3,
522
-
index_files: self.__unsafe_private_named.4,
523
-
spa_mode: self.__unsafe_private_named.5,
524
-
extra_data: Some(extra_data),
525
-
}
526
-
}
527
-
}
528
-
529
-
impl<'a> Settings<'a> {
530
-
pub fn uri(
531
-
uri: impl Into<jacquard_common::CowStr<'a>>,
532
-
) -> Result<
533
-
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
534
-
jacquard_common::types::uri::UriError,
535
-
> {
536
-
jacquard_common::types::uri::RecordUri::try_from_uri(
537
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
538
-
)
539
-
}
540
-
}
541
-
542
-
/// Typed wrapper for GetRecord response with this collection's record type.
543
-
#[derive(
544
-
serde::Serialize,
545
-
serde::Deserialize,
546
-
Debug,
547
-
Clone,
548
-
PartialEq,
549
-
Eq,
550
-
jacquard_derive::IntoStatic
551
-
)]
552
-
#[serde(rename_all = "camelCase")]
553
-
pub struct SettingsGetRecordOutput<'a> {
554
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
555
-
#[serde(borrow)]
556
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
557
-
#[serde(borrow)]
558
-
pub uri: jacquard_common::types::string::AtUri<'a>,
559
-
#[serde(borrow)]
560
-
pub value: Settings<'a>,
561
-
}
562
-
563
-
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
564
-
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
565
-
use jacquard_common::IntoStatic;
566
-
output.value.into_static()
567
-
}
568
-
}
569
-
570
-
impl jacquard_common::types::collection::Collection for Settings<'_> {
571
-
const NSID: &'static str = "place.wisp.settings";
572
-
type Record = SettingsRecord;
573
-
}
574
-
575
-
/// Marker type for deserializing records from this collection.
576
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
577
-
pub struct SettingsRecord;
578
-
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
579
-
const NSID: &'static str = "place.wisp.settings";
580
-
const ENCODING: &'static str = "application/json";
581
-
type Output<'de> = SettingsGetRecordOutput<'de>;
582
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
583
-
}
584
-
585
-
impl jacquard_common::types::collection::Collection for SettingsRecord {
586
-
const NSID: &'static str = "place.wisp.settings";
587
-
type Record = SettingsRecord;
588
-
}
589
-
590
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
591
-
fn nsid() -> &'static str {
592
-
"place.wisp.settings"
593
-
}
594
-
fn def_name() -> &'static str {
595
-
"main"
596
-
}
597
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
598
-
lexicon_doc_place_wisp_settings()
599
-
}
600
-
fn validate(
601
-
&self,
602
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
603
-
if let Some(ref value) = self.custom404 {
604
-
#[allow(unused_comparisons)]
605
-
if <str>::len(value.as_ref()) > 500usize {
606
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
607
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
608
-
"custom404",
609
-
),
610
-
max: 500usize,
611
-
actual: <str>::len(value.as_ref()),
612
-
});
613
-
}
614
-
}
615
-
if let Some(ref value) = self.headers {
616
-
#[allow(unused_comparisons)]
617
-
if value.len() > 50usize {
618
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
619
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
620
-
"headers",
621
-
),
622
-
max: 50usize,
623
-
actual: value.len(),
624
-
});
625
-
}
626
-
}
627
-
if let Some(ref value) = self.index_files {
628
-
#[allow(unused_comparisons)]
629
-
if value.len() > 10usize {
630
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
631
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
632
-
"index_files",
633
-
),
634
-
max: 10usize,
635
-
actual: value.len(),
636
-
});
637
-
}
638
-
}
639
-
if let Some(ref value) = self.spa_mode {
640
-
#[allow(unused_comparisons)]
641
-
if <str>::len(value.as_ref()) > 500usize {
642
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
643
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
644
-
"spa_mode",
645
-
),
646
-
max: 500usize,
647
-
actual: <str>::len(value.as_ref()),
648
-
});
649
-
}
650
-
}
651
-
Ok(())
652
-
}
653
-
}
···
-1408
cli/src/place_wisp/subfs.rs
-1408
cli/src/place_wisp/subfs.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.subfs
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
#[jacquard_derive::lexicon]
9
-
#[derive(
10
-
serde::Serialize,
11
-
serde::Deserialize,
12
-
Debug,
13
-
Clone,
14
-
PartialEq,
15
-
Eq,
16
-
jacquard_derive::IntoStatic
17
-
)]
18
-
#[serde(rename_all = "camelCase")]
19
-
pub struct Directory<'a> {
20
-
#[serde(borrow)]
21
-
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
22
-
#[serde(borrow)]
23
-
pub r#type: jacquard_common::CowStr<'a>,
24
-
}
25
-
26
-
pub mod directory_state {
27
-
28
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
-
#[allow(unused)]
30
-
use ::core::marker::PhantomData;
31
-
mod sealed {
32
-
pub trait Sealed {}
33
-
}
34
-
/// State trait tracking which required fields have been set
35
-
pub trait State: sealed::Sealed {
36
-
type Type;
37
-
type Entries;
38
-
}
39
-
/// Empty state - all required fields are unset
40
-
pub struct Empty(());
41
-
impl sealed::Sealed for Empty {}
42
-
impl State for Empty {
43
-
type Type = Unset;
44
-
type Entries = Unset;
45
-
}
46
-
///State transition - sets the `type` field to Set
47
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
-
impl<S: State> sealed::Sealed for SetType<S> {}
49
-
impl<S: State> State for SetType<S> {
50
-
type Type = Set<members::r#type>;
51
-
type Entries = S::Entries;
52
-
}
53
-
///State transition - sets the `entries` field to Set
54
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
-
impl<S: State> State for SetEntries<S> {
57
-
type Type = S::Type;
58
-
type Entries = Set<members::entries>;
59
-
}
60
-
/// Marker types for field names
61
-
#[allow(non_camel_case_types)]
62
-
pub mod members {
63
-
///Marker type for the `type` field
64
-
pub struct r#type(());
65
-
///Marker type for the `entries` field
66
-
pub struct entries(());
67
-
}
68
-
}
69
-
70
-
/// Builder for constructing an instance of this type
71
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
-
__unsafe_private_named: (
74
-
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
75
-
::core::option::Option<jacquard_common::CowStr<'a>>,
76
-
),
77
-
_phantom: ::core::marker::PhantomData<&'a ()>,
78
-
}
79
-
80
-
impl<'a> Directory<'a> {
81
-
/// Create a new builder for this type
82
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
-
DirectoryBuilder::new()
84
-
}
85
-
}
86
-
87
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
-
/// Create a new builder with all fields unset
89
-
pub fn new() -> Self {
90
-
DirectoryBuilder {
91
-
_phantom_state: ::core::marker::PhantomData,
92
-
__unsafe_private_named: (None, None),
93
-
_phantom: ::core::marker::PhantomData,
94
-
}
95
-
}
96
-
}
97
-
98
-
impl<'a, S> DirectoryBuilder<'a, S>
99
-
where
100
-
S: directory_state::State,
101
-
S::Entries: directory_state::IsUnset,
102
-
{
103
-
/// Set the `entries` field (required)
104
-
pub fn entries(
105
-
mut self,
106
-
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
107
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
-
DirectoryBuilder {
110
-
_phantom_state: ::core::marker::PhantomData,
111
-
__unsafe_private_named: self.__unsafe_private_named,
112
-
_phantom: ::core::marker::PhantomData,
113
-
}
114
-
}
115
-
}
116
-
117
-
impl<'a, S> DirectoryBuilder<'a, S>
118
-
where
119
-
S: directory_state::State,
120
-
S::Type: directory_state::IsUnset,
121
-
{
122
-
/// Set the `type` field (required)
123
-
pub fn r#type(
124
-
mut self,
125
-
value: impl Into<jacquard_common::CowStr<'a>>,
126
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
-
DirectoryBuilder {
129
-
_phantom_state: ::core::marker::PhantomData,
130
-
__unsafe_private_named: self.__unsafe_private_named,
131
-
_phantom: ::core::marker::PhantomData,
132
-
}
133
-
}
134
-
}
135
-
136
-
impl<'a, S> DirectoryBuilder<'a, S>
137
-
where
138
-
S: directory_state::State,
139
-
S::Type: directory_state::IsSet,
140
-
S::Entries: directory_state::IsSet,
141
-
{
142
-
/// Build the final struct
143
-
pub fn build(self) -> Directory<'a> {
144
-
Directory {
145
-
entries: self.__unsafe_private_named.0.unwrap(),
146
-
r#type: self.__unsafe_private_named.1.unwrap(),
147
-
extra_data: Default::default(),
148
-
}
149
-
}
150
-
/// Build the final struct with custom extra_data
151
-
pub fn build_with_data(
152
-
self,
153
-
extra_data: std::collections::BTreeMap<
154
-
jacquard_common::smol_str::SmolStr,
155
-
jacquard_common::types::value::Data<'a>,
156
-
>,
157
-
) -> Directory<'a> {
158
-
Directory {
159
-
entries: self.__unsafe_private_named.0.unwrap(),
160
-
r#type: self.__unsafe_private_named.1.unwrap(),
161
-
extra_data: Some(extra_data),
162
-
}
163
-
}
164
-
}
165
-
166
-
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
-
::jacquard_lexicon::lexicon::LexiconDoc {
168
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
-
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
170
-
revision: None,
171
-
description: None,
172
-
defs: {
173
-
let mut map = ::std::collections::BTreeMap::new();
174
-
map.insert(
175
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
-
description: None,
178
-
required: Some(
179
-
vec![
180
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
-
],
183
-
),
184
-
nullable: None,
185
-
properties: {
186
-
#[allow(unused_mut)]
187
-
let mut map = ::std::collections::BTreeMap::new();
188
-
map.insert(
189
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
-
description: None,
192
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
-
description: None,
194
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
-
}),
196
-
min_length: None,
197
-
max_length: Some(500usize),
198
-
}),
199
-
);
200
-
map.insert(
201
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
-
description: None,
204
-
format: None,
205
-
default: None,
206
-
min_length: None,
207
-
max_length: None,
208
-
min_graphemes: None,
209
-
max_graphemes: None,
210
-
r#enum: None,
211
-
r#const: None,
212
-
known_values: None,
213
-
}),
214
-
);
215
-
map
216
-
},
217
-
}),
218
-
);
219
-
map.insert(
220
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
-
description: None,
223
-
required: Some(
224
-
vec![
225
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
-
::jacquard_common::smol_str::SmolStr::new_static("node")
227
-
],
228
-
),
229
-
nullable: None,
230
-
properties: {
231
-
#[allow(unused_mut)]
232
-
let mut map = ::std::collections::BTreeMap::new();
233
-
map.insert(
234
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
-
description: None,
237
-
format: None,
238
-
default: None,
239
-
min_length: None,
240
-
max_length: Some(255usize),
241
-
min_graphemes: None,
242
-
max_graphemes: None,
243
-
r#enum: None,
244
-
r#const: None,
245
-
known_values: None,
246
-
}),
247
-
);
248
-
map.insert(
249
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
-
description: None,
252
-
refs: vec![
253
-
::jacquard_common::CowStr::new_static("#file"),
254
-
::jacquard_common::CowStr::new_static("#directory"),
255
-
::jacquard_common::CowStr::new_static("#subfs")
256
-
],
257
-
closed: None,
258
-
}),
259
-
);
260
-
map
261
-
},
262
-
}),
263
-
);
264
-
map.insert(
265
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
-
description: None,
268
-
required: Some(
269
-
vec![
270
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
-
],
273
-
),
274
-
nullable: None,
275
-
properties: {
276
-
#[allow(unused_mut)]
277
-
let mut map = ::std::collections::BTreeMap::new();
278
-
map.insert(
279
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
-
description: None,
282
-
default: None,
283
-
r#const: None,
284
-
}),
285
-
);
286
-
map.insert(
287
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
-
description: None,
290
-
accept: None,
291
-
max_size: None,
292
-
}),
293
-
);
294
-
map.insert(
295
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
-
description: Some(
298
-
::jacquard_common::CowStr::new_static(
299
-
"Content encoding (e.g., gzip for compressed files)",
300
-
),
301
-
),
302
-
format: None,
303
-
default: None,
304
-
min_length: None,
305
-
max_length: None,
306
-
min_graphemes: None,
307
-
max_graphemes: None,
308
-
r#enum: None,
309
-
r#const: None,
310
-
known_values: None,
311
-
}),
312
-
);
313
-
map.insert(
314
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
-
description: Some(
317
-
::jacquard_common::CowStr::new_static(
318
-
"Original MIME type before compression",
319
-
),
320
-
),
321
-
format: None,
322
-
default: None,
323
-
min_length: None,
324
-
max_length: None,
325
-
min_graphemes: None,
326
-
max_graphemes: None,
327
-
r#enum: None,
328
-
r#const: None,
329
-
known_values: None,
330
-
}),
331
-
);
332
-
map.insert(
333
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
-
description: None,
336
-
format: None,
337
-
default: None,
338
-
min_length: None,
339
-
max_length: None,
340
-
min_graphemes: None,
341
-
max_graphemes: None,
342
-
r#enum: None,
343
-
r#const: None,
344
-
known_values: None,
345
-
}),
346
-
);
347
-
map
348
-
},
349
-
}),
350
-
);
351
-
map.insert(
352
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
-
description: Some(
355
-
::jacquard_common::CowStr::new_static(
356
-
"Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
357
-
),
358
-
),
359
-
key: None,
360
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
-
description: None,
362
-
required: Some(
363
-
vec![
364
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
365
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
366
-
],
367
-
),
368
-
nullable: None,
369
-
properties: {
370
-
#[allow(unused_mut)]
371
-
let mut map = ::std::collections::BTreeMap::new();
372
-
map.insert(
373
-
::jacquard_common::smol_str::SmolStr::new_static(
374
-
"createdAt",
375
-
),
376
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
377
-
description: None,
378
-
format: Some(
379
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
380
-
),
381
-
default: None,
382
-
min_length: None,
383
-
max_length: None,
384
-
min_graphemes: None,
385
-
max_graphemes: None,
386
-
r#enum: None,
387
-
r#const: None,
388
-
known_values: None,
389
-
}),
390
-
);
391
-
map.insert(
392
-
::jacquard_common::smol_str::SmolStr::new_static(
393
-
"fileCount",
394
-
),
395
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
396
-
description: None,
397
-
default: None,
398
-
minimum: Some(0i64),
399
-
maximum: Some(1000i64),
400
-
r#enum: None,
401
-
r#const: None,
402
-
}),
403
-
);
404
-
map.insert(
405
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
406
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
407
-
description: None,
408
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
409
-
}),
410
-
);
411
-
map
412
-
},
413
-
}),
414
-
}),
415
-
);
416
-
map.insert(
417
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
418
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
419
-
description: None,
420
-
required: Some(
421
-
vec![
422
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
423
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
424
-
],
425
-
),
426
-
nullable: None,
427
-
properties: {
428
-
#[allow(unused_mut)]
429
-
let mut map = ::std::collections::BTreeMap::new();
430
-
map.insert(
431
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
432
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
433
-
description: Some(
434
-
::jacquard_common::CowStr::new_static(
435
-
"AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.",
436
-
),
437
-
),
438
-
format: Some(
439
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
440
-
),
441
-
default: None,
442
-
min_length: None,
443
-
max_length: None,
444
-
min_graphemes: None,
445
-
max_graphemes: None,
446
-
r#enum: None,
447
-
r#const: None,
448
-
known_values: None,
449
-
}),
450
-
);
451
-
map.insert(
452
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
453
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
454
-
description: None,
455
-
format: None,
456
-
default: None,
457
-
min_length: None,
458
-
max_length: None,
459
-
min_graphemes: None,
460
-
max_graphemes: None,
461
-
r#enum: None,
462
-
r#const: None,
463
-
known_values: None,
464
-
}),
465
-
);
466
-
map
467
-
},
468
-
}),
469
-
);
470
-
map
471
-
},
472
-
}
473
-
}
474
-
475
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
476
-
fn nsid() -> &'static str {
477
-
"place.wisp.subfs"
478
-
}
479
-
fn def_name() -> &'static str {
480
-
"directory"
481
-
}
482
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
483
-
lexicon_doc_place_wisp_subfs()
484
-
}
485
-
fn validate(
486
-
&self,
487
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
488
-
{
489
-
let value = &self.entries;
490
-
#[allow(unused_comparisons)]
491
-
if value.len() > 500usize {
492
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
493
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
494
-
"entries",
495
-
),
496
-
max: 500usize,
497
-
actual: value.len(),
498
-
});
499
-
}
500
-
}
501
-
Ok(())
502
-
}
503
-
}
504
-
505
-
#[jacquard_derive::lexicon]
506
-
#[derive(
507
-
serde::Serialize,
508
-
serde::Deserialize,
509
-
Debug,
510
-
Clone,
511
-
PartialEq,
512
-
Eq,
513
-
jacquard_derive::IntoStatic
514
-
)]
515
-
#[serde(rename_all = "camelCase")]
516
-
pub struct Entry<'a> {
517
-
#[serde(borrow)]
518
-
pub name: jacquard_common::CowStr<'a>,
519
-
#[serde(borrow)]
520
-
pub node: EntryNode<'a>,
521
-
}
522
-
523
-
pub mod entry_state {
524
-
525
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
526
-
#[allow(unused)]
527
-
use ::core::marker::PhantomData;
528
-
mod sealed {
529
-
pub trait Sealed {}
530
-
}
531
-
/// State trait tracking which required fields have been set
532
-
pub trait State: sealed::Sealed {
533
-
type Name;
534
-
type Node;
535
-
}
536
-
/// Empty state - all required fields are unset
537
-
pub struct Empty(());
538
-
impl sealed::Sealed for Empty {}
539
-
impl State for Empty {
540
-
type Name = Unset;
541
-
type Node = Unset;
542
-
}
543
-
///State transition - sets the `name` field to Set
544
-
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
545
-
impl<S: State> sealed::Sealed for SetName<S> {}
546
-
impl<S: State> State for SetName<S> {
547
-
type Name = Set<members::name>;
548
-
type Node = S::Node;
549
-
}
550
-
///State transition - sets the `node` field to Set
551
-
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
552
-
impl<S: State> sealed::Sealed for SetNode<S> {}
553
-
impl<S: State> State for SetNode<S> {
554
-
type Name = S::Name;
555
-
type Node = Set<members::node>;
556
-
}
557
-
/// Marker types for field names
558
-
#[allow(non_camel_case_types)]
559
-
pub mod members {
560
-
///Marker type for the `name` field
561
-
pub struct name(());
562
-
///Marker type for the `node` field
563
-
pub struct node(());
564
-
}
565
-
}
566
-
567
-
/// Builder for constructing an instance of this type
568
-
pub struct EntryBuilder<'a, S: entry_state::State> {
569
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
570
-
__unsafe_private_named: (
571
-
::core::option::Option<jacquard_common::CowStr<'a>>,
572
-
::core::option::Option<EntryNode<'a>>,
573
-
),
574
-
_phantom: ::core::marker::PhantomData<&'a ()>,
575
-
}
576
-
577
-
impl<'a> Entry<'a> {
578
-
/// Create a new builder for this type
579
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
580
-
EntryBuilder::new()
581
-
}
582
-
}
583
-
584
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
585
-
/// Create a new builder with all fields unset
586
-
pub fn new() -> Self {
587
-
EntryBuilder {
588
-
_phantom_state: ::core::marker::PhantomData,
589
-
__unsafe_private_named: (None, None),
590
-
_phantom: ::core::marker::PhantomData,
591
-
}
592
-
}
593
-
}
594
-
595
-
impl<'a, S> EntryBuilder<'a, S>
596
-
where
597
-
S: entry_state::State,
598
-
S::Name: entry_state::IsUnset,
599
-
{
600
-
/// Set the `name` field (required)
601
-
pub fn name(
602
-
mut self,
603
-
value: impl Into<jacquard_common::CowStr<'a>>,
604
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
605
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
606
-
EntryBuilder {
607
-
_phantom_state: ::core::marker::PhantomData,
608
-
__unsafe_private_named: self.__unsafe_private_named,
609
-
_phantom: ::core::marker::PhantomData,
610
-
}
611
-
}
612
-
}
613
-
614
-
impl<'a, S> EntryBuilder<'a, S>
615
-
where
616
-
S: entry_state::State,
617
-
S::Node: entry_state::IsUnset,
618
-
{
619
-
/// Set the `node` field (required)
620
-
pub fn node(
621
-
mut self,
622
-
value: impl Into<EntryNode<'a>>,
623
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
624
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
625
-
EntryBuilder {
626
-
_phantom_state: ::core::marker::PhantomData,
627
-
__unsafe_private_named: self.__unsafe_private_named,
628
-
_phantom: ::core::marker::PhantomData,
629
-
}
630
-
}
631
-
}
632
-
633
-
impl<'a, S> EntryBuilder<'a, S>
634
-
where
635
-
S: entry_state::State,
636
-
S::Name: entry_state::IsSet,
637
-
S::Node: entry_state::IsSet,
638
-
{
639
-
/// Build the final struct
640
-
pub fn build(self) -> Entry<'a> {
641
-
Entry {
642
-
name: self.__unsafe_private_named.0.unwrap(),
643
-
node: self.__unsafe_private_named.1.unwrap(),
644
-
extra_data: Default::default(),
645
-
}
646
-
}
647
-
/// Build the final struct with custom extra_data
648
-
pub fn build_with_data(
649
-
self,
650
-
extra_data: std::collections::BTreeMap<
651
-
jacquard_common::smol_str::SmolStr,
652
-
jacquard_common::types::value::Data<'a>,
653
-
>,
654
-
) -> Entry<'a> {
655
-
Entry {
656
-
name: self.__unsafe_private_named.0.unwrap(),
657
-
node: self.__unsafe_private_named.1.unwrap(),
658
-
extra_data: Some(extra_data),
659
-
}
660
-
}
661
-
}
662
-
663
-
#[jacquard_derive::open_union]
664
-
#[derive(
665
-
serde::Serialize,
666
-
serde::Deserialize,
667
-
Debug,
668
-
Clone,
669
-
PartialEq,
670
-
Eq,
671
-
jacquard_derive::IntoStatic
672
-
)]
673
-
#[serde(tag = "$type")]
674
-
#[serde(bound(deserialize = "'de: 'a"))]
675
-
pub enum EntryNode<'a> {
676
-
#[serde(rename = "place.wisp.subfs#file")]
677
-
File(Box<crate::place_wisp::subfs::File<'a>>),
678
-
#[serde(rename = "place.wisp.subfs#directory")]
679
-
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
680
-
#[serde(rename = "place.wisp.subfs#subfs")]
681
-
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
682
-
}
683
-
684
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
685
-
fn nsid() -> &'static str {
686
-
"place.wisp.subfs"
687
-
}
688
-
fn def_name() -> &'static str {
689
-
"entry"
690
-
}
691
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
692
-
lexicon_doc_place_wisp_subfs()
693
-
}
694
-
fn validate(
695
-
&self,
696
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
697
-
{
698
-
let value = &self.name;
699
-
#[allow(unused_comparisons)]
700
-
if <str>::len(value.as_ref()) > 255usize {
701
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
702
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
703
-
"name",
704
-
),
705
-
max: 255usize,
706
-
actual: <str>::len(value.as_ref()),
707
-
});
708
-
}
709
-
}
710
-
Ok(())
711
-
}
712
-
}
713
-
714
-
#[jacquard_derive::lexicon]
715
-
#[derive(
716
-
serde::Serialize,
717
-
serde::Deserialize,
718
-
Debug,
719
-
Clone,
720
-
PartialEq,
721
-
Eq,
722
-
jacquard_derive::IntoStatic
723
-
)]
724
-
#[serde(rename_all = "camelCase")]
725
-
pub 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
-
}
743
-
744
-
pub mod file_state {
745
-
746
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
747
-
#[allow(unused)]
748
-
use ::core::marker::PhantomData;
749
-
mod sealed {
750
-
pub trait Sealed {}
751
-
}
752
-
/// State trait tracking which required fields have been set
753
-
pub trait State: sealed::Sealed {
754
-
type Type;
755
-
type Blob;
756
-
}
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> {
775
-
type Type = S::Type;
776
-
type Blob = Set<members::blob>;
777
-
}
778
-
/// Marker types for field names
779
-
#[allow(non_camel_case_types)]
780
-
pub mod members {
781
-
///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
-
788
-
/// Builder for constructing an instance of this type
789
-
pub struct FileBuilder<'a, S: file_state::State> {
790
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
791
-
__unsafe_private_named: (
792
-
::core::option::Option<bool>,
793
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
794
-
::core::option::Option<jacquard_common::CowStr<'a>>,
795
-
::core::option::Option<jacquard_common::CowStr<'a>>,
796
-
::core::option::Option<jacquard_common::CowStr<'a>>,
797
-
),
798
-
_phantom: ::core::marker::PhantomData<&'a ()>,
799
-
}
800
-
801
-
impl<'a> File<'a> {
802
-
/// Create a new builder for this type
803
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
804
-
FileBuilder::new()
805
-
}
806
-
}
807
-
808
-
impl<'a> FileBuilder<'a, file_state::Empty> {
809
-
/// Create a new builder with all fields unset
810
-
pub fn new() -> Self {
811
-
FileBuilder {
812
-
_phantom_state: ::core::marker::PhantomData,
813
-
__unsafe_private_named: (None, None, None, None, None),
814
-
_phantom: ::core::marker::PhantomData,
815
-
}
816
-
}
817
-
}
818
-
819
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
820
-
/// Set the `base64` field (optional)
821
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
822
-
self.__unsafe_private_named.0 = value.into();
823
-
self
824
-
}
825
-
/// Set the `base64` field to an Option value (optional)
826
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
827
-
self.__unsafe_private_named.0 = value;
828
-
self
829
-
}
830
-
}
831
-
832
-
impl<'a, S> FileBuilder<'a, S>
833
-
where
834
-
S: file_state::State,
835
-
S::Blob: file_state::IsUnset,
836
-
{
837
-
/// Set the `blob` field (required)
838
-
pub fn blob(
839
-
mut self,
840
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
841
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
842
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
843
-
FileBuilder {
844
-
_phantom_state: ::core::marker::PhantomData,
845
-
__unsafe_private_named: self.__unsafe_private_named,
846
-
_phantom: ::core::marker::PhantomData,
847
-
}
848
-
}
849
-
}
850
-
851
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
852
-
/// Set the `encoding` field (optional)
853
-
pub fn encoding(
854
-
mut self,
855
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
856
-
) -> Self {
857
-
self.__unsafe_private_named.2 = value.into();
858
-
self
859
-
}
860
-
/// Set the `encoding` field to an Option value (optional)
861
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
862
-
self.__unsafe_private_named.2 = value;
863
-
self
864
-
}
865
-
}
866
-
867
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
868
-
/// Set the `mimeType` field (optional)
869
-
pub fn mime_type(
870
-
mut self,
871
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
872
-
) -> Self {
873
-
self.__unsafe_private_named.3 = value.into();
874
-
self
875
-
}
876
-
/// Set the `mimeType` field to an Option value (optional)
877
-
pub fn maybe_mime_type(
878
-
mut self,
879
-
value: Option<jacquard_common::CowStr<'a>>,
880
-
) -> Self {
881
-
self.__unsafe_private_named.3 = value;
882
-
self
883
-
}
884
-
}
885
-
886
-
impl<'a, S> FileBuilder<'a, S>
887
-
where
888
-
S: file_state::State,
889
-
S::Type: file_state::IsUnset,
890
-
{
891
-
/// Set the `type` field (required)
892
-
pub fn r#type(
893
-
mut self,
894
-
value: impl Into<jacquard_common::CowStr<'a>>,
895
-
) -> FileBuilder<'a, file_state::SetType<S>> {
896
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
897
-
FileBuilder {
898
-
_phantom_state: ::core::marker::PhantomData,
899
-
__unsafe_private_named: self.__unsafe_private_named,
900
-
_phantom: ::core::marker::PhantomData,
901
-
}
902
-
}
903
-
}
904
-
905
-
impl<'a, S> FileBuilder<'a, S>
906
-
where
907
-
S: file_state::State,
908
-
S::Type: file_state::IsSet,
909
-
S::Blob: file_state::IsSet,
910
-
{
911
-
/// Build the final struct
912
-
pub fn build(self) -> File<'a> {
913
-
File {
914
-
base64: self.__unsafe_private_named.0,
915
-
blob: self.__unsafe_private_named.1.unwrap(),
916
-
encoding: self.__unsafe_private_named.2,
917
-
mime_type: self.__unsafe_private_named.3,
918
-
r#type: self.__unsafe_private_named.4.unwrap(),
919
-
extra_data: Default::default(),
920
-
}
921
-
}
922
-
/// Build the final struct with custom extra_data
923
-
pub fn build_with_data(
924
-
self,
925
-
extra_data: std::collections::BTreeMap<
926
-
jacquard_common::smol_str::SmolStr,
927
-
jacquard_common::types::value::Data<'a>,
928
-
>,
929
-
) -> File<'a> {
930
-
File {
931
-
base64: self.__unsafe_private_named.0,
932
-
blob: self.__unsafe_private_named.1.unwrap(),
933
-
encoding: self.__unsafe_private_named.2,
934
-
mime_type: self.__unsafe_private_named.3,
935
-
r#type: self.__unsafe_private_named.4.unwrap(),
936
-
extra_data: Some(extra_data),
937
-
}
938
-
}
939
-
}
940
-
941
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
942
-
fn nsid() -> &'static str {
943
-
"place.wisp.subfs"
944
-
}
945
-
fn def_name() -> &'static str {
946
-
"file"
947
-
}
948
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
949
-
lexicon_doc_place_wisp_subfs()
950
-
}
951
-
fn validate(
952
-
&self,
953
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
954
-
Ok(())
955
-
}
956
-
}
957
-
958
-
/// Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.
959
-
#[jacquard_derive::lexicon]
960
-
#[derive(
961
-
serde::Serialize,
962
-
serde::Deserialize,
963
-
Debug,
964
-
Clone,
965
-
PartialEq,
966
-
Eq,
967
-
jacquard_derive::IntoStatic
968
-
)]
969
-
#[serde(rename_all = "camelCase")]
970
-
pub 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
-
}
977
-
978
-
pub mod subfs_record_state {
979
-
980
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
981
-
#[allow(unused)]
982
-
use ::core::marker::PhantomData;
983
-
mod sealed {
984
-
pub trait Sealed {}
985
-
}
986
-
/// State trait tracking which required fields have been set
987
-
pub trait State: sealed::Sealed {
988
-
type Root;
989
-
type CreatedAt;
990
-
}
991
-
/// Empty state - all required fields are unset
992
-
pub struct Empty(());
993
-
impl sealed::Sealed for Empty {}
994
-
impl State for Empty {
995
-
type Root = Unset;
996
-
type CreatedAt = Unset;
997
-
}
998
-
///State transition - sets the `root` field to Set
999
-
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1000
-
impl<S: State> sealed::Sealed for SetRoot<S> {}
1001
-
impl<S: State> State for SetRoot<S> {
1002
-
type Root = Set<members::root>;
1003
-
type CreatedAt = S::CreatedAt;
1004
-
}
1005
-
///State transition - sets the `created_at` field to Set
1006
-
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1007
-
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1008
-
impl<S: State> State for SetCreatedAt<S> {
1009
-
type Root = S::Root;
1010
-
type CreatedAt = Set<members::created_at>;
1011
-
}
1012
-
/// Marker types for field names
1013
-
#[allow(non_camel_case_types)]
1014
-
pub mod members {
1015
-
///Marker type for the `root` field
1016
-
pub struct root(());
1017
-
///Marker type for the `created_at` field
1018
-
pub struct created_at(());
1019
-
}
1020
-
}
1021
-
1022
-
/// Builder for constructing an instance of this type
1023
-
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
1024
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1025
-
__unsafe_private_named: (
1026
-
::core::option::Option<jacquard_common::types::string::Datetime>,
1027
-
::core::option::Option<i64>,
1028
-
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
1029
-
),
1030
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1031
-
}
1032
-
1033
-
impl<'a> SubfsRecord<'a> {
1034
-
/// Create a new builder for this type
1035
-
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1036
-
SubfsRecordBuilder::new()
1037
-
}
1038
-
}
1039
-
1040
-
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1041
-
/// Create a new builder with all fields unset
1042
-
pub fn new() -> Self {
1043
-
SubfsRecordBuilder {
1044
-
_phantom_state: ::core::marker::PhantomData,
1045
-
__unsafe_private_named: (None, None, None),
1046
-
_phantom: ::core::marker::PhantomData,
1047
-
}
1048
-
}
1049
-
}
1050
-
1051
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1052
-
where
1053
-
S: subfs_record_state::State,
1054
-
S::CreatedAt: subfs_record_state::IsUnset,
1055
-
{
1056
-
/// Set the `createdAt` field (required)
1057
-
pub fn created_at(
1058
-
mut self,
1059
-
value: impl Into<jacquard_common::types::string::Datetime>,
1060
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
1061
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1062
-
SubfsRecordBuilder {
1063
-
_phantom_state: ::core::marker::PhantomData,
1064
-
__unsafe_private_named: self.__unsafe_private_named,
1065
-
_phantom: ::core::marker::PhantomData,
1066
-
}
1067
-
}
1068
-
}
1069
-
1070
-
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
1071
-
/// Set the `fileCount` field (optional)
1072
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1073
-
self.__unsafe_private_named.1 = value.into();
1074
-
self
1075
-
}
1076
-
/// Set the `fileCount` field to an Option value (optional)
1077
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1078
-
self.__unsafe_private_named.1 = value;
1079
-
self
1080
-
}
1081
-
}
1082
-
1083
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1084
-
where
1085
-
S: subfs_record_state::State,
1086
-
S::Root: subfs_record_state::IsUnset,
1087
-
{
1088
-
/// Set the `root` field (required)
1089
-
pub fn root(
1090
-
mut self,
1091
-
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
1092
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
1093
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1094
-
SubfsRecordBuilder {
1095
-
_phantom_state: ::core::marker::PhantomData,
1096
-
__unsafe_private_named: self.__unsafe_private_named,
1097
-
_phantom: ::core::marker::PhantomData,
1098
-
}
1099
-
}
1100
-
}
1101
-
1102
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1103
-
where
1104
-
S: subfs_record_state::State,
1105
-
S::Root: subfs_record_state::IsSet,
1106
-
S::CreatedAt: subfs_record_state::IsSet,
1107
-
{
1108
-
/// Build the final struct
1109
-
pub fn build(self) -> SubfsRecord<'a> {
1110
-
SubfsRecord {
1111
-
created_at: self.__unsafe_private_named.0.unwrap(),
1112
-
file_count: self.__unsafe_private_named.1,
1113
-
root: self.__unsafe_private_named.2.unwrap(),
1114
-
extra_data: Default::default(),
1115
-
}
1116
-
}
1117
-
/// Build the final struct with custom extra_data
1118
-
pub fn build_with_data(
1119
-
self,
1120
-
extra_data: std::collections::BTreeMap<
1121
-
jacquard_common::smol_str::SmolStr,
1122
-
jacquard_common::types::value::Data<'a>,
1123
-
>,
1124
-
) -> SubfsRecord<'a> {
1125
-
SubfsRecord {
1126
-
created_at: self.__unsafe_private_named.0.unwrap(),
1127
-
file_count: self.__unsafe_private_named.1,
1128
-
root: self.__unsafe_private_named.2.unwrap(),
1129
-
extra_data: Some(extra_data),
1130
-
}
1131
-
}
1132
-
}
1133
-
1134
-
impl<'a> SubfsRecord<'a> {
1135
-
pub fn uri(
1136
-
uri: impl Into<jacquard_common::CowStr<'a>>,
1137
-
) -> Result<
1138
-
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
1139
-
jacquard_common::types::uri::UriError,
1140
-
> {
1141
-
jacquard_common::types::uri::RecordUri::try_from_uri(
1142
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1143
-
)
1144
-
}
1145
-
}
1146
-
1147
-
/// Typed wrapper for GetRecord response with this collection's record type.
1148
-
#[derive(
1149
-
serde::Serialize,
1150
-
serde::Deserialize,
1151
-
Debug,
1152
-
Clone,
1153
-
PartialEq,
1154
-
Eq,
1155
-
jacquard_derive::IntoStatic
1156
-
)]
1157
-
#[serde(rename_all = "camelCase")]
1158
-
pub struct SubfsRecordGetRecordOutput<'a> {
1159
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1160
-
#[serde(borrow)]
1161
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1162
-
#[serde(borrow)]
1163
-
pub uri: jacquard_common::types::string::AtUri<'a>,
1164
-
#[serde(borrow)]
1165
-
pub value: SubfsRecord<'a>,
1166
-
}
1167
-
1168
-
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
1169
-
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
1170
-
use jacquard_common::IntoStatic;
1171
-
output.value.into_static()
1172
-
}
1173
-
}
1174
-
1175
-
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
1176
-
const NSID: &'static str = "place.wisp.subfs";
1177
-
type Record = SubfsRecordRecord;
1178
-
}
1179
-
1180
-
/// Marker type for deserializing records from this collection.
1181
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1182
-
pub struct SubfsRecordRecord;
1183
-
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
1184
-
const NSID: &'static str = "place.wisp.subfs";
1185
-
const ENCODING: &'static str = "application/json";
1186
-
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
1187
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1188
-
}
1189
-
1190
-
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
1191
-
const NSID: &'static str = "place.wisp.subfs";
1192
-
type Record = SubfsRecordRecord;
1193
-
}
1194
-
1195
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
1196
-
fn nsid() -> &'static str {
1197
-
"place.wisp.subfs"
1198
-
}
1199
-
fn def_name() -> &'static str {
1200
-
"main"
1201
-
}
1202
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1203
-
lexicon_doc_place_wisp_subfs()
1204
-
}
1205
-
fn validate(
1206
-
&self,
1207
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1208
-
if let Some(ref value) = self.file_count {
1209
-
if *value > 1000i64 {
1210
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1211
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1212
-
"file_count",
1213
-
),
1214
-
max: 1000i64,
1215
-
actual: *value,
1216
-
});
1217
-
}
1218
-
}
1219
-
if let Some(ref value) = self.file_count {
1220
-
if *value < 0i64 {
1221
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1222
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1223
-
"file_count",
1224
-
),
1225
-
min: 0i64,
1226
-
actual: *value,
1227
-
});
1228
-
}
1229
-
}
1230
-
Ok(())
1231
-
}
1232
-
}
1233
-
1234
-
#[jacquard_derive::lexicon]
1235
-
#[derive(
1236
-
serde::Serialize,
1237
-
serde::Deserialize,
1238
-
Debug,
1239
-
Clone,
1240
-
PartialEq,
1241
-
Eq,
1242
-
jacquard_derive::IntoStatic
1243
-
)]
1244
-
#[serde(rename_all = "camelCase")]
1245
-
pub struct Subfs<'a> {
1246
-
/// AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.
1247
-
#[serde(borrow)]
1248
-
pub subject: jacquard_common::types::string::AtUri<'a>,
1249
-
#[serde(borrow)]
1250
-
pub r#type: jacquard_common::CowStr<'a>,
1251
-
}
1252
-
1253
-
pub mod subfs_state {
1254
-
1255
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1256
-
#[allow(unused)]
1257
-
use ::core::marker::PhantomData;
1258
-
mod sealed {
1259
-
pub trait Sealed {}
1260
-
}
1261
-
/// State trait tracking which required fields have been set
1262
-
pub trait State: sealed::Sealed {
1263
-
type Type;
1264
-
type Subject;
1265
-
}
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>;
1286
-
}
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(());
1294
-
}
1295
-
}
1296
-
1297
-
/// Builder for constructing an instance of this type
1298
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1299
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1300
-
__unsafe_private_named: (
1301
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1302
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1303
-
),
1304
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1305
-
}
1306
-
1307
-
impl<'a> Subfs<'a> {
1308
-
/// Create a new builder for this type
1309
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1310
-
SubfsBuilder::new()
1311
-
}
1312
-
}
1313
-
1314
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1315
-
/// Create a new builder with all fields unset
1316
-
pub fn new() -> Self {
1317
-
SubfsBuilder {
1318
-
_phantom_state: ::core::marker::PhantomData,
1319
-
__unsafe_private_named: (None, None),
1320
-
_phantom: ::core::marker::PhantomData,
1321
-
}
1322
-
}
1323
-
}
1324
-
1325
-
impl<'a, S> SubfsBuilder<'a, S>
1326
-
where
1327
-
S: subfs_state::State,
1328
-
S::Subject: subfs_state::IsUnset,
1329
-
{
1330
-
/// Set the `subject` field (required)
1331
-
pub fn subject(
1332
-
mut self,
1333
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1334
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1335
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1336
-
SubfsBuilder {
1337
-
_phantom_state: ::core::marker::PhantomData,
1338
-
__unsafe_private_named: self.__unsafe_private_named,
1339
-
_phantom: ::core::marker::PhantomData,
1340
-
}
1341
-
}
1342
-
}
1343
-
1344
-
impl<'a, S> SubfsBuilder<'a, S>
1345
-
where
1346
-
S: subfs_state::State,
1347
-
S::Type: subfs_state::IsUnset,
1348
-
{
1349
-
/// Set the `type` field (required)
1350
-
pub fn r#type(
1351
-
mut self,
1352
-
value: impl Into<jacquard_common::CowStr<'a>>,
1353
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1354
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1355
-
SubfsBuilder {
1356
-
_phantom_state: ::core::marker::PhantomData,
1357
-
__unsafe_private_named: self.__unsafe_private_named,
1358
-
_phantom: ::core::marker::PhantomData,
1359
-
}
1360
-
}
1361
-
}
1362
-
1363
-
impl<'a, S> SubfsBuilder<'a, S>
1364
-
where
1365
-
S: subfs_state::State,
1366
-
S::Type: subfs_state::IsSet,
1367
-
S::Subject: subfs_state::IsSet,
1368
-
{
1369
-
/// Build the final struct
1370
-
pub fn build(self) -> Subfs<'a> {
1371
-
Subfs {
1372
-
subject: self.__unsafe_private_named.0.unwrap(),
1373
-
r#type: self.__unsafe_private_named.1.unwrap(),
1374
-
extra_data: Default::default(),
1375
-
}
1376
-
}
1377
-
/// Build the final struct with custom extra_data
1378
-
pub fn build_with_data(
1379
-
self,
1380
-
extra_data: std::collections::BTreeMap<
1381
-
jacquard_common::smol_str::SmolStr,
1382
-
jacquard_common::types::value::Data<'a>,
1383
-
>,
1384
-
) -> Subfs<'a> {
1385
-
Subfs {
1386
-
subject: self.__unsafe_private_named.0.unwrap(),
1387
-
r#type: self.__unsafe_private_named.1.unwrap(),
1388
-
extra_data: Some(extra_data),
1389
-
}
1390
-
}
1391
-
}
1392
-
1393
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1394
-
fn nsid() -> &'static str {
1395
-
"place.wisp.subfs"
1396
-
}
1397
-
fn def_name() -> &'static str {
1398
-
"subfs"
1399
-
}
1400
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1401
-
lexicon_doc_place_wisp_subfs()
1402
-
}
1403
-
fn validate(
1404
-
&self,
1405
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1406
-
Ok(())
1407
-
}
1408
-
}
···
-8
cli/src/place_wisp.rs
-8
cli/src/place_wisp.rs
+12
-12
cli/src/pull.rs
+12
-12
cli/src/pull.rs
···
1
use crate::blob_map;
2
use crate::download;
3
use crate::metadata::SiteMetadata;
4
-
use crate::place_wisp::fs::*;
5
use crate::subfs_utils;
6
use jacquard::CowStr;
7
use 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;
414
415
-
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());
417
418
if to_fetch.is_empty() {
···
516
517
/// Extract subfs URIs from a subfs::Directory (helper for pull)
518
fn 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
};
530
531
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
547
fn 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
}
629
630
/// 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;
633
634
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
};
···
1
use crate::blob_map;
2
use crate::download;
3
use crate::metadata::SiteMetadata;
4
+
use wisp_lexicons::place_wisp::fs::*;
5
use crate::subfs_utils;
6
use jacquard::CowStr;
7
use 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;
414
415
+
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());
417
418
if to_fetch.is_empty() {
···
516
517
/// Extract subfs URIs from a subfs::Directory (helper for pull)
518
fn 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
};
530
531
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
547
fn 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
}
629
630
/// 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;
633
634
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
};
+1
-1
cli/src/serve.rs
+1
-1
cli/src/serve.rs
+14
-14
cli/src/subfs_utils.rs
+14
-14
cli/src/subfs_utils.rs
···
6
use miette::IntoDiagnostic;
7
use std::collections::HashMap;
8
9
-
use crate::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
10
-
use crate::place_wisp::subfs::SubfsRecord;
11
12
/// Extract all subfs URIs from a directory tree with their mount paths
13
pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> {
···
145
146
/// Extract subfs URIs from a subfs::Directory
147
fn extract_subfs_uris_from_subfs_dir(
148
-
directory: &crate::place_wisp::subfs::Directory,
149
current_path: String,
150
) -> Vec<(String, String)> {
151
let mut uris = Vec::new();
152
153
for entry in &directory.entries {
154
match &entry.node {
155
-
crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
156
// Check if this is a chunk entry (chunk0, chunk1, etc.)
157
// Chunks should be flat-merged, so use the parent's path
158
let mount_path = if entry.name.starts_with("chunk") &&
···
171
172
uris.push((subfs_node.subject.to_string(), mount_path));
173
}
174
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
175
let full_path = if current_path.is_empty() {
176
entry.name.to_string()
177
} else {
···
204
for (mount_path, subfs_record) in all_subfs {
205
// Check if this record only contains chunk subfs references (no files)
206
let only_has_chunks = subfs_record.root.entries.iter().all(|e| {
207
-
matches!(&e.node, crate::place_wisp::subfs::EntryNode::Subfs(_)) &&
208
e.name.starts_with("chunk") &&
209
e.name.chars().skip(5).all(|c| c.is_ascii_digit())
210
});
···
232
/// Extract blobs from a subfs directory (works with subfs::Directory)
233
/// Returns a map of file paths to their blob refs and CIDs
234
fn extract_subfs_blobs(
235
-
directory: &crate::place_wisp::subfs::Directory,
236
current_path: String,
237
) -> HashMap<String, (BlobRef<'static>, String)> {
238
let mut blob_map = HashMap::new();
···
245
};
246
247
match &entry.node {
248
-
crate::place_wisp::subfs::EntryNode::File(file_node) => {
249
let blob_ref = &file_node.blob;
250
let cid_string = blob_ref.blob().r#ref.to_string();
251
blob_map.insert(
···
253
(blob_ref.clone().into_static(), cid_string)
254
);
255
}
256
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
257
let sub_map = extract_subfs_blobs(subdir, full_path);
258
blob_map.extend(sub_map);
259
}
260
-
crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
261
// Nested subfs - these should be resolved recursively in the main flow
262
// For now, we skip them (they'll be fetched separately)
263
eprintln!(" โ ๏ธ Found nested subfs at {}, skipping (should be fetched separately)", full_path);
264
}
265
-
crate::place_wisp::subfs::EntryNode::Unknown(_) => {
266
// Skip unknown nodes
267
}
268
}
···
352
flat: bool,
353
) -> miette::Result<FsDirectory<'static>> {
354
use jacquard_common::CowStr;
355
-
use crate::place_wisp::fs::{Entry, Subfs};
356
357
let path_parts: Vec<&str> = target_path.split('/').collect();
358
···
430
431
// Construct AT-URI and convert to RecordUri
432
let at_uri = AtUri::new(uri).into_diagnostic()?;
433
-
let record_uri: RecordUri<'_, crate::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
434
435
let rkey = record_uri.rkey()
436
.ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))?
···
489
}
490
491
/// Estimate the JSON size of a single entry
492
-
fn estimate_entry_size(entry: &crate::place_wisp::fs::Entry) -> usize {
493
match serde_json::to_string(entry) {
494
Ok(json) => json.len(),
495
Err(_) => 500, // Conservative estimate if serialization fails
···
6
use miette::IntoDiagnostic;
7
use std::collections::HashMap;
8
9
+
use wisp_lexicons::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
10
+
use wisp_lexicons::place_wisp::subfs::SubfsRecord;
11
12
/// Extract all subfs URIs from a directory tree with their mount paths
13
pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> {
···
145
146
/// Extract subfs URIs from a subfs::Directory
147
fn extract_subfs_uris_from_subfs_dir(
148
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
149
current_path: String,
150
) -> Vec<(String, String)> {
151
let mut uris = Vec::new();
152
153
for entry in &directory.entries {
154
match &entry.node {
155
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
156
// Check if this is a chunk entry (chunk0, chunk1, etc.)
157
// Chunks should be flat-merged, so use the parent's path
158
let mount_path = if entry.name.starts_with("chunk") &&
···
171
172
uris.push((subfs_node.subject.to_string(), mount_path));
173
}
174
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
175
let full_path = if current_path.is_empty() {
176
entry.name.to_string()
177
} else {
···
204
for (mount_path, subfs_record) in all_subfs {
205
// Check if this record only contains chunk subfs references (no files)
206
let only_has_chunks = subfs_record.root.entries.iter().all(|e| {
207
+
matches!(&e.node, wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_)) &&
208
e.name.starts_with("chunk") &&
209
e.name.chars().skip(5).all(|c| c.is_ascii_digit())
210
});
···
232
/// Extract blobs from a subfs directory (works with subfs::Directory)
233
/// Returns a map of file paths to their blob refs and CIDs
234
fn extract_subfs_blobs(
235
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
236
current_path: String,
237
) -> HashMap<String, (BlobRef<'static>, String)> {
238
let mut blob_map = HashMap::new();
···
245
};
246
247
match &entry.node {
248
+
wisp_lexicons::place_wisp::subfs::EntryNode::File(file_node) => {
249
let blob_ref = &file_node.blob;
250
let cid_string = blob_ref.blob().r#ref.to_string();
251
blob_map.insert(
···
253
(blob_ref.clone().into_static(), cid_string)
254
);
255
}
256
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
257
let sub_map = extract_subfs_blobs(subdir, full_path);
258
blob_map.extend(sub_map);
259
}
260
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
261
// Nested subfs - these should be resolved recursively in the main flow
262
// For now, we skip them (they'll be fetched separately)
263
eprintln!(" โ ๏ธ Found nested subfs at {}, skipping (should be fetched separately)", full_path);
264
}
265
+
wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(_) => {
266
// Skip unknown nodes
267
}
268
}
···
352
flat: bool,
353
) -> miette::Result<FsDirectory<'static>> {
354
use jacquard_common::CowStr;
355
+
use wisp_lexicons::place_wisp::fs::{Entry, Subfs};
356
357
let path_parts: Vec<&str> = target_path.split('/').collect();
358
···
430
431
// Construct AT-URI and convert to RecordUri
432
let at_uri = AtUri::new(uri).into_diagnostic()?;
433
+
let record_uri: RecordUri<'_, wisp_lexicons::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
434
435
let rkey = record_uri.rkey()
436
.ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))?
···
489
}
490
491
/// Estimate the JSON size of a single entry
492
+
fn estimate_entry_size(entry: &wisp_lexicons::place_wisp::fs::Entry) -> usize {
493
match serde_json::to_string(entry) {
494
Ok(json) => json.len(),
495
Err(_) => 500, // Conservative estimate if serialization fails
+21
docker-compose.yml
+21
docker-compose.yml
···
···
1
+
services:
2
+
postgres:
3
+
image: postgres:16-alpine
4
+
container_name: wisp-postgres
5
+
restart: unless-stopped
6
+
environment:
7
+
POSTGRES_USER: postgres
8
+
POSTGRES_PASSWORD: postgres
9
+
POSTGRES_DB: wisp
10
+
ports:
11
+
- "5432:5432"
12
+
volumes:
13
+
- postgres_data:/var/lib/postgresql/data
14
+
healthcheck:
15
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
16
+
interval: 5s
17
+
timeout: 5s
18
+
retries: 5
19
+
20
+
volumes:
21
+
postgres_data:
+3
-1
docs/astro.config.mjs
+3
-1
docs/astro.config.mjs
+9
docs/src/assets/tangled-icon.svg
+9
docs/src/assets/tangled-icon.svg
···
···
1
+
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" id="svg1" width="25" height="25" viewBox="0 0 25 25" sodipodi:docname="tangled_dolly_silhouette.png">
2
+
<defs id="defs1"/>
3
+
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1">
4
+
<inkscape:page x="0" y="0" width="25" height="25" id="page2" margin="0" bleed="0"/>
5
+
</sodipodi:namedview>
6
+
<g inkscape:groupmode="layer" inkscape:label="Image" id="g1">
7
+
<path style="fill:#000000;stroke-width:1.12248" d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z" id="path1"/>
8
+
</g>
9
+
</svg>
+26
docs/src/components/SocialIcons.astro
+26
docs/src/components/SocialIcons.astro
···
···
1
+
---
2
+
// Custom social icons component to use the Tangled icon
3
+
---
4
+
5
+
<div class="sl-flex">
6
+
<a
7
+
href="https://tangled.org/nekomimi.pet/wisp.place-monorepo"
8
+
rel="me"
9
+
class="sl-flex"
10
+
aria-label="Tangled"
11
+
>
12
+
<svg
13
+
xmlns="http://www.w3.org/2000/svg"
14
+
viewBox="0 0 25 25"
15
+
width="16"
16
+
height="16"
17
+
aria-hidden="true"
18
+
focusable="false"
19
+
>
20
+
<path
21
+
style="fill:currentColor;stroke-width:1.12248"
22
+
d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"
23
+
></path>
24
+
</svg>
25
+
</a>
26
+
</div>
+6
-11
docs/src/content/docs/cli.md
+6
-11
docs/src/content/docs/cli.md
···
71
72
engine: 'nixery'
73
74
-
clone:
75
-
skip: false
76
-
depth: 1
77
-
submodules: false
78
-
79
dependencies:
80
nixpkgs:
81
- nodejs
···
141
# Pull a site to a specific directory
142
wisp-cli pull your-handle.bsky.social \
143
--site my-site \
144
-
--output ./my-site
145
146
# Pull to current directory
147
wisp-cli pull your-handle.bsky.social \
···
238
### Pull Command
239
240
```bash
241
-
wisp-cli pull [OPTIONS] <INPUT>
242
243
Arguments:
244
<INPUT> Handle or DID
245
246
Options:
247
-s, --site <SITE> Site name to download
248
-
-o, --output <OUTPUT> Output directory [default: .]
249
-h, --help Print help
250
```
251
252
### Serve Command
253
254
```bash
255
-
wisp-cli serve [OPTIONS] <INPUT>
256
257
Arguments:
258
<INPUT> Handle or DID
259
260
Options:
261
-s, --site <SITE> Site name to serve
262
-
-o, --output <OUTPUT> Site files directory [default: .]
263
-
-p, --port <PORT> Port to serve on [default: 8080]
264
--spa Enable SPA mode (serve index.html for all routes)
265
--directory Enable directory listing mode for paths without index files
266
-h, --help Print help
···
71
72
engine: 'nixery'
73
74
dependencies:
75
nixpkgs:
76
- nodejs
···
136
# Pull a site to a specific directory
137
wisp-cli pull your-handle.bsky.social \
138
--site my-site \
139
+
--path ./my-site
140
141
# Pull to current directory
142
wisp-cli pull your-handle.bsky.social \
···
233
### Pull Command
234
235
```bash
236
+
wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
237
238
Arguments:
239
<INPUT> Handle or DID
240
241
Options:
242
-s, --site <SITE> Site name to download
243
+
-p, --path <PATH> Output directory [default: .]
244
-h, --help Print help
245
```
246
247
### Serve Command
248
249
```bash
250
+
wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
251
252
Arguments:
253
<INPUT> Handle or DID
254
255
Options:
256
-s, --site <SITE> Site name to serve
257
+
-p, --path <PATH> Site files directory [default: .]
258
+
-P, --port <PORT> Port to serve on [default: 8080]
259
--spa Enable SPA mode (serve index.html for all routes)
260
--directory Enable directory listing mode for paths without index files
261
-h, --help Print help
+15
-15
docs/src/styles/custom.css
+15
-15
docs/src/styles/custom.css
···
5
/* Increase base font size by 10% */
6
font-size: 110%;
7
8
-
/* Light theme - Warm beige background from app */
9
-
--sl-color-bg: oklch(0.90 0.012 35);
10
-
--sl-color-bg-sidebar: oklch(0.93 0.01 35);
11
-
--sl-color-bg-nav: oklch(0.93 0.01 35);
12
-
--sl-color-text: oklch(0.18 0.01 30);
13
-
--sl-color-text-accent: oklch(0.78 0.15 345);
14
-
--sl-color-accent: oklch(0.78 0.15 345);
15
-
--sl-color-accent-low: oklch(0.95 0.03 345);
16
-
--sl-color-border: oklch(0.75 0.015 30);
17
-
--sl-color-gray-1: oklch(0.52 0.015 30);
18
-
--sl-color-gray-2: oklch(0.42 0.015 30);
19
-
--sl-color-gray-3: oklch(0.33 0.015 30);
20
-
--sl-color-gray-4: oklch(0.25 0.015 30);
21
-
--sl-color-gray-5: oklch(0.75 0.015 30);
22
--sl-color-bg-accent: oklch(0.88 0.01 35);
23
}
24
···
70
/* Sidebar active/hover state text contrast fix */
71
.sidebar a[aria-current="page"],
72
.sidebar a[aria-current="page"] span {
73
-
color: oklch(0.23 0.015 285) !important;
74
}
75
76
[data-theme="dark"] .sidebar a[aria-current="page"],
···
5
/* Increase base font size by 10% */
6
font-size: 110%;
7
8
+
/* Light theme - Warm beige with improved contrast */
9
+
--sl-color-bg: oklch(0.92 0.012 35);
10
+
--sl-color-bg-sidebar: oklch(0.95 0.008 35);
11
+
--sl-color-bg-nav: oklch(0.95 0.008 35);
12
+
--sl-color-text: oklch(0.15 0.015 30);
13
+
--sl-color-text-accent: oklch(0.65 0.18 345);
14
+
--sl-color-accent: oklch(0.65 0.18 345);
15
+
--sl-color-accent-low: oklch(0.92 0.05 345);
16
+
--sl-color-border: oklch(0.65 0.02 30);
17
+
--sl-color-gray-1: oklch(0.45 0.02 30);
18
+
--sl-color-gray-2: oklch(0.35 0.02 30);
19
+
--sl-color-gray-3: oklch(0.28 0.02 30);
20
+
--sl-color-gray-4: oklch(0.20 0.015 30);
21
+
--sl-color-gray-5: oklch(0.65 0.02 30);
22
--sl-color-bg-accent: oklch(0.88 0.01 35);
23
}
24
···
70
/* Sidebar active/hover state text contrast fix */
71
.sidebar a[aria-current="page"],
72
.sidebar a[aria-current="page"] span {
73
+
color: oklch(0.15 0.015 30) !important;
74
}
75
76
[data-theme="dark"] .sidebar a[aria-current="page"],
-318
flake.lock
-318
flake.lock
···
1
-
{
2
-
"nodes": {
3
-
"crane": {
4
-
"flake": false,
5
-
"locked": {
6
-
"lastModified": 1758758545,
7
-
"narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=",
8
-
"owner": "ipetkov",
9
-
"repo": "crane",
10
-
"rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364",
11
-
"type": "github"
12
-
},
13
-
"original": {
14
-
"owner": "ipetkov",
15
-
"ref": "v0.21.1",
16
-
"repo": "crane",
17
-
"type": "github"
18
-
}
19
-
},
20
-
"dream2nix": {
21
-
"inputs": {
22
-
"nixpkgs": [
23
-
"nci",
24
-
"nixpkgs"
25
-
],
26
-
"purescript-overlay": "purescript-overlay",
27
-
"pyproject-nix": "pyproject-nix"
28
-
},
29
-
"locked": {
30
-
"lastModified": 1754978539,
31
-
"narHash": "sha256-nrDovydywSKRbWim9Ynmgj8SBm8LK3DI2WuhIqzOHYI=",
32
-
"owner": "nix-community",
33
-
"repo": "dream2nix",
34
-
"rev": "fbec3263cb4895ac86ee9506cdc4e6919a1a2214",
35
-
"type": "github"
36
-
},
37
-
"original": {
38
-
"owner": "nix-community",
39
-
"repo": "dream2nix",
40
-
"type": "github"
41
-
}
42
-
},
43
-
"fenix": {
44
-
"inputs": {
45
-
"nixpkgs": [
46
-
"nixpkgs"
47
-
],
48
-
"rust-analyzer-src": "rust-analyzer-src"
49
-
},
50
-
"locked": {
51
-
"lastModified": 1762584108,
52
-
"narHash": "sha256-wZUW7dlXMXaRdvNbaADqhF8gg9bAfFiMV+iyFQiDv+Y=",
53
-
"owner": "nix-community",
54
-
"repo": "fenix",
55
-
"rev": "32f3ad3b6c690061173e1ac16708874975ec6056",
56
-
"type": "github"
57
-
},
58
-
"original": {
59
-
"owner": "nix-community",
60
-
"repo": "fenix",
61
-
"type": "github"
62
-
}
63
-
},
64
-
"flake-compat": {
65
-
"flake": false,
66
-
"locked": {
67
-
"lastModified": 1696426674,
68
-
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
69
-
"owner": "edolstra",
70
-
"repo": "flake-compat",
71
-
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
72
-
"type": "github"
73
-
},
74
-
"original": {
75
-
"owner": "edolstra",
76
-
"repo": "flake-compat",
77
-
"type": "github"
78
-
}
79
-
},
80
-
"mk-naked-shell": {
81
-
"flake": false,
82
-
"locked": {
83
-
"lastModified": 1681286841,
84
-
"narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
85
-
"owner": "90-008",
86
-
"repo": "mk-naked-shell",
87
-
"rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
88
-
"type": "github"
89
-
},
90
-
"original": {
91
-
"owner": "90-008",
92
-
"repo": "mk-naked-shell",
93
-
"type": "github"
94
-
}
95
-
},
96
-
"nci": {
97
-
"inputs": {
98
-
"crane": "crane",
99
-
"dream2nix": "dream2nix",
100
-
"mk-naked-shell": "mk-naked-shell",
101
-
"nixpkgs": [
102
-
"nixpkgs"
103
-
],
104
-
"parts": "parts",
105
-
"rust-overlay": "rust-overlay",
106
-
"treefmt": "treefmt"
107
-
},
108
-
"locked": {
109
-
"lastModified": 1762582646,
110
-
"narHash": "sha256-MMzE4xccG+8qbLhdaZoeFDUKWUOn3B4lhp5dZmgukmM=",
111
-
"owner": "90-008",
112
-
"repo": "nix-cargo-integration",
113
-
"rev": "0993c449377049fa8868a664e8290ac6658e0b9a",
114
-
"type": "github"
115
-
},
116
-
"original": {
117
-
"owner": "90-008",
118
-
"repo": "nix-cargo-integration",
119
-
"type": "github"
120
-
}
121
-
},
122
-
"nixpkgs": {
123
-
"locked": {
124
-
"lastModified": 1762361079,
125
-
"narHash": "sha256-lz718rr1BDpZBYk7+G8cE6wee3PiBUpn8aomG/vLLiY=",
126
-
"owner": "nixos",
127
-
"repo": "nixpkgs",
128
-
"rev": "ffcdcf99d65c61956d882df249a9be53e5902ea5",
129
-
"type": "github"
130
-
},
131
-
"original": {
132
-
"owner": "nixos",
133
-
"ref": "nixpkgs-unstable",
134
-
"repo": "nixpkgs",
135
-
"type": "github"
136
-
}
137
-
},
138
-
"parts": {
139
-
"inputs": {
140
-
"nixpkgs-lib": [
141
-
"nci",
142
-
"nixpkgs"
143
-
]
144
-
},
145
-
"locked": {
146
-
"lastModified": 1762440070,
147
-
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
148
-
"owner": "hercules-ci",
149
-
"repo": "flake-parts",
150
-
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
151
-
"type": "github"
152
-
},
153
-
"original": {
154
-
"owner": "hercules-ci",
155
-
"repo": "flake-parts",
156
-
"type": "github"
157
-
}
158
-
},
159
-
"parts_2": {
160
-
"inputs": {
161
-
"nixpkgs-lib": [
162
-
"nixpkgs"
163
-
]
164
-
},
165
-
"locked": {
166
-
"lastModified": 1762440070,
167
-
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
168
-
"owner": "hercules-ci",
169
-
"repo": "flake-parts",
170
-
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
171
-
"type": "github"
172
-
},
173
-
"original": {
174
-
"owner": "hercules-ci",
175
-
"repo": "flake-parts",
176
-
"type": "github"
177
-
}
178
-
},
179
-
"purescript-overlay": {
180
-
"inputs": {
181
-
"flake-compat": "flake-compat",
182
-
"nixpkgs": [
183
-
"nci",
184
-
"dream2nix",
185
-
"nixpkgs"
186
-
],
187
-
"slimlock": "slimlock"
188
-
},
189
-
"locked": {
190
-
"lastModified": 1728546539,
191
-
"narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=",
192
-
"owner": "thomashoneyman",
193
-
"repo": "purescript-overlay",
194
-
"rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4",
195
-
"type": "github"
196
-
},
197
-
"original": {
198
-
"owner": "thomashoneyman",
199
-
"repo": "purescript-overlay",
200
-
"type": "github"
201
-
}
202
-
},
203
-
"pyproject-nix": {
204
-
"inputs": {
205
-
"nixpkgs": [
206
-
"nci",
207
-
"dream2nix",
208
-
"nixpkgs"
209
-
]
210
-
},
211
-
"locked": {
212
-
"lastModified": 1752481895,
213
-
"narHash": "sha256-luVj97hIMpCbwhx3hWiRwjP2YvljWy8FM+4W9njDhLA=",
214
-
"owner": "pyproject-nix",
215
-
"repo": "pyproject.nix",
216
-
"rev": "16ee295c25107a94e59a7fc7f2e5322851781162",
217
-
"type": "github"
218
-
},
219
-
"original": {
220
-
"owner": "pyproject-nix",
221
-
"repo": "pyproject.nix",
222
-
"type": "github"
223
-
}
224
-
},
225
-
"root": {
226
-
"inputs": {
227
-
"fenix": "fenix",
228
-
"nci": "nci",
229
-
"nixpkgs": "nixpkgs",
230
-
"parts": "parts_2"
231
-
}
232
-
},
233
-
"rust-analyzer-src": {
234
-
"flake": false,
235
-
"locked": {
236
-
"lastModified": 1762438844,
237
-
"narHash": "sha256-ApIKJf6CcMsV2nYBXhGF95BmZMO/QXPhgfSnkA/rVUo=",
238
-
"owner": "rust-lang",
239
-
"repo": "rust-analyzer",
240
-
"rev": "4bf516ee5a960c1e2eee9fedd9b1c9e976a19c86",
241
-
"type": "github"
242
-
},
243
-
"original": {
244
-
"owner": "rust-lang",
245
-
"ref": "nightly",
246
-
"repo": "rust-analyzer",
247
-
"type": "github"
248
-
}
249
-
},
250
-
"rust-overlay": {
251
-
"inputs": {
252
-
"nixpkgs": [
253
-
"nci",
254
-
"nixpkgs"
255
-
]
256
-
},
257
-
"locked": {
258
-
"lastModified": 1762569282,
259
-
"narHash": "sha256-vINZAJpXQTZd5cfh06Rcw7hesH7sGSvi+Tn+HUieJn8=",
260
-
"owner": "oxalica",
261
-
"repo": "rust-overlay",
262
-
"rev": "a35a6144b976f70827c2fe2f5c89d16d8f9179d8",
263
-
"type": "github"
264
-
},
265
-
"original": {
266
-
"owner": "oxalica",
267
-
"repo": "rust-overlay",
268
-
"type": "github"
269
-
}
270
-
},
271
-
"slimlock": {
272
-
"inputs": {
273
-
"nixpkgs": [
274
-
"nci",
275
-
"dream2nix",
276
-
"purescript-overlay",
277
-
"nixpkgs"
278
-
]
279
-
},
280
-
"locked": {
281
-
"lastModified": 1688756706,
282
-
"narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=",
283
-
"owner": "thomashoneyman",
284
-
"repo": "slimlock",
285
-
"rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c",
286
-
"type": "github"
287
-
},
288
-
"original": {
289
-
"owner": "thomashoneyman",
290
-
"repo": "slimlock",
291
-
"type": "github"
292
-
}
293
-
},
294
-
"treefmt": {
295
-
"inputs": {
296
-
"nixpkgs": [
297
-
"nci",
298
-
"nixpkgs"
299
-
]
300
-
},
301
-
"locked": {
302
-
"lastModified": 1762410071,
303
-
"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",
304
-
"owner": "numtide",
305
-
"repo": "treefmt-nix",
306
-
"rev": "97a30861b13c3731a84e09405414398fbf3e109f",
307
-
"type": "github"
308
-
},
309
-
"original": {
310
-
"owner": "numtide",
311
-
"repo": "treefmt-nix",
312
-
"type": "github"
313
-
}
314
-
}
315
-
},
316
-
"root": "root",
317
-
"version": 7
318
-
}
···
-59
flake.nix
-59
flake.nix
···
1
-
{
2
-
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
3
-
inputs.nci.url = "github:90-008/nix-cargo-integration";
4
-
inputs.nci.inputs.nixpkgs.follows = "nixpkgs";
5
-
inputs.parts.url = "github:hercules-ci/flake-parts";
6
-
inputs.parts.inputs.nixpkgs-lib.follows = "nixpkgs";
7
-
inputs.fenix = {
8
-
url = "github:nix-community/fenix";
9
-
inputs.nixpkgs.follows = "nixpkgs";
10
-
};
11
-
12
-
outputs = inputs @ {
13
-
parts,
14
-
nci,
15
-
...
16
-
}:
17
-
parts.lib.mkFlake {inherit inputs;} {
18
-
systems = ["x86_64-linux" "aarch64-darwin"];
19
-
imports = [
20
-
nci.flakeModule
21
-
./crates.nix
22
-
];
23
-
perSystem = {
24
-
pkgs,
25
-
config,
26
-
...
27
-
}: let
28
-
crateOutputs = config.nci.outputs."wisp-cli";
29
-
mkRenamedPackage = name: pkg: isWindows: pkgs.runCommand name {} ''
30
-
mkdir -p $out/bin
31
-
if [ -f ${pkg}/bin/wisp-cli.exe ]; then
32
-
cp ${pkg}/bin/wisp-cli.exe $out/bin/${name}
33
-
elif [ -f ${pkg}/bin/wisp-cli ]; then
34
-
cp ${pkg}/bin/wisp-cli $out/bin/${name}
35
-
else
36
-
echo "Error: Could not find wisp-cli binary in ${pkg}/bin/"
37
-
ls -la ${pkg}/bin/ || true
38
-
exit 1
39
-
fi
40
-
'';
41
-
in {
42
-
devShells.default = crateOutputs.devShell;
43
-
packages.default = crateOutputs.packages.release;
44
-
packages.wisp-cli-x86_64-linux = mkRenamedPackage "wisp-cli-x86_64-linux" crateOutputs.packages.release false;
45
-
packages.wisp-cli-aarch64-linux = mkRenamedPackage "wisp-cli-aarch64-linux" crateOutputs.allTargets."aarch64-unknown-linux-gnu".packages.release false;
46
-
packages.wisp-cli-x86_64-windows = mkRenamedPackage "wisp-cli-x86_64-windows.exe" crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release true;
47
-
packages.wisp-cli-aarch64-darwin = mkRenamedPackage "wisp-cli-aarch64-darwin" crateOutputs.allTargets."aarch64-apple-darwin".packages.release false;
48
-
packages.all = pkgs.symlinkJoin {
49
-
name = "wisp-cli-all";
50
-
paths = [
51
-
config.packages.wisp-cli-x86_64-linux
52
-
config.packages.wisp-cli-aarch64-linux
53
-
config.packages.wisp-cli-x86_64-windows
54
-
config.packages.wisp-cli-aarch64-darwin
55
-
];
56
-
};
57
-
};
58
-
};
59
-
}
···
+59
lexicons/fs.json
+59
lexicons/fs.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.fs",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Virtual filesystem manifest for a Wisp site",
8
+
"record": {
9
+
"type": "object",
10
+
"required": ["site", "root", "createdAt"],
11
+
"properties": {
12
+
"site": { "type": "string" },
13
+
"root": { "type": "ref", "ref": "#directory" },
14
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
15
+
"createdAt": { "type": "string", "format": "datetime" }
16
+
}
17
+
}
18
+
},
19
+
"file": {
20
+
"type": "object",
21
+
"required": ["type", "blob"],
22
+
"properties": {
23
+
"type": { "type": "string", "const": "file" },
24
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
25
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
26
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
27
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
28
+
},
29
+
"directory": {
30
+
"type": "object",
31
+
"required": ["type", "entries"],
32
+
"properties": {
33
+
"type": { "type": "string", "const": "directory" },
34
+
"entries": {
35
+
"type": "array",
36
+
"maxLength": 500,
37
+
"items": { "type": "ref", "ref": "#entry" }
38
+
}
39
+
}
40
+
},
41
+
"entry": {
42
+
"type": "object",
43
+
"required": ["name", "node"],
44
+
"properties": {
45
+
"name": { "type": "string", "maxLength": 255 },
46
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
+
}
48
+
},
49
+
"subfs": {
50
+
"type": "object",
51
+
"required": ["type", "subject"],
52
+
"properties": {
53
+
"type": { "type": "string", "const": "subfs" },
54
+
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
55
+
"flat": { "type": "boolean", "description": "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." }
56
+
}
57
+
}
58
+
}
59
+
}
+76
lexicons/settings.json
+76
lexicons/settings.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.settings",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Configuration settings for a static site hosted on wisp.place",
8
+
"key": "any",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"directoryListing": {
13
+
"type": "boolean",
14
+
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
15
+
"default": false
16
+
},
17
+
"spaMode": {
18
+
"type": "string",
19
+
"description": "File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
20
+
"maxLength": 500
21
+
},
22
+
"custom404": {
23
+
"type": "string",
24
+
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
25
+
"maxLength": 500
26
+
},
27
+
"indexFiles": {
28
+
"type": "array",
29
+
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
30
+
"items": {
31
+
"type": "string",
32
+
"maxLength": 255
33
+
},
34
+
"maxLength": 10
35
+
},
36
+
"cleanUrls": {
37
+
"type": "boolean",
38
+
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
39
+
"default": false
40
+
},
41
+
"headers": {
42
+
"type": "array",
43
+
"description": "Custom HTTP headers to set on responses",
44
+
"items": {
45
+
"type": "ref",
46
+
"ref": "#customHeader"
47
+
},
48
+
"maxLength": 50
49
+
}
50
+
}
51
+
}
52
+
},
53
+
"customHeader": {
54
+
"type": "object",
55
+
"description": "Custom HTTP header configuration",
56
+
"required": ["name", "value"],
57
+
"properties": {
58
+
"name": {
59
+
"type": "string",
60
+
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
61
+
"maxLength": 100
62
+
},
63
+
"value": {
64
+
"type": "string",
65
+
"description": "HTTP header value",
66
+
"maxLength": 1000
67
+
},
68
+
"path": {
69
+
"type": "string",
70
+
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
71
+
"maxLength": 500
72
+
}
73
+
}
74
+
}
75
+
}
76
+
}
+59
lexicons/subfs.json
+59
lexicons/subfs.json
···
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.subfs",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
8
+
"record": {
9
+
"type": "object",
10
+
"required": ["root", "createdAt"],
11
+
"properties": {
12
+
"root": { "type": "ref", "ref": "#directory" },
13
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
14
+
"createdAt": { "type": "string", "format": "datetime" }
15
+
}
16
+
}
17
+
},
18
+
"file": {
19
+
"type": "object",
20
+
"required": ["type", "blob"],
21
+
"properties": {
22
+
"type": { "type": "string", "const": "file" },
23
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
24
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
25
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
26
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
27
+
}
28
+
},
29
+
"directory": {
30
+
"type": "object",
31
+
"required": ["type", "entries"],
32
+
"properties": {
33
+
"type": { "type": "string", "const": "directory" },
34
+
"entries": {
35
+
"type": "array",
36
+
"maxLength": 500,
37
+
"items": { "type": "ref", "ref": "#entry" }
38
+
}
39
+
}
40
+
},
41
+
"entry": {
42
+
"type": "object",
43
+
"required": ["name", "node"],
44
+
"properties": {
45
+
"name": { "type": "string", "maxLength": 255 },
46
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
+
}
48
+
},
49
+
"subfs": {
50
+
"type": "object",
51
+
"required": ["type", "subject"],
52
+
"properties": {
53
+
"type": { "type": "string", "const": "subfs" },
54
+
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures." }
55
+
}
56
+
}
57
+
}
58
+
}
59
+
+7
-2
package.json
+7
-2
package.json
···
11
"@tailwindcss/cli": "^4.1.17",
12
"atproto-ui": "^0.12.0",
13
"bun-plugin-tailwind": "^0.1.2",
14
"tailwindcss": "^4.1.17"
15
},
16
"scripts": {
···
23
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
24
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
25
"hosting:dev": "cd apps/hosting-service && npm run dev",
26
-
"hosting:start": "cd apps/hosting-service && npm run start"
27
},
28
"trustedDependencies": [
29
"@parcel/watcher",
30
"bun",
31
"esbuild"
32
-
]
33
}
···
11
"@tailwindcss/cli": "^4.1.17",
12
"atproto-ui": "^0.12.0",
13
"bun-plugin-tailwind": "^0.1.2",
14
+
"elysia": "^1.4.18",
15
"tailwindcss": "^4.1.17"
16
},
17
"scripts": {
···
24
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
25
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
26
"hosting:dev": "cd apps/hosting-service && npm run dev",
27
+
"hosting:start": "cd apps/hosting-service && npm run start",
28
+
"codegen": "./scripts/codegen.sh"
29
},
30
"trustedDependencies": [
31
"@parcel/watcher",
32
"bun",
33
"esbuild"
34
+
],
35
+
"devDependencies": {
36
+
"@types/bun": "^1.3.5"
37
+
}
38
}
+244
packages/@wisp/fs-utils/src/tree.test.ts
+244
packages/@wisp/fs-utils/src/tree.test.ts
···
···
1
+
import { describe, test, expect } from 'bun:test'
2
+
import { processUploadedFiles, type UploadedFile } from './tree'
3
+
4
+
describe('processUploadedFiles', () => {
5
+
test('should preserve nested directory structure', () => {
6
+
const files: UploadedFile[] = [
7
+
{
8
+
name: 'mysite/index.html',
9
+
content: Buffer.from('<html>'),
10
+
mimeType: 'text/html',
11
+
size: 6
12
+
},
13
+
{
14
+
name: 'mysite/_astro/main.js',
15
+
content: Buffer.from('console.log()'),
16
+
mimeType: 'application/javascript',
17
+
size: 13
18
+
},
19
+
{
20
+
name: 'mysite/_astro/styles.css',
21
+
content: Buffer.from('body {}'),
22
+
mimeType: 'text/css',
23
+
size: 7
24
+
},
25
+
{
26
+
name: 'mysite/images/logo.png',
27
+
content: Buffer.from([0x89, 0x50, 0x4e, 0x47]),
28
+
mimeType: 'image/png',
29
+
size: 4
30
+
}
31
+
]
32
+
33
+
const result = processUploadedFiles(files)
34
+
35
+
expect(result.fileCount).toBe(4)
36
+
expect(result.directory.entries).toHaveLength(3) // index.html, _astro/, images/
37
+
38
+
// Check _astro directory exists
39
+
const astroEntry = result.directory.entries.find(e => e.name === '_astro')
40
+
expect(astroEntry).toBeTruthy()
41
+
expect('type' in astroEntry!.node && astroEntry!.node.type).toBe('directory')
42
+
43
+
if ('entries' in astroEntry!.node) {
44
+
const astroDir = astroEntry!.node
45
+
expect(astroDir.entries).toHaveLength(2) // main.js, styles.css
46
+
expect(astroDir.entries.find(e => e.name === 'main.js')).toBeTruthy()
47
+
expect(astroDir.entries.find(e => e.name === 'styles.css')).toBeTruthy()
48
+
}
49
+
50
+
// Check images directory exists
51
+
const imagesEntry = result.directory.entries.find(e => e.name === 'images')
52
+
expect(imagesEntry).toBeTruthy()
53
+
expect('type' in imagesEntry!.node && imagesEntry!.node.type).toBe('directory')
54
+
55
+
if ('entries' in imagesEntry!.node) {
56
+
const imagesDir = imagesEntry!.node
57
+
expect(imagesDir.entries).toHaveLength(1) // logo.png
58
+
expect(imagesDir.entries.find(e => e.name === 'logo.png')).toBeTruthy()
59
+
}
60
+
})
61
+
62
+
test('should handle deeply nested directories', () => {
63
+
const files: UploadedFile[] = [
64
+
{
65
+
name: 'site/a/b/c/d/deep.txt',
66
+
content: Buffer.from('deep'),
67
+
mimeType: 'text/plain',
68
+
size: 4
69
+
}
70
+
]
71
+
72
+
const result = processUploadedFiles(files)
73
+
74
+
expect(result.fileCount).toBe(1)
75
+
76
+
// Navigate through nested structure
77
+
const aEntry = result.directory.entries.find(e => e.name === 'a')
78
+
expect(aEntry).toBeTruthy()
79
+
expect('type' in aEntry!.node && aEntry!.node.type).toBe('directory')
80
+
81
+
if ('entries' in aEntry!.node) {
82
+
const bEntry = aEntry!.node.entries.find(e => e.name === 'b')
83
+
expect(bEntry).toBeTruthy()
84
+
expect('type' in bEntry!.node && bEntry!.node.type).toBe('directory')
85
+
86
+
if ('entries' in bEntry!.node) {
87
+
const cEntry = bEntry!.node.entries.find(e => e.name === 'c')
88
+
expect(cEntry).toBeTruthy()
89
+
expect('type' in cEntry!.node && cEntry!.node.type).toBe('directory')
90
+
91
+
if ('entries' in cEntry!.node) {
92
+
const dEntry = cEntry!.node.entries.find(e => e.name === 'd')
93
+
expect(dEntry).toBeTruthy()
94
+
expect('type' in dEntry!.node && dEntry!.node.type).toBe('directory')
95
+
96
+
if ('entries' in dEntry!.node) {
97
+
const fileEntry = dEntry!.node.entries.find(e => e.name === 'deep.txt')
98
+
expect(fileEntry).toBeTruthy()
99
+
expect('type' in fileEntry!.node && fileEntry!.node.type).toBe('file')
100
+
}
101
+
}
102
+
}
103
+
}
104
+
})
105
+
106
+
test('should handle files at root level', () => {
107
+
const files: UploadedFile[] = [
108
+
{
109
+
name: 'mysite/index.html',
110
+
content: Buffer.from('<html>'),
111
+
mimeType: 'text/html',
112
+
size: 6
113
+
},
114
+
{
115
+
name: 'mysite/robots.txt',
116
+
content: Buffer.from('User-agent: *'),
117
+
mimeType: 'text/plain',
118
+
size: 13
119
+
}
120
+
]
121
+
122
+
const result = processUploadedFiles(files)
123
+
124
+
expect(result.fileCount).toBe(2)
125
+
expect(result.directory.entries).toHaveLength(2)
126
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
127
+
expect(result.directory.entries.find(e => e.name === 'robots.txt')).toBeTruthy()
128
+
})
129
+
130
+
test('should skip .git directories', () => {
131
+
const files: UploadedFile[] = [
132
+
{
133
+
name: 'mysite/index.html',
134
+
content: Buffer.from('<html>'),
135
+
mimeType: 'text/html',
136
+
size: 6
137
+
},
138
+
{
139
+
name: 'mysite/.git/config',
140
+
content: Buffer.from('[core]'),
141
+
mimeType: 'text/plain',
142
+
size: 6
143
+
},
144
+
{
145
+
name: 'mysite/.gitignore',
146
+
content: Buffer.from('node_modules'),
147
+
mimeType: 'text/plain',
148
+
size: 12
149
+
}
150
+
]
151
+
152
+
const result = processUploadedFiles(files)
153
+
154
+
expect(result.fileCount).toBe(2) // Only index.html and .gitignore
155
+
expect(result.directory.entries).toHaveLength(2)
156
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
157
+
expect(result.directory.entries.find(e => e.name === '.gitignore')).toBeTruthy()
158
+
expect(result.directory.entries.find(e => e.name === '.git')).toBeFalsy()
159
+
})
160
+
161
+
test('should handle mixed root and nested files', () => {
162
+
const files: UploadedFile[] = [
163
+
{
164
+
name: 'mysite/index.html',
165
+
content: Buffer.from('<html>'),
166
+
mimeType: 'text/html',
167
+
size: 6
168
+
},
169
+
{
170
+
name: 'mysite/about/index.html',
171
+
content: Buffer.from('<html>'),
172
+
mimeType: 'text/html',
173
+
size: 6
174
+
},
175
+
{
176
+
name: 'mysite/about/team.html',
177
+
content: Buffer.from('<html>'),
178
+
mimeType: 'text/html',
179
+
size: 6
180
+
},
181
+
{
182
+
name: 'mysite/robots.txt',
183
+
content: Buffer.from('User-agent: *'),
184
+
mimeType: 'text/plain',
185
+
size: 13
186
+
}
187
+
]
188
+
189
+
const result = processUploadedFiles(files)
190
+
191
+
expect(result.fileCount).toBe(4)
192
+
expect(result.directory.entries).toHaveLength(3) // index.html, about/, robots.txt
193
+
194
+
const aboutEntry = result.directory.entries.find(e => e.name === 'about')
195
+
expect(aboutEntry).toBeTruthy()
196
+
expect('type' in aboutEntry!.node && aboutEntry!.node.type).toBe('directory')
197
+
198
+
if ('entries' in aboutEntry!.node) {
199
+
const aboutDir = aboutEntry!.node
200
+
expect(aboutDir.entries).toHaveLength(2) // index.html, team.html
201
+
expect(aboutDir.entries.find(e => e.name === 'index.html')).toBeTruthy()
202
+
expect(aboutDir.entries.find(e => e.name === 'team.html')).toBeTruthy()
203
+
}
204
+
})
205
+
206
+
test('should handle empty file array', () => {
207
+
const files: UploadedFile[] = []
208
+
209
+
const result = processUploadedFiles(files)
210
+
211
+
expect(result.fileCount).toBe(0)
212
+
expect(result.directory.entries).toHaveLength(0)
213
+
})
214
+
215
+
test('should strip base folder name from paths', () => {
216
+
// This tests the behavior where file.name includes the base folder
217
+
// e.g., "mysite/index.html" should become "index.html" at root
218
+
const files: UploadedFile[] = [
219
+
{
220
+
name: 'build-output/index.html',
221
+
content: Buffer.from('<html>'),
222
+
mimeType: 'text/html',
223
+
size: 6
224
+
},
225
+
{
226
+
name: 'build-output/assets/main.js',
227
+
content: Buffer.from('console.log()'),
228
+
mimeType: 'application/javascript',
229
+
size: 13
230
+
}
231
+
]
232
+
233
+
const result = processUploadedFiles(files)
234
+
235
+
expect(result.fileCount).toBe(2)
236
+
237
+
// Should have index.html at root and assets/ directory
238
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
239
+
expect(result.directory.entries.find(e => e.name === 'assets')).toBeTruthy()
240
+
241
+
// Should NOT have 'build-output' directory
242
+
expect(result.directory.entries.find(e => e.name === 'build-output')).toBeFalsy()
243
+
})
244
+
})
-59
packages/@wisp/lexicons/lexicons/fs.json
-59
packages/@wisp/lexicons/lexicons/fs.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.fs",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Virtual filesystem manifest for a Wisp site",
8
-
"record": {
9
-
"type": "object",
10
-
"required": ["site", "root", "createdAt"],
11
-
"properties": {
12
-
"site": { "type": "string" },
13
-
"root": { "type": "ref", "ref": "#directory" },
14
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
15
-
"createdAt": { "type": "string", "format": "datetime" }
16
-
}
17
-
}
18
-
},
19
-
"file": {
20
-
"type": "object",
21
-
"required": ["type", "blob"],
22
-
"properties": {
23
-
"type": { "type": "string", "const": "file" },
24
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
25
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
26
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
27
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
28
-
},
29
-
"directory": {
30
-
"type": "object",
31
-
"required": ["type", "entries"],
32
-
"properties": {
33
-
"type": { "type": "string", "const": "directory" },
34
-
"entries": {
35
-
"type": "array",
36
-
"maxLength": 500,
37
-
"items": { "type": "ref", "ref": "#entry" }
38
-
}
39
-
}
40
-
},
41
-
"entry": {
42
-
"type": "object",
43
-
"required": ["name", "node"],
44
-
"properties": {
45
-
"name": { "type": "string", "maxLength": 255 },
46
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
-
}
48
-
},
49
-
"subfs": {
50
-
"type": "object",
51
-
"required": ["type", "subject"],
52
-
"properties": {
53
-
"type": { "type": "string", "const": "subfs" },
54
-
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
55
-
"flat": { "type": "boolean", "description": "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." }
56
-
}
57
-
}
58
-
}
59
-
}
···
-76
packages/@wisp/lexicons/lexicons/settings.json
-76
packages/@wisp/lexicons/lexicons/settings.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.settings",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Configuration settings for a static site hosted on wisp.place",
8
-
"key": "any",
9
-
"record": {
10
-
"type": "object",
11
-
"properties": {
12
-
"directoryListing": {
13
-
"type": "boolean",
14
-
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
15
-
"default": false
16
-
},
17
-
"spaMode": {
18
-
"type": "string",
19
-
"description": "File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
20
-
"maxLength": 500
21
-
},
22
-
"custom404": {
23
-
"type": "string",
24
-
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
25
-
"maxLength": 500
26
-
},
27
-
"indexFiles": {
28
-
"type": "array",
29
-
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
30
-
"items": {
31
-
"type": "string",
32
-
"maxLength": 255
33
-
},
34
-
"maxLength": 10
35
-
},
36
-
"cleanUrls": {
37
-
"type": "boolean",
38
-
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
39
-
"default": false
40
-
},
41
-
"headers": {
42
-
"type": "array",
43
-
"description": "Custom HTTP headers to set on responses",
44
-
"items": {
45
-
"type": "ref",
46
-
"ref": "#customHeader"
47
-
},
48
-
"maxLength": 50
49
-
}
50
-
}
51
-
}
52
-
},
53
-
"customHeader": {
54
-
"type": "object",
55
-
"description": "Custom HTTP header configuration",
56
-
"required": ["name", "value"],
57
-
"properties": {
58
-
"name": {
59
-
"type": "string",
60
-
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
61
-
"maxLength": 100
62
-
},
63
-
"value": {
64
-
"type": "string",
65
-
"description": "HTTP header value",
66
-
"maxLength": 1000
67
-
},
68
-
"path": {
69
-
"type": "string",
70
-
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
71
-
"maxLength": 500
72
-
}
73
-
}
74
-
}
75
-
}
76
-
}
···
-59
packages/@wisp/lexicons/lexicons/subfs.json
-59
packages/@wisp/lexicons/lexicons/subfs.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.subfs",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
8
-
"record": {
9
-
"type": "object",
10
-
"required": ["root", "createdAt"],
11
-
"properties": {
12
-
"root": { "type": "ref", "ref": "#directory" },
13
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
14
-
"createdAt": { "type": "string", "format": "datetime" }
15
-
}
16
-
}
17
-
},
18
-
"file": {
19
-
"type": "object",
20
-
"required": ["type", "blob"],
21
-
"properties": {
22
-
"type": { "type": "string", "const": "file" },
23
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
24
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
25
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
26
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
27
-
}
28
-
},
29
-
"directory": {
30
-
"type": "object",
31
-
"required": ["type", "entries"],
32
-
"properties": {
33
-
"type": { "type": "string", "const": "directory" },
34
-
"entries": {
35
-
"type": "array",
36
-
"maxLength": 500,
37
-
"items": { "type": "ref", "ref": "#entry" }
38
-
}
39
-
}
40
-
},
41
-
"entry": {
42
-
"type": "object",
43
-
"required": ["name", "node"],
44
-
"properties": {
45
-
"name": { "type": "string", "maxLength": 255 },
46
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
-
}
48
-
},
49
-
"subfs": {
50
-
"type": "object",
51
-
"required": ["type", "subject"],
52
-
"properties": {
53
-
"type": { "type": "string", "const": "subfs" },
54
-
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures." }
55
-
}
56
-
}
57
-
}
58
-
}
59
-
···
+1
-1
packages/@wisp/lexicons/package.json
+1
-1
packages/@wisp/lexicons/package.json
+1
-1
packages/@wisp/lexicons/src/index.ts
+1
-1
packages/@wisp/lexicons/src/index.ts
+1
-1
packages/@wisp/lexicons/src/lexicons.ts
+1
-1
packages/@wisp/lexicons/src/lexicons.ts
+1
-1
packages/@wisp/observability/README.md
+1
-1
packages/@wisp/observability/README.md
+1
-2
packages/@wisp/observability/src/core.ts
+1
-2
packages/@wisp/observability/src/core.ts
···
168
},
169
170
debug(message: string, service: string, context?: Record<string, any>, traceId?: string) {
171
-
const env = typeof Bun !== 'undefined' ? Bun.env.NODE_ENV : process.env.NODE_ENV;
172
-
if (env !== 'production') {
173
this.log('debug', message, service, context, traceId)
174
}
175
},
+11
-11
packages/@wisp/observability/src/exporters.ts
+11
-11
packages/@wisp/observability/src/exporters.ts
···
3
* Integrates with Grafana Loki for logs and Prometheus/OTLP for metrics
4
*/
5
6
-
import { LogEntry, ErrorEntry, MetricEntry } from './core'
7
-
import { metrics, MeterProvider } from '@opentelemetry/api'
8
import { MeterProvider as SdkMeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
9
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
10
import { Resource } from '@opentelemetry/resources'
···
61
62
// Load from environment variables if not provided
63
if (!this.config.lokiUrl) {
64
-
this.config.lokiUrl = process.env.GRAFANA_LOKI_URL || Bun?.env?.GRAFANA_LOKI_URL
65
}
66
67
if (!this.config.prometheusUrl) {
68
-
this.config.prometheusUrl = process.env.GRAFANA_PROMETHEUS_URL || Bun?.env?.GRAFANA_PROMETHEUS_URL
69
}
70
71
// Load Loki authentication from environment
72
if (!this.config.lokiAuth?.bearerToken && !this.config.lokiAuth?.username) {
73
-
const token = process.env.GRAFANA_LOKI_TOKEN || Bun?.env?.GRAFANA_LOKI_TOKEN
74
-
const username = process.env.GRAFANA_LOKI_USERNAME || Bun?.env?.GRAFANA_LOKI_USERNAME
75
-
const password = process.env.GRAFANA_LOKI_PASSWORD || Bun?.env?.GRAFANA_LOKI_PASSWORD
76
77
if (token) {
78
this.config.lokiAuth = { ...this.config.lokiAuth, bearerToken: token }
···
83
84
// Load Prometheus authentication from environment
85
if (!this.config.prometheusAuth?.bearerToken && !this.config.prometheusAuth?.username) {
86
-
const token = process.env.GRAFANA_PROMETHEUS_TOKEN || Bun?.env?.GRAFANA_PROMETHEUS_TOKEN
87
-
const username = process.env.GRAFANA_PROMETHEUS_USERNAME || Bun?.env?.GRAFANA_PROMETHEUS_USERNAME
88
-
const password = process.env.GRAFANA_PROMETHEUS_PASSWORD || Bun?.env?.GRAFANA_PROMETHEUS_PASSWORD
89
90
if (token) {
91
this.config.prometheusAuth = { ...this.config.prometheusAuth, bearerToken: token }
···
120
class LokiExporter {
121
private buffer: LogEntry[] = []
122
private errorBuffer: ErrorEntry[] = []
123
-
private flushTimer?: Timer | NodeJS.Timer
124
private config: GrafanaConfig = {}
125
126
initialize(config: GrafanaConfig) {
···
3
* Integrates with Grafana Loki for logs and Prometheus/OTLP for metrics
4
*/
5
6
+
import type { LogEntry, ErrorEntry, MetricEntry } from './core'
7
+
import { metrics, type MeterProvider } from '@opentelemetry/api'
8
import { MeterProvider as SdkMeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
9
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
10
import { Resource } from '@opentelemetry/resources'
···
61
62
// Load from environment variables if not provided
63
if (!this.config.lokiUrl) {
64
+
this.config.lokiUrl = process.env.GRAFANA_LOKI_URL
65
}
66
67
if (!this.config.prometheusUrl) {
68
+
this.config.prometheusUrl = process.env.GRAFANA_PROMETHEUS_URL
69
}
70
71
// Load Loki authentication from environment
72
if (!this.config.lokiAuth?.bearerToken && !this.config.lokiAuth?.username) {
73
+
const token = process.env.GRAFANA_LOKI_TOKEN
74
+
const username = process.env.GRAFANA_LOKI_USERNAME
75
+
const password = process.env.GRAFANA_LOKI_PASSWORD
76
77
if (token) {
78
this.config.lokiAuth = { ...this.config.lokiAuth, bearerToken: token }
···
83
84
// Load Prometheus authentication from environment
85
if (!this.config.prometheusAuth?.bearerToken && !this.config.prometheusAuth?.username) {
86
+
const token = process.env.GRAFANA_PROMETHEUS_TOKEN
87
+
const username = process.env.GRAFANA_PROMETHEUS_USERNAME
88
+
const password = process.env.GRAFANA_PROMETHEUS_PASSWORD
89
90
if (token) {
91
this.config.prometheusAuth = { ...this.config.prometheusAuth, bearerToken: token }
···
120
class LokiExporter {
121
private buffer: LogEntry[] = []
122
private errorBuffer: ErrorEntry[] = []
123
+
private flushTimer?: NodeJS.Timeout
124
private config: GrafanaConfig = {}
125
126
initialize(config: GrafanaConfig) {
+128
-27
packages/@wisp/safe-fetch/src/index.ts
+128
-27
packages/@wisp/safe-fetch/src/index.ts
···
28
const MAX_BLOB_SIZE = 500 * 1024 * 1024; // 500MB
29
const MAX_REDIRECTS = 10;
30
31
function isBlockedHost(hostname: string): boolean {
32
const lowerHost = hostname.toLowerCase();
33
···
44
return false;
45
}
46
47
export async function safeFetch(
48
url: string,
49
-
options?: RequestInit & { maxSize?: number; timeout?: number }
50
): Promise<Response> {
51
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT;
52
const maxSize = options?.maxSize ?? MAX_RESPONSE_SIZE;
53
54
-
// Parse and validate URL
55
let parsedUrl: URL;
56
try {
57
parsedUrl = new URL(url);
···
68
throw new Error(`Blocked host: ${hostname}`);
69
}
70
71
-
const controller = new AbortController();
72
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
73
74
-
try {
75
-
const response = await fetch(url, {
76
-
...options,
77
-
signal: controller.signal,
78
-
redirect: 'follow',
79
-
headers: {
80
-
'User-Agent': 'wisp-place hosting-service',
81
-
...(options?.headers || {}),
82
-
},
83
-
});
84
85
-
const contentLength = response.headers.get('content-length');
86
-
if (contentLength && parseInt(contentLength, 10) > maxSize) {
87
-
throw new Error(`Response too large: ${contentLength} bytes`);
88
}
89
90
-
return response;
91
-
} catch (err) {
92
-
if (err instanceof Error && err.name === 'AbortError') {
93
-
throw new Error(`Request timeout after ${timeoutMs}ms`);
94
-
}
95
-
throw err;
96
-
} finally {
97
-
clearTimeout(timeoutId);
98
}
99
}
100
101
export async function safeFetchJson<T = any>(
102
url: string,
103
-
options?: RequestInit & { maxSize?: number; timeout?: number }
104
): Promise<T> {
105
const maxJsonSize = options?.maxSize ?? MAX_JSON_SIZE;
106
const response = await safeFetch(url, { ...options, maxSize: maxJsonSize });
···
146
147
export async function safeFetchBlob(
148
url: string,
149
-
options?: RequestInit & { maxSize?: number; timeout?: number }
150
): Promise<Uint8Array> {
151
const maxBlobSize = options?.maxSize ?? MAX_BLOB_SIZE;
152
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT_BLOB;
···
28
const MAX_BLOB_SIZE = 500 * 1024 * 1024; // 500MB
29
const MAX_REDIRECTS = 10;
30
31
+
// Retry configuration
32
+
const MAX_RETRIES = 3;
33
+
const INITIAL_RETRY_DELAY = 1000; // 1 second
34
+
const MAX_RETRY_DELAY = 10000; // 10 seconds
35
+
36
function isBlockedHost(hostname: string): boolean {
37
const lowerHost = hostname.toLowerCase();
38
···
49
return false;
50
}
51
52
+
/**
53
+
* Check if an error is retryable (network/SSL errors, not HTTP errors)
54
+
*/
55
+
function isRetryableError(err: unknown): boolean {
56
+
if (!(err instanceof Error)) return false;
57
+
58
+
// Network errors (ECONNRESET, ENOTFOUND, etc.)
59
+
const errorCode = (err as any).code;
60
+
if (errorCode) {
61
+
const retryableCodes = [
62
+
'ECONNRESET',
63
+
'ECONNREFUSED',
64
+
'ETIMEDOUT',
65
+
'ENOTFOUND',
66
+
'ENETUNREACH',
67
+
'EAI_AGAIN',
68
+
'EPIPE',
69
+
'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', // SSL/TLS handshake failures
70
+
'ERR_SSL_WRONG_VERSION_NUMBER',
71
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
72
+
];
73
+
if (retryableCodes.includes(errorCode)) {
74
+
return true;
75
+
}
76
+
}
77
+
78
+
// Timeout errors
79
+
if (err.name === 'AbortError' || err.message.includes('timeout')) {
80
+
return true;
81
+
}
82
+
83
+
// Fetch failures (generic network errors)
84
+
if (err.message.includes('fetch failed')) {
85
+
return true;
86
+
}
87
+
88
+
return false;
89
+
}
90
+
91
+
/**
92
+
* Sleep for a given number of milliseconds
93
+
*/
94
+
function sleep(ms: number): Promise<void> {
95
+
return new Promise(resolve => setTimeout(resolve, ms));
96
+
}
97
+
98
+
/**
99
+
* Retry a function with exponential backoff
100
+
*/
101
+
async function withRetry<T>(
102
+
fn: () => Promise<T>,
103
+
options: { maxRetries?: number; initialDelay?: number; maxDelay?: number; context?: string } = {}
104
+
): Promise<T> {
105
+
const maxRetries = options.maxRetries ?? MAX_RETRIES;
106
+
const initialDelay = options.initialDelay ?? INITIAL_RETRY_DELAY;
107
+
const maxDelay = options.maxDelay ?? MAX_RETRY_DELAY;
108
+
const context = options.context ?? 'Request';
109
+
110
+
let lastError: unknown;
111
+
112
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
113
+
try {
114
+
return await fn();
115
+
} catch (err) {
116
+
lastError = err;
117
+
118
+
// Don't retry if this is the last attempt or error is not retryable
119
+
if (attempt === maxRetries || !isRetryableError(err)) {
120
+
throw err;
121
+
}
122
+
123
+
// Calculate delay with exponential backoff
124
+
const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
125
+
126
+
const errorCode = (err as any)?.code;
127
+
const errorMsg = err instanceof Error ? err.message : String(err);
128
+
console.warn(
129
+
`${context} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${errorMsg}${errorCode ? ` [${errorCode}]` : ''} - retrying in ${delay}ms`
130
+
);
131
+
132
+
await sleep(delay);
133
+
}
134
+
}
135
+
136
+
throw lastError;
137
+
}
138
+
139
export async function safeFetch(
140
url: string,
141
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
142
): Promise<Response> {
143
+
const shouldRetry = options?.retry !== false; // Default to true
144
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT;
145
const maxSize = options?.maxSize ?? MAX_RESPONSE_SIZE;
146
147
+
// Parse and validate URL (done once, outside retry loop)
148
let parsedUrl: URL;
149
try {
150
parsedUrl = new URL(url);
···
161
throw new Error(`Blocked host: ${hostname}`);
162
}
163
164
+
const fetchFn = async () => {
165
+
const controller = new AbortController();
166
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
167
+
168
+
try {
169
+
const response = await fetch(url, {
170
+
...options,
171
+
signal: controller.signal,
172
+
redirect: 'follow',
173
+
headers: {
174
+
'User-Agent': 'wisp-place hosting-service',
175
+
...(options?.headers || {}),
176
+
},
177
+
});
178
179
+
const contentLength = response.headers.get('content-length');
180
+
if (contentLength && parseInt(contentLength, 10) > maxSize) {
181
+
throw new Error(`Response too large: ${contentLength} bytes`);
182
+
}
183
184
+
return response;
185
+
} catch (err) {
186
+
if (err instanceof Error && err.name === 'AbortError') {
187
+
throw new Error(`Request timeout after ${timeoutMs}ms`);
188
+
}
189
+
throw err;
190
+
} finally {
191
+
clearTimeout(timeoutId);
192
}
193
+
};
194
195
+
if (shouldRetry) {
196
+
return withRetry(fetchFn, { context: `Fetch ${parsedUrl.hostname}` });
197
+
} else {
198
+
return fetchFn();
199
}
200
}
201
202
export async function safeFetchJson<T = any>(
203
url: string,
204
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
205
): Promise<T> {
206
const maxJsonSize = options?.maxSize ?? MAX_JSON_SIZE;
207
const response = await safeFetch(url, { ...options, maxSize: maxJsonSize });
···
247
248
export async function safeFetchBlob(
249
url: string,
250
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
251
): Promise<Uint8Array> {
252
const maxBlobSize = options?.maxSize ?? MAX_BLOB_SIZE;
253
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT_BLOB;
+28
scripts/codegen.sh
+28
scripts/codegen.sh
···
···
1
+
#!/bin/bash
2
+
set -e
3
+
4
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
6
+
7
+
# Parse arguments
8
+
AUTO_ACCEPT=""
9
+
if [[ "$1" == "-y" || "$1" == "--yes" ]]; then
10
+
AUTO_ACCEPT="yes |"
11
+
fi
12
+
13
+
echo "=== Generating TypeScript lexicons ==="
14
+
cd "$ROOT_DIR/packages/@wisp/lexicons"
15
+
eval "$AUTO_ACCEPT npm run codegen"
16
+
17
+
echo "=== Generating Rust lexicons ==="
18
+
echo "Installing jacquard-lexgen..."
19
+
cargo install jacquard-lexgen --version 0.9.5 2>/dev/null || true
20
+
echo "Running jacquard-codegen..."
21
+
echo " Input: $ROOT_DIR/lexicons"
22
+
echo " Output: $ROOT_DIR/cli/crates/lexicons/src"
23
+
jacquard-codegen -i "$ROOT_DIR/lexicons" -o "$ROOT_DIR/cli/crates/lexicons/src"
24
+
25
+
# Add extern crate alloc for the macro to work
26
+
sed -i '' '1s/^/extern crate alloc;\n\n/' "$ROOT_DIR/cli/crates/lexicons/src/lib.rs"
27
+
28
+
echo "=== Done ==="
+1
-1
tsconfig.json
+1
-1
tsconfig.json
···
33
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35
"types": [
36
-
"bun-types"
37
] /* Specify type package names to be included without being referenced in a source file. */,
38
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
···
33
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35
"types": [
36
+
"bun"
37
] /* Specify type package names to be included without being referenced in a source file. */,
38
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */