Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

Compare changes

Choose any two refs to compare.

+1
.gitignore
···
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.env
# dependencies
···
+
.research/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.env
# dependencies
+8 -2
apps/hosting-service/src/index.ts
···
import { createLogger, initializeGrafanaExporters } from '@wisp/observability';
import { mkdirSync, existsSync } from 'fs';
import { backfillCache } from './lib/backfill';
-
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode } from './lib/db';
// Initialize Grafana exporters if configured
initializeGrafanaExporters({
···
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
// Parse CLI arguments
const args = process.argv.slice(2);
···
console.log('๐Ÿ”„ Backfill requested, starting cache backfill...');
backfillCache({
skipExisting: true,
-
concurrency: 3,
}).then((stats) => {
console.log('โœ… Cache backfill completed');
}).catch((err) => {
···
Cache: ${CACHE_DIR}
Firehose: Connected to Firehose
Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'}
`);
// Graceful shutdown
···
console.log('\n๐Ÿ›‘ Shutting down...');
firehose.stop();
stopDomainCacheCleanup();
server.close();
process.exit(0);
});
···
console.log('\n๐Ÿ›‘ Shutting down...');
firehose.stop();
stopDomainCacheCleanup();
server.close();
process.exit(0);
});
···
import { createLogger, initializeGrafanaExporters } from '@wisp/observability';
import { mkdirSync, existsSync } from 'fs';
import { backfillCache } from './lib/backfill';
+
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode, closeDatabase } from './lib/db';
// Initialize Grafana exporters if configured
initializeGrafanaExporters({
···
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
+
const BACKFILL_CONCURRENCY = process.env.BACKFILL_CONCURRENCY
+
? parseInt(process.env.BACKFILL_CONCURRENCY)
+
: undefined; // Let backfill.ts default (10) apply
// Parse CLI arguments
const args = process.argv.slice(2);
···
console.log('๐Ÿ”„ Backfill requested, starting cache backfill...');
backfillCache({
skipExisting: true,
+
concurrency: BACKFILL_CONCURRENCY,
}).then((stats) => {
console.log('โœ… Cache backfill completed');
}).catch((err) => {
···
Cache: ${CACHE_DIR}
Firehose: Connected to Firehose
Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'}
+
Backfill: ${backfillOnStartup ? `ENABLED (concurrency: ${BACKFILL_CONCURRENCY || 10})` : 'DISABLED'}
`);
// Graceful shutdown
···
console.log('\n๐Ÿ›‘ Shutting down...');
firehose.stop();
stopDomainCacheCleanup();
+
await closeDatabase();
server.close();
process.exit(0);
});
···
console.log('\n๐Ÿ›‘ Shutting down...');
firehose.stop();
stopDomainCacheCleanup();
+
await closeDatabase();
server.close();
process.exit(0);
});
+65 -57
apps/hosting-service/src/lib/backfill.ts
···
console.log(`โš™๏ธ Limited to ${maxSites} sites for backfill`);
}
-
// Process sites in batches
-
const batches: typeof sites[] = [];
-
for (let i = 0; i < sites.length; i += concurrency) {
-
batches.push(sites.slice(i, i + concurrency));
-
}
-
let processed = 0;
-
for (const batch of batches) {
-
await Promise.all(
-
batch.map(async (site) => {
-
try {
-
// Check if already cached
-
if (skipExisting && isCached(site.did, site.rkey)) {
-
stats.skipped++;
-
processed++;
-
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
-
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
-
return;
-
}
-
// Fetch site record
-
const siteData = await fetchSiteRecord(site.did, site.rkey);
-
if (!siteData) {
-
stats.failed++;
-
processed++;
-
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
-
console.log(`โŒ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
-
return;
-
}
-
-
// Get PDS endpoint
-
const pdsEndpoint = await getPdsForDid(site.did);
-
if (!pdsEndpoint) {
-
stats.failed++;
-
processed++;
-
logger.error('PDS not found during backfill', null, { did: site.did });
-
console.log(`โŒ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
-
return;
-
}
-
// Mark site as being cached to prevent serving stale content during update
-
markSiteAsBeingCached(site.did, site.rkey);
-
try {
-
// Download and cache site
-
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
-
// Clear redirect rules cache since the site was updated
-
clearRedirectRulesCache(site.did, site.rkey);
-
stats.cached++;
-
processed++;
-
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
-
console.log(`โœ… [${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
-
} finally {
-
// Always unmark, even if caching fails
-
unmarkSiteAsBeingCached(site.did, site.rkey);
-
}
-
} catch (err) {
stats.failed++;
processed++;
-
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
-
console.log(`โŒ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
}
-
})
-
);
}
stats.duration = Date.now() - startTime;
···
console.log(`โš™๏ธ Limited to ${maxSites} sites for backfill`);
}
+
// Process sites with sliding window concurrency pool
+
const executing = new Set<Promise<void>>();
let processed = 0;
+
for (const site of sites) {
+
// Create task for this site
+
const processSite = async () => {
+
try {
+
// Check if already cached
+
if (skipExisting && isCached(site.did, site.rkey)) {
+
stats.skipped++;
+
processed++;
+
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
+
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
+
return;
+
}
+
// Fetch site record
+
const siteData = await fetchSiteRecord(site.did, site.rkey);
+
if (!siteData) {
+
stats.failed++;
+
processed++;
+
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
+
console.log(`โŒ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
+
return;
+
}
+
// Get PDS endpoint
+
const pdsEndpoint = await getPdsForDid(site.did);
+
if (!pdsEndpoint) {
stats.failed++;
processed++;
+
logger.error('PDS not found during backfill', null, { did: site.did });
+
console.log(`โŒ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
+
return;
+
}
+
+
// Mark site as being cached to prevent serving stale content during update
+
markSiteAsBeingCached(site.did, site.rkey);
+
+
try {
+
// Download and cache site
+
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
+
// Clear redirect rules cache since the site was updated
+
clearRedirectRulesCache(site.did, site.rkey);
+
stats.cached++;
+
processed++;
+
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
+
console.log(`โœ… [${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
+
} finally {
+
// Always unmark, even if caching fails
+
unmarkSiteAsBeingCached(site.did, site.rkey);
}
+
} catch (err) {
+
stats.failed++;
+
processed++;
+
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
+
console.log(`โŒ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
+
}
+
};
+
+
// Add to executing pool and remove when done
+
const promise = processSite().finally(() => executing.delete(promise));
+
executing.add(promise);
+
+
// When pool is full, wait for at least one to complete
+
if (executing.size >= concurrency) {
+
await Promise.race(executing);
+
}
}
+
+
// Wait for all remaining tasks to complete
+
await Promise.all(executing);
stats.duration = Date.now() - startTime;
+32 -1
apps/hosting-service/src/lib/db.ts
···
return hashNum & 0x7FFFFFFFFFFFFFFFn;
}
/**
* Acquire a distributed lock using PostgreSQL advisory locks
* Returns true if lock was acquired, false if already held by another instance
···
try {
const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
-
return result[0]?.acquired === true;
} catch (err) {
console.error('Failed to acquire lock', { key, error: err });
return false;
···
try {
await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
} catch (err) {
console.error('Failed to release lock', { key, error: err });
}
}
···
return hashNum & 0x7FFFFFFFFFFFFFFFn;
}
+
// Track active locks for cleanup on shutdown
+
const activeLocks = new Set<string>();
+
/**
* Acquire a distributed lock using PostgreSQL advisory locks
* Returns true if lock was acquired, false if already held by another instance
···
try {
const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
+
const acquired = result[0]?.acquired === true;
+
if (acquired) {
+
activeLocks.add(key);
+
}
+
return acquired;
} catch (err) {
console.error('Failed to acquire lock', { key, error: err });
return false;
···
try {
await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
+
activeLocks.delete(key);
} catch (err) {
console.error('Failed to release lock', { key, error: err });
+
// Still remove from tracking even if unlock fails
+
activeLocks.delete(key);
+
}
+
}
+
+
/**
+
* Close all database connections
+
* Call this during graceful shutdown
+
*/
+
export async function closeDatabase(): Promise<void> {
+
try {
+
// Release all active advisory locks before closing connections
+
if (activeLocks.size > 0) {
+
console.log(`[DB] Releasing ${activeLocks.size} active advisory locks before shutdown`);
+
for (const key of activeLocks) {
+
await releaseLock(key);
+
}
+
}
+
+
await sql.end({ timeout: 5 });
+
console.log('[DB] Database connections closed');
+
} catch (err) {
+
console.error('[DB] Error closing database connections:', err);
}
}
+26 -2
apps/hosting-service/src/lib/utils.ts
···
export async function fetchSiteRecord(did: string, rkey: string): Promise<{ record: WispFsRecord; cid: string } | null> {
try {
const pdsEndpoint = await getPdsForDid(did);
-
if (!pdsEndpoint) return null;
const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
const data = await safeFetchJson(url);
···
cid: data.cid || ''
};
} catch (err) {
-
console.error('Failed to fetch site record', did, rkey, err);
return null;
}
}
···
export async function fetchSiteRecord(did: string, rkey: string): Promise<{ record: WispFsRecord; cid: string } | null> {
try {
const pdsEndpoint = await getPdsForDid(did);
+
if (!pdsEndpoint) {
+
console.error('[hosting-service] Failed to get PDS endpoint for DID', { did, rkey });
+
return null;
+
}
const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
const data = await safeFetchJson(url);
···
cid: data.cid || ''
};
} catch (err) {
+
const errorCode = (err as any)?.code;
+
const errorMsg = err instanceof Error ? err.message : String(err);
+
+
// Better error logging to distinguish between network errors and 404s
+
if (errorMsg.includes('HTTP 404') || errorMsg.includes('Not Found')) {
+
console.log('[hosting-service] Site record not found', { did, rkey });
+
} else if (errorCode && ['ECONNRESET', 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', 'ETIMEDOUT'].includes(errorCode)) {
+
console.error('[hosting-service] Network/SSL error fetching site record (after retries)', {
+
did,
+
rkey,
+
error: errorMsg,
+
code: errorCode
+
});
+
} else {
+
console.error('[hosting-service] Failed to fetch site record', {
+
did,
+
rkey,
+
error: errorMsg,
+
code: errorCode
+
});
+
}
+
return null;
}
}
+1 -1
apps/main-app/package.json
···
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
-
"elysia": "latest",
"ignore": "^7.0.5",
"iron-session": "^8.0.4",
"lucide-react": "^0.546.0",
···
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+
"elysia": "^1.4.18",
"ignore": "^7.0.5",
"iron-session": "^8.0.4",
"lucide-react": "^0.546.0",
+4 -4
apps/main-app/public/acceptable-use/acceptable-use.tsx
···
function AcceptableUsePage() {
return (
-
<div className="min-h-screen bg-background">
{/* Header */}
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
</div>
{/* Footer */}
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
···
function AcceptableUsePage() {
return (
+
<div className="w-full min-h-screen bg-background flex flex-col">
{/* Header */}
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
</div>
{/* Footer */}
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
+1 -1
apps/main-app/public/components/ui/checkbox.tsx
···
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
-
"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",
className
)}
{...props}
···
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
+
"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",
className
)}
{...props}
+6 -6
apps/main-app/public/editor/editor.tsx
···
return (
<div className="w-full min-h-screen bg-background">
{/* Header Skeleton */}
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
}
return (
-
<div className="w-full min-h-screen bg-background">
{/* Header */}
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
</div>
{/* Footer */}
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
···
return (
<div className="w-full min-h-screen bg-background">
{/* Header Skeleton */}
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
}
return (
+
<div className="w-full min-h-screen bg-background flex flex-col">
{/* Header */}
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
<span className="text-xl font-semibold text-foreground">
···
</div>
{/* Footer */}
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
+74 -66
apps/main-app/public/index.tsx
···
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const navigationKeys = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Enter', 'Escape']
-
// Mark that we should preserve the index for navigation keys
if (navigationKeys.includes(e.key)) {
preserveIndexRef.current = true
···
setIndex(-1)
setIsOpen(false)
onSelect?.(handle)
-
// Auto-submit the form if enabled
if (autoSubmit && inputRef.current) {
const form = inputRef.current.closest('form')
···
height: 'calc(1.5rem + 12px)',
borderRadius: '4px',
cursor: 'pointer',
-
backgroundColor: i === index ? 'hsl(var(--accent) / 0.5)' : 'transparent',
transition: 'background-color 0.1s'
}}
onMouseEnter={() => setIndex(i)}
···
width: '1.5rem',
height: '1.5rem',
borderRadius: '50%',
-
backgroundColor: 'hsl(var(--muted))',
overflow: 'hidden',
flexShrink: 0
}}
···
<img
src={actor.avatar}
alt=""
style={{
display: 'block',
width: '100%',
···
return (
<>
-
<div className="min-h-screen">
{/* Header */}
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
-
<span className="text-xl font-semibold text-foreground">
wisp.place
</span>
</div>
-
<div className="flex items-center gap-3">
<Button
-
variant="ghost"
size="sm"
onClick={() => setShowForm(true)}
>
Sign In
</Button>
-
<Button
-
size="sm"
-
className="bg-accent text-accent-foreground hover:bg-accent/90"
-
asChild
-
>
-
<a href="https://docs.wisp.place" target="_blank" rel="noopener noreferrer">
-
Read the Docs
-
</a>
-
</Button>
</div>
</div>
</header>
{/* Hero Section */}
-
<section className="container mx-auto px-4 py-20 md:py-32">
<div className="max-w-4xl mx-auto text-center">
-
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 mb-8">
-
<span className="w-2 h-2 bg-accent rounded-full animate-pulse"></span>
-
<span className="text-sm text-foreground">
-
Built on AT Protocol
-
</span>
-
</div>
-
-
<h1 className="text-5xl md:text-7xl font-bold text-balance mb-6 leading-tight">
-
Your Website.Your Control. Lightning Fast.
</h1>
-
<p className="text-xl md:text-2xl text-muted-foreground text-balance mb-10 leading-relaxed max-w-3xl mx-auto">
-
Host static sites in your AT Protocol account. You
-
keep ownership and control. We just serve them fast
-
through our CDN.
</p>
-
<div className="max-w-md mx-auto relative">
<div
-
className={`transition-all duration-500 ease-in-out ${
-
showForm
-
? 'opacity-0 -translate-y-5 pointer-events-none'
-
: 'opacity-100 translate-y-0'
-
}`}
>
-
<Button
-
size="lg"
-
className="bg-primary text-primary-foreground hover:bg-primary/90 text-lg px-8 py-6 w-full"
-
onClick={() => setShowForm(true)}
-
>
-
Log in with AT Proto
-
<ArrowRight className="ml-2 w-5 h-5" />
-
</Button>
</div>
<div
-
className={`transition-all duration-500 ease-in-out absolute inset-0 ${
-
showForm
-
? 'opacity-100 translate-y-0'
-
: 'opacity-0 translate-y-5 pointer-events-none'
-
}`}
>
<form
onSubmit={async (e) => {
···
</ActorTypeahead>
<button
type="submit"
-
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"
>
Continue
<ArrowRight className="ml-2 w-5 h-5" />
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
-
Upload your static site
</h3>
<p className="text-muted-foreground">
-
Your HTML, CSS, and JavaScript files are
-
stored in your AT Protocol account as
-
gzipped blobs and a manifest record.
</p>
</div>
</div>
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
-
We serve it globally
</h3>
<p className="text-muted-foreground">
-
Wisp.place reads your site from your
-
account and delivers it through our CDN
-
for fast loading anywhere.
</p>
</div>
</div>
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
-
You stay in control
</h3>
<p className="text-muted-foreground">
-
Update or remove your site anytime
-
through your AT Protocol account. No
-
lock-in, no middleman ownership.
</p>
</div>
</div>
···
</section>
{/* Footer */}
-
<footer className="border-t border-border/40 bg-muted/20">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
···
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const navigationKeys = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Enter', 'Escape']
+
// Mark that we should preserve the index for navigation keys
if (navigationKeys.includes(e.key)) {
preserveIndexRef.current = true
···
setIndex(-1)
setIsOpen(false)
onSelect?.(handle)
+
// Auto-submit the form if enabled
if (autoSubmit && inputRef.current) {
const form = inputRef.current.closest('form')
···
height: 'calc(1.5rem + 12px)',
borderRadius: '4px',
cursor: 'pointer',
+
backgroundColor: i === index ? 'color-mix(in oklch, var(--accent) 50%, transparent)' : 'transparent',
transition: 'background-color 0.1s'
}}
onMouseEnter={() => setIndex(i)}
···
width: '1.5rem',
height: '1.5rem',
borderRadius: '50%',
+
backgroundColor: 'var(--muted)',
overflow: 'hidden',
flexShrink: 0
}}
···
<img
src={actor.avatar}
alt=""
+
loading="lazy"
style={{
display: 'block',
width: '100%',
···
return (
<>
+
<div className="w-full min-h-screen flex flex-col">
{/* Header */}
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
+
<span className="text-lg font-semibold text-foreground">
wisp.place
</span>
</div>
+
<div className="flex items-center gap-4">
+
<a
+
href="https://docs.wisp.place"
+
target="_blank"
+
rel="noopener noreferrer"
+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
+
>
+
Read the Docs
+
</a>
<Button
+
variant="outline"
size="sm"
+
className="btn-hover-lift"
onClick={() => setShowForm(true)}
>
Sign In
</Button>
</div>
</div>
</header>
{/* Hero Section */}
+
<section className="container mx-auto px-4 py-24 md:py-36">
<div className="max-w-4xl mx-auto text-center">
+
{/* Main Headline */}
+
<h1 className="animate-fade-in-up animate-delay-100 text-5xl md:text-7xl font-bold mb-2 leading-tight tracking-tight">
+
Deploy Anywhere.
+
</h1>
+
<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">
+
For Free. Forever.
</h1>
+
{/* Subheadline */}
+
<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">
+
The easiest way to deploy and orchestrate static sites.
+
Push updates instantly. Host on our infrastructure or yours.
+
All powered by AT Protocol.
</p>
+
{/* CTA Buttons */}
+
<div className="animate-fade-in-up animate-delay-400 max-w-lg mx-auto relative">
<div
+
className={`transition-all duration-500 ease-in-out ${showForm
+
? 'opacity-0 -translate-y-5 pointer-events-none absolute inset-0'
+
: 'opacity-100 translate-y-0'
+
}`}
>
+
<div className="flex flex-col sm:flex-row gap-3 justify-center">
+
<Button
+
size="lg"
+
className="bg-foreground text-background hover:bg-foreground/90 text-base px-6 py-5 btn-hover-lift"
+
onClick={() => setShowForm(true)}
+
>
+
<span className="mr-2 font-bold">@</span>
+
Deploy with AT
+
</Button>
+
<Button
+
variant="outline"
+
size="lg"
+
className="text-base px-6 py-5 btn-hover-lift"
+
asChild
+
>
+
<a href="https://docs.wisp.place/cli/" target="_blank" rel="noopener noreferrer">
+
<span className="font-mono mr-2 text-muted-foreground">&gt;_</span>
+
Install wisp-cli
+
</a>
+
</Button>
+
</div>
</div>
<div
+
className={`transition-all duration-500 ease-in-out ${showForm
+
? 'opacity-100 translate-y-0'
+
: 'opacity-0 translate-y-5 pointer-events-none absolute inset-0'
+
}`}
>
<form
onSubmit={async (e) => {
···
</ActorTypeahead>
<button
type="submit"
+
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"
>
Continue
<ArrowRight className="ml-2 w-5 h-5" />
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
+
Drop in your files
</h3>
<p className="text-muted-foreground">
+
Upload your site through our dashboard or push with the CLI.
+
Everything gets stored directly in your AT Protocol account.
</p>
</div>
</div>
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
+
We handle the rest
</h3>
<p className="text-muted-foreground">
+
Your site goes live instantly on our global CDN.
+
Custom domains, HTTPS, cachingโ€”all automatic.
</p>
</div>
</div>
···
</div>
<div>
<h3 className="text-xl font-semibold mb-2">
+
Push updates instantly
</h3>
<p className="text-muted-foreground">
+
Ship changes in seconds. Update through the dashboard,
+
run wisp-cli deploy, or wire up your CI/CD pipeline.
</p>
</div>
</div>
···
</section>
{/* Footer */}
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
<div className="container mx-auto px-4 py-8">
<div className="text-center text-sm text-muted-foreground">
<p>
+16 -19
apps/main-app/public/onboarding/onboarding.tsx
···
return (
<div className="w-full min-h-screen bg-background">
{/* Header */}
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<Globe className="w-5 h-5 text-primary-foreground" />
···
<div className="mb-8">
<div className="flex items-center justify-center gap-2 mb-4">
<div
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
-
step === 'domain'
-
? 'bg-primary text-primary-foreground'
-
: 'bg-green-500 text-white'
-
}`}
>
{step === 'domain' ? (
'1'
···
</div>
<div className="w-16 h-0.5 bg-border"></div>
<div
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
-
step === 'upload'
-
? 'bg-primary text-primary-foreground'
-
: step === 'domain'
-
? 'bg-muted text-muted-foreground'
-
: 'bg-green-500 text-white'
-
}`}
>
{step === 'complete' ? (
<CheckCircle2 className="w-5 h-5" />
···
{!isCheckingAvailability &&
isAvailable !== null && (
<div
-
className={`absolute right-3 top-1/2 -translate-y-1/2 ${
-
isAvailable
-
? 'text-green-500'
-
: 'text-red-500'
-
}`}
>
{isAvailable ? 'โœ“' : 'โœ—'}
</div>
···
return (
<div className="w-full min-h-screen bg-background">
{/* Header */}
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<Globe className="w-5 h-5 text-primary-foreground" />
···
<div className="mb-8">
<div className="flex items-center justify-center gap-2 mb-4">
<div
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'domain'
+
? 'bg-primary text-primary-foreground'
+
: 'bg-green-500 text-white'
+
}`}
>
{step === 'domain' ? (
'1'
···
</div>
<div className="w-16 h-0.5 bg-border"></div>
<div
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'upload'
+
? 'bg-primary text-primary-foreground'
+
: step === 'domain'
+
? 'bg-muted text-muted-foreground'
+
: 'bg-green-500 text-white'
+
}`}
>
{step === 'complete' ? (
<CheckCircle2 className="w-5 h-5" />
···
{!isCheckingAvailability &&
isAvailable !== null && (
<div
+
className={`absolute right-3 top-1/2 -translate-y-1/2 ${isAvailable
+
? 'text-green-500'
+
: 'text-red-500'
+
}`}
>
{isAvailable ? 'โœ“' : 'โœ—'}
</div>
+212 -39
apps/main-app/public/styles/global.css
···
:root {
color-scheme: light;
-
/* Warm beige background inspired by Sunset design #E9DDD8 */
-
--background: oklch(0.90 0.012 35);
-
/* Very dark brown text for strong contrast #2A2420 */
-
--foreground: oklch(0.18 0.01 30);
-
/* Slightly lighter card background */
-
--card: oklch(0.93 0.01 35);
-
--card-foreground: oklch(0.18 0.01 30);
-
--popover: oklch(0.93 0.01 35);
-
--popover-foreground: oklch(0.18 0.01 30);
-
/* Dark brown primary inspired by #645343 */
-
--primary: oklch(0.35 0.02 35);
-
--primary-foreground: oklch(0.95 0.01 35);
-
/* Bright pink accent for links #FFAAD2 */
-
--accent: oklch(0.78 0.15 345);
-
--accent-foreground: oklch(0.18 0.01 30);
-
/* Medium taupe secondary inspired by #867D76 */
-
--secondary: oklch(0.52 0.015 30);
-
--secondary-foreground: oklch(0.95 0.01 35);
-
/* Light warm muted background */
--muted: oklch(0.88 0.01 35);
-
--muted-foreground: oklch(0.42 0.015 30);
-
--border: oklch(0.75 0.015 30);
-
--input: oklch(0.92 0.01 35);
-
--ring: oklch(0.72 0.08 15);
-
--destructive: oklch(0.577 0.245 27.325);
-
--destructive-foreground: oklch(0.985 0 0);
-
--chart-1: oklch(0.78 0.15 345);
--chart-2: oklch(0.32 0.04 285);
-
--chart-3: oklch(0.56 0.08 220);
-
--chart-4: oklch(0.85 0.02 130);
-
--chart-5: oklch(0.93 0.03 85);
--radius: 0.75rem;
-
--sidebar: oklch(0.985 0 0);
-
--sidebar-foreground: oklch(0.145 0 0);
-
--sidebar-primary: oklch(0.205 0 0);
-
--sidebar-primary-foreground: oklch(0.985 0 0);
-
--sidebar-accent: oklch(0.97 0 0);
-
--sidebar-accent-foreground: oklch(0.205 0 0);
-
--sidebar-border: oklch(0.922 0 0);
-
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
···
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@keyframes arrow-bounce {
-
0%, 100% {
transform: translateX(0);
}
50% {
transform: translateX(4px);
}
···
border-radius: 0.5rem;
padding: 1rem;
overflow-x: auto;
-
border: 1px solid hsl(var(--border));
}
.shiki-wrapper pre {
margin: 0 !important;
padding: 0 !important;
}
···
:root {
color-scheme: light;
+
/* Warm beige background inspired by Sunset design */
+
--background: oklch(0.92 0.012 35);
+
/* Very dark brown text for strong contrast */
+
--foreground: oklch(0.15 0.015 30);
+
/* Slightly lighter card background for elevation */
+
--card: oklch(0.95 0.008 35);
+
--card-foreground: oklch(0.15 0.015 30);
+
--popover: oklch(0.96 0.006 35);
+
--popover-foreground: oklch(0.15 0.015 30);
+
/* Dark brown primary - darker for better contrast */
+
--primary: oklch(0.30 0.025 35);
+
--primary-foreground: oklch(0.96 0.008 35);
+
/* Deeper pink accent for better visibility */
+
--accent: oklch(0.65 0.18 345);
+
--accent-foreground: oklch(0.15 0.015 30);
+
/* Darker taupe secondary for better contrast */
+
--secondary: oklch(0.85 0.012 30);
+
--secondary-foreground: oklch(0.25 0.02 30);
+
/* Muted areas with better distinction */
--muted: oklch(0.88 0.01 35);
+
--muted-foreground: oklch(0.35 0.02 30);
+
/* Significantly darker border for visibility */
+
--border: oklch(0.65 0.02 30);
+
/* Input backgrounds lighter than cards */
+
--input: oklch(0.97 0.005 35);
+
--ring: oklch(0.55 0.12 345);
+
--destructive: oklch(0.50 0.20 25);
+
--destructive-foreground: oklch(0.98 0 0);
+
--chart-1: oklch(0.65 0.18 345);
--chart-2: oklch(0.32 0.04 285);
+
--chart-3: oklch(0.50 0.10 220);
+
--chart-4: oklch(0.70 0.08 130);
+
--chart-5: oklch(0.75 0.06 85);
--radius: 0.75rem;
+
--sidebar: oklch(0.94 0.008 35);
+
--sidebar-foreground: oklch(0.15 0.015 30);
+
--sidebar-primary: oklch(0.30 0.025 35);
+
--sidebar-primary-foreground: oklch(0.96 0.008 35);
+
--sidebar-accent: oklch(0.90 0.01 35);
+
--sidebar-accent-foreground: oklch(0.20 0.02 30);
+
--sidebar-border: oklch(0.65 0.02 30);
+
--sidebar-ring: oklch(0.55 0.12 345);
}
.dark {
···
* {
@apply border-border outline-ring/50;
}
+
+
html {
+
scrollbar-gutter: stable;
+
}
+
body {
@apply bg-background text-foreground;
}
}
@keyframes arrow-bounce {
+
+
0%,
+
100% {
transform: translateX(0);
}
+
50% {
transform: translateX(4px);
}
···
border-radius: 0.5rem;
padding: 1rem;
overflow-x: auto;
+
border: 1px solid var(--border);
}
.shiki-wrapper pre {
margin: 0 !important;
padding: 0 !important;
}
+
+
/* ========== Landing Page Animations ========== */
+
+
/* Animated gradient for headline text */
+
@keyframes gradient-shift {
+
+
0%,
+
100% {
+
background-position: 0% 50%;
+
}
+
+
50% {
+
background-position: 100% 50%;
+
}
+
}
+
+
.text-gradient-animate {
+
background: linear-gradient(90deg,
+
oklch(0.55 0.22 350),
+
oklch(0.60 0.24 10),
+
oklch(0.55 0.22 350));
+
background-size: 200% auto;
+
-webkit-background-clip: text;
+
background-clip: text;
+
-webkit-text-fill-color: transparent;
+
animation: gradient-shift 4s ease-in-out infinite;
+
}
+
+
.dark .text-gradient-animate {
+
background: linear-gradient(90deg,
+
oklch(0.75 0.12 295),
+
oklch(0.85 0.10 5),
+
oklch(0.75 0.12 295));
+
background-size: 200% auto;
+
-webkit-background-clip: text;
+
background-clip: text;
+
-webkit-text-fill-color: transparent;
+
}
+
+
/* Floating/breathing animation for hero elements */
+
@keyframes float {
+
+
0%,
+
100% {
+
transform: translateY(0);
+
}
+
+
50% {
+
transform: translateY(-8px);
+
}
+
}
+
+
.animate-float {
+
animation: float 3s ease-in-out infinite;
+
}
+
+
.animate-float-delayed {
+
animation: float 3s ease-in-out infinite;
+
animation-delay: 0.5s;
+
}
+
+
/* Staggered fade-in animation */
+
@keyframes fade-in-up {
+
from {
+
opacity: 0;
+
transform: translateY(20px);
+
}
+
+
to {
+
opacity: 1;
+
transform: translateY(0);
+
}
+
}
+
+
.animate-fade-in-up {
+
animation: fade-in-up 0.6s ease-out forwards;
+
opacity: 0;
+
}
+
+
.animate-delay-100 {
+
animation-delay: 0.1s;
+
}
+
+
.animate-delay-200 {
+
animation-delay: 0.2s;
+
}
+
+
.animate-delay-300 {
+
animation-delay: 0.3s;
+
}
+
+
.animate-delay-400 {
+
animation-delay: 0.4s;
+
}
+
+
.animate-delay-500 {
+
animation-delay: 0.5s;
+
}
+
+
.animate-delay-600 {
+
animation-delay: 0.6s;
+
}
+
+
/* Terminal cursor blink */
+
@keyframes cursor-blink {
+
+
0%,
+
50% {
+
opacity: 1;
+
}
+
+
51%,
+
100% {
+
opacity: 0;
+
}
+
}
+
+
.animate-cursor-blink {
+
animation: cursor-blink 1s step-end infinite;
+
}
+
+
/* Button hover scale effect */
+
.btn-hover-lift {
+
transition: all 0.2s ease-out;
+
}
+
+
.btn-hover-lift:hover {
+
transform: translateY(-2px);
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+
}
+
+
.btn-hover-lift:active {
+
transform: translateY(0);
+
}
+
+
/* Subtle pulse for feature dots */
+
@keyframes dot-pulse {
+
+
0%,
+
100% {
+
transform: scale(1);
+
opacity: 1;
+
}
+
+
50% {
+
transform: scale(1.2);
+
opacity: 0.8;
+
}
+
}
+
+
.animate-dot-pulse {
+
animation: dot-pulse 2s ease-in-out infinite;
+
}
+
+
.animate-dot-pulse-delayed-1 {
+
animation: dot-pulse 2s ease-in-out infinite;
+
animation-delay: 0.3s;
+
}
+
+
.animate-dot-pulse-delayed-2 {
+
animation: dot-pulse 2s ease-in-out infinite;
+
animation-delay: 0.6s;
+
}
+23 -3
apps/main-app/src/index.ts
···
cleanupExpiredSessions,
rotateKeysIfNeeded
} from './lib/oauth-client'
-
import { getCookieSecret } from './lib/db'
import { authRoutes } from './routes/auth'
import { wispRoutes } from './routes/wisp'
import { domainRoutes } from './routes/domain'
···
setInterval(runMaintenance, 60 * 60 * 1000)
// Start DNS verification worker (runs every 10 minutes)
const dnsVerifier = new DNSVerificationWorker(
10 * 60 * 1000, // 10 minutes
(msg, data) => {
···
}
)
-
dnsVerifier.start()
-
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
export const app = new Elysia({
serve: {
···
console.log(
`๐ŸฆŠ Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
···
cleanupExpiredSessions,
rotateKeysIfNeeded
} from './lib/oauth-client'
+
import { getCookieSecret, closeDatabase } from './lib/db'
import { authRoutes } from './routes/auth'
import { wispRoutes } from './routes/wisp'
import { domainRoutes } from './routes/domain'
···
setInterval(runMaintenance, 60 * 60 * 1000)
// Start DNS verification worker (runs every 10 minutes)
+
// Can be disabled via DISABLE_DNS_WORKER=true environment variable
const dnsVerifier = new DNSVerificationWorker(
10 * 60 * 1000, // 10 minutes
(msg, data) => {
···
}
)
+
if (Bun.env.DISABLE_DNS_WORKER !== 'true') {
+
dnsVerifier.start()
+
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
+
} else {
+
logger.info('DNS Verifier disabled via DISABLE_DNS_WORKER environment variable')
+
}
export const app = new Elysia({
serve: {
···
console.log(
`๐ŸฆŠ Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
+
+
// Graceful shutdown
+
process.on('SIGINT', async () => {
+
console.log('\n๐Ÿ›‘ Shutting down...')
+
dnsVerifier.stop()
+
await closeDatabase()
+
process.exit(0)
+
})
+
+
process.on('SIGTERM', async () => {
+
console.log('\n๐Ÿ›‘ Shutting down...')
+
dnsVerifier.stop()
+
await closeDatabase()
+
process.exit(0)
+
})
+13
apps/main-app/src/lib/db.ts
···
console.log('[CookieSecret] Generated new cookie signing secret');
return secret;
};
···
console.log('[CookieSecret] Generated new cookie signing secret');
return secret;
};
+
+
/**
+
* Close database connection
+
* Call this during graceful shutdown
+
*/
+
export const closeDatabase = async (): Promise<void> => {
+
try {
+
await db.end();
+
console.log('[DB] Database connection closed');
+
} catch (err) {
+
console.error('[DB] Error closing database connection:', err);
+
}
+
};
+10 -16
apps/main-app/src/routes/wisp.ts
···
const logger = createLogger('main-app')
-
function isValidSiteName(siteName: string): boolean {
if (!siteName || typeof siteName !== 'string') return false;
// Length check (AT Protocol rkey limit)
···
continue;
}
-
console.log(`Processing file ${i + 1}/${fileArray.length}:`, file.name, file.size, 'bytes');
updateJobProgress(jobId, {
filesProcessed: i + 1,
-
currentFile: file.name
});
// Skip files that match ignore patterns
-
const normalizedPath = file.name.replace(/^[^\/]*\//, '');
if (shouldIgnore(ignoreMatcher, normalizedPath)) {
-
console.log(`Skipping ignored file: ${file.name}`);
skippedFiles.push({
-
name: file.name,
reason: 'matched ignore pattern'
});
continue;
···
const maxSize = MAX_FILE_SIZE;
if (file.size > maxSize) {
skippedFiles.push({
-
name: file.name,
reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)`
});
continue;
···
// Text files: compress AND base64 encode
finalContent = Buffer.from(compressedContent.toString('base64'), 'binary');
base64Encoded = true;
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
-
console.log(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
-
logger.info(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
} else {
// Audio files: just compress, no base64
finalContent = compressedContent;
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
-
console.log(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
-
logger.info(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
}
} else {
// Binary files: upload directly
finalContent = originalContent;
-
console.log(`Uploading ${file.name} directly: ${originalContent.length} bytes (no compression)`);
-
logger.info(`Uploading ${file.name} directly: ${originalContent.length} bytes (binary)`);
}
uploadedFiles.push({
-
name: file.name,
content: finalContent,
mimeType: originalMimeType,
size: finalContent.length,
···
const logger = createLogger('main-app')
+
export function isValidSiteName(siteName: string): boolean {
if (!siteName || typeof siteName !== 'string') return false;
// Length check (AT Protocol rkey limit)
···
continue;
}
+
// Use webkitRelativePath when available (directory uploads), fallback to name for regular file uploads
+
const webkitPath = 'webkitRelativePath' in file ? String(file.webkitRelativePath) : '';
+
const filePath = webkitPath || file.name;
+
updateJobProgress(jobId, {
filesProcessed: i + 1,
+
currentFile: filePath
});
// Skip files that match ignore patterns
+
const normalizedPath = filePath.replace(/^[^\/]*\//, '');
if (shouldIgnore(ignoreMatcher, normalizedPath)) {
skippedFiles.push({
+
name: filePath,
reason: 'matched ignore pattern'
});
continue;
···
const maxSize = MAX_FILE_SIZE;
if (file.size > maxSize) {
skippedFiles.push({
+
name: filePath,
reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)`
});
continue;
···
// Text files: compress AND base64 encode
finalContent = Buffer.from(compressedContent.toString('base64'), 'binary');
base64Encoded = true;
} else {
// Audio files: just compress, no base64
finalContent = compressedContent;
}
} else {
// Binary files: upload directly
finalContent = originalContent;
}
uploadedFiles.push({
+
name: filePath,
content: finalContent,
mimeType: originalMimeType,
size: finalContent.length,
+14 -4
bun.lock
···
"@tailwindcss/cli": "^4.1.17",
"atproto-ui": "^0.12.0",
"bun-plugin-tailwind": "^0.1.2",
"tailwindcss": "^4.1.17",
},
},
"apps/hosting-service": {
···
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
-
"elysia": "latest",
"ignore": "^7.0.5",
"iron-session": "^8.0.4",
"lucide-react": "^0.546.0",
···
"typescript": "^5.9.3",
},
"peerDependencies": {
-
"hono": "",
},
"optionalPeers": [
"hono",
···
"@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=="],
-
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
···
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
-
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
···
"@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"@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=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
···
"uint8arrays/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
"wisp-hosting-service/@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=="],
"@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=="],
···
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
}
}
···
"@tailwindcss/cli": "^4.1.17",
"atproto-ui": "^0.12.0",
"bun-plugin-tailwind": "^0.1.2",
+
"elysia": "^1.4.18",
"tailwindcss": "^4.1.17",
+
},
+
"devDependencies": {
+
"@types/bun": "^1.3.5",
},
},
"apps/hosting-service": {
···
"bun-plugin-tailwind": "^0.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+
"elysia": "^1.4.18",
"ignore": "^7.0.5",
"iron-session": "^8.0.4",
"lucide-react": "^0.546.0",
···
"typescript": "^5.9.3",
},
"peerDependencies": {
+
"hono": "^4.10.7",
},
"optionalPeers": [
"hono",
···
"@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=="],
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
···
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
···
"@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
"@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
+
"@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=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
···
"uint8arrays/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
"wisp-hosting-service/@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=="],
+
+
"wisp-hosting-service/@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
"@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=="],
···
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
+
+
"wisp-hosting-service/@types/bun/bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
}
}
+181 -242
cli/Cargo.lock
···
version = 4
[[package]]
-
name = "abnf"
-
version = "0.13.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
-
dependencies = [
-
"abnf-core",
-
"nom",
-
]
-
-
[[package]]
-
name = "abnf-core"
-
version = "0.5.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
-
dependencies = [
-
"nom",
-
]
-
-
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android_system_properties"
···
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
···
]
[[package]]
-
name = "btree-range-map"
-
version = "0.7.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
-
dependencies = [
-
"btree-slab",
-
"cc-traits",
-
"range-traits",
-
"serde",
-
"slab",
-
]
-
-
[[package]]
-
name = "btree-slab"
-
version = "0.6.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
-
dependencies = [
-
"cc-traits",
-
"slab",
-
"smallvec",
-
]
-
-
[[package]]
name = "buf_redux"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"find-msvc-tools",
"shlex",
-
]
-
-
[[package]]
-
name = "cc-traits"
-
version = "2.0.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
-
dependencies = [
-
"slab",
]
[[package]]
···
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
-
"serde_core",
]
[[package]]
···
]
[[package]]
-
name = "dyn-clone"
-
version = "1.0.20"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
-
-
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"futures-core",
"futures-sink",
"http",
-
"indexmap 2.12.1",
"slab",
"tokio",
"tokio-util",
···
]
[[package]]
-
name = "hashbrown"
-
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
···
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-
[[package]]
-
name = "hex_fmt"
-
version = "0.3.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
[[package]]
name = "hickory-proto"
···
[[package]]
name = "indexmap"
-
version = "1.9.3"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-
dependencies = [
-
"autocfg",
-
"hashbrown 0.12.3",
-
"serde",
-
]
-
-
[[package]]
-
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
-
"serde",
-
"serde_core",
]
[[package]]
···
"portable-atomic",
"unicode-width 0.2.2",
"web-time",
-
]
-
-
[[package]]
-
name = "indoc"
-
version = "2.0.7"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
-
dependencies = [
-
"rustversion",
]
[[package]]
···
[[package]]
name = "jacquard"
-
version = "0.9.3"
dependencies = [
"bytes",
"getrandom 0.2.16",
···
[[package]]
name = "jacquard-api"
-
version = "0.9.2"
dependencies = [
"bon",
"bytes",
···
"miette",
"rustversion",
"serde",
"serde_ipld_dagcbor",
"thiserror 2.0.17",
"unicode-segmentation",
···
[[package]]
name = "jacquard-common"
-
version = "0.9.2"
dependencies = [
"base64 0.22.1",
"bon",
"bytes",
"chrono",
"ciborium",
"cid",
"futures",
"getrandom 0.2.16",
"getrandom 0.3.4",
"http",
"ipld-core",
"k256",
-
"langtag",
"miette",
"multibase",
"multihash",
"n0-future 0.1.3",
"ouroboros",
"p256",
"rand 0.9.2",
"regex",
"regex-lite",
"reqwest",
"serde",
"serde_html_form",
"serde_ipld_dagcbor",
"serde_json",
"signature",
"smol_str",
"thiserror 2.0.17",
"tokio",
"tokio-tungstenite-wasm",
···
[[package]]
name = "jacquard-derive"
-
version = "0.9.3"
dependencies = [
"heck 0.5.0",
"jacquard-lexicon",
···
[[package]]
name = "jacquard-identity"
-
version = "0.9.2"
dependencies = [
"bon",
"bytes",
···
"jacquard-common",
"jacquard-lexicon",
"miette",
-
"mini-moka",
"percent-encoding",
"reqwest",
"serde",
···
[[package]]
name = "jacquard-lexicon"
-
version = "0.9.2"
dependencies = [
"cid",
"dashmap",
···
[[package]]
name = "jacquard-oauth"
-
version = "0.9.2"
dependencies = [
"base64 0.22.1",
"bytes",
···
]
[[package]]
-
name = "langtag"
-
version = "0.4.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
-
dependencies = [
-
"serde",
-
"static-regular-grammar",
-
"thiserror 1.0.69",
-
]
-
-
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "markup5ever"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
-
name = "mini-moka"
version = "0.10.99"
dependencies = [
"crossbeam-channel",
"crossbeam-utils",
···
"triomphe",
"web-time",
]
-
-
[[package]]
-
name = "minimal-lexical"
-
version = "0.2.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
···
]
[[package]]
name = "n0-future"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
-
name = "nom"
-
version = "7.1.3"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-
dependencies = [
-
"memchr",
-
"minimal-lexical",
-
]
-
-
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
"elliptic-curve",
-
]
-
-
[[package]]
-
name = "proc-macro-error"
-
version = "1.0.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-
dependencies = [
-
"proc-macro-error-attr",
-
"proc-macro2",
-
"quote",
-
"syn 1.0.109",
-
"version_check",
-
]
-
-
[[package]]
-
name = "proc-macro-error-attr"
-
version = "1.0.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-
dependencies = [
-
"proc-macro2",
-
"quote",
-
"version_check",
]
[[package]]
···
]
[[package]]
-
name = "range-traits"
-
version = "0.3.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
-
-
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
-
name = "ref-cast"
-
version = "1.0.25"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
-
dependencies = [
-
"ref-cast-impl",
-
]
-
-
[[package]]
-
name = "ref-cast-impl"
-
version = "1.0.25"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
-
dependencies = [
-
"proc-macro2",
-
"quote",
-
"syn 2.0.111",
-
]
-
-
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
-
name = "schemars"
-
version = "0.9.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
-
dependencies = [
-
"dyn-clone",
-
"ref-cast",
-
"serde",
-
"serde_json",
-
]
-
-
[[package]]
-
name = "schemars"
-
version = "1.1.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
-
dependencies = [
-
"dyn-clone",
-
"ref-cast",
-
"serde",
-
"serde_json",
-
]
-
-
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"core-foundation-sys",
"libc",
]
[[package]]
name = "send_wrapper"
···
[[package]]
name = "serde_html_form"
-
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
dependencies = [
"form_urlencoded",
-
"indexmap 2.12.1",
"itoa",
-
"ryu",
"serde_core",
]
···
"base64 0.22.1",
"chrono",
"hex",
-
"indexmap 1.9.3",
-
"indexmap 2.12.1",
-
"schemars 0.9.0",
-
"schemars 1.1.0",
"serde_core",
"serde_json",
"serde_with_macros",
···
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spin"
···
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
-
name = "static-regular-grammar"
-
version = "2.0.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
-
dependencies = [
-
"abnf",
-
"btree-range-map",
-
"ciborium",
-
"hex_fmt",
-
"indoc",
-
"proc-macro-error",
-
"proc-macro2",
-
"quote",
-
"serde",
-
"sha2",
-
"syn 2.0.111",
-
"thiserror 1.0.69",
-
]
-
-
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
-
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
-
"time-macros",
]
[[package]]
···
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
-
-
[[package]]
-
name = "time-macros"
-
version = "0.2.24"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
-
dependencies = [
-
"num-conv",
-
"time-core",
-
]
[[package]]
name = "tiny_http"
···
"tower-http",
"url",
"walkdir",
]
[[package]]
···
version = 4
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"alloc-no-stdlib",
]
+
+
[[package]]
+
name = "allocator-api2"
+
version = "0.2.21"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
···
"proc-macro2",
"quote",
"syn 2.0.111",
+
]
+
+
[[package]]
+
name = "atomic-polyfill"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+
dependencies = [
+
"critical-section",
]
[[package]]
···
]
[[package]]
name = "buf_redux"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
···
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
+
name = "cobs"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+
dependencies = [
+
"thiserror 2.0.17",
+
]
+
+
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "critical-section"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]]
···
]
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "embedded-io"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+
[[package]]
+
name = "embedded-io"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+
name = "foldhash"
+
version = "0.1.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"futures-core",
"futures-sink",
"http",
+
"indexmap",
"slab",
"tokio",
"tokio-util",
···
]
[[package]]
+
name = "hash32"
+
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+
dependencies = [
+
"byteorder",
+
]
[[package]]
name = "hashbrown"
···
[[package]]
name = "hashbrown"
+
version = "0.15.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
dependencies = [
+
"allocator-api2",
+
"equivalent",
+
"foldhash",
+
]
+
+
[[package]]
+
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
+
name = "heapless"
+
version = "0.7.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+
dependencies = [
+
"atomic-polyfill",
+
"hash32",
+
"rustc_version",
+
"serde",
+
"spin 0.9.8",
+
"stable_deref_trait",
+
]
+
+
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
···
[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
]
[[package]]
···
"portable-atomic",
"unicode-width 0.2.2",
"web-time",
]
[[package]]
···
[[package]]
name = "jacquard"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"bytes",
"getrandom 0.2.16",
···
[[package]]
name = "jacquard-api"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"bon",
"bytes",
···
"miette",
"rustversion",
"serde",
+
"serde_bytes",
"serde_ipld_dagcbor",
"thiserror 2.0.17",
"unicode-segmentation",
···
[[package]]
name = "jacquard-common"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"base64 0.22.1",
"bon",
"bytes",
"chrono",
"ciborium",
+
"ciborium-io",
"cid",
"futures",
"getrandom 0.2.16",
"getrandom 0.3.4",
+
"hashbrown 0.15.5",
"http",
"ipld-core",
"k256",
+
"maitake-sync",
"miette",
"multibase",
"multihash",
"n0-future 0.1.3",
"ouroboros",
+
"oxilangtag",
"p256",
+
"postcard",
"rand 0.9.2",
"regex",
+
"regex-automata",
"regex-lite",
"reqwest",
"serde",
+
"serde_bytes",
"serde_html_form",
"serde_ipld_dagcbor",
"serde_json",
"signature",
"smol_str",
+
"spin 0.10.0",
"thiserror 2.0.17",
"tokio",
"tokio-tungstenite-wasm",
···
[[package]]
name = "jacquard-derive"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"heck 0.5.0",
"jacquard-lexicon",
···
[[package]]
name = "jacquard-identity"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"bon",
"bytes",
···
"jacquard-common",
"jacquard-lexicon",
"miette",
+
"mini-moka-wasm",
+
"n0-future 0.1.3",
"percent-encoding",
"reqwest",
"serde",
···
[[package]]
name = "jacquard-lexicon"
+
version = "0.9.5"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"cid",
"dashmap",
···
[[package]]
name = "jacquard-oauth"
+
version = "0.9.6"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"base64 0.22.1",
"bytes",
···
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
+
name = "maitake-sync"
+
version = "0.1.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5"
+
dependencies = [
+
"cordyceps",
+
"loom",
+
"mycelium-bitfield",
+
"pin-project",
+
"portable-atomic",
+
]
+
+
[[package]]
name = "markup5ever"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "mini-moka-wasm"
version = "0.10.99"
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
dependencies = [
"crossbeam-channel",
"crossbeam-utils",
···
"triomphe",
"web-time",
]
[[package]]
name = "miniz_oxide"
···
]
[[package]]
+
name = "mycelium-bitfield"
+
version = "0.1.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc"
+
+
[[package]]
name = "n0-future"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
[[package]]
+
name = "oxilangtag"
+
version = "0.1.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb"
+
dependencies = [
+
"serde",
+
]
+
+
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
+
name = "postcard"
+
version = "1.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+
dependencies = [
+
"cobs",
+
"embedded-io 0.4.0",
+
"embedded-io 0.6.1",
+
"heapless",
+
"serde",
+
]
+
+
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
"elliptic-curve",
]
[[package]]
···
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
+
name = "rustc_version"
+
version = "0.4.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+
dependencies = [
+
"semver",
+
]
+
+
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"core-foundation-sys",
"libc",
]
+
+
[[package]]
+
name = "semver"
+
version = "1.0.27"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "send_wrapper"
···
[[package]]
name = "serde_html_form"
+
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94"
dependencies = [
"form_urlencoded",
+
"indexmap",
"itoa",
"serde_core",
]
···
"base64 0.22.1",
"chrono",
"hex",
"serde_core",
"serde_json",
"serde_with_macros",
···
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
dependencies = [
+
"lock_api",
+
]
[[package]]
name = "spin"
···
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
]
[[package]]
···
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "tiny_http"
···
"tower-http",
"url",
"walkdir",
+
"wisp-lexicons",
+
]
+
+
[[package]]
+
name = "wisp-lexicons"
+
version = "0.1.0"
+
dependencies = [
+
"jacquard-common",
+
"jacquard-derive",
+
"jacquard-lexicon",
+
"rustversion",
+
"serde",
]
[[package]]
+8 -7
cli/Cargo.toml
···
place_wisp = []
[dependencies]
-
jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] }
-
jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" }
-
jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] }
-
jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] }
-
jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] }
-
jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" }
-
jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" }
#jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
#jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
#jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
···
place_wisp = []
[dependencies]
+
jacquard = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["loopback"] }
+
jacquard-oauth = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
jacquard-api = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["streaming"] }
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["websocket"] }
+
jacquard-identity = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["dns"] }
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
wisp-lexicons = { path = "crates/lexicons" }
#jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
#jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
#jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
+72 -2
cli/README.md
···
## Usage
### Basic Deployment
Deploy the current directory:
```bash
-
wisp-cli nekomimi.ppet --path . --site my-site
```
Deploy a specific directory:
···
wisp-cli alice.bsky.social --path ./dist/ --site my-site
```
### Authentication Methods
#### OAuth (Recommended)
···
## Command-Line Options
```
-
wisp-cli [OPTIONS] <INPUT>
Arguments:
<INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
···
-s, --site <SITE> Site name (defaults to directory name)
--store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
--password <PASSWORD> App Password for authentication (alternative to OAuth)
-h, --help Print help
-V, --version Print version
```
## How It Works
···
## Usage
+
### Commands
+
+
The CLI supports three main commands:
+
- **deploy**: Upload a site to your PDS (default command)
+
- **pull**: Download a site from a PDS to a local directory
+
- **serve**: Serve a site locally with real-time firehose updates
+
### Basic Deployment
Deploy the current directory:
```bash
+
wisp-cli nekomimi.pet --path . --site my-site
```
Deploy a specific directory:
···
wisp-cli alice.bsky.social --path ./dist/ --site my-site
```
+
Or use the explicit `deploy` subcommand:
+
+
```bash
+
wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site
+
```
+
+
### Pull a Site
+
+
Download a site from a PDS to a local directory:
+
+
```bash
+
wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site
+
```
+
+
This will download all files from the site to the specified directory.
+
+
### Serve a Site Locally
+
+
Serve a site locally with real-time updates from the firehose:
+
+
```bash
+
wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080
+
```
+
+
This will:
+
1. Download the site to the specified path
+
2. Start a local server on the specified port (default: 8080)
+
3. Watch the firehose for updates and automatically reload files when changed
+
### Authentication Methods
#### OAuth (Recommended)
···
## Command-Line Options
+
### Deploy Command
+
```
+
wisp-cli [deploy] [OPTIONS] <INPUT>
Arguments:
<INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
···
-s, --site <SITE> Site name (defaults to directory name)
--store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
--password <PASSWORD> App Password for authentication (alternative to OAuth)
+
--directory Enable directory listing mode for paths without index files
+
--spa Enable SPA mode (serve index.html for all routes)
+
-y, --yes Skip confirmation prompts (automatically accept warnings)
-h, --help Print help
-V, --version Print version
+
```
+
+
### Pull Command
+
+
```
+
wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
+
+
Arguments:
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
+
+
Options:
+
-s, --site <SITE> Site name (record key)
+
-p, --path <PATH> Output directory for the downloaded site [default: .]
+
-h, --help Print help
+
```
+
+
### Serve Command
+
+
```
+
wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
+
+
Arguments:
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
+
+
Options:
+
-s, --site <SITE> Site name (record key)
+
-p, --path <PATH> Output directory for the site files [default: .]
+
-P, --port <PORT> Port to serve on [default: 8080]
+
-h, --help Print help
```
## How It Works
+15
cli/crates/lexicons/Cargo.toml
···
···
+
[package]
+
name = "wisp-lexicons"
+
version = "0.1.0"
+
edition = "2024"
+
+
[features]
+
default = ["place_wisp"]
+
place_wisp = []
+
+
[dependencies]
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
+
serde = { version = "1.0", features = ["derive"] }
+
rustversion = "1.0"
+43
cli/crates/lexicons/src/builder_types.rs
···
···
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
/// Marker type indicating a builder field has been set
+
pub struct Set<T>(pub T);
+
impl<T> Set<T> {
+
/// Extract the inner value
+
#[inline]
+
pub fn into_inner(self) -> T {
+
self.0
+
}
+
}
+
+
/// Marker type indicating a builder field has not been set
+
pub struct Unset;
+
/// Trait indicating a builder field is set (has a value)
+
#[rustversion::attr(
+
since(1.78.0),
+
diagnostic::on_unimplemented(
+
message = "the field `{Self}` was not set, but this method requires it to be set",
+
label = "the field `{Self}` was not set"
+
)
+
)]
+
pub trait IsSet: private::Sealed {}
+
/// Trait indicating a builder field is unset (no value yet)
+
#[rustversion::attr(
+
since(1.78.0),
+
diagnostic::on_unimplemented(
+
message = "the field `{Self}` was already set, but this method requires it to be unset",
+
label = "the field `{Self}` was already set"
+
)
+
)]
+
pub trait IsUnset: private::Sealed {}
+
impl<T> IsSet for Set<T> {}
+
impl IsUnset for Unset {}
+
mod private {
+
/// Sealed trait to prevent external implementations
+
pub trait Sealed {}
+
impl<T> Sealed for super::Set<T> {}
+
impl Sealed for super::Unset {}
+
}
+11
cli/crates/lexicons/src/lib.rs
···
···
+
extern crate alloc;
+
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
pub mod builder_types;
+
+
#[cfg(feature = "place_wisp")]
+
pub mod place_wisp;
+1490
cli/crates/lexicons/src/place_wisp/fs.rs
···
···
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// Lexicon: place.wisp.fs
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Directory<'a> {
+
#[serde(borrow)]
+
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod directory_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Type;
+
type Entries;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Type = Unset;
+
type Entries = Unset;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Type = Set<members::r#type>;
+
type Entries = S::Entries;
+
}
+
///State transition - sets the `entries` field to Set
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
+
impl<S: State> State for SetEntries<S> {
+
type Type = S::Type;
+
type Entries = Set<members::entries>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `type` field
+
pub struct r#type(());
+
///Marker type for the `entries` field
+
pub struct entries(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Directory<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
+
DirectoryBuilder::new()
+
}
+
}
+
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Entries: directory_state::IsUnset,
+
{
+
/// Set the `entries` field (required)
+
pub fn entries(
+
mut self,
+
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Type: directory_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Type: directory_state::IsSet,
+
S::Entries: directory_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Directory<'a> {
+
Directory {
+
entries: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Directory<'a> {
+
Directory {
+
entries: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
::jacquard_lexicon::lexicon::LexiconDoc {
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
+
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
+
revision: None,
+
description: None,
+
defs: {
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
+
description: None,
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
+
description: None,
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
+
}),
+
min_length: None,
+
max_length: Some(500usize),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_common::smol_str::SmolStr::new_static("node")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(255usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
+
description: None,
+
refs: vec![
+
::jacquard_common::CowStr::new_static("#file"),
+
::jacquard_common::CowStr::new_static("#directory"),
+
::jacquard_common::CowStr::new_static("#subfs")
+
],
+
closed: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
+
description: None,
+
default: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
+
description: None,
+
accept: None,
+
max_size: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Content encoding (e.g., gzip for compressed files)",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Original MIME type before compression",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Virtual filesystem manifest for a Wisp site",
+
),
+
),
+
key: None,
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"createdAt",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: Some(
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
+
),
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"fileCount",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
+
description: None,
+
default: None,
+
minimum: Some(0i64),
+
maximum: Some(1000i64),
+
r#enum: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
+
description: None,
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("flat"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
+
description: None,
+
default: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
+
),
+
),
+
format: Some(
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
+
),
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map
+
},
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.fs"
+
}
+
fn def_name() -> &'static str {
+
"directory"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_fs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
{
+
let value = &self.entries;
+
#[allow(unused_comparisons)]
+
if value.len() > 500usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"entries",
+
),
+
max: 500usize,
+
actual: value.len(),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Entry<'a> {
+
#[serde(borrow)]
+
pub name: jacquard_common::CowStr<'a>,
+
#[serde(borrow)]
+
pub node: EntryNode<'a>,
+
}
+
+
pub mod entry_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Node;
+
type Name;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Node = Unset;
+
type Name = Unset;
+
}
+
///State transition - sets the `node` field to Set
+
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetNode<S> {}
+
impl<S: State> State for SetNode<S> {
+
type Node = Set<members::node>;
+
type Name = S::Name;
+
}
+
///State transition - sets the `name` field to Set
+
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetName<S> {}
+
impl<S: State> State for SetName<S> {
+
type Node = S::Node;
+
type Name = Set<members::name>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `node` field
+
pub struct node(());
+
///Marker type for the `name` field
+
pub struct name(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct EntryBuilder<'a, S: entry_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<EntryNode<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Entry<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
+
EntryBuilder::new()
+
}
+
}
+
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Name: entry_state::IsUnset,
+
{
+
/// Set the `name` field (required)
+
pub fn name(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Node: entry_state::IsUnset,
+
{
+
/// Set the `node` field (required)
+
pub fn node(
+
mut self,
+
value: impl Into<EntryNode<'a>>,
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Node: entry_state::IsSet,
+
S::Name: entry_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Entry<'a> {
+
Entry {
+
name: self.__unsafe_private_named.0.unwrap(),
+
node: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Entry<'a> {
+
Entry {
+
name: self.__unsafe_private_named.0.unwrap(),
+
node: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
#[jacquard_derive::open_union]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(tag = "$type")]
+
#[serde(bound(deserialize = "'de: 'a"))]
+
pub enum EntryNode<'a> {
+
#[serde(rename = "place.wisp.fs#file")]
+
File(Box<crate::place_wisp::fs::File<'a>>),
+
#[serde(rename = "place.wisp.fs#directory")]
+
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
+
#[serde(rename = "place.wisp.fs#subfs")]
+
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.fs"
+
}
+
fn def_name() -> &'static str {
+
"entry"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_fs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
{
+
let value = &self.name;
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 255usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"name",
+
),
+
max: 255usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct File<'a> {
+
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub base64: std::option::Option<bool>,
+
/// Content blob ref
+
#[serde(borrow)]
+
pub blob: jacquard_common::types::blob::BlobRef<'a>,
+
/// Content encoding (e.g., gzip for compressed files)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
+
/// Original MIME type before compression
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod file_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Type;
+
type Blob;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Type = Unset;
+
type Blob = Unset;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Type = Set<members::r#type>;
+
type Blob = S::Blob;
+
}
+
///State transition - sets the `blob` field to Set
+
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetBlob<S> {}
+
impl<S: State> State for SetBlob<S> {
+
type Type = S::Type;
+
type Blob = Set<members::blob>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `type` field
+
pub struct r#type(());
+
///Marker type for the `blob` field
+
pub struct blob(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct FileBuilder<'a, S: file_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<bool>,
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> File<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
+
FileBuilder::new()
+
}
+
}
+
+
impl<'a> FileBuilder<'a, file_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `base64` field (optional)
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
+
self.__unsafe_private_named.0 = value.into();
+
self
+
}
+
/// Set the `base64` field to an Option value (optional)
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
+
self.__unsafe_private_named.0 = value;
+
self
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Blob: file_state::IsUnset,
+
{
+
/// Set the `blob` field (required)
+
pub fn blob(
+
mut self,
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `encoding` field (optional)
+
pub fn encoding(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.2 = value.into();
+
self
+
}
+
/// Set the `encoding` field to an Option value (optional)
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
+
self.__unsafe_private_named.2 = value;
+
self
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `mimeType` field (optional)
+
pub fn mime_type(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value.into();
+
self
+
}
+
/// Set the `mimeType` field to an Option value (optional)
+
pub fn maybe_mime_type(
+
mut self,
+
value: Option<jacquard_common::CowStr<'a>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value;
+
self
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Type: file_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> FileBuilder<'a, file_state::SetType<S>> {
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Type: file_state::IsSet,
+
S::Blob: file_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> File<'a> {
+
File {
+
base64: self.__unsafe_private_named.0,
+
blob: self.__unsafe_private_named.1.unwrap(),
+
encoding: self.__unsafe_private_named.2,
+
mime_type: self.__unsafe_private_named.3,
+
r#type: self.__unsafe_private_named.4.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> File<'a> {
+
File {
+
base64: self.__unsafe_private_named.0,
+
blob: self.__unsafe_private_named.1.unwrap(),
+
encoding: self.__unsafe_private_named.2,
+
mime_type: self.__unsafe_private_named.3,
+
r#type: self.__unsafe_private_named.4.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.fs"
+
}
+
fn def_name() -> &'static str {
+
"file"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_fs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
Ok(())
+
}
+
}
+
+
/// Virtual filesystem manifest for a Wisp site
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Fs<'a> {
+
pub created_at: jacquard_common::types::string::Datetime,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub file_count: std::option::Option<i64>,
+
#[serde(borrow)]
+
pub root: crate::place_wisp::fs::Directory<'a>,
+
#[serde(borrow)]
+
pub site: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod fs_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type CreatedAt;
+
type Site;
+
type Root;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type CreatedAt = Unset;
+
type Site = Unset;
+
type Root = Unset;
+
}
+
///State transition - sets the `created_at` field to Set
+
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
+
impl<S: State> State for SetCreatedAt<S> {
+
type CreatedAt = Set<members::created_at>;
+
type Site = S::Site;
+
type Root = S::Root;
+
}
+
///State transition - sets the `site` field to Set
+
pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetSite<S> {}
+
impl<S: State> State for SetSite<S> {
+
type CreatedAt = S::CreatedAt;
+
type Site = Set<members::site>;
+
type Root = S::Root;
+
}
+
///State transition - sets the `root` field to Set
+
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetRoot<S> {}
+
impl<S: State> State for SetRoot<S> {
+
type CreatedAt = S::CreatedAt;
+
type Site = S::Site;
+
type Root = Set<members::root>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `created_at` field
+
pub struct created_at(());
+
///Marker type for the `site` field
+
pub struct site(());
+
///Marker type for the `root` field
+
pub struct root(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct FsBuilder<'a, S: fs_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<jacquard_common::types::string::Datetime>,
+
::core::option::Option<i64>,
+
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Fs<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
+
FsBuilder::new()
+
}
+
}
+
+
impl<'a> FsBuilder<'a, fs_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
FsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> FsBuilder<'a, S>
+
where
+
S: fs_state::State,
+
S::CreatedAt: fs_state::IsUnset,
+
{
+
/// Set the `createdAt` field (required)
+
pub fn created_at(
+
mut self,
+
value: impl Into<jacquard_common::types::string::Datetime>,
+
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
FsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
+
/// Set the `fileCount` field (optional)
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
+
self.__unsafe_private_named.1 = value.into();
+
self
+
}
+
/// Set the `fileCount` field to an Option value (optional)
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
+
self.__unsafe_private_named.1 = value;
+
self
+
}
+
}
+
+
impl<'a, S> FsBuilder<'a, S>
+
where
+
S: fs_state::State,
+
S::Root: fs_state::IsUnset,
+
{
+
/// Set the `root` field (required)
+
pub fn root(
+
mut self,
+
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
+
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
+
FsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> FsBuilder<'a, S>
+
where
+
S: fs_state::State,
+
S::Site: fs_state::IsUnset,
+
{
+
/// Set the `site` field (required)
+
pub fn site(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> FsBuilder<'a, fs_state::SetSite<S>> {
+
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
+
FsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> FsBuilder<'a, S>
+
where
+
S: fs_state::State,
+
S::CreatedAt: fs_state::IsSet,
+
S::Site: fs_state::IsSet,
+
S::Root: fs_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Fs<'a> {
+
Fs {
+
created_at: self.__unsafe_private_named.0.unwrap(),
+
file_count: self.__unsafe_private_named.1,
+
root: self.__unsafe_private_named.2.unwrap(),
+
site: self.__unsafe_private_named.3.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Fs<'a> {
+
Fs {
+
created_at: self.__unsafe_private_named.0.unwrap(),
+
file_count: self.__unsafe_private_named.1,
+
root: self.__unsafe_private_named.2.unwrap(),
+
site: self.__unsafe_private_named.3.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> Fs<'a> {
+
pub fn uri(
+
uri: impl Into<jacquard_common::CowStr<'a>>,
+
) -> Result<
+
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
+
jacquard_common::types::uri::UriError,
+
> {
+
jacquard_common::types::uri::RecordUri::try_from_uri(
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
+
)
+
}
+
}
+
+
/// Typed wrapper for GetRecord response with this collection's record type.
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct FsGetRecordOutput<'a> {
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
+
#[serde(borrow)]
+
pub uri: jacquard_common::types::string::AtUri<'a>,
+
#[serde(borrow)]
+
pub value: Fs<'a>,
+
}
+
+
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
+
fn from(output: FsGetRecordOutput<'_>) -> Self {
+
use jacquard_common::IntoStatic;
+
output.value.into_static()
+
}
+
}
+
+
impl jacquard_common::types::collection::Collection for Fs<'_> {
+
const NSID: &'static str = "place.wisp.fs";
+
type Record = FsRecord;
+
}
+
+
/// Marker type for deserializing records from this collection.
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
+
pub struct FsRecord;
+
impl jacquard_common::xrpc::XrpcResp for FsRecord {
+
const NSID: &'static str = "place.wisp.fs";
+
const ENCODING: &'static str = "application/json";
+
type Output<'de> = FsGetRecordOutput<'de>;
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
+
}
+
+
impl jacquard_common::types::collection::Collection for FsRecord {
+
const NSID: &'static str = "place.wisp.fs";
+
type Record = FsRecord;
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.fs"
+
}
+
fn def_name() -> &'static str {
+
"main"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_fs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
if let Some(ref value) = self.file_count {
+
if *value > 1000i64 {
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"file_count",
+
),
+
max: 1000i64,
+
actual: *value,
+
});
+
}
+
}
+
if let Some(ref value) = self.file_count {
+
if *value < 0i64 {
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"file_count",
+
),
+
min: 0i64,
+
actual: *value,
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Subfs<'a> {
+
/// 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.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub flat: std::option::Option<bool>,
+
/// AT-URI pointing to a place.wisp.subfs record containing this subtree.
+
#[serde(borrow)]
+
pub subject: jacquard_common::types::string::AtUri<'a>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod subfs_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Type;
+
type Subject;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Type = Unset;
+
type Subject = Unset;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Type = Set<members::r#type>;
+
type Subject = S::Subject;
+
}
+
///State transition - sets the `subject` field to Set
+
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetSubject<S> {}
+
impl<S: State> State for SetSubject<S> {
+
type Type = S::Type;
+
type Subject = Set<members::subject>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `type` field
+
pub struct r#type(());
+
///Marker type for the `subject` field
+
pub struct subject(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<bool>,
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Subfs<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
+
SubfsBuilder::new()
+
}
+
}
+
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
+
/// Set the `flat` field (optional)
+
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
+
self.__unsafe_private_named.0 = value.into();
+
self
+
}
+
/// Set the `flat` field to an Option value (optional)
+
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
+
self.__unsafe_private_named.0 = value;
+
self
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Subject: subfs_state::IsUnset,
+
{
+
/// Set the `subject` field (required)
+
pub fn subject(
+
mut self,
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Type: subfs_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Type: subfs_state::IsSet,
+
S::Subject: subfs_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Subfs<'a> {
+
Subfs {
+
flat: self.__unsafe_private_named.0,
+
subject: self.__unsafe_private_named.1.unwrap(),
+
r#type: self.__unsafe_private_named.2.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Subfs<'a> {
+
Subfs {
+
flat: self.__unsafe_private_named.0,
+
subject: self.__unsafe_private_named.1.unwrap(),
+
r#type: self.__unsafe_private_named.2.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.fs"
+
}
+
fn def_name() -> &'static str {
+
"subfs"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_fs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
Ok(())
+
}
+
}
+653
cli/crates/lexicons/src/place_wisp/settings.rs
···
···
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// Lexicon: place.wisp.settings
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
/// Custom HTTP header configuration
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic,
+
Default
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct CustomHeader<'a> {
+
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
+
#[serde(borrow)]
+
pub name: jacquard_common::CowStr<'a>,
+
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
+
/// HTTP header value
+
#[serde(borrow)]
+
pub value: jacquard_common::CowStr<'a>,
+
}
+
+
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
+
'static,
+
> {
+
::jacquard_lexicon::lexicon::LexiconDoc {
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
+
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
+
revision: None,
+
description: None,
+
defs: {
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Custom HTTP header configuration",
+
),
+
),
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_common::smol_str::SmolStr::new_static("value")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(100usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("path"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(500usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("value"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static("HTTP header value"),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(1000usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Configuration settings for a static site hosted on wisp.place",
+
),
+
),
+
key: Some(::jacquard_common::CowStr::new_static("any")),
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: None,
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"cleanUrls",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
+
description: None,
+
default: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"custom404",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(500usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"directoryListing",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
+
description: None,
+
default: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("headers"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Custom HTTP headers to set on responses",
+
),
+
),
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
+
description: None,
+
r#ref: ::jacquard_common::CowStr::new_static(
+
"#customHeader",
+
),
+
}),
+
min_length: None,
+
max_length: Some(50usize),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"indexFiles",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
+
),
+
),
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(255usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
min_length: None,
+
max_length: Some(10usize),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"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.",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(500usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
}),
+
);
+
map
+
},
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.settings"
+
}
+
fn def_name() -> &'static str {
+
"customHeader"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_settings()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
{
+
let value = &self.name;
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 100usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"name",
+
),
+
max: 100usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
if let Some(ref value) = self.path {
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 500usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"path",
+
),
+
max: 500usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
{
+
let value = &self.value;
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 1000usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"value",
+
),
+
max: 1000usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
/// Configuration settings for a static site hosted on wisp.place
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Settings<'a> {
+
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub clean_urls: std::option::Option<bool>,
+
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
+
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub directory_listing: std::option::Option<bool>,
+
/// Custom HTTP headers to set on responses
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
+
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
+
/// 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.
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
+
}
+
+
pub mod settings_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct SettingsBuilder<'a, S: settings_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<bool>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<bool>,
+
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
+
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Settings<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
+
SettingsBuilder::new()
+
}
+
}
+
+
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
SettingsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None, None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `cleanUrls` field (optional)
+
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
+
self.__unsafe_private_named.0 = value.into();
+
self
+
}
+
/// Set the `cleanUrls` field to an Option value (optional)
+
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
+
self.__unsafe_private_named.0 = value;
+
self
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `custom404` field (optional)
+
pub fn custom404(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.1 = value.into();
+
self
+
}
+
/// Set the `custom404` field to an Option value (optional)
+
pub fn maybe_custom404(
+
mut self,
+
value: Option<jacquard_common::CowStr<'a>>,
+
) -> Self {
+
self.__unsafe_private_named.1 = value;
+
self
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `directoryListing` field (optional)
+
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
+
self.__unsafe_private_named.2 = value.into();
+
self
+
}
+
/// Set the `directoryListing` field to an Option value (optional)
+
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
+
self.__unsafe_private_named.2 = value;
+
self
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `headers` field (optional)
+
pub fn headers(
+
mut self,
+
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value.into();
+
self
+
}
+
/// Set the `headers` field to an Option value (optional)
+
pub fn maybe_headers(
+
mut self,
+
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value;
+
self
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `indexFiles` field (optional)
+
pub fn index_files(
+
mut self,
+
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
+
) -> Self {
+
self.__unsafe_private_named.4 = value.into();
+
self
+
}
+
/// Set the `indexFiles` field to an Option value (optional)
+
pub fn maybe_index_files(
+
mut self,
+
value: Option<Vec<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.4 = value;
+
self
+
}
+
}
+
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
+
/// Set the `spaMode` field (optional)
+
pub fn spa_mode(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.5 = value.into();
+
self
+
}
+
/// Set the `spaMode` field to an Option value (optional)
+
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
+
self.__unsafe_private_named.5 = value;
+
self
+
}
+
}
+
+
impl<'a, S> SettingsBuilder<'a, S>
+
where
+
S: settings_state::State,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Settings<'a> {
+
Settings {
+
clean_urls: self.__unsafe_private_named.0,
+
custom404: self.__unsafe_private_named.1,
+
directory_listing: self.__unsafe_private_named.2,
+
headers: self.__unsafe_private_named.3,
+
index_files: self.__unsafe_private_named.4,
+
spa_mode: self.__unsafe_private_named.5,
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Settings<'a> {
+
Settings {
+
clean_urls: self.__unsafe_private_named.0,
+
custom404: self.__unsafe_private_named.1,
+
directory_listing: self.__unsafe_private_named.2,
+
headers: self.__unsafe_private_named.3,
+
index_files: self.__unsafe_private_named.4,
+
spa_mode: self.__unsafe_private_named.5,
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> Settings<'a> {
+
pub fn uri(
+
uri: impl Into<jacquard_common::CowStr<'a>>,
+
) -> Result<
+
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
+
jacquard_common::types::uri::UriError,
+
> {
+
jacquard_common::types::uri::RecordUri::try_from_uri(
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
+
)
+
}
+
}
+
+
/// Typed wrapper for GetRecord response with this collection's record type.
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct SettingsGetRecordOutput<'a> {
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
+
#[serde(borrow)]
+
pub uri: jacquard_common::types::string::AtUri<'a>,
+
#[serde(borrow)]
+
pub value: Settings<'a>,
+
}
+
+
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
+
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
+
use jacquard_common::IntoStatic;
+
output.value.into_static()
+
}
+
}
+
+
impl jacquard_common::types::collection::Collection for Settings<'_> {
+
const NSID: &'static str = "place.wisp.settings";
+
type Record = SettingsRecord;
+
}
+
+
/// Marker type for deserializing records from this collection.
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
+
pub struct SettingsRecord;
+
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
+
const NSID: &'static str = "place.wisp.settings";
+
const ENCODING: &'static str = "application/json";
+
type Output<'de> = SettingsGetRecordOutput<'de>;
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
+
}
+
+
impl jacquard_common::types::collection::Collection for SettingsRecord {
+
const NSID: &'static str = "place.wisp.settings";
+
type Record = SettingsRecord;
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.settings"
+
}
+
fn def_name() -> &'static str {
+
"main"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_settings()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
if let Some(ref value) = self.custom404 {
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 500usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"custom404",
+
),
+
max: 500usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
if let Some(ref value) = self.headers {
+
#[allow(unused_comparisons)]
+
if value.len() > 50usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"headers",
+
),
+
max: 50usize,
+
actual: value.len(),
+
});
+
}
+
}
+
if let Some(ref value) = self.index_files {
+
#[allow(unused_comparisons)]
+
if value.len() > 10usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"index_files",
+
),
+
max: 10usize,
+
actual: value.len(),
+
});
+
}
+
}
+
if let Some(ref value) = self.spa_mode {
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 500usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"spa_mode",
+
),
+
max: 500usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+1408
cli/crates/lexicons/src/place_wisp/subfs.rs
···
···
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// Lexicon: place.wisp.subfs
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Directory<'a> {
+
#[serde(borrow)]
+
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod directory_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Type;
+
type Entries;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Type = Unset;
+
type Entries = Unset;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Type = Set<members::r#type>;
+
type Entries = S::Entries;
+
}
+
///State transition - sets the `entries` field to Set
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
+
impl<S: State> State for SetEntries<S> {
+
type Type = S::Type;
+
type Entries = Set<members::entries>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `type` field
+
pub struct r#type(());
+
///Marker type for the `entries` field
+
pub struct entries(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Directory<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
+
DirectoryBuilder::new()
+
}
+
}
+
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Entries: directory_state::IsUnset,
+
{
+
/// Set the `entries` field (required)
+
pub fn entries(
+
mut self,
+
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Type: directory_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
DirectoryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> DirectoryBuilder<'a, S>
+
where
+
S: directory_state::State,
+
S::Type: directory_state::IsSet,
+
S::Entries: directory_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Directory<'a> {
+
Directory {
+
entries: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Directory<'a> {
+
Directory {
+
entries: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
::jacquard_lexicon::lexicon::LexiconDoc {
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
+
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
+
revision: None,
+
description: None,
+
defs: {
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
+
description: None,
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
+
description: None,
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
+
}),
+
min_length: None,
+
max_length: Some(500usize),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_common::smol_str::SmolStr::new_static("node")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: Some(255usize),
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
+
description: None,
+
refs: vec![
+
::jacquard_common::CowStr::new_static("#file"),
+
::jacquard_common::CowStr::new_static("#directory"),
+
::jacquard_common::CowStr::new_static("#subfs")
+
],
+
closed: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
+
description: None,
+
default: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
+
description: None,
+
accept: None,
+
max_size: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Content encoding (e.g., gzip for compressed files)",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"Original MIME type before compression",
+
),
+
),
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"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.",
+
),
+
),
+
key: None,
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"createdAt",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: Some(
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
+
),
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static(
+
"fileCount",
+
),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
+
description: None,
+
default: None,
+
minimum: Some(0i64),
+
maximum: Some(1000i64),
+
r#enum: None,
+
r#const: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
+
description: None,
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
+
}),
+
);
+
map
+
},
+
}),
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
+
description: None,
+
required: Some(
+
vec![
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
+
],
+
),
+
nullable: None,
+
properties: {
+
#[allow(unused_mut)]
+
let mut map = ::std::collections::BTreeMap::new();
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: Some(
+
::jacquard_common::CowStr::new_static(
+
"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.",
+
),
+
),
+
format: Some(
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
+
),
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map.insert(
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
+
description: None,
+
format: None,
+
default: None,
+
min_length: None,
+
max_length: None,
+
min_graphemes: None,
+
max_graphemes: None,
+
r#enum: None,
+
r#const: None,
+
known_values: None,
+
}),
+
);
+
map
+
},
+
}),
+
);
+
map
+
},
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.subfs"
+
}
+
fn def_name() -> &'static str {
+
"directory"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_subfs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
{
+
let value = &self.entries;
+
#[allow(unused_comparisons)]
+
if value.len() > 500usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"entries",
+
),
+
max: 500usize,
+
actual: value.len(),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Entry<'a> {
+
#[serde(borrow)]
+
pub name: jacquard_common::CowStr<'a>,
+
#[serde(borrow)]
+
pub node: EntryNode<'a>,
+
}
+
+
pub mod entry_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Name;
+
type Node;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Name = Unset;
+
type Node = Unset;
+
}
+
///State transition - sets the `name` field to Set
+
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetName<S> {}
+
impl<S: State> State for SetName<S> {
+
type Name = Set<members::name>;
+
type Node = S::Node;
+
}
+
///State transition - sets the `node` field to Set
+
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetNode<S> {}
+
impl<S: State> State for SetNode<S> {
+
type Name = S::Name;
+
type Node = Set<members::node>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `name` field
+
pub struct name(());
+
///Marker type for the `node` field
+
pub struct node(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct EntryBuilder<'a, S: entry_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<EntryNode<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Entry<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
+
EntryBuilder::new()
+
}
+
}
+
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Name: entry_state::IsUnset,
+
{
+
/// Set the `name` field (required)
+
pub fn name(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Node: entry_state::IsUnset,
+
{
+
/// Set the `node` field (required)
+
pub fn node(
+
mut self,
+
value: impl Into<EntryNode<'a>>,
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
EntryBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> EntryBuilder<'a, S>
+
where
+
S: entry_state::State,
+
S::Name: entry_state::IsSet,
+
S::Node: entry_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Entry<'a> {
+
Entry {
+
name: self.__unsafe_private_named.0.unwrap(),
+
node: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Entry<'a> {
+
Entry {
+
name: self.__unsafe_private_named.0.unwrap(),
+
node: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
#[jacquard_derive::open_union]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(tag = "$type")]
+
#[serde(bound(deserialize = "'de: 'a"))]
+
pub enum EntryNode<'a> {
+
#[serde(rename = "place.wisp.subfs#file")]
+
File(Box<crate::place_wisp::subfs::File<'a>>),
+
#[serde(rename = "place.wisp.subfs#directory")]
+
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
+
#[serde(rename = "place.wisp.subfs#subfs")]
+
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.subfs"
+
}
+
fn def_name() -> &'static str {
+
"entry"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_subfs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
{
+
let value = &self.name;
+
#[allow(unused_comparisons)]
+
if <str>::len(value.as_ref()) > 255usize {
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"name",
+
),
+
max: 255usize,
+
actual: <str>::len(value.as_ref()),
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct File<'a> {
+
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub base64: std::option::Option<bool>,
+
/// Content blob ref
+
#[serde(borrow)]
+
pub blob: jacquard_common::types::blob::BlobRef<'a>,
+
/// Content encoding (e.g., gzip for compressed files)
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
+
/// Original MIME type before compression
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod file_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Blob;
+
type Type;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Blob = Unset;
+
type Type = Unset;
+
}
+
///State transition - sets the `blob` field to Set
+
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetBlob<S> {}
+
impl<S: State> State for SetBlob<S> {
+
type Blob = Set<members::blob>;
+
type Type = S::Type;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Blob = S::Blob;
+
type Type = Set<members::r#type>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `blob` field
+
pub struct blob(());
+
///Marker type for the `type` field
+
pub struct r#type(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct FileBuilder<'a, S: file_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<bool>,
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> File<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
+
FileBuilder::new()
+
}
+
}
+
+
impl<'a> FileBuilder<'a, file_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `base64` field (optional)
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
+
self.__unsafe_private_named.0 = value.into();
+
self
+
}
+
/// Set the `base64` field to an Option value (optional)
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
+
self.__unsafe_private_named.0 = value;
+
self
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Blob: file_state::IsUnset,
+
{
+
/// Set the `blob` field (required)
+
pub fn blob(
+
mut self,
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `encoding` field (optional)
+
pub fn encoding(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.2 = value.into();
+
self
+
}
+
/// Set the `encoding` field to an Option value (optional)
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
+
self.__unsafe_private_named.2 = value;
+
self
+
}
+
}
+
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
+
/// Set the `mimeType` field (optional)
+
pub fn mime_type(
+
mut self,
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value.into();
+
self
+
}
+
/// Set the `mimeType` field to an Option value (optional)
+
pub fn maybe_mime_type(
+
mut self,
+
value: Option<jacquard_common::CowStr<'a>>,
+
) -> Self {
+
self.__unsafe_private_named.3 = value;
+
self
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Type: file_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> FileBuilder<'a, file_state::SetType<S>> {
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
+
FileBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> FileBuilder<'a, S>
+
where
+
S: file_state::State,
+
S::Blob: file_state::IsSet,
+
S::Type: file_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> File<'a> {
+
File {
+
base64: self.__unsafe_private_named.0,
+
blob: self.__unsafe_private_named.1.unwrap(),
+
encoding: self.__unsafe_private_named.2,
+
mime_type: self.__unsafe_private_named.3,
+
r#type: self.__unsafe_private_named.4.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> File<'a> {
+
File {
+
base64: self.__unsafe_private_named.0,
+
blob: self.__unsafe_private_named.1.unwrap(),
+
encoding: self.__unsafe_private_named.2,
+
mime_type: self.__unsafe_private_named.3,
+
r#type: self.__unsafe_private_named.4.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.subfs"
+
}
+
fn def_name() -> &'static str {
+
"file"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_subfs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
Ok(())
+
}
+
}
+
+
/// 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.
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct SubfsRecord<'a> {
+
pub created_at: jacquard_common::types::string::Datetime,
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
pub file_count: std::option::Option<i64>,
+
#[serde(borrow)]
+
pub root: crate::place_wisp::subfs::Directory<'a>,
+
}
+
+
pub mod subfs_record_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Root;
+
type CreatedAt;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Root = Unset;
+
type CreatedAt = Unset;
+
}
+
///State transition - sets the `root` field to Set
+
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetRoot<S> {}
+
impl<S: State> State for SetRoot<S> {
+
type Root = Set<members::root>;
+
type CreatedAt = S::CreatedAt;
+
}
+
///State transition - sets the `created_at` field to Set
+
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
+
impl<S: State> State for SetCreatedAt<S> {
+
type Root = S::Root;
+
type CreatedAt = Set<members::created_at>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `root` field
+
pub struct root(());
+
///Marker type for the `created_at` field
+
pub struct created_at(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<jacquard_common::types::string::Datetime>,
+
::core::option::Option<i64>,
+
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> SubfsRecord<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
+
SubfsRecordBuilder::new()
+
}
+
}
+
+
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
SubfsRecordBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsRecordBuilder<'a, S>
+
where
+
S: subfs_record_state::State,
+
S::CreatedAt: subfs_record_state::IsUnset,
+
{
+
/// Set the `createdAt` field (required)
+
pub fn created_at(
+
mut self,
+
value: impl Into<jacquard_common::types::string::Datetime>,
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
SubfsRecordBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
+
/// Set the `fileCount` field (optional)
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
+
self.__unsafe_private_named.1 = value.into();
+
self
+
}
+
/// Set the `fileCount` field to an Option value (optional)
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
+
self.__unsafe_private_named.1 = value;
+
self
+
}
+
}
+
+
impl<'a, S> SubfsRecordBuilder<'a, S>
+
where
+
S: subfs_record_state::State,
+
S::Root: subfs_record_state::IsUnset,
+
{
+
/// Set the `root` field (required)
+
pub fn root(
+
mut self,
+
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
+
SubfsRecordBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsRecordBuilder<'a, S>
+
where
+
S: subfs_record_state::State,
+
S::Root: subfs_record_state::IsSet,
+
S::CreatedAt: subfs_record_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> SubfsRecord<'a> {
+
SubfsRecord {
+
created_at: self.__unsafe_private_named.0.unwrap(),
+
file_count: self.__unsafe_private_named.1,
+
root: self.__unsafe_private_named.2.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> SubfsRecord<'a> {
+
SubfsRecord {
+
created_at: self.__unsafe_private_named.0.unwrap(),
+
file_count: self.__unsafe_private_named.1,
+
root: self.__unsafe_private_named.2.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> SubfsRecord<'a> {
+
pub fn uri(
+
uri: impl Into<jacquard_common::CowStr<'a>>,
+
) -> Result<
+
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
+
jacquard_common::types::uri::UriError,
+
> {
+
jacquard_common::types::uri::RecordUri::try_from_uri(
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
+
)
+
}
+
}
+
+
/// Typed wrapper for GetRecord response with this collection's record type.
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct SubfsRecordGetRecordOutput<'a> {
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
+
#[serde(borrow)]
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
+
#[serde(borrow)]
+
pub uri: jacquard_common::types::string::AtUri<'a>,
+
#[serde(borrow)]
+
pub value: SubfsRecord<'a>,
+
}
+
+
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
+
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
+
use jacquard_common::IntoStatic;
+
output.value.into_static()
+
}
+
}
+
+
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
+
const NSID: &'static str = "place.wisp.subfs";
+
type Record = SubfsRecordRecord;
+
}
+
+
/// Marker type for deserializing records from this collection.
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
+
pub struct SubfsRecordRecord;
+
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
+
const NSID: &'static str = "place.wisp.subfs";
+
const ENCODING: &'static str = "application/json";
+
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
+
}
+
+
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
+
const NSID: &'static str = "place.wisp.subfs";
+
type Record = SubfsRecordRecord;
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.subfs"
+
}
+
fn def_name() -> &'static str {
+
"main"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_subfs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
if let Some(ref value) = self.file_count {
+
if *value > 1000i64 {
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"file_count",
+
),
+
max: 1000i64,
+
actual: *value,
+
});
+
}
+
}
+
if let Some(ref value) = self.file_count {
+
if *value < 0i64 {
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
+
"file_count",
+
),
+
min: 0i64,
+
actual: *value,
+
});
+
}
+
}
+
Ok(())
+
}
+
}
+
+
#[jacquard_derive::lexicon]
+
#[derive(
+
serde::Serialize,
+
serde::Deserialize,
+
Debug,
+
Clone,
+
PartialEq,
+
Eq,
+
jacquard_derive::IntoStatic
+
)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Subfs<'a> {
+
/// 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.
+
#[serde(borrow)]
+
pub subject: jacquard_common::types::string::AtUri<'a>,
+
#[serde(borrow)]
+
pub r#type: jacquard_common::CowStr<'a>,
+
}
+
+
pub mod subfs_state {
+
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
+
#[allow(unused)]
+
use ::core::marker::PhantomData;
+
mod sealed {
+
pub trait Sealed {}
+
}
+
/// State trait tracking which required fields have been set
+
pub trait State: sealed::Sealed {
+
type Subject;
+
type Type;
+
}
+
/// Empty state - all required fields are unset
+
pub struct Empty(());
+
impl sealed::Sealed for Empty {}
+
impl State for Empty {
+
type Subject = Unset;
+
type Type = Unset;
+
}
+
///State transition - sets the `subject` field to Set
+
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetSubject<S> {}
+
impl<S: State> State for SetSubject<S> {
+
type Subject = Set<members::subject>;
+
type Type = S::Type;
+
}
+
///State transition - sets the `type` field to Set
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
+
impl<S: State> sealed::Sealed for SetType<S> {}
+
impl<S: State> State for SetType<S> {
+
type Subject = S::Subject;
+
type Type = Set<members::r#type>;
+
}
+
/// Marker types for field names
+
#[allow(non_camel_case_types)]
+
pub mod members {
+
///Marker type for the `subject` field
+
pub struct subject(());
+
///Marker type for the `type` field
+
pub struct r#type(());
+
}
+
}
+
+
/// Builder for constructing an instance of this type
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
+
__unsafe_private_named: (
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
+
::core::option::Option<jacquard_common::CowStr<'a>>,
+
),
+
_phantom: ::core::marker::PhantomData<&'a ()>,
+
}
+
+
impl<'a> Subfs<'a> {
+
/// Create a new builder for this type
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
+
SubfsBuilder::new()
+
}
+
}
+
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
+
/// Create a new builder with all fields unset
+
pub fn new() -> Self {
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: (None, None),
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Subject: subfs_state::IsUnset,
+
{
+
/// Set the `subject` field (required)
+
pub fn subject(
+
mut self,
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Type: subfs_state::IsUnset,
+
{
+
/// Set the `type` field (required)
+
pub fn r#type(
+
mut self,
+
value: impl Into<jacquard_common::CowStr<'a>>,
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
+
SubfsBuilder {
+
_phantom_state: ::core::marker::PhantomData,
+
__unsafe_private_named: self.__unsafe_private_named,
+
_phantom: ::core::marker::PhantomData,
+
}
+
}
+
}
+
+
impl<'a, S> SubfsBuilder<'a, S>
+
where
+
S: subfs_state::State,
+
S::Subject: subfs_state::IsSet,
+
S::Type: subfs_state::IsSet,
+
{
+
/// Build the final struct
+
pub fn build(self) -> Subfs<'a> {
+
Subfs {
+
subject: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Default::default(),
+
}
+
}
+
/// Build the final struct with custom extra_data
+
pub fn build_with_data(
+
self,
+
extra_data: std::collections::BTreeMap<
+
jacquard_common::smol_str::SmolStr,
+
jacquard_common::types::value::Data<'a>,
+
>,
+
) -> Subfs<'a> {
+
Subfs {
+
subject: self.__unsafe_private_named.0.unwrap(),
+
r#type: self.__unsafe_private_named.1.unwrap(),
+
extra_data: Some(extra_data),
+
}
+
}
+
}
+
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
+
fn nsid() -> &'static str {
+
"place.wisp.subfs"
+
}
+
fn def_name() -> &'static str {
+
"subfs"
+
}
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
+
lexicon_doc_place_wisp_subfs()
+
}
+
fn validate(
+
&self,
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
+
Ok(())
+
}
+
}
+8
cli/crates/lexicons/src/place_wisp.rs
···
···
+
// @generated by jacquard-lexicon. DO NOT EDIT.
+
//
+
// This file was automatically generated from Lexicon schemas.
+
// Any manual changes will be overwritten on the next regeneration.
+
+
pub mod fs;
+
pub mod settings;
+
pub mod subfs;
+1 -1
cli/src/blob_map.rs
···
use jacquard_common::IntoStatic;
use std::collections::HashMap;
-
use crate::place_wisp::fs::{Directory, EntryNode};
/// Extract blob information from a directory tree
/// Returns a map of file paths to their blob refs and CIDs
···
use jacquard_common::IntoStatic;
use std::collections::HashMap;
+
use wisp_lexicons::place_wisp::fs::{Directory, EntryNode};
/// Extract blob information from a directory tree
/// Returns a map of file paths to their blob refs and CIDs
-43
cli/src/builder_types.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
/// Marker type indicating a builder field has been set
-
pub struct Set<T>(pub T);
-
impl<T> Set<T> {
-
/// Extract the inner value
-
#[inline]
-
pub fn into_inner(self) -> T {
-
self.0
-
}
-
}
-
-
/// Marker type indicating a builder field has not been set
-
pub struct Unset;
-
/// Trait indicating a builder field is set (has a value)
-
#[rustversion::attr(
-
since(1.78.0),
-
diagnostic::on_unimplemented(
-
message = "the field `{Self}` was not set, but this method requires it to be set",
-
label = "the field `{Self}` was not set"
-
)
-
)]
-
pub trait IsSet: private::Sealed {}
-
/// Trait indicating a builder field is unset (no value yet)
-
#[rustversion::attr(
-
since(1.78.0),
-
diagnostic::on_unimplemented(
-
message = "the field `{Self}` was already set, but this method requires it to be unset",
-
label = "the field `{Self}` was already set"
-
)
-
)]
-
pub trait IsUnset: private::Sealed {}
-
impl<T> IsSet for Set<T> {}
-
impl IsUnset for Unset {}
-
mod private {
-
/// Sealed trait to prevent external implementations
-
pub trait Sealed {}
-
impl<T> Sealed for super::Set<T> {}
-
impl Sealed for super::Unset {}
-
}
···
-9
cli/src/lib.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
pub mod builder_types;
-
-
#[cfg(feature = "place_wisp")]
-
pub mod place_wisp;
···
+28 -31
cli/src/main.rs
···
-
mod builder_types;
-
mod place_wisp;
mod cid;
mod blob_map;
mod metadata;
···
use futures::stream::{self, StreamExt};
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
-
use place_wisp::fs::*;
-
use place_wisp::settings::*;
/// Maximum number of concurrent file uploads to the PDS
const MAX_CONCURRENT_UPLOADS: usize = 2;
···
struct Args {
#[command(subcommand)]
command: Option<Commands>,
-
// Deploy arguments (when no subcommand is specified)
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
-
#[arg(global = true, conflicts_with = "command")]
input: Option<CowStr<'static>>,
/// Path to the directory containing your static site
-
#[arg(short, long, global = true, conflicts_with = "command")]
path: Option<PathBuf>,
/// Site name (defaults to directory name)
-
#[arg(short, long, global = true, conflicts_with = "command")]
site: Option<String>,
/// Path to auth store file
-
#[arg(long, global = true, conflicts_with = "command")]
store: Option<String>,
/// App Password for authentication
-
#[arg(long, global = true, conflicts_with = "command")]
password: Option<CowStr<'static>>,
/// Enable directory listing mode for paths without index files
-
#[arg(long, global = true, conflicts_with = "command")]
directory: bool,
/// Enable SPA mode (serve index.html for all routes)
-
#[arg(long, global = true, conflicts_with = "command")]
spa: bool,
/// Skip confirmation prompts (automatically accept warnings)
-
#[arg(short = 'y', long, global = true, conflicts_with = "command")]
yes: bool,
}
···
/// Output directory for the downloaded site
#[arg(short, long, default_value = ".")]
-
output: PathBuf,
},
/// Serve a site locally with real-time firehose updates
Serve {
···
/// Output directory for the site files
#[arg(short, long, default_value = ".")]
-
output: PathBuf,
/// Port to serve on
-
#[arg(short, long, default_value = "8080")]
port: u16,
},
}
···
run_with_oauth(input, store, path, site, directory, spa, yes).await
}
}
-
Some(Commands::Pull { input, site, output }) => {
-
pull::pull_site(input, CowStr::from(site), output).await
}
-
Some(Commands::Serve { input, site, output, port }) => {
-
serve::serve_site(input, CowStr::from(site), output, port).await
}
None => {
// Legacy mode: if input is provided, assume deploy command
···
let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
let chunk_size = subfs_utils::estimate_directory_size(chunk);
-
let chunk_manifest = crate::place_wisp::subfs::SubfsRecord::new()
.root(convert_fs_dir_to_subfs_dir(chunk.clone()))
.file_count(Some(chunk_file_count as i64))
.created_at(Datetime::now())
···
// Each chunk reference MUST have flat: true to merge chunk contents
println!(" โ†’ Creating parent subfs with {} chunk references...", chunk_uris.len());
use jacquard_common::CowStr;
-
use crate::place_wisp::fs::{Subfs};
// Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···
let parent_tid = Tid::now_0();
let parent_rkey = parent_tid.to_string();
-
let parent_manifest = crate::place_wisp::subfs::SubfsRecord::new()
.root(parent_root_subfs)
.file_count(Some(largest_dir.file_count as i64))
.created_at(Datetime::now())
···
let subfs_tid = Tid::now_0();
let subfs_rkey = subfs_tid.to_string();
-
let subfs_manifest = crate::place_wisp::subfs::SubfsRecord::new()
.root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
.file_count(Some(largest_dir.file_count as i64))
.created_at(Datetime::now())
···
/// Convert fs::Directory to subfs::Directory
/// They have the same structure, but different types
-
fn convert_fs_dir_to_subfs_dir(fs_dir: place_wisp::fs::Directory<'static>) -> place_wisp::subfs::Directory<'static> {
-
use place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
let node = match entry.node {
-
place_wisp::fs::EntryNode::File(file) => {
SubfsEntryNode::File(Box::new(SubfsFile::new()
.r#type(file.r#type)
.blob(file.blob)
···
.base64(file.base64)
.build()))
}
-
place_wisp::fs::EntryNode::Directory(dir) => {
SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
}
-
place_wisp::fs::EntryNode::Subfs(subfs) => {
// Nested subfs in the directory we're converting
// Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
-
SubfsEntryNode::Subfs(Box::new(place_wisp::subfs::Subfs::new()
.r#type(subfs.r#type)
.subject(subfs.subject)
.build()))
}
-
place_wisp::fs::EntryNode::Unknown(unknown) => {
SubfsEntryNode::Unknown(unknown)
}
};
···
mod cid;
mod blob_map;
mod metadata;
···
use futures::stream::{self, StreamExt};
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
+
use wisp_lexicons::place_wisp::fs::*;
+
use wisp_lexicons::place_wisp::settings::*;
/// Maximum number of concurrent file uploads to the PDS
const MAX_CONCURRENT_UPLOADS: usize = 2;
···
struct Args {
#[command(subcommand)]
command: Option<Commands>,
+
// Deploy arguments (when no subcommand is specified)
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
input: Option<CowStr<'static>>,
/// Path to the directory containing your static site
+
#[arg(short, long)]
path: Option<PathBuf>,
/// Site name (defaults to directory name)
+
#[arg(short, long)]
site: Option<String>,
/// Path to auth store file
+
#[arg(long)]
store: Option<String>,
/// App Password for authentication
+
#[arg(long)]
password: Option<CowStr<'static>>,
/// Enable directory listing mode for paths without index files
+
#[arg(long)]
directory: bool,
/// Enable SPA mode (serve index.html for all routes)
+
#[arg(long)]
spa: bool,
/// Skip confirmation prompts (automatically accept warnings)
+
#[arg(short = 'y', long)]
yes: bool,
}
···
/// Output directory for the downloaded site
#[arg(short, long, default_value = ".")]
+
path: PathBuf,
},
/// Serve a site locally with real-time firehose updates
Serve {
···
/// Output directory for the site files
#[arg(short, long, default_value = ".")]
+
path: PathBuf,
/// Port to serve on
+
#[arg(short = 'P', long, default_value = "8080")]
port: u16,
},
}
···
run_with_oauth(input, store, path, site, directory, spa, yes).await
}
}
+
Some(Commands::Pull { input, site, path }) => {
+
pull::pull_site(input, CowStr::from(site), path).await
}
+
Some(Commands::Serve { input, site, path, port }) => {
+
serve::serve_site(input, CowStr::from(site), path, port).await
}
None => {
// Legacy mode: if input is provided, assume deploy command
···
let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
let chunk_size = subfs_utils::estimate_directory_size(chunk);
+
let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
.root(convert_fs_dir_to_subfs_dir(chunk.clone()))
.file_count(Some(chunk_file_count as i64))
.created_at(Datetime::now())
···
// Each chunk reference MUST have flat: true to merge chunk contents
println!(" โ†’ Creating parent subfs with {} chunk references...", chunk_uris.len());
use jacquard_common::CowStr;
+
use wisp_lexicons::place_wisp::fs::{Subfs};
// Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···
let parent_tid = Tid::now_0();
let parent_rkey = parent_tid.to_string();
+
let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
.root(parent_root_subfs)
.file_count(Some(largest_dir.file_count as i64))
.created_at(Datetime::now())
···
let subfs_tid = Tid::now_0();
let subfs_rkey = subfs_tid.to_string();
+
let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
.root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
.file_count(Some(largest_dir.file_count as i64))
.created_at(Datetime::now())
···
/// Convert fs::Directory to subfs::Directory
/// They have the same structure, but different types
+
fn convert_fs_dir_to_subfs_dir(fs_dir: wisp_lexicons::place_wisp::fs::Directory<'static>) -> wisp_lexicons::place_wisp::subfs::Directory<'static> {
+
use wisp_lexicons::place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
let node = match entry.node {
+
wisp_lexicons::place_wisp::fs::EntryNode::File(file) => {
SubfsEntryNode::File(Box::new(SubfsFile::new()
.r#type(file.r#type)
.blob(file.blob)
···
.base64(file.base64)
.build()))
}
+
wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => {
SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
}
+
wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => {
// Nested subfs in the directory we're converting
// Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
+
SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new()
.r#type(subfs.r#type)
.subject(subfs.subject)
.build()))
}
+
wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => {
SubfsEntryNode::Unknown(unknown)
}
};
-9
cli/src/mod.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
pub mod builder_types;
-
-
#[cfg(feature = "place_wisp")]
-
pub mod place_wisp;
···
-1490
cli/src/place_wisp/fs.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// Lexicon: place.wisp.fs
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Directory<'a> {
-
#[serde(borrow)]
-
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod directory_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Entries;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Entries = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Entries = S::Entries;
-
}
-
///State transition - sets the `entries` field to Set
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
-
impl<S: State> State for SetEntries<S> {
-
type Type = S::Type;
-
type Entries = Set<members::entries>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `entries` field
-
pub struct entries(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Directory<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
-
DirectoryBuilder::new()
-
}
-
}
-
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Entries: directory_state::IsUnset,
-
{
-
/// Set the `entries` field (required)
-
pub fn entries(
-
mut self,
-
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Type: directory_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Type: directory_state::IsSet,
-
S::Entries: directory_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Directory<'a> {
-
Directory {
-
entries: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Directory<'a> {
-
Directory {
-
entries: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
::jacquard_lexicon::lexicon::LexiconDoc {
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
-
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
-
revision: None,
-
description: None,
-
defs: {
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
-
description: None,
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
-
description: None,
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
-
}),
-
min_length: None,
-
max_length: Some(500usize),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_common::smol_str::SmolStr::new_static("node")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(255usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
-
description: None,
-
refs: vec![
-
::jacquard_common::CowStr::new_static("#file"),
-
::jacquard_common::CowStr::new_static("#directory"),
-
::jacquard_common::CowStr::new_static("#subfs")
-
],
-
closed: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
-
description: None,
-
default: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
-
description: None,
-
accept: None,
-
max_size: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Content encoding (e.g., gzip for compressed files)",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Original MIME type before compression",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Virtual filesystem manifest for a Wisp site",
-
),
-
),
-
key: None,
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"createdAt",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: Some(
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
-
),
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"fileCount",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
-
description: None,
-
default: None,
-
minimum: Some(0i64),
-
maximum: Some(1000i64),
-
r#enum: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
-
description: None,
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("flat"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
-
description: None,
-
default: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
-
),
-
),
-
format: Some(
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
-
),
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map
-
},
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.fs"
-
}
-
fn def_name() -> &'static str {
-
"directory"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_fs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
{
-
let value = &self.entries;
-
#[allow(unused_comparisons)]
-
if value.len() > 500usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"entries",
-
),
-
max: 500usize,
-
actual: value.len(),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Entry<'a> {
-
#[serde(borrow)]
-
pub name: jacquard_common::CowStr<'a>,
-
#[serde(borrow)]
-
pub node: EntryNode<'a>,
-
}
-
-
pub mod entry_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Name;
-
type Node;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Name = Unset;
-
type Node = Unset;
-
}
-
///State transition - sets the `name` field to Set
-
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetName<S> {}
-
impl<S: State> State for SetName<S> {
-
type Name = Set<members::name>;
-
type Node = S::Node;
-
}
-
///State transition - sets the `node` field to Set
-
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetNode<S> {}
-
impl<S: State> State for SetNode<S> {
-
type Name = S::Name;
-
type Node = Set<members::node>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `name` field
-
pub struct name(());
-
///Marker type for the `node` field
-
pub struct node(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct EntryBuilder<'a, S: entry_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<EntryNode<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Entry<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
-
EntryBuilder::new()
-
}
-
}
-
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Name: entry_state::IsUnset,
-
{
-
/// Set the `name` field (required)
-
pub fn name(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Node: entry_state::IsUnset,
-
{
-
/// Set the `node` field (required)
-
pub fn node(
-
mut self,
-
value: impl Into<EntryNode<'a>>,
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Name: entry_state::IsSet,
-
S::Node: entry_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Entry<'a> {
-
Entry {
-
name: self.__unsafe_private_named.0.unwrap(),
-
node: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Entry<'a> {
-
Entry {
-
name: self.__unsafe_private_named.0.unwrap(),
-
node: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
#[jacquard_derive::open_union]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(tag = "$type")]
-
#[serde(bound(deserialize = "'de: 'a"))]
-
pub enum EntryNode<'a> {
-
#[serde(rename = "place.wisp.fs#file")]
-
File(Box<crate::place_wisp::fs::File<'a>>),
-
#[serde(rename = "place.wisp.fs#directory")]
-
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
-
#[serde(rename = "place.wisp.fs#subfs")]
-
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.fs"
-
}
-
fn def_name() -> &'static str {
-
"entry"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_fs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
{
-
let value = &self.name;
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 255usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"name",
-
),
-
max: 255usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct File<'a> {
-
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub base64: Option<bool>,
-
/// Content blob ref
-
#[serde(borrow)]
-
pub blob: jacquard_common::types::blob::BlobRef<'a>,
-
/// Content encoding (e.g., gzip for compressed files)
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub encoding: Option<jacquard_common::CowStr<'a>>,
-
/// Original MIME type before compression
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub mime_type: Option<jacquard_common::CowStr<'a>>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod file_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Blob;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Blob = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Blob = S::Blob;
-
}
-
///State transition - sets the `blob` field to Set
-
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetBlob<S> {}
-
impl<S: State> State for SetBlob<S> {
-
type Type = S::Type;
-
type Blob = Set<members::blob>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `blob` field
-
pub struct blob(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct FileBuilder<'a, S: file_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<bool>,
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> File<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
-
FileBuilder::new()
-
}
-
}
-
-
impl<'a> FileBuilder<'a, file_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `base64` field (optional)
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
-
self.__unsafe_private_named.0 = value.into();
-
self
-
}
-
/// Set the `base64` field to an Option value (optional)
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
-
self.__unsafe_private_named.0 = value;
-
self
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Blob: file_state::IsUnset,
-
{
-
/// Set the `blob` field (required)
-
pub fn blob(
-
mut self,
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `encoding` field (optional)
-
pub fn encoding(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.2 = value.into();
-
self
-
}
-
/// Set the `encoding` field to an Option value (optional)
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
-
self.__unsafe_private_named.2 = value;
-
self
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `mimeType` field (optional)
-
pub fn mime_type(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value.into();
-
self
-
}
-
/// Set the `mimeType` field to an Option value (optional)
-
pub fn maybe_mime_type(
-
mut self,
-
value: Option<jacquard_common::CowStr<'a>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value;
-
self
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Type: file_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> FileBuilder<'a, file_state::SetType<S>> {
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Type: file_state::IsSet,
-
S::Blob: file_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> File<'a> {
-
File {
-
base64: self.__unsafe_private_named.0,
-
blob: self.__unsafe_private_named.1.unwrap(),
-
encoding: self.__unsafe_private_named.2,
-
mime_type: self.__unsafe_private_named.3,
-
r#type: self.__unsafe_private_named.4.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> File<'a> {
-
File {
-
base64: self.__unsafe_private_named.0,
-
blob: self.__unsafe_private_named.1.unwrap(),
-
encoding: self.__unsafe_private_named.2,
-
mime_type: self.__unsafe_private_named.3,
-
r#type: self.__unsafe_private_named.4.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.fs"
-
}
-
fn def_name() -> &'static str {
-
"file"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_fs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
Ok(())
-
}
-
}
-
-
/// Virtual filesystem manifest for a Wisp site
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Fs<'a> {
-
pub created_at: jacquard_common::types::string::Datetime,
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub file_count: Option<i64>,
-
#[serde(borrow)]
-
pub root: crate::place_wisp::fs::Directory<'a>,
-
#[serde(borrow)]
-
pub site: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod fs_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Site;
-
type Root;
-
type CreatedAt;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Site = Unset;
-
type Root = Unset;
-
type CreatedAt = Unset;
-
}
-
///State transition - sets the `site` field to Set
-
pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetSite<S> {}
-
impl<S: State> State for SetSite<S> {
-
type Site = Set<members::site>;
-
type Root = S::Root;
-
type CreatedAt = S::CreatedAt;
-
}
-
///State transition - sets the `root` field to Set
-
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetRoot<S> {}
-
impl<S: State> State for SetRoot<S> {
-
type Site = S::Site;
-
type Root = Set<members::root>;
-
type CreatedAt = S::CreatedAt;
-
}
-
///State transition - sets the `created_at` field to Set
-
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
-
impl<S: State> State for SetCreatedAt<S> {
-
type Site = S::Site;
-
type Root = S::Root;
-
type CreatedAt = Set<members::created_at>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `site` field
-
pub struct site(());
-
///Marker type for the `root` field
-
pub struct root(());
-
///Marker type for the `created_at` field
-
pub struct created_at(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct FsBuilder<'a, S: fs_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<jacquard_common::types::string::Datetime>,
-
::core::option::Option<i64>,
-
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Fs<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
-
FsBuilder::new()
-
}
-
}
-
-
impl<'a> FsBuilder<'a, fs_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
FsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> FsBuilder<'a, S>
-
where
-
S: fs_state::State,
-
S::CreatedAt: fs_state::IsUnset,
-
{
-
/// Set the `createdAt` field (required)
-
pub fn created_at(
-
mut self,
-
value: impl Into<jacquard_common::types::string::Datetime>,
-
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
FsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
-
/// Set the `fileCount` field (optional)
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
-
self.__unsafe_private_named.1 = value.into();
-
self
-
}
-
/// Set the `fileCount` field to an Option value (optional)
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
-
self.__unsafe_private_named.1 = value;
-
self
-
}
-
}
-
-
impl<'a, S> FsBuilder<'a, S>
-
where
-
S: fs_state::State,
-
S::Root: fs_state::IsUnset,
-
{
-
/// Set the `root` field (required)
-
pub fn root(
-
mut self,
-
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
-
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
-
FsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> FsBuilder<'a, S>
-
where
-
S: fs_state::State,
-
S::Site: fs_state::IsUnset,
-
{
-
/// Set the `site` field (required)
-
pub fn site(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> FsBuilder<'a, fs_state::SetSite<S>> {
-
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
-
FsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> FsBuilder<'a, S>
-
where
-
S: fs_state::State,
-
S::Site: fs_state::IsSet,
-
S::Root: fs_state::IsSet,
-
S::CreatedAt: fs_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Fs<'a> {
-
Fs {
-
created_at: self.__unsafe_private_named.0.unwrap(),
-
file_count: self.__unsafe_private_named.1,
-
root: self.__unsafe_private_named.2.unwrap(),
-
site: self.__unsafe_private_named.3.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Fs<'a> {
-
Fs {
-
created_at: self.__unsafe_private_named.0.unwrap(),
-
file_count: self.__unsafe_private_named.1,
-
root: self.__unsafe_private_named.2.unwrap(),
-
site: self.__unsafe_private_named.3.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> Fs<'a> {
-
pub fn uri(
-
uri: impl Into<jacquard_common::CowStr<'a>>,
-
) -> Result<
-
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
-
jacquard_common::types::uri::UriError,
-
> {
-
jacquard_common::types::uri::RecordUri::try_from_uri(
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
-
)
-
}
-
}
-
-
/// Typed wrapper for GetRecord response with this collection's record type.
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct FsGetRecordOutput<'a> {
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
-
#[serde(borrow)]
-
pub uri: jacquard_common::types::string::AtUri<'a>,
-
#[serde(borrow)]
-
pub value: Fs<'a>,
-
}
-
-
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
-
fn from(output: FsGetRecordOutput<'_>) -> Self {
-
use jacquard_common::IntoStatic;
-
output.value.into_static()
-
}
-
}
-
-
impl jacquard_common::types::collection::Collection for Fs<'_> {
-
const NSID: &'static str = "place.wisp.fs";
-
type Record = FsRecord;
-
}
-
-
/// Marker type for deserializing records from this collection.
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
-
pub struct FsRecord;
-
impl jacquard_common::xrpc::XrpcResp for FsRecord {
-
const NSID: &'static str = "place.wisp.fs";
-
const ENCODING: &'static str = "application/json";
-
type Output<'de> = FsGetRecordOutput<'de>;
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
-
}
-
-
impl jacquard_common::types::collection::Collection for FsRecord {
-
const NSID: &'static str = "place.wisp.fs";
-
type Record = FsRecord;
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.fs"
-
}
-
fn def_name() -> &'static str {
-
"main"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_fs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
if let Some(ref value) = self.file_count {
-
if *value > 1000i64 {
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"file_count",
-
),
-
max: 1000i64,
-
actual: *value,
-
});
-
}
-
}
-
if let Some(ref value) = self.file_count {
-
if *value < 0i64 {
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"file_count",
-
),
-
min: 0i64,
-
actual: *value,
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Subfs<'a> {
-
/// 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.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub flat: Option<bool>,
-
/// AT-URI pointing to a place.wisp.subfs record containing this subtree.
-
#[serde(borrow)]
-
pub subject: jacquard_common::types::string::AtUri<'a>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod subfs_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Subject;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Subject = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Subject = S::Subject;
-
}
-
///State transition - sets the `subject` field to Set
-
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetSubject<S> {}
-
impl<S: State> State for SetSubject<S> {
-
type Type = S::Type;
-
type Subject = Set<members::subject>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `subject` field
-
pub struct subject(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<bool>,
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Subfs<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
-
SubfsBuilder::new()
-
}
-
}
-
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
-
/// Set the `flat` field (optional)
-
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
-
self.__unsafe_private_named.0 = value.into();
-
self
-
}
-
/// Set the `flat` field to an Option value (optional)
-
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
-
self.__unsafe_private_named.0 = value;
-
self
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Subject: subfs_state::IsUnset,
-
{
-
/// Set the `subject` field (required)
-
pub fn subject(
-
mut self,
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Type: subfs_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Type: subfs_state::IsSet,
-
S::Subject: subfs_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Subfs<'a> {
-
Subfs {
-
flat: self.__unsafe_private_named.0,
-
subject: self.__unsafe_private_named.1.unwrap(),
-
r#type: self.__unsafe_private_named.2.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Subfs<'a> {
-
Subfs {
-
flat: self.__unsafe_private_named.0,
-
subject: self.__unsafe_private_named.1.unwrap(),
-
r#type: self.__unsafe_private_named.2.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.fs"
-
}
-
fn def_name() -> &'static str {
-
"subfs"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_fs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
Ok(())
-
}
-
}
···
-653
cli/src/place_wisp/settings.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// Lexicon: place.wisp.settings
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
/// Custom HTTP header configuration
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic,
-
Default
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct CustomHeader<'a> {
-
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
-
#[serde(borrow)]
-
pub name: jacquard_common::CowStr<'a>,
-
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
-
/// HTTP header value
-
#[serde(borrow)]
-
pub value: jacquard_common::CowStr<'a>,
-
}
-
-
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
-
'static,
-
> {
-
::jacquard_lexicon::lexicon::LexiconDoc {
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
-
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
-
revision: None,
-
description: None,
-
defs: {
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Custom HTTP header configuration",
-
),
-
),
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_common::smol_str::SmolStr::new_static("value")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(100usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("path"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(500usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("value"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static("HTTP header value"),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(1000usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Configuration settings for a static site hosted on wisp.place",
-
),
-
),
-
key: Some(::jacquard_common::CowStr::new_static("any")),
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: None,
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"cleanUrls",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
-
description: None,
-
default: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"custom404",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(500usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"directoryListing",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
-
description: None,
-
default: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("headers"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Custom HTTP headers to set on responses",
-
),
-
),
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
-
description: None,
-
r#ref: ::jacquard_common::CowStr::new_static(
-
"#customHeader",
-
),
-
}),
-
min_length: None,
-
max_length: Some(50usize),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"indexFiles",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
-
),
-
),
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(255usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
min_length: None,
-
max_length: Some(10usize),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"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.",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(500usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
}),
-
);
-
map
-
},
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.settings"
-
}
-
fn def_name() -> &'static str {
-
"customHeader"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_settings()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
{
-
let value = &self.name;
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 100usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"name",
-
),
-
max: 100usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
if let Some(ref value) = self.path {
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 500usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"path",
-
),
-
max: 500usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
{
-
let value = &self.value;
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 1000usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"value",
-
),
-
max: 1000usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
/// Configuration settings for a static site hosted on wisp.place
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Settings<'a> {
-
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub clean_urls: std::option::Option<bool>,
-
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
-
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub directory_listing: std::option::Option<bool>,
-
/// Custom HTTP headers to set on responses
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
-
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
-
/// 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.
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
-
}
-
-
pub mod settings_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct SettingsBuilder<'a, S: settings_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<bool>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<bool>,
-
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
-
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Settings<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
-
SettingsBuilder::new()
-
}
-
}
-
-
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
SettingsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None, None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `cleanUrls` field (optional)
-
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
-
self.__unsafe_private_named.0 = value.into();
-
self
-
}
-
/// Set the `cleanUrls` field to an Option value (optional)
-
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
-
self.__unsafe_private_named.0 = value;
-
self
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `custom404` field (optional)
-
pub fn custom404(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.1 = value.into();
-
self
-
}
-
/// Set the `custom404` field to an Option value (optional)
-
pub fn maybe_custom404(
-
mut self,
-
value: Option<jacquard_common::CowStr<'a>>,
-
) -> Self {
-
self.__unsafe_private_named.1 = value;
-
self
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `directoryListing` field (optional)
-
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
-
self.__unsafe_private_named.2 = value.into();
-
self
-
}
-
/// Set the `directoryListing` field to an Option value (optional)
-
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
-
self.__unsafe_private_named.2 = value;
-
self
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `headers` field (optional)
-
pub fn headers(
-
mut self,
-
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value.into();
-
self
-
}
-
/// Set the `headers` field to an Option value (optional)
-
pub fn maybe_headers(
-
mut self,
-
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value;
-
self
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `indexFiles` field (optional)
-
pub fn index_files(
-
mut self,
-
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
-
) -> Self {
-
self.__unsafe_private_named.4 = value.into();
-
self
-
}
-
/// Set the `indexFiles` field to an Option value (optional)
-
pub fn maybe_index_files(
-
mut self,
-
value: Option<Vec<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.4 = value;
-
self
-
}
-
}
-
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
-
/// Set the `spaMode` field (optional)
-
pub fn spa_mode(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.5 = value.into();
-
self
-
}
-
/// Set the `spaMode` field to an Option value (optional)
-
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
-
self.__unsafe_private_named.5 = value;
-
self
-
}
-
}
-
-
impl<'a, S> SettingsBuilder<'a, S>
-
where
-
S: settings_state::State,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Settings<'a> {
-
Settings {
-
clean_urls: self.__unsafe_private_named.0,
-
custom404: self.__unsafe_private_named.1,
-
directory_listing: self.__unsafe_private_named.2,
-
headers: self.__unsafe_private_named.3,
-
index_files: self.__unsafe_private_named.4,
-
spa_mode: self.__unsafe_private_named.5,
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Settings<'a> {
-
Settings {
-
clean_urls: self.__unsafe_private_named.0,
-
custom404: self.__unsafe_private_named.1,
-
directory_listing: self.__unsafe_private_named.2,
-
headers: self.__unsafe_private_named.3,
-
index_files: self.__unsafe_private_named.4,
-
spa_mode: self.__unsafe_private_named.5,
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> Settings<'a> {
-
pub fn uri(
-
uri: impl Into<jacquard_common::CowStr<'a>>,
-
) -> Result<
-
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
-
jacquard_common::types::uri::UriError,
-
> {
-
jacquard_common::types::uri::RecordUri::try_from_uri(
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
-
)
-
}
-
}
-
-
/// Typed wrapper for GetRecord response with this collection's record type.
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct SettingsGetRecordOutput<'a> {
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
-
#[serde(borrow)]
-
pub uri: jacquard_common::types::string::AtUri<'a>,
-
#[serde(borrow)]
-
pub value: Settings<'a>,
-
}
-
-
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
-
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
-
use jacquard_common::IntoStatic;
-
output.value.into_static()
-
}
-
}
-
-
impl jacquard_common::types::collection::Collection for Settings<'_> {
-
const NSID: &'static str = "place.wisp.settings";
-
type Record = SettingsRecord;
-
}
-
-
/// Marker type for deserializing records from this collection.
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
-
pub struct SettingsRecord;
-
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
-
const NSID: &'static str = "place.wisp.settings";
-
const ENCODING: &'static str = "application/json";
-
type Output<'de> = SettingsGetRecordOutput<'de>;
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
-
}
-
-
impl jacquard_common::types::collection::Collection for SettingsRecord {
-
const NSID: &'static str = "place.wisp.settings";
-
type Record = SettingsRecord;
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.settings"
-
}
-
fn def_name() -> &'static str {
-
"main"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_settings()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
if let Some(ref value) = self.custom404 {
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 500usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"custom404",
-
),
-
max: 500usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
if let Some(ref value) = self.headers {
-
#[allow(unused_comparisons)]
-
if value.len() > 50usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"headers",
-
),
-
max: 50usize,
-
actual: value.len(),
-
});
-
}
-
}
-
if let Some(ref value) = self.index_files {
-
#[allow(unused_comparisons)]
-
if value.len() > 10usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"index_files",
-
),
-
max: 10usize,
-
actual: value.len(),
-
});
-
}
-
}
-
if let Some(ref value) = self.spa_mode {
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 500usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"spa_mode",
-
),
-
max: 500usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
···
-1408
cli/src/place_wisp/subfs.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// Lexicon: place.wisp.subfs
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Directory<'a> {
-
#[serde(borrow)]
-
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod directory_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Entries;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Entries = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Entries = S::Entries;
-
}
-
///State transition - sets the `entries` field to Set
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
-
impl<S: State> State for SetEntries<S> {
-
type Type = S::Type;
-
type Entries = Set<members::entries>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `entries` field
-
pub struct entries(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Directory<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
-
DirectoryBuilder::new()
-
}
-
}
-
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Entries: directory_state::IsUnset,
-
{
-
/// Set the `entries` field (required)
-
pub fn entries(
-
mut self,
-
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Type: directory_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
DirectoryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> DirectoryBuilder<'a, S>
-
where
-
S: directory_state::State,
-
S::Type: directory_state::IsSet,
-
S::Entries: directory_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Directory<'a> {
-
Directory {
-
entries: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Directory<'a> {
-
Directory {
-
entries: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
::jacquard_lexicon::lexicon::LexiconDoc {
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
-
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
-
revision: None,
-
description: None,
-
defs: {
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
-
description: None,
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
-
description: None,
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
-
}),
-
min_length: None,
-
max_length: Some(500usize),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_common::smol_str::SmolStr::new_static("node")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: Some(255usize),
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
-
description: None,
-
refs: vec![
-
::jacquard_common::CowStr::new_static("#file"),
-
::jacquard_common::CowStr::new_static("#directory"),
-
::jacquard_common::CowStr::new_static("#subfs")
-
],
-
closed: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
-
description: None,
-
default: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
-
description: None,
-
accept: None,
-
max_size: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Content encoding (e.g., gzip for compressed files)",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"Original MIME type before compression",
-
),
-
),
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"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.",
-
),
-
),
-
key: None,
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"createdAt",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: Some(
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
-
),
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static(
-
"fileCount",
-
),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
-
description: None,
-
default: None,
-
minimum: Some(0i64),
-
maximum: Some(1000i64),
-
r#enum: None,
-
r#const: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
-
description: None,
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
-
}),
-
);
-
map
-
},
-
}),
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
-
description: None,
-
required: Some(
-
vec![
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
-
],
-
),
-
nullable: None,
-
properties: {
-
#[allow(unused_mut)]
-
let mut map = ::std::collections::BTreeMap::new();
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: Some(
-
::jacquard_common::CowStr::new_static(
-
"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.",
-
),
-
),
-
format: Some(
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
-
),
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map.insert(
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
-
description: None,
-
format: None,
-
default: None,
-
min_length: None,
-
max_length: None,
-
min_graphemes: None,
-
max_graphemes: None,
-
r#enum: None,
-
r#const: None,
-
known_values: None,
-
}),
-
);
-
map
-
},
-
}),
-
);
-
map
-
},
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.subfs"
-
}
-
fn def_name() -> &'static str {
-
"directory"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_subfs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
{
-
let value = &self.entries;
-
#[allow(unused_comparisons)]
-
if value.len() > 500usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"entries",
-
),
-
max: 500usize,
-
actual: value.len(),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Entry<'a> {
-
#[serde(borrow)]
-
pub name: jacquard_common::CowStr<'a>,
-
#[serde(borrow)]
-
pub node: EntryNode<'a>,
-
}
-
-
pub mod entry_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Name;
-
type Node;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Name = Unset;
-
type Node = Unset;
-
}
-
///State transition - sets the `name` field to Set
-
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetName<S> {}
-
impl<S: State> State for SetName<S> {
-
type Name = Set<members::name>;
-
type Node = S::Node;
-
}
-
///State transition - sets the `node` field to Set
-
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetNode<S> {}
-
impl<S: State> State for SetNode<S> {
-
type Name = S::Name;
-
type Node = Set<members::node>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `name` field
-
pub struct name(());
-
///Marker type for the `node` field
-
pub struct node(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct EntryBuilder<'a, S: entry_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<EntryNode<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Entry<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
-
EntryBuilder::new()
-
}
-
}
-
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Name: entry_state::IsUnset,
-
{
-
/// Set the `name` field (required)
-
pub fn name(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Node: entry_state::IsUnset,
-
{
-
/// Set the `node` field (required)
-
pub fn node(
-
mut self,
-
value: impl Into<EntryNode<'a>>,
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
EntryBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> EntryBuilder<'a, S>
-
where
-
S: entry_state::State,
-
S::Name: entry_state::IsSet,
-
S::Node: entry_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Entry<'a> {
-
Entry {
-
name: self.__unsafe_private_named.0.unwrap(),
-
node: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Entry<'a> {
-
Entry {
-
name: self.__unsafe_private_named.0.unwrap(),
-
node: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
#[jacquard_derive::open_union]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(tag = "$type")]
-
#[serde(bound(deserialize = "'de: 'a"))]
-
pub enum EntryNode<'a> {
-
#[serde(rename = "place.wisp.subfs#file")]
-
File(Box<crate::place_wisp::subfs::File<'a>>),
-
#[serde(rename = "place.wisp.subfs#directory")]
-
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
-
#[serde(rename = "place.wisp.subfs#subfs")]
-
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.subfs"
-
}
-
fn def_name() -> &'static str {
-
"entry"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_subfs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
{
-
let value = &self.name;
-
#[allow(unused_comparisons)]
-
if <str>::len(value.as_ref()) > 255usize {
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"name",
-
),
-
max: 255usize,
-
actual: <str>::len(value.as_ref()),
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct File<'a> {
-
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub base64: Option<bool>,
-
/// Content blob ref
-
#[serde(borrow)]
-
pub blob: jacquard_common::types::blob::BlobRef<'a>,
-
/// Content encoding (e.g., gzip for compressed files)
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub encoding: Option<jacquard_common::CowStr<'a>>,
-
/// Original MIME type before compression
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub mime_type: Option<jacquard_common::CowStr<'a>>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod file_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Blob;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Blob = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Blob = S::Blob;
-
}
-
///State transition - sets the `blob` field to Set
-
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetBlob<S> {}
-
impl<S: State> State for SetBlob<S> {
-
type Type = S::Type;
-
type Blob = Set<members::blob>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `blob` field
-
pub struct blob(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct FileBuilder<'a, S: file_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<bool>,
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> File<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
-
FileBuilder::new()
-
}
-
}
-
-
impl<'a> FileBuilder<'a, file_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `base64` field (optional)
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
-
self.__unsafe_private_named.0 = value.into();
-
self
-
}
-
/// Set the `base64` field to an Option value (optional)
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
-
self.__unsafe_private_named.0 = value;
-
self
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Blob: file_state::IsUnset,
-
{
-
/// Set the `blob` field (required)
-
pub fn blob(
-
mut self,
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `encoding` field (optional)
-
pub fn encoding(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.2 = value.into();
-
self
-
}
-
/// Set the `encoding` field to an Option value (optional)
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
-
self.__unsafe_private_named.2 = value;
-
self
-
}
-
}
-
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
-
/// Set the `mimeType` field (optional)
-
pub fn mime_type(
-
mut self,
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value.into();
-
self
-
}
-
/// Set the `mimeType` field to an Option value (optional)
-
pub fn maybe_mime_type(
-
mut self,
-
value: Option<jacquard_common::CowStr<'a>>,
-
) -> Self {
-
self.__unsafe_private_named.3 = value;
-
self
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Type: file_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> FileBuilder<'a, file_state::SetType<S>> {
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
-
FileBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> FileBuilder<'a, S>
-
where
-
S: file_state::State,
-
S::Type: file_state::IsSet,
-
S::Blob: file_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> File<'a> {
-
File {
-
base64: self.__unsafe_private_named.0,
-
blob: self.__unsafe_private_named.1.unwrap(),
-
encoding: self.__unsafe_private_named.2,
-
mime_type: self.__unsafe_private_named.3,
-
r#type: self.__unsafe_private_named.4.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> File<'a> {
-
File {
-
base64: self.__unsafe_private_named.0,
-
blob: self.__unsafe_private_named.1.unwrap(),
-
encoding: self.__unsafe_private_named.2,
-
mime_type: self.__unsafe_private_named.3,
-
r#type: self.__unsafe_private_named.4.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.subfs"
-
}
-
fn def_name() -> &'static str {
-
"file"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_subfs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
Ok(())
-
}
-
}
-
-
/// 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.
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct SubfsRecord<'a> {
-
pub created_at: jacquard_common::types::string::Datetime,
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
pub file_count: Option<i64>,
-
#[serde(borrow)]
-
pub root: crate::place_wisp::subfs::Directory<'a>,
-
}
-
-
pub mod subfs_record_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Root;
-
type CreatedAt;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Root = Unset;
-
type CreatedAt = Unset;
-
}
-
///State transition - sets the `root` field to Set
-
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetRoot<S> {}
-
impl<S: State> State for SetRoot<S> {
-
type Root = Set<members::root>;
-
type CreatedAt = S::CreatedAt;
-
}
-
///State transition - sets the `created_at` field to Set
-
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
-
impl<S: State> State for SetCreatedAt<S> {
-
type Root = S::Root;
-
type CreatedAt = Set<members::created_at>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `root` field
-
pub struct root(());
-
///Marker type for the `created_at` field
-
pub struct created_at(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<jacquard_common::types::string::Datetime>,
-
::core::option::Option<i64>,
-
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> SubfsRecord<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
-
SubfsRecordBuilder::new()
-
}
-
}
-
-
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
SubfsRecordBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsRecordBuilder<'a, S>
-
where
-
S: subfs_record_state::State,
-
S::CreatedAt: subfs_record_state::IsUnset,
-
{
-
/// Set the `createdAt` field (required)
-
pub fn created_at(
-
mut self,
-
value: impl Into<jacquard_common::types::string::Datetime>,
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
SubfsRecordBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
-
/// Set the `fileCount` field (optional)
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
-
self.__unsafe_private_named.1 = value.into();
-
self
-
}
-
/// Set the `fileCount` field to an Option value (optional)
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
-
self.__unsafe_private_named.1 = value;
-
self
-
}
-
}
-
-
impl<'a, S> SubfsRecordBuilder<'a, S>
-
where
-
S: subfs_record_state::State,
-
S::Root: subfs_record_state::IsUnset,
-
{
-
/// Set the `root` field (required)
-
pub fn root(
-
mut self,
-
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
-
SubfsRecordBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsRecordBuilder<'a, S>
-
where
-
S: subfs_record_state::State,
-
S::Root: subfs_record_state::IsSet,
-
S::CreatedAt: subfs_record_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> SubfsRecord<'a> {
-
SubfsRecord {
-
created_at: self.__unsafe_private_named.0.unwrap(),
-
file_count: self.__unsafe_private_named.1,
-
root: self.__unsafe_private_named.2.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> SubfsRecord<'a> {
-
SubfsRecord {
-
created_at: self.__unsafe_private_named.0.unwrap(),
-
file_count: self.__unsafe_private_named.1,
-
root: self.__unsafe_private_named.2.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> SubfsRecord<'a> {
-
pub fn uri(
-
uri: impl Into<jacquard_common::CowStr<'a>>,
-
) -> Result<
-
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
-
jacquard_common::types::uri::UriError,
-
> {
-
jacquard_common::types::uri::RecordUri::try_from_uri(
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
-
)
-
}
-
}
-
-
/// Typed wrapper for GetRecord response with this collection's record type.
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct SubfsRecordGetRecordOutput<'a> {
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
-
#[serde(borrow)]
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
-
#[serde(borrow)]
-
pub uri: jacquard_common::types::string::AtUri<'a>,
-
#[serde(borrow)]
-
pub value: SubfsRecord<'a>,
-
}
-
-
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
-
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
-
use jacquard_common::IntoStatic;
-
output.value.into_static()
-
}
-
}
-
-
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
-
const NSID: &'static str = "place.wisp.subfs";
-
type Record = SubfsRecordRecord;
-
}
-
-
/// Marker type for deserializing records from this collection.
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
-
pub struct SubfsRecordRecord;
-
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
-
const NSID: &'static str = "place.wisp.subfs";
-
const ENCODING: &'static str = "application/json";
-
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
-
}
-
-
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
-
const NSID: &'static str = "place.wisp.subfs";
-
type Record = SubfsRecordRecord;
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.subfs"
-
}
-
fn def_name() -> &'static str {
-
"main"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_subfs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
if let Some(ref value) = self.file_count {
-
if *value > 1000i64 {
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"file_count",
-
),
-
max: 1000i64,
-
actual: *value,
-
});
-
}
-
}
-
if let Some(ref value) = self.file_count {
-
if *value < 0i64 {
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
-
"file_count",
-
),
-
min: 0i64,
-
actual: *value,
-
});
-
}
-
}
-
Ok(())
-
}
-
}
-
-
#[jacquard_derive::lexicon]
-
#[derive(
-
serde::Serialize,
-
serde::Deserialize,
-
Debug,
-
Clone,
-
PartialEq,
-
Eq,
-
jacquard_derive::IntoStatic
-
)]
-
#[serde(rename_all = "camelCase")]
-
pub struct Subfs<'a> {
-
/// 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.
-
#[serde(borrow)]
-
pub subject: jacquard_common::types::string::AtUri<'a>,
-
#[serde(borrow)]
-
pub r#type: jacquard_common::CowStr<'a>,
-
}
-
-
pub mod subfs_state {
-
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
-
#[allow(unused)]
-
use ::core::marker::PhantomData;
-
mod sealed {
-
pub trait Sealed {}
-
}
-
/// State trait tracking which required fields have been set
-
pub trait State: sealed::Sealed {
-
type Type;
-
type Subject;
-
}
-
/// Empty state - all required fields are unset
-
pub struct Empty(());
-
impl sealed::Sealed for Empty {}
-
impl State for Empty {
-
type Type = Unset;
-
type Subject = Unset;
-
}
-
///State transition - sets the `type` field to Set
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetType<S> {}
-
impl<S: State> State for SetType<S> {
-
type Type = Set<members::r#type>;
-
type Subject = S::Subject;
-
}
-
///State transition - sets the `subject` field to Set
-
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
-
impl<S: State> sealed::Sealed for SetSubject<S> {}
-
impl<S: State> State for SetSubject<S> {
-
type Type = S::Type;
-
type Subject = Set<members::subject>;
-
}
-
/// Marker types for field names
-
#[allow(non_camel_case_types)]
-
pub mod members {
-
///Marker type for the `type` field
-
pub struct r#type(());
-
///Marker type for the `subject` field
-
pub struct subject(());
-
}
-
}
-
-
/// Builder for constructing an instance of this type
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
-
__unsafe_private_named: (
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
-
::core::option::Option<jacquard_common::CowStr<'a>>,
-
),
-
_phantom: ::core::marker::PhantomData<&'a ()>,
-
}
-
-
impl<'a> Subfs<'a> {
-
/// Create a new builder for this type
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
-
SubfsBuilder::new()
-
}
-
}
-
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
-
/// Create a new builder with all fields unset
-
pub fn new() -> Self {
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: (None, None),
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Subject: subfs_state::IsUnset,
-
{
-
/// Set the `subject` field (required)
-
pub fn subject(
-
mut self,
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Type: subfs_state::IsUnset,
-
{
-
/// Set the `type` field (required)
-
pub fn r#type(
-
mut self,
-
value: impl Into<jacquard_common::CowStr<'a>>,
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
-
SubfsBuilder {
-
_phantom_state: ::core::marker::PhantomData,
-
__unsafe_private_named: self.__unsafe_private_named,
-
_phantom: ::core::marker::PhantomData,
-
}
-
}
-
}
-
-
impl<'a, S> SubfsBuilder<'a, S>
-
where
-
S: subfs_state::State,
-
S::Type: subfs_state::IsSet,
-
S::Subject: subfs_state::IsSet,
-
{
-
/// Build the final struct
-
pub fn build(self) -> Subfs<'a> {
-
Subfs {
-
subject: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Default::default(),
-
}
-
}
-
/// Build the final struct with custom extra_data
-
pub fn build_with_data(
-
self,
-
extra_data: std::collections::BTreeMap<
-
jacquard_common::smol_str::SmolStr,
-
jacquard_common::types::value::Data<'a>,
-
>,
-
) -> Subfs<'a> {
-
Subfs {
-
subject: self.__unsafe_private_named.0.unwrap(),
-
r#type: self.__unsafe_private_named.1.unwrap(),
-
extra_data: Some(extra_data),
-
}
-
}
-
}
-
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
-
fn nsid() -> &'static str {
-
"place.wisp.subfs"
-
}
-
fn def_name() -> &'static str {
-
"subfs"
-
}
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
-
lexicon_doc_place_wisp_subfs()
-
}
-
fn validate(
-
&self,
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
-
Ok(())
-
}
-
}
···
-8
cli/src/place_wisp.rs
···
-
// @generated by jacquard-lexicon. DO NOT EDIT.
-
//
-
// This file was automatically generated from Lexicon schemas.
-
// Any manual changes will be overwritten on the next regeneration.
-
-
pub mod fs;
-
pub mod settings;
-
pub mod subfs;
···
+12 -12
cli/src/pull.rs
···
use crate::blob_map;
use crate::download;
use crate::metadata::SiteMetadata;
-
use crate::place_wisp::fs::*;
use crate::subfs_utils;
use jacquard::CowStr;
use jacquard::prelude::IdentityResolver;
···
) -> miette::Result<Directory<'static>> {
use jacquard_common::IntoStatic;
use jacquard_common::types::value::from_data;
-
use crate::place_wisp::subfs::SubfsRecord;
-
let mut all_subfs_map: HashMap<String, crate::place_wisp::subfs::Directory> = HashMap::new();
let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new());
if to_fetch.is_empty() {
···
/// Extract subfs URIs from a subfs::Directory (helper for pull)
fn extract_subfs_uris_from_subfs_dir(
-
directory: &crate::place_wisp::subfs::Directory,
current_path: String,
) -> Vec<(String, String)> {
let mut uris = Vec::new();
···
};
match &entry.node {
-
crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
uris.push((subfs_node.subject.to_string(), full_path.clone()));
}
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path);
uris.extend(nested);
}
···
/// Recursively replace subfs nodes with their actual content
fn replace_subfs_with_content(
directory: Directory,
-
subfs_map: &HashMap<String, crate::place_wisp::subfs::Directory>,
current_path: String,
) -> Directory<'static> {
use jacquard_common::IntoStatic;
···
}
/// Convert a subfs entry to a fs entry (they have the same structure but different types)
-
fn convert_subfs_entry_to_fs(subfs_entry: crate::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
use jacquard_common::IntoStatic;
let node = match subfs_entry.node {
-
crate::place_wisp::subfs::EntryNode::File(file) => {
EntryNode::File(Box::new(
File::new()
.r#type(file.r#type.into_static())
···
.build()
))
}
-
crate::place_wisp::subfs::EntryNode::Directory(dir) => {
let converted_entries: Vec<Entry<'static>> = dir
.entries
.into_iter()
···
.build()
))
}
-
crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
// Nested subfs should have been expanded already - if we get here, it means expansion failed
// Treat it like a directory reference that should have been expanded
eprintln!(" โš ๏ธ Warning: unexpanded nested subfs at path, treating as empty directory");
···
.build()
))
}
-
crate::place_wisp::subfs::EntryNode::Unknown(unknown) => {
EntryNode::Unknown(unknown)
}
};
···
use crate::blob_map;
use crate::download;
use crate::metadata::SiteMetadata;
+
use wisp_lexicons::place_wisp::fs::*;
use crate::subfs_utils;
use jacquard::CowStr;
use jacquard::prelude::IdentityResolver;
···
) -> miette::Result<Directory<'static>> {
use jacquard_common::IntoStatic;
use jacquard_common::types::value::from_data;
+
use wisp_lexicons::place_wisp::subfs::SubfsRecord;
+
let mut all_subfs_map: HashMap<String, wisp_lexicons::place_wisp::subfs::Directory> = HashMap::new();
let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new());
if to_fetch.is_empty() {
···
/// Extract subfs URIs from a subfs::Directory (helper for pull)
fn extract_subfs_uris_from_subfs_dir(
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
current_path: String,
) -> Vec<(String, String)> {
let mut uris = Vec::new();
···
};
match &entry.node {
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
uris.push((subfs_node.subject.to_string(), full_path.clone()));
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path);
uris.extend(nested);
}
···
/// Recursively replace subfs nodes with their actual content
fn replace_subfs_with_content(
directory: Directory,
+
subfs_map: &HashMap<String, wisp_lexicons::place_wisp::subfs::Directory>,
current_path: String,
) -> Directory<'static> {
use jacquard_common::IntoStatic;
···
}
/// Convert a subfs entry to a fs entry (they have the same structure but different types)
+
fn convert_subfs_entry_to_fs(subfs_entry: wisp_lexicons::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
use jacquard_common::IntoStatic;
let node = match subfs_entry.node {
+
wisp_lexicons::place_wisp::subfs::EntryNode::File(file) => {
EntryNode::File(Box::new(
File::new()
.r#type(file.r#type.into_static())
···
.build()
))
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(dir) => {
let converted_entries: Vec<Entry<'static>> = dir
.entries
.into_iter()
···
.build()
))
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
// Nested subfs should have been expanded already - if we get here, it means expansion failed
// Treat it like a directory reference that should have been expanded
eprintln!(" โš ๏ธ Warning: unexpanded nested subfs at path, treating as empty directory");
···
.build()
))
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(unknown) => {
EntryNode::Unknown(unknown)
}
};
+1 -1
cli/src/serve.rs
···
use crate::pull::pull_site;
use crate::redirects::{load_redirect_rules, match_redirect_rule, RedirectRule};
-
use crate::place_wisp::settings::Settings;
use axum::{
Router,
extract::Request,
···
use crate::pull::pull_site;
use crate::redirects::{load_redirect_rules, match_redirect_rule, RedirectRule};
+
use wisp_lexicons::place_wisp::settings::Settings;
use axum::{
Router,
extract::Request,
+14 -14
cli/src/subfs_utils.rs
···
use miette::IntoDiagnostic;
use std::collections::HashMap;
-
use crate::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
-
use crate::place_wisp::subfs::SubfsRecord;
/// Extract all subfs URIs from a directory tree with their mount paths
pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> {
···
/// Extract subfs URIs from a subfs::Directory
fn extract_subfs_uris_from_subfs_dir(
-
directory: &crate::place_wisp::subfs::Directory,
current_path: String,
) -> Vec<(String, String)> {
let mut uris = Vec::new();
for entry in &directory.entries {
match &entry.node {
-
crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
// Check if this is a chunk entry (chunk0, chunk1, etc.)
// Chunks should be flat-merged, so use the parent's path
let mount_path = if entry.name.starts_with("chunk") &&
···
uris.push((subfs_node.subject.to_string(), mount_path));
}
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
let full_path = if current_path.is_empty() {
entry.name.to_string()
} else {
···
for (mount_path, subfs_record) in all_subfs {
// Check if this record only contains chunk subfs references (no files)
let only_has_chunks = subfs_record.root.entries.iter().all(|e| {
-
matches!(&e.node, crate::place_wisp::subfs::EntryNode::Subfs(_)) &&
e.name.starts_with("chunk") &&
e.name.chars().skip(5).all(|c| c.is_ascii_digit())
});
···
/// Extract blobs from a subfs directory (works with subfs::Directory)
/// Returns a map of file paths to their blob refs and CIDs
fn extract_subfs_blobs(
-
directory: &crate::place_wisp::subfs::Directory,
current_path: String,
) -> HashMap<String, (BlobRef<'static>, String)> {
let mut blob_map = HashMap::new();
···
};
match &entry.node {
-
crate::place_wisp::subfs::EntryNode::File(file_node) => {
let blob_ref = &file_node.blob;
let cid_string = blob_ref.blob().r#ref.to_string();
blob_map.insert(
···
(blob_ref.clone().into_static(), cid_string)
);
}
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
let sub_map = extract_subfs_blobs(subdir, full_path);
blob_map.extend(sub_map);
}
-
crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
// Nested subfs - these should be resolved recursively in the main flow
// For now, we skip them (they'll be fetched separately)
eprintln!(" โš ๏ธ Found nested subfs at {}, skipping (should be fetched separately)", full_path);
}
-
crate::place_wisp::subfs::EntryNode::Unknown(_) => {
// Skip unknown nodes
}
}
···
flat: bool,
) -> miette::Result<FsDirectory<'static>> {
use jacquard_common::CowStr;
-
use crate::place_wisp::fs::{Entry, Subfs};
let path_parts: Vec<&str> = target_path.split('/').collect();
···
// Construct AT-URI and convert to RecordUri
let at_uri = AtUri::new(uri).into_diagnostic()?;
-
let record_uri: RecordUri<'_, crate::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
let rkey = record_uri.rkey()
.ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))?
···
}
/// Estimate the JSON size of a single entry
-
fn estimate_entry_size(entry: &crate::place_wisp::fs::Entry) -> usize {
match serde_json::to_string(entry) {
Ok(json) => json.len(),
Err(_) => 500, // Conservative estimate if serialization fails
···
use miette::IntoDiagnostic;
use std::collections::HashMap;
+
use wisp_lexicons::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
+
use wisp_lexicons::place_wisp::subfs::SubfsRecord;
/// Extract all subfs URIs from a directory tree with their mount paths
pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> {
···
/// Extract subfs URIs from a subfs::Directory
fn extract_subfs_uris_from_subfs_dir(
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
current_path: String,
) -> Vec<(String, String)> {
let mut uris = Vec::new();
for entry in &directory.entries {
match &entry.node {
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
// Check if this is a chunk entry (chunk0, chunk1, etc.)
// Chunks should be flat-merged, so use the parent's path
let mount_path = if entry.name.starts_with("chunk") &&
···
uris.push((subfs_node.subject.to_string(), mount_path));
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
let full_path = if current_path.is_empty() {
entry.name.to_string()
} else {
···
for (mount_path, subfs_record) in all_subfs {
// Check if this record only contains chunk subfs references (no files)
let only_has_chunks = subfs_record.root.entries.iter().all(|e| {
+
matches!(&e.node, wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_)) &&
e.name.starts_with("chunk") &&
e.name.chars().skip(5).all(|c| c.is_ascii_digit())
});
···
/// Extract blobs from a subfs directory (works with subfs::Directory)
/// Returns a map of file paths to their blob refs and CIDs
fn extract_subfs_blobs(
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
current_path: String,
) -> HashMap<String, (BlobRef<'static>, String)> {
let mut blob_map = HashMap::new();
···
};
match &entry.node {
+
wisp_lexicons::place_wisp::subfs::EntryNode::File(file_node) => {
let blob_ref = &file_node.blob;
let cid_string = blob_ref.blob().r#ref.to_string();
blob_map.insert(
···
(blob_ref.clone().into_static(), cid_string)
);
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
let sub_map = extract_subfs_blobs(subdir, full_path);
blob_map.extend(sub_map);
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
// Nested subfs - these should be resolved recursively in the main flow
// For now, we skip them (they'll be fetched separately)
eprintln!(" โš ๏ธ Found nested subfs at {}, skipping (should be fetched separately)", full_path);
}
+
wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(_) => {
// Skip unknown nodes
}
}
···
flat: bool,
) -> miette::Result<FsDirectory<'static>> {
use jacquard_common::CowStr;
+
use wisp_lexicons::place_wisp::fs::{Entry, Subfs};
let path_parts: Vec<&str> = target_path.split('/').collect();
···
// Construct AT-URI and convert to RecordUri
let at_uri = AtUri::new(uri).into_diagnostic()?;
+
let record_uri: RecordUri<'_, wisp_lexicons::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
let rkey = record_uri.rkey()
.ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))?
···
}
/// Estimate the JSON size of a single entry
+
fn estimate_entry_size(entry: &wisp_lexicons::place_wisp::fs::Entry) -> usize {
match serde_json::to_string(entry) {
Ok(json) => json.len(),
Err(_) => 500, // Conservative estimate if serialization fails
+3 -1
docs/astro.config.mjs
···
integrations: [
starlight({
title: 'Wisp.place Docs',
-
social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/tangled-org/wisp.place' }],
sidebar: [
{
label: 'Getting Started',
···
integrations: [
starlight({
title: 'Wisp.place Docs',
+
components: {
+
SocialIcons: './src/components/SocialIcons.astro',
+
},
sidebar: [
{
label: 'Getting Started',
+9
docs/src/assets/tangled-icon.svg
···
···
+
<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">
+
<defs id="defs1"/>
+
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1">
+
<inkscape:page x="0" y="0" width="25" height="25" id="page2" margin="0" bleed="0"/>
+
</sodipodi:namedview>
+
<g inkscape:groupmode="layer" inkscape:label="Image" id="g1">
+
<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"/>
+
</g>
+
</svg>
+26
docs/src/components/SocialIcons.astro
···
···
+
---
+
// Custom social icons component to use the Tangled icon
+
---
+
+
<div class="sl-flex">
+
<a
+
href="https://tangled.org/nekomimi.pet/wisp.place-monorepo"
+
rel="me"
+
class="sl-flex"
+
aria-label="Tangled"
+
>
+
<svg
+
xmlns="http://www.w3.org/2000/svg"
+
viewBox="0 0 25 25"
+
width="16"
+
height="16"
+
aria-hidden="true"
+
focusable="false"
+
>
+
<path
+
style="fill:currentColor;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"
+
></path>
+
</svg>
+
</a>
+
</div>
-5
docs/src/content/docs/cli.md
···
engine: 'nixery'
-
clone:
-
skip: false
-
depth: 1
-
submodules: false
-
dependencies:
nixpkgs:
- nodejs
···
engine: 'nixery'
dependencies:
nixpkgs:
- nodejs
+15 -15
docs/src/styles/custom.css
···
/* Increase base font size by 10% */
font-size: 110%;
-
/* Light theme - Warm beige background from app */
-
--sl-color-bg: oklch(0.90 0.012 35);
-
--sl-color-bg-sidebar: oklch(0.93 0.01 35);
-
--sl-color-bg-nav: oklch(0.93 0.01 35);
-
--sl-color-text: oklch(0.18 0.01 30);
-
--sl-color-text-accent: oklch(0.78 0.15 345);
-
--sl-color-accent: oklch(0.78 0.15 345);
-
--sl-color-accent-low: oklch(0.95 0.03 345);
-
--sl-color-border: oklch(0.75 0.015 30);
-
--sl-color-gray-1: oklch(0.52 0.015 30);
-
--sl-color-gray-2: oklch(0.42 0.015 30);
-
--sl-color-gray-3: oklch(0.33 0.015 30);
-
--sl-color-gray-4: oklch(0.25 0.015 30);
-
--sl-color-gray-5: oklch(0.75 0.015 30);
--sl-color-bg-accent: oklch(0.88 0.01 35);
}
···
/* Sidebar active/hover state text contrast fix */
.sidebar a[aria-current="page"],
.sidebar a[aria-current="page"] span {
-
color: oklch(0.23 0.015 285) !important;
}
[data-theme="dark"] .sidebar a[aria-current="page"],
···
/* Increase base font size by 10% */
font-size: 110%;
+
/* Light theme - Warm beige with improved contrast */
+
--sl-color-bg: oklch(0.92 0.012 35);
+
--sl-color-bg-sidebar: oklch(0.95 0.008 35);
+
--sl-color-bg-nav: oklch(0.95 0.008 35);
+
--sl-color-text: oklch(0.15 0.015 30);
+
--sl-color-text-accent: oklch(0.65 0.18 345);
+
--sl-color-accent: oklch(0.65 0.18 345);
+
--sl-color-accent-low: oklch(0.92 0.05 345);
+
--sl-color-border: oklch(0.65 0.02 30);
+
--sl-color-gray-1: oklch(0.45 0.02 30);
+
--sl-color-gray-2: oklch(0.35 0.02 30);
+
--sl-color-gray-3: oklch(0.28 0.02 30);
+
--sl-color-gray-4: oklch(0.20 0.015 30);
+
--sl-color-gray-5: oklch(0.65 0.02 30);
--sl-color-bg-accent: oklch(0.88 0.01 35);
}
···
/* Sidebar active/hover state text contrast fix */
.sidebar a[aria-current="page"],
.sidebar a[aria-current="page"] span {
+
color: oklch(0.15 0.015 30) !important;
}
[data-theme="dark"] .sidebar a[aria-current="page"],
+59
lexicons/fs.json
···
···
+
{
+
"lexicon": 1,
+
"id": "place.wisp.fs",
+
"defs": {
+
"main": {
+
"type": "record",
+
"description": "Virtual filesystem manifest for a Wisp site",
+
"record": {
+
"type": "object",
+
"required": ["site", "root", "createdAt"],
+
"properties": {
+
"site": { "type": "string" },
+
"root": { "type": "ref", "ref": "#directory" },
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
+
"createdAt": { "type": "string", "format": "datetime" }
+
}
+
}
+
},
+
"file": {
+
"type": "object",
+
"required": ["type", "blob"],
+
"properties": {
+
"type": { "type": "string", "const": "file" },
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
+
},
+
"directory": {
+
"type": "object",
+
"required": ["type", "entries"],
+
"properties": {
+
"type": { "type": "string", "const": "directory" },
+
"entries": {
+
"type": "array",
+
"maxLength": 500,
+
"items": { "type": "ref", "ref": "#entry" }
+
}
+
}
+
},
+
"entry": {
+
"type": "object",
+
"required": ["name", "node"],
+
"properties": {
+
"name": { "type": "string", "maxLength": 255 },
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
+
}
+
},
+
"subfs": {
+
"type": "object",
+
"required": ["type", "subject"],
+
"properties": {
+
"type": { "type": "string", "const": "subfs" },
+
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
+
"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." }
+
}
+
}
+
}
+
}
+76
lexicons/settings.json
···
···
+
{
+
"lexicon": 1,
+
"id": "place.wisp.settings",
+
"defs": {
+
"main": {
+
"type": "record",
+
"description": "Configuration settings for a static site hosted on wisp.place",
+
"key": "any",
+
"record": {
+
"type": "object",
+
"properties": {
+
"directoryListing": {
+
"type": "boolean",
+
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
+
"default": false
+
},
+
"spaMode": {
+
"type": "string",
+
"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.",
+
"maxLength": 500
+
},
+
"custom404": {
+
"type": "string",
+
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
+
"maxLength": 500
+
},
+
"indexFiles": {
+
"type": "array",
+
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
+
"items": {
+
"type": "string",
+
"maxLength": 255
+
},
+
"maxLength": 10
+
},
+
"cleanUrls": {
+
"type": "boolean",
+
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
+
"default": false
+
},
+
"headers": {
+
"type": "array",
+
"description": "Custom HTTP headers to set on responses",
+
"items": {
+
"type": "ref",
+
"ref": "#customHeader"
+
},
+
"maxLength": 50
+
}
+
}
+
}
+
},
+
"customHeader": {
+
"type": "object",
+
"description": "Custom HTTP header configuration",
+
"required": ["name", "value"],
+
"properties": {
+
"name": {
+
"type": "string",
+
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
+
"maxLength": 100
+
},
+
"value": {
+
"type": "string",
+
"description": "HTTP header value",
+
"maxLength": 1000
+
},
+
"path": {
+
"type": "string",
+
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
+
"maxLength": 500
+
}
+
}
+
}
+
}
+
}
+59
lexicons/subfs.json
···
···
+
{
+
"lexicon": 1,
+
"id": "place.wisp.subfs",
+
"defs": {
+
"main": {
+
"type": "record",
+
"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.",
+
"record": {
+
"type": "object",
+
"required": ["root", "createdAt"],
+
"properties": {
+
"root": { "type": "ref", "ref": "#directory" },
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
+
"createdAt": { "type": "string", "format": "datetime" }
+
}
+
}
+
},
+
"file": {
+
"type": "object",
+
"required": ["type", "blob"],
+
"properties": {
+
"type": { "type": "string", "const": "file" },
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
+
}
+
},
+
"directory": {
+
"type": "object",
+
"required": ["type", "entries"],
+
"properties": {
+
"type": { "type": "string", "const": "directory" },
+
"entries": {
+
"type": "array",
+
"maxLength": 500,
+
"items": { "type": "ref", "ref": "#entry" }
+
}
+
}
+
},
+
"entry": {
+
"type": "object",
+
"required": ["name", "node"],
+
"properties": {
+
"name": { "type": "string", "maxLength": 255 },
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
+
}
+
},
+
"subfs": {
+
"type": "object",
+
"required": ["type", "subject"],
+
"properties": {
+
"type": { "type": "string", "const": "subfs" },
+
"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." }
+
}
+
}
+
}
+
}
+
+7 -2
package.json
···
"@tailwindcss/cli": "^4.1.17",
"atproto-ui": "^0.12.0",
"bun-plugin-tailwind": "^0.1.2",
"tailwindcss": "^4.1.17"
},
"scripts": {
···
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
"hosting:dev": "cd apps/hosting-service && npm run dev",
-
"hosting:start": "cd apps/hosting-service && npm run start"
},
"trustedDependencies": [
"@parcel/watcher",
"bun",
"esbuild"
-
]
}
···
"@tailwindcss/cli": "^4.1.17",
"atproto-ui": "^0.12.0",
"bun-plugin-tailwind": "^0.1.2",
+
"elysia": "^1.4.18",
"tailwindcss": "^4.1.17"
},
"scripts": {
···
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
"hosting:dev": "cd apps/hosting-service && npm run dev",
+
"hosting:start": "cd apps/hosting-service && npm run start",
+
"codegen": "./scripts/codegen.sh"
},
"trustedDependencies": [
"@parcel/watcher",
"bun",
"esbuild"
+
],
+
"devDependencies": {
+
"@types/bun": "^1.3.5"
+
}
}
+244
packages/@wisp/fs-utils/src/tree.test.ts
···
···
+
import { describe, test, expect } from 'bun:test'
+
import { processUploadedFiles, type UploadedFile } from './tree'
+
+
describe('processUploadedFiles', () => {
+
test('should preserve nested directory structure', () => {
+
const files: UploadedFile[] = [
+
{
+
name: 'mysite/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/_astro/main.js',
+
content: Buffer.from('console.log()'),
+
mimeType: 'application/javascript',
+
size: 13
+
},
+
{
+
name: 'mysite/_astro/styles.css',
+
content: Buffer.from('body {}'),
+
mimeType: 'text/css',
+
size: 7
+
},
+
{
+
name: 'mysite/images/logo.png',
+
content: Buffer.from([0x89, 0x50, 0x4e, 0x47]),
+
mimeType: 'image/png',
+
size: 4
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(4)
+
expect(result.directory.entries).toHaveLength(3) // index.html, _astro/, images/
+
+
// Check _astro directory exists
+
const astroEntry = result.directory.entries.find(e => e.name === '_astro')
+
expect(astroEntry).toBeTruthy()
+
expect('type' in astroEntry!.node && astroEntry!.node.type).toBe('directory')
+
+
if ('entries' in astroEntry!.node) {
+
const astroDir = astroEntry!.node
+
expect(astroDir.entries).toHaveLength(2) // main.js, styles.css
+
expect(astroDir.entries.find(e => e.name === 'main.js')).toBeTruthy()
+
expect(astroDir.entries.find(e => e.name === 'styles.css')).toBeTruthy()
+
}
+
+
// Check images directory exists
+
const imagesEntry = result.directory.entries.find(e => e.name === 'images')
+
expect(imagesEntry).toBeTruthy()
+
expect('type' in imagesEntry!.node && imagesEntry!.node.type).toBe('directory')
+
+
if ('entries' in imagesEntry!.node) {
+
const imagesDir = imagesEntry!.node
+
expect(imagesDir.entries).toHaveLength(1) // logo.png
+
expect(imagesDir.entries.find(e => e.name === 'logo.png')).toBeTruthy()
+
}
+
})
+
+
test('should handle deeply nested directories', () => {
+
const files: UploadedFile[] = [
+
{
+
name: 'site/a/b/c/d/deep.txt',
+
content: Buffer.from('deep'),
+
mimeType: 'text/plain',
+
size: 4
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(1)
+
+
// Navigate through nested structure
+
const aEntry = result.directory.entries.find(e => e.name === 'a')
+
expect(aEntry).toBeTruthy()
+
expect('type' in aEntry!.node && aEntry!.node.type).toBe('directory')
+
+
if ('entries' in aEntry!.node) {
+
const bEntry = aEntry!.node.entries.find(e => e.name === 'b')
+
expect(bEntry).toBeTruthy()
+
expect('type' in bEntry!.node && bEntry!.node.type).toBe('directory')
+
+
if ('entries' in bEntry!.node) {
+
const cEntry = bEntry!.node.entries.find(e => e.name === 'c')
+
expect(cEntry).toBeTruthy()
+
expect('type' in cEntry!.node && cEntry!.node.type).toBe('directory')
+
+
if ('entries' in cEntry!.node) {
+
const dEntry = cEntry!.node.entries.find(e => e.name === 'd')
+
expect(dEntry).toBeTruthy()
+
expect('type' in dEntry!.node && dEntry!.node.type).toBe('directory')
+
+
if ('entries' in dEntry!.node) {
+
const fileEntry = dEntry!.node.entries.find(e => e.name === 'deep.txt')
+
expect(fileEntry).toBeTruthy()
+
expect('type' in fileEntry!.node && fileEntry!.node.type).toBe('file')
+
}
+
}
+
}
+
}
+
})
+
+
test('should handle files at root level', () => {
+
const files: UploadedFile[] = [
+
{
+
name: 'mysite/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/robots.txt',
+
content: Buffer.from('User-agent: *'),
+
mimeType: 'text/plain',
+
size: 13
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(2)
+
expect(result.directory.entries).toHaveLength(2)
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
+
expect(result.directory.entries.find(e => e.name === 'robots.txt')).toBeTruthy()
+
})
+
+
test('should skip .git directories', () => {
+
const files: UploadedFile[] = [
+
{
+
name: 'mysite/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/.git/config',
+
content: Buffer.from('[core]'),
+
mimeType: 'text/plain',
+
size: 6
+
},
+
{
+
name: 'mysite/.gitignore',
+
content: Buffer.from('node_modules'),
+
mimeType: 'text/plain',
+
size: 12
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(2) // Only index.html and .gitignore
+
expect(result.directory.entries).toHaveLength(2)
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
+
expect(result.directory.entries.find(e => e.name === '.gitignore')).toBeTruthy()
+
expect(result.directory.entries.find(e => e.name === '.git')).toBeFalsy()
+
})
+
+
test('should handle mixed root and nested files', () => {
+
const files: UploadedFile[] = [
+
{
+
name: 'mysite/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/about/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/about/team.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'mysite/robots.txt',
+
content: Buffer.from('User-agent: *'),
+
mimeType: 'text/plain',
+
size: 13
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(4)
+
expect(result.directory.entries).toHaveLength(3) // index.html, about/, robots.txt
+
+
const aboutEntry = result.directory.entries.find(e => e.name === 'about')
+
expect(aboutEntry).toBeTruthy()
+
expect('type' in aboutEntry!.node && aboutEntry!.node.type).toBe('directory')
+
+
if ('entries' in aboutEntry!.node) {
+
const aboutDir = aboutEntry!.node
+
expect(aboutDir.entries).toHaveLength(2) // index.html, team.html
+
expect(aboutDir.entries.find(e => e.name === 'index.html')).toBeTruthy()
+
expect(aboutDir.entries.find(e => e.name === 'team.html')).toBeTruthy()
+
}
+
})
+
+
test('should handle empty file array', () => {
+
const files: UploadedFile[] = []
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(0)
+
expect(result.directory.entries).toHaveLength(0)
+
})
+
+
test('should strip base folder name from paths', () => {
+
// This tests the behavior where file.name includes the base folder
+
// e.g., "mysite/index.html" should become "index.html" at root
+
const files: UploadedFile[] = [
+
{
+
name: 'build-output/index.html',
+
content: Buffer.from('<html>'),
+
mimeType: 'text/html',
+
size: 6
+
},
+
{
+
name: 'build-output/assets/main.js',
+
content: Buffer.from('console.log()'),
+
mimeType: 'application/javascript',
+
size: 13
+
}
+
]
+
+
const result = processUploadedFiles(files)
+
+
expect(result.fileCount).toBe(2)
+
+
// Should have index.html at root and assets/ directory
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
+
expect(result.directory.entries.find(e => e.name === 'assets')).toBeTruthy()
+
+
// Should NOT have 'build-output' directory
+
expect(result.directory.entries.find(e => e.name === 'build-output')).toBeFalsy()
+
})
+
})
-59
packages/@wisp/lexicons/lexicons/fs.json
···
-
{
-
"lexicon": 1,
-
"id": "place.wisp.fs",
-
"defs": {
-
"main": {
-
"type": "record",
-
"description": "Virtual filesystem manifest for a Wisp site",
-
"record": {
-
"type": "object",
-
"required": ["site", "root", "createdAt"],
-
"properties": {
-
"site": { "type": "string" },
-
"root": { "type": "ref", "ref": "#directory" },
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
-
"createdAt": { "type": "string", "format": "datetime" }
-
}
-
}
-
},
-
"file": {
-
"type": "object",
-
"required": ["type", "blob"],
-
"properties": {
-
"type": { "type": "string", "const": "file" },
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
-
},
-
"directory": {
-
"type": "object",
-
"required": ["type", "entries"],
-
"properties": {
-
"type": { "type": "string", "const": "directory" },
-
"entries": {
-
"type": "array",
-
"maxLength": 500,
-
"items": { "type": "ref", "ref": "#entry" }
-
}
-
}
-
},
-
"entry": {
-
"type": "object",
-
"required": ["name", "node"],
-
"properties": {
-
"name": { "type": "string", "maxLength": 255 },
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
-
}
-
},
-
"subfs": {
-
"type": "object",
-
"required": ["type", "subject"],
-
"properties": {
-
"type": { "type": "string", "const": "subfs" },
-
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
-
"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." }
-
}
-
}
-
}
-
}
···
-76
packages/@wisp/lexicons/lexicons/settings.json
···
-
{
-
"lexicon": 1,
-
"id": "place.wisp.settings",
-
"defs": {
-
"main": {
-
"type": "record",
-
"description": "Configuration settings for a static site hosted on wisp.place",
-
"key": "any",
-
"record": {
-
"type": "object",
-
"properties": {
-
"directoryListing": {
-
"type": "boolean",
-
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
-
"default": false
-
},
-
"spaMode": {
-
"type": "string",
-
"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.",
-
"maxLength": 500
-
},
-
"custom404": {
-
"type": "string",
-
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
-
"maxLength": 500
-
},
-
"indexFiles": {
-
"type": "array",
-
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
-
"items": {
-
"type": "string",
-
"maxLength": 255
-
},
-
"maxLength": 10
-
},
-
"cleanUrls": {
-
"type": "boolean",
-
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
-
"default": false
-
},
-
"headers": {
-
"type": "array",
-
"description": "Custom HTTP headers to set on responses",
-
"items": {
-
"type": "ref",
-
"ref": "#customHeader"
-
},
-
"maxLength": 50
-
}
-
}
-
}
-
},
-
"customHeader": {
-
"type": "object",
-
"description": "Custom HTTP header configuration",
-
"required": ["name", "value"],
-
"properties": {
-
"name": {
-
"type": "string",
-
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
-
"maxLength": 100
-
},
-
"value": {
-
"type": "string",
-
"description": "HTTP header value",
-
"maxLength": 1000
-
},
-
"path": {
-
"type": "string",
-
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
-
"maxLength": 500
-
}
-
}
-
}
-
}
-
}
···
-59
packages/@wisp/lexicons/lexicons/subfs.json
···
-
{
-
"lexicon": 1,
-
"id": "place.wisp.subfs",
-
"defs": {
-
"main": {
-
"type": "record",
-
"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.",
-
"record": {
-
"type": "object",
-
"required": ["root", "createdAt"],
-
"properties": {
-
"root": { "type": "ref", "ref": "#directory" },
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
-
"createdAt": { "type": "string", "format": "datetime" }
-
}
-
}
-
},
-
"file": {
-
"type": "object",
-
"required": ["type", "blob"],
-
"properties": {
-
"type": { "type": "string", "const": "file" },
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
-
}
-
},
-
"directory": {
-
"type": "object",
-
"required": ["type", "entries"],
-
"properties": {
-
"type": { "type": "string", "const": "directory" },
-
"entries": {
-
"type": "array",
-
"maxLength": 500,
-
"items": { "type": "ref", "ref": "#entry" }
-
}
-
}
-
},
-
"entry": {
-
"type": "object",
-
"required": ["name", "node"],
-
"properties": {
-
"name": { "type": "string", "maxLength": 255 },
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
-
}
-
},
-
"subfs": {
-
"type": "object",
-
"required": ["type", "subject"],
-
"properties": {
-
"type": { "type": "string", "const": "subfs" },
-
"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." }
-
}
-
}
-
}
-
}
-
···
+1 -1
packages/@wisp/lexicons/package.json
···
}
},
"scripts": {
-
"codegen": "lex gen-server ./src ./lexicons"
},
"dependencies": {
"@atproto/lexicon": "^0.5.1",
···
}
},
"scripts": {
+
"codegen": "lex gen-server ./src ../../../lexicons/*.json"
},
"dependencies": {
"@atproto/lexicon": "^0.5.1",
+1 -1
packages/@wisp/lexicons/src/index.ts
···
type MethodConfigOrHandler,
createServer as createXrpcServer,
} from '@atproto/xrpc-server'
-
import { schemas } from './lexicons'
export function createServer(options?: XrpcOptions): Server {
return new Server(options)
···
type MethodConfigOrHandler,
createServer as createXrpcServer,
} from '@atproto/xrpc-server'
+
import { schemas } from './lexicons.js'
export function createServer(options?: XrpcOptions): Server {
return new Server(options)
+1 -1
packages/@wisp/lexicons/src/lexicons.ts
···
ValidationError,
type ValidationResult,
} from '@atproto/lexicon'
-
import { type $Typed, is$typed, maybe$typed } from './util'
export const schemaDict = {
PlaceWispFs: {
···
ValidationError,
type ValidationResult,
} from '@atproto/lexicon'
+
import { type $Typed, is$typed, maybe$typed } from './util.js'
export const schemaDict = {
PlaceWispFs: {
+1 -1
packages/@wisp/observability/README.md
···
## License
-
Private
···
## License
+
MIT
+1 -2
packages/@wisp/observability/src/core.ts
···
},
debug(message: string, service: string, context?: Record<string, any>, traceId?: string) {
-
const env = typeof Bun !== 'undefined' ? Bun.env.NODE_ENV : process.env.NODE_ENV;
-
if (env !== 'production') {
this.log('debug', message, service, context, traceId)
}
},
···
},
debug(message: string, service: string, context?: Record<string, any>, traceId?: string) {
+
if (process.env.NODE_ENV !== 'production') {
this.log('debug', message, service, context, traceId)
}
},
+9 -9
packages/@wisp/observability/src/exporters.ts
···
// Load from environment variables if not provided
if (!this.config.lokiUrl) {
-
this.config.lokiUrl = process.env.GRAFANA_LOKI_URL || Bun?.env?.GRAFANA_LOKI_URL
}
if (!this.config.prometheusUrl) {
-
this.config.prometheusUrl = process.env.GRAFANA_PROMETHEUS_URL || Bun?.env?.GRAFANA_PROMETHEUS_URL
}
// Load Loki authentication from environment
if (!this.config.lokiAuth?.bearerToken && !this.config.lokiAuth?.username) {
-
const token = process.env.GRAFANA_LOKI_TOKEN || Bun?.env?.GRAFANA_LOKI_TOKEN
-
const username = process.env.GRAFANA_LOKI_USERNAME || Bun?.env?.GRAFANA_LOKI_USERNAME
-
const password = process.env.GRAFANA_LOKI_PASSWORD || Bun?.env?.GRAFANA_LOKI_PASSWORD
if (token) {
this.config.lokiAuth = { ...this.config.lokiAuth, bearerToken: token }
···
// Load Prometheus authentication from environment
if (!this.config.prometheusAuth?.bearerToken && !this.config.prometheusAuth?.username) {
-
const token = process.env.GRAFANA_PROMETHEUS_TOKEN || Bun?.env?.GRAFANA_PROMETHEUS_TOKEN
-
const username = process.env.GRAFANA_PROMETHEUS_USERNAME || Bun?.env?.GRAFANA_PROMETHEUS_USERNAME
-
const password = process.env.GRAFANA_PROMETHEUS_PASSWORD || Bun?.env?.GRAFANA_PROMETHEUS_PASSWORD
if (token) {
this.config.prometheusAuth = { ...this.config.prometheusAuth, bearerToken: token }
···
class LokiExporter {
private buffer: LogEntry[] = []
private errorBuffer: ErrorEntry[] = []
-
private flushTimer?: Timer | NodeJS.Timer
private config: GrafanaConfig = {}
initialize(config: GrafanaConfig) {
···
// Load from environment variables if not provided
if (!this.config.lokiUrl) {
+
this.config.lokiUrl = process.env.GRAFANA_LOKI_URL
}
if (!this.config.prometheusUrl) {
+
this.config.prometheusUrl = process.env.GRAFANA_PROMETHEUS_URL
}
// Load Loki authentication from environment
if (!this.config.lokiAuth?.bearerToken && !this.config.lokiAuth?.username) {
+
const token = process.env.GRAFANA_LOKI_TOKEN
+
const username = process.env.GRAFANA_LOKI_USERNAME
+
const password = process.env.GRAFANA_LOKI_PASSWORD
if (token) {
this.config.lokiAuth = { ...this.config.lokiAuth, bearerToken: token }
···
// Load Prometheus authentication from environment
if (!this.config.prometheusAuth?.bearerToken && !this.config.prometheusAuth?.username) {
+
const token = process.env.GRAFANA_PROMETHEUS_TOKEN
+
const username = process.env.GRAFANA_PROMETHEUS_USERNAME
+
const password = process.env.GRAFANA_PROMETHEUS_PASSWORD
if (token) {
this.config.prometheusAuth = { ...this.config.prometheusAuth, bearerToken: token }
···
class LokiExporter {
private buffer: LogEntry[] = []
private errorBuffer: ErrorEntry[] = []
+
private flushTimer?: NodeJS.Timeout
private config: GrafanaConfig = {}
initialize(config: GrafanaConfig) {
+128 -27
packages/@wisp/safe-fetch/src/index.ts
···
const MAX_BLOB_SIZE = 500 * 1024 * 1024; // 500MB
const MAX_REDIRECTS = 10;
function isBlockedHost(hostname: string): boolean {
const lowerHost = hostname.toLowerCase();
···
return false;
}
export async function safeFetch(
url: string,
-
options?: RequestInit & { maxSize?: number; timeout?: number }
): Promise<Response> {
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT;
const maxSize = options?.maxSize ?? MAX_RESPONSE_SIZE;
-
// Parse and validate URL
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
···
throw new Error(`Blocked host: ${hostname}`);
}
-
const controller = new AbortController();
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
-
try {
-
const response = await fetch(url, {
-
...options,
-
signal: controller.signal,
-
redirect: 'follow',
-
headers: {
-
'User-Agent': 'wisp-place hosting-service',
-
...(options?.headers || {}),
-
},
-
});
-
const contentLength = response.headers.get('content-length');
-
if (contentLength && parseInt(contentLength, 10) > maxSize) {
-
throw new Error(`Response too large: ${contentLength} bytes`);
}
-
return response;
-
} catch (err) {
-
if (err instanceof Error && err.name === 'AbortError') {
-
throw new Error(`Request timeout after ${timeoutMs}ms`);
-
}
-
throw err;
-
} finally {
-
clearTimeout(timeoutId);
}
}
export async function safeFetchJson<T = any>(
url: string,
-
options?: RequestInit & { maxSize?: number; timeout?: number }
): Promise<T> {
const maxJsonSize = options?.maxSize ?? MAX_JSON_SIZE;
const response = await safeFetch(url, { ...options, maxSize: maxJsonSize });
···
export async function safeFetchBlob(
url: string,
-
options?: RequestInit & { maxSize?: number; timeout?: number }
): Promise<Uint8Array> {
const maxBlobSize = options?.maxSize ?? MAX_BLOB_SIZE;
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT_BLOB;
···
const MAX_BLOB_SIZE = 500 * 1024 * 1024; // 500MB
const MAX_REDIRECTS = 10;
+
// Retry configuration
+
const MAX_RETRIES = 3;
+
const INITIAL_RETRY_DELAY = 1000; // 1 second
+
const MAX_RETRY_DELAY = 10000; // 10 seconds
+
function isBlockedHost(hostname: string): boolean {
const lowerHost = hostname.toLowerCase();
···
return false;
}
+
/**
+
* Check if an error is retryable (network/SSL errors, not HTTP errors)
+
*/
+
function isRetryableError(err: unknown): boolean {
+
if (!(err instanceof Error)) return false;
+
+
// Network errors (ECONNRESET, ENOTFOUND, etc.)
+
const errorCode = (err as any).code;
+
if (errorCode) {
+
const retryableCodes = [
+
'ECONNRESET',
+
'ECONNREFUSED',
+
'ETIMEDOUT',
+
'ENOTFOUND',
+
'ENETUNREACH',
+
'EAI_AGAIN',
+
'EPIPE',
+
'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', // SSL/TLS handshake failures
+
'ERR_SSL_WRONG_VERSION_NUMBER',
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
+
];
+
if (retryableCodes.includes(errorCode)) {
+
return true;
+
}
+
}
+
+
// Timeout errors
+
if (err.name === 'AbortError' || err.message.includes('timeout')) {
+
return true;
+
}
+
+
// Fetch failures (generic network errors)
+
if (err.message.includes('fetch failed')) {
+
return true;
+
}
+
+
return false;
+
}
+
+
/**
+
* Sleep for a given number of milliseconds
+
*/
+
function sleep(ms: number): Promise<void> {
+
return new Promise(resolve => setTimeout(resolve, ms));
+
}
+
+
/**
+
* Retry a function with exponential backoff
+
*/
+
async function withRetry<T>(
+
fn: () => Promise<T>,
+
options: { maxRetries?: number; initialDelay?: number; maxDelay?: number; context?: string } = {}
+
): Promise<T> {
+
const maxRetries = options.maxRetries ?? MAX_RETRIES;
+
const initialDelay = options.initialDelay ?? INITIAL_RETRY_DELAY;
+
const maxDelay = options.maxDelay ?? MAX_RETRY_DELAY;
+
const context = options.context ?? 'Request';
+
+
let lastError: unknown;
+
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
+
try {
+
return await fn();
+
} catch (err) {
+
lastError = err;
+
+
// Don't retry if this is the last attempt or error is not retryable
+
if (attempt === maxRetries || !isRetryableError(err)) {
+
throw err;
+
}
+
+
// Calculate delay with exponential backoff
+
const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
+
+
const errorCode = (err as any)?.code;
+
const errorMsg = err instanceof Error ? err.message : String(err);
+
console.warn(
+
`${context} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${errorMsg}${errorCode ? ` [${errorCode}]` : ''} - retrying in ${delay}ms`
+
);
+
+
await sleep(delay);
+
}
+
}
+
+
throw lastError;
+
}
+
export async function safeFetch(
url: string,
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
): Promise<Response> {
+
const shouldRetry = options?.retry !== false; // Default to true
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT;
const maxSize = options?.maxSize ?? MAX_RESPONSE_SIZE;
+
// Parse and validate URL (done once, outside retry loop)
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
···
throw new Error(`Blocked host: ${hostname}`);
}
+
const fetchFn = async () => {
+
const controller = new AbortController();
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
+
+
try {
+
const response = await fetch(url, {
+
...options,
+
signal: controller.signal,
+
redirect: 'follow',
+
headers: {
+
'User-Agent': 'wisp-place hosting-service',
+
...(options?.headers || {}),
+
},
+
});
+
const contentLength = response.headers.get('content-length');
+
if (contentLength && parseInt(contentLength, 10) > maxSize) {
+
throw new Error(`Response too large: ${contentLength} bytes`);
+
}
+
return response;
+
} catch (err) {
+
if (err instanceof Error && err.name === 'AbortError') {
+
throw new Error(`Request timeout after ${timeoutMs}ms`);
+
}
+
throw err;
+
} finally {
+
clearTimeout(timeoutId);
}
+
};
+
if (shouldRetry) {
+
return withRetry(fetchFn, { context: `Fetch ${parsedUrl.hostname}` });
+
} else {
+
return fetchFn();
}
}
export async function safeFetchJson<T = any>(
url: string,
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
): Promise<T> {
const maxJsonSize = options?.maxSize ?? MAX_JSON_SIZE;
const response = await safeFetch(url, { ...options, maxSize: maxJsonSize });
···
export async function safeFetchBlob(
url: string,
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
): Promise<Uint8Array> {
const maxBlobSize = options?.maxSize ?? MAX_BLOB_SIZE;
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT_BLOB;
+28
scripts/codegen.sh
···
···
+
#!/bin/bash
+
set -e
+
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
+
+
# Parse arguments
+
AUTO_ACCEPT=""
+
if [[ "$1" == "-y" || "$1" == "--yes" ]]; then
+
AUTO_ACCEPT="yes |"
+
fi
+
+
echo "=== Generating TypeScript lexicons ==="
+
cd "$ROOT_DIR/packages/@wisp/lexicons"
+
eval "$AUTO_ACCEPT npm run codegen"
+
+
echo "=== Generating Rust lexicons ==="
+
echo "Installing jacquard-lexgen..."
+
cargo install jacquard-lexgen --version 0.9.5 2>/dev/null || true
+
echo "Running jacquard-codegen..."
+
echo " Input: $ROOT_DIR/lexicons"
+
echo " Output: $ROOT_DIR/cli/crates/lexicons/src"
+
jacquard-codegen -i "$ROOT_DIR/lexicons" -o "$ROOT_DIR/cli/crates/lexicons/src"
+
+
# Add extern crate alloc for the macro to work
+
sed -i '' '1s/^/extern crate alloc;\n\n/' "$ROOT_DIR/cli/crates/lexicons/src/lib.rs"
+
+
echo "=== Done ==="
+1 -1
tsconfig.json
···
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
"types": [
-
"bun-types"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
···
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
"types": [
+
"bun"
] /* Specify type package names to be included without being referenced in a source file. */,
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */