buckyos 0.7.151 → 0.7.153

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -285,10 +285,100 @@ const runReportCommand: CommandDefinition = {
285
285
  },
286
286
  }
287
287
 
288
+ // Block-controls (a mind map…): the catalogue each control describes itself in, and its exchange formats
289
+ // (脑图方案 §8.1). The service dispatches by control kind: `control.*` in buckyos/src/frame/aiworkspace/README.md.
290
+ const controlsCommand: CommandDefinition = {
291
+ verb: 'controls',
292
+ summary: 'Block-controls a canvas offers: `list` (kind, what it is, when to use it), `describe <kind>` (the full self-description to follow)',
293
+ positionals: [{ name: 'action', description: 'list | describe', required: true }, { name: 'kind', description: 'Control kind for describe, e.g. mindmap', required: false }],
294
+ options: [],
295
+ inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['list', 'describe'] }, kind: { type: 'string' } }, required: ['action'], additionalProperties: false },
296
+ outputSchema: OBJECT_OUTPUT,
297
+ resultSchemaVersion: 1,
298
+ access: { mode: 'fixed', level: 'read' },
299
+ asyncMode: 'sync',
300
+ requiresSession: true,
301
+ examples: ['buckyos aiws controls list', 'buckyos aiws controls describe mindmap'],
302
+ handler: (ctx, input) => {
303
+ const action = requiredInputString(input, 'action')
304
+ if (action === 'list') return aiws(ctx, 'control.list', {})
305
+ if (action !== 'describe') throw new UsageError('AIWS_USAGE', 'controls: list or describe <kind>')
306
+ return aiws(ctx, 'control.describe', { kind: requiredInputString(input, 'kind') })
307
+ },
308
+ }
309
+
310
+ /** `markdown` / `opml` from an explicit option or the file name. */
311
+ function exchangeFormat(explicit: string | undefined, file: string): string {
312
+ if (explicit) return explicit
313
+ return /\.(opml|xml)$/i.test(file) ? 'opml' : 'markdown'
314
+ }
315
+
316
+ const importCommand: CommandDefinition = {
317
+ verb: 'import',
318
+ summary: 'Create a Block-control from an exchange file: a mind map from a Markdown outline (nested list) or OPML',
319
+ positionals: [{ name: 'file', description: 'Local file (.md / .opml)', required: true }],
320
+ options: [
321
+ WORKSPACE_OPTION,
322
+ { name: 'parent', property: 'parent_id', description: 'Surface (or group) id to put the control on', type: 'string', required: true },
323
+ { name: 'kind', property: 'kind', description: 'Control kind (default mindmap)', type: 'string' },
324
+ { name: 'format', property: 'format', description: 'markdown | opml (default: by the file extension)', type: 'string', enum: ['markdown', 'opml'] },
325
+ { name: 'at', property: 'at', description: 'x,y on a free canvas: where the first root goes', type: 'string' },
326
+ { name: 'title', property: 'title', description: 'Title of the control (default: the single root, or the OPML title)', type: 'string' },
327
+ ],
328
+ inputSchema: workspaceInput({ file: { type: 'string', minLength: 1 }, parent_id: { type: 'string', minLength: 1 }, kind: { type: 'string' }, format: { type: 'string' }, at: { type: 'string' }, title: { type: 'string' } }, ['file', 'parent_id']),
329
+ outputSchema: OBJECT_OUTPUT,
330
+ resultSchemaVersion: 1,
331
+ access: { mode: 'fixed', level: 'write' },
332
+ asyncMode: 'sync',
333
+ requiresSession: true,
334
+ examples: ['buckyos aiws import plan.md --workspace ws_abc --parent sf_main --at 100,80', 'buckyos aiws import ideas.opml --workspace ws_abc --parent sf_main'],
335
+ handler: async (ctx, input) => {
336
+ const file = requiredInputString(input, 'file')
337
+ const params: Record<string, unknown> = {
338
+ workspace_id: requiredInputString(input, 'workspace_id'), parent_id: requiredInputString(input, 'parent_id'),
339
+ kind: inputString(input, 'kind') ?? 'mindmap', format: exchangeFormat(inputString(input, 'format'), file), text: await getHost().readTextFile(file),
340
+ }
341
+ const at = inputString(input, 'at')
342
+ if (at) {
343
+ const [x, y] = at.split(',').map(Number)
344
+ if (!Number.isFinite(x) || !Number.isFinite(y)) throw new UsageError('AIWS_USAGE', '--at is x,y')
345
+ params.at = { x, y }
346
+ }
347
+ const title = inputString(input, 'title')
348
+ if (title) params.title = title
349
+ return aiws(ctx, 'control.import', params)
350
+ },
351
+ }
352
+
353
+ const exportCommand: CommandDefinition = {
354
+ verb: 'export',
355
+ summary: 'A Block-control in an exchange format: a mind map as a Markdown outline or OPML',
356
+ positionals: [{ name: 'entity_id', description: 'The control (container) id', required: true }],
357
+ options: [
358
+ WORKSPACE_OPTION,
359
+ { name: 'format', property: 'format', description: 'markdown (default) | opml', type: 'string', enum: ['markdown', 'opml'] },
360
+ { name: 'out', property: 'out', description: 'Write the text to this file instead of answering it', type: 'string' },
361
+ ],
362
+ inputSchema: workspaceInput({ entity_id: { type: 'string', minLength: 1 }, format: { type: 'string' }, out: { type: 'string' } }, ['entity_id']),
363
+ outputSchema: OBJECT_OUTPUT,
364
+ resultSchemaVersion: 1,
365
+ access: { mode: 'fixed', level: 'read' },
366
+ asyncMode: 'sync',
367
+ requiresSession: true,
368
+ examples: ['buckyos aiws export mm_plan --workspace ws_abc', 'buckyos aiws export mm_plan --workspace ws_abc --format opml --out plan.opml'],
369
+ handler: async (ctx, input) => {
370
+ const result = await aiws(ctx, 'control.export', { workspace_id: requiredInputString(input, 'workspace_id'), entity_id: requiredInputString(input, 'entity_id'), format: inputString(input, 'format') ?? 'markdown' })
371
+ const out = inputString(input, 'out')
372
+ if (!out) return result
373
+ await getHost().writeTextFile(out, String(result.text ?? ''))
374
+ return { ok: true, format: result.format, kind: result.kind, path: out }
375
+ },
376
+ }
377
+
288
378
  export function createAiwsModule(): CommandModule {
289
379
  return {
290
380
  name: 'aiws',
291
- summary: 'AI Workspace: read a workspace, and execute wish runs as their executor (claim, heartbeat, report)',
292
- commands: [infoCommand, readCommand, queryCommand, runGetCommand, runClaimCommand, runHeartbeatCommand, runFilesCommand, runToolCommand, runProgramCommand, runReportCommand],
381
+ summary: 'AI Workspace: read a workspace, Block-controls (catalogue, import, export), and execute wish runs as their executor (claim, heartbeat, report)',
382
+ commands: [infoCommand, readCommand, queryCommand, controlsCommand, importCommand, exportCommand, runGetCommand, runClaimCommand, runHeartbeatCommand, runFilesCommand, runToolCommand, runProgramCommand, runReportCommand],
293
383
  }
294
384
  }
@@ -23,6 +23,7 @@ import {
23
23
  rejectUnknown,
24
24
  stableJsonDigest,
25
25
  validatePermissions,
26
+ validateAppPresentation,
26
27
  validateContentHandlers,
27
28
  validateServiceConfigTips,
28
29
  validateSnapshot,
@@ -85,6 +86,7 @@ interface AppMeta {
85
86
  owner: string
86
87
  author: string
87
88
  show_name: string
89
+ presentation?: Record<string, unknown>
88
90
  categories: string[]
89
91
  permissions: unknown[]
90
92
  content_handlers?: unknown[]
@@ -718,6 +720,7 @@ async function buildCommand(
718
720
  categories: appMeta.categories,
719
721
  pkg_list: pkgList,
720
722
  show_name: appMeta.show_name,
723
+ ...(appMeta.presentation ? { presentation: appMeta.presentation } : {}),
721
724
  ...(appMeta.permissions.length ? { permissions: appMeta.permissions } : {}),
722
725
  ...(appMeta.content_handlers?.length ? { content_handlers: appMeta.content_handlers } : {}),
723
726
  selector_type: appMeta.selector_type,
@@ -893,6 +896,7 @@ function parseAppMeta(value: Record<string, unknown>): AppMeta {
893
896
  'owner',
894
897
  'author',
895
898
  'show_name',
899
+ 'presentation',
896
900
  'categories',
897
901
  'permissions',
898
902
  'content_handlers',
@@ -938,6 +942,11 @@ function parseAppMeta(value: Record<string, unknown>): AppMeta {
938
942
  owner,
939
943
  author: developmentString(value.author, 'app.json.author'),
940
944
  show_name: developmentString(value.show_name, 'app.json.show_name'),
945
+ ...(value.presentation === undefined ? {} : {
946
+ presentation: developmentValidation(() =>
947
+ validateAppPresentation(value.presentation, 'app.json.presentation')
948
+ ),
949
+ }),
941
950
  categories: categories as string[],
942
951
  permissions,
943
952
  ...(value.content_handlers === undefined ? {} : { content_handlers: developmentValidation(() => validateContentHandlers(value.content_handlers, permissions)) }),
@@ -874,7 +874,34 @@ function validateObjectGraph(
874
874
  }
875
875
  }
876
876
 
877
+ export function validateAppPresentation(value: unknown, label: string): Record<string, unknown> {
878
+ const presentation = expectObject(value, label)
879
+ rejectUnknown(presentation, ['title', 'summary', 'description', 'icons', 'links', 'license'], label)
880
+ for (const field of ['title', 'summary', 'description', 'icons', 'links']) {
881
+ const values = expectObject(presentation[field], `${label}.${field}`)
882
+ for (const [key, item] of Object.entries(values)) {
883
+ if (typeof item !== 'string') {
884
+ throw invalid('appdoc', `${label}.${field}.${key} must be a string`)
885
+ }
886
+ if (field === 'icons') {
887
+ try {
888
+ ndn.ObjId.fromString(item)
889
+ } catch {
890
+ throw invalid('appdoc', `${label}.${field}.${key} must be an ObjectId`)
891
+ }
892
+ }
893
+ }
894
+ }
895
+ if (typeof presentation.license !== 'string') {
896
+ throw invalid('appdoc', `${label}.license must be a string`)
897
+ }
898
+ return presentation
899
+ }
900
+
877
901
  export function validateAppDocShape(appDoc: Record<string, unknown>): void {
902
+ if (appDoc.presentation !== undefined && appDoc.presentation !== null) {
903
+ validateAppPresentation(appDoc.presentation, 'APPDOC.presentation')
904
+ }
878
905
  rejectUnknown(
879
906
  appDoc,
880
907
  [
package/cli/version.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate-cli-version.mjs. Do not edit.
2
- export const PACKAGE_VERSION = '0.7.151'
2
+ export const PACKAGE_VERSION = '0.7.153'
3
3
  export const TOOL_VERSION = PACKAGE_VERSION
4
4
  export const SDK_VERSION = PACKAGE_VERSION
5
5
  export const PROTOCOL_VERSION = '1'
package/dist/nfsp.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"nfsp.cjs","sources":["../src/nfsp.ts"],"sourcesContent":["/**\n * NFSP v0 client for nfs_server (buckyos/src/frame/nfs_server).\n *\n * Speaks the control plane (`POST /nfs/v1/{method}`), the data plane\n * (`GET /nfs/v1/read/{node_id}`), the minimal tus upload area\n * (`PATCH|HEAD /nfs/v1/uploads/{fb}`) and the watch SSE stream\n * (`GET /nfs/v1/watch`).\n *\n * Protocol references:\n * - cyfs-ndn/doc/NamedFileSystem_Protocol_v0.md (NFSP v0)\n * - buckyos/product/bucky_file/nfs_server.md\n * - buckyos/src/frame/nfs_server/README.md (v1 降级契约)\n *\n * Client contract highlights (nfs_server README §4.2):\n * - `revision` is an opaque equality token: only compare, never order.\n * - Write ops require `seq` (exactly-once); the client auto-assigns one per\n * write and replays with the SAME seq on network retry.\n * - Leases are advisory versus server-local bypass writers: commit may fail\n * with TARGET_MISMATCH{reason:\"bypass_modified\"} — surface, don't retry.\n * - watch is lossy: any `resync` event means \"re-list what you care about\".\n *\n * Uses only fetch/WHATWG streams, so it runs in the browser and in Node ≥ 18.\n */\n\n// ---------------------------------------------------------------------------\n// Wire types\n// ---------------------------------------------------------------------------\n\nexport const NFSP_VERSION = 'nfsp/0'\n\n/** `{\"type\":\"live\",...}` or `{\"type\":\"object\",...}` (NFSP §3.1.2). */\nexport type LiveRef = { type: 'live'; node_id: string; gen?: number }\nexport type ObjectRef = { type: 'object'; obj_id: string; inner_path?: string }\nexport type WireRef = LiveRef | ObjectRef\n\nexport const liveRef = (nodeId: string, gen = 0): LiveRef => ({\n type: 'live',\n node_id: nodeId,\n gen,\n})\n\n/** The `at` locator: ref > uri > path (NFSP §3.4). */\nexport interface Locator {\n realm?: string\n path?: string\n uri?: string\n ref?: WireRef\n}\n\n/** Sugar: accept a dfs path string, a `xxx://` uri, a WireRef or a Locator. */\nexport type LocatorLike = string | WireRef | Locator\n\nexport const toLocator = (at: LocatorLike): Locator => {\n if (typeof at === 'string') {\n if (at.includes('://')) return { uri: at }\n return { realm: 'dfs', path: at }\n }\n if ('type' in at) return { ref: at }\n return at\n}\n\nexport type WantGroup = 'base' | 'ident' | 'access' | 'meta'\n\nexport type NodeKind = 'dir' | 'file' | 'symlink' | 'view' | 'collection' | 'group'\n\n/**\n * The caller's effective permission on a node. `traverse` = only the way to\n * something shared deeper down: listable, but only those children are shown.\n */\nexport type AccessLevel = 'none' | 'traverse' | 'read' | 'write' | 'manage'\n\n/** `user:<user_id>`, `zone_users` (any signed-in user) or `public` (anyone, read only). */\nexport type AclPrincipal = `user:${string}` | 'zone_users' | 'public'\n\nexport interface AclEntry {\n principal: AclPrincipal\n level: Exclude<AccessLevel, 'none' | 'traverse'>\n}\n\n/** `get_acl` result; the entry lists only appear for callers who may manage. */\nexport interface AclInfo {\n path: string\n access_level: AccessLevel\n can_manage: boolean\n revision: string\n inherit?: boolean\n entries?: AclEntry[]\n /** Grants arriving from ancestors, with the directory they come from. */\n inherited?: (AclEntry & { from: string })[]\n /** Built-in rules (zone owner, home owner) that no record can remove. */\n builtin?: (AclEntry & { reason: 'zone_owner' | 'home_owner' })[]\n updated_by?: string\n updated_at?: number\n}\n\nexport interface AclRecordInfo {\n path: string\n inherit: boolean\n entries: AclEntry[]\n home_owner: string | null\n updated_by: string\n updated_at: number\n revision: string\n /** The path no longer exists (removed outside the protocol). */\n missing: boolean\n}\n\nexport interface Capabilities {\n list: boolean\n read: boolean\n accepts_content: boolean\n accepts_references: boolean\n remove_semantics: 'destroy' | 'unlink' | 'none'\n ordered: boolean\n}\n\n/** resolve/stat result (fields beyond `base` appear per the `want` mask). */\nexport interface NodeInfo {\n kind: NodeKind\n state: string\n ref: WireRef\n copy_ref?: WireRef | null\n capabilities: Capabilities\n access_level?: AccessLevel\n revision?: string\n locations?: unknown[]\n // base\n name?: string\n size?: number\n mtime?: number\n ctime?: number\n flags?: string[]\n // ident\n node_id?: string\n gen?: number\n etag?: string\n obj_id?: string\n // access\n access_urls?: { kind: string; url: string }[]\n // meta\n meta_summary?: Record<string, number>\n // view / collection extras\n view_id?: string\n collection_id?: string\n title?: string\n origin?: string\n stale?: boolean\n}\n\nexport type EntryBinding = 'native' | 'reference' | 'member' | 'derived'\n\n/** Compact target of a listing entry (not a full NodeInfo). */\nexport interface EntryTarget {\n ref: WireRef\n copy_ref?: WireRef | null\n kind: NodeKind\n /** Attribute groups per the `want` mask: base → size/mtime/flags, ident → etag/obj_id, access → access_urls. */\n attrs?: {\n size?: number\n mtime?: number\n flags?: string[]\n etag?: string\n obj_id?: string\n access_urls?: { kind: string; url: string }[]\n } & Record<string, unknown>\n /** `stale` when a referenced target no longer resolves, `denied` when the caller may not see it. */\n target_state?: string\n access_level?: AccessLevel\n}\n\nexport interface Entry {\n name: string\n binding: EntryBinding\n entry_ref?: string\n target: EntryTarget\n canonical_path?: string\n context?: { count?: number; provenance?: Record<string, unknown> } & Record<string, unknown>\n}\n\nexport interface Listing {\n container: NodeInfo\n entries: Entry[]\n truncated?: boolean\n next_cursor?: string\n revision_changed?: boolean\n watch_token?: string\n /** Same-name virtual bindings shadowed by native entries (`native_shadow`). */\n conflicts?: { name: string; reason: string; entry_ref?: string; target?: EntryTarget }[]\n}\n\nexport interface ListOptions {\n cursor?: string\n limit?: number\n order?: 'name' | 'mtime' | 'size' | 'manual'\n filter?: { kind?: NodeKind[]; name_glob?: string }\n}\n\nexport interface HelloResult {\n version: string\n session: string\n features: string[]\n limits: { max_batch: number; max_list: number; replay_window: number; attr_ttl_ms: number }\n realms: { id: string; writable: boolean }[]\n /** Who this session acts as (`admin` = the zone owner or a trusted service). */\n identity?: { user_id: string | null; role: 'admin' | 'user' | 'anonymous' }\n}\n\nexport type BatchOp =\n | { m: 'walk'; args: { name?: string; names?: string[]; entry_ref?: string } }\n | { m: 'stat' | 'resolve'; args?: { name?: string }; want?: WantGroup[] }\n | { m: 'list'; args?: ListOptions; want?: WantGroup[] }\n\nexport interface BatchResult {\n completed: number\n results: ({ ok: true; result: unknown } | { ok: false; error: NfspErrorBody })[]\n}\n\nexport interface OpenWriteResult {\n fb_handle: string\n upload_url: string\n lease: { lease_id: string; seq: number; ttl_ms: number }\n target: { path: string; exists: boolean }\n}\n\nexport interface CommitResult {\n ref: WireRef\n entry_ref: string\n revision: string\n obj: { sha256: string; size: number }\n}\n\nexport interface CommitObjectResult {\n ref: WireRef\n entry_ref: string\n revision: string\n obj_id: string\n kind: 'file' | 'dir'\n obj: { size: number; sha256?: string }\n}\n\nexport interface MetaRecord {\n ns: string\n key: string\n value: unknown\n source?: unknown\n confidence?: number\n anchor?: string\n visibility?: string\n}\n\nexport interface SearchHit {\n copy_ref?: WireRef | null\n match_source: string\n canonical_path: string\n explain?: Record<string, unknown>\n [k: string]: unknown\n}\n\nexport interface SearchResult {\n hits: SearchHit[]\n partial: boolean\n sources: { mode: string; state: string; took_ms?: number; reason?: string }[]\n next_cursor?: string\n}\n\nexport interface GrantResult {\n cap_id: string\n token: string\n subtree: string\n ops: string[]\n expires_at?: number\n}\n\n// ---------- shares (`share.*`, nfs-server share module) ----------\n\nexport type ShareAccess = 'holder' | 'password'\nexport type ShareStatus = 'active' | 'stopping' | 'stopped' | 'expired' | 'failed'\nexport type ShareKind = 'link' | 'app_delivery'\nexport type ShareTargetApp = 'messagehub' | 'homestation'\nexport type ShareContentKind = 'image' | 'video' | 'audio' | 'file' | 'folder'\nexport type ShareDeliveryState = 'handed_off' | 'confirmed' | 'canceled' | 'failed' | 'unknown'\nexport type ShareOperationState = 'preparing' | 'running' | 'completed' | 'failed' | 'canceled'\n\nexport interface ShareStatsTotals {\n views: number\n downloads: number\n bytes: number\n visitors: number\n last_access_at?: number | null\n}\n\nexport interface ShareInfo {\n share_id: string\n kind: ShareKind\n title: string\n status: ShareStatus\n status_reason?: string | null\n hidden: boolean\n revision: number\n created_at: number\n updated_at: number\n stopped_at?: number | null\n owner_user_id: string\n created_by_app?: string | null\n related_share_id?: string | null\n obj_id: string\n obj_type?: string\n name?: string\n mime?: string | null\n total_size?: number\n item_count?: number\n content_kind?: ShareContentKind\n excluded?: { path: string; reason: string }[]\n prepared_at?: number\n source_path: string\n retained: boolean\n /** Link shares. `url` is only returned to the owner. */\n access?: ShareAccess\n has_password?: boolean\n allow_download?: boolean\n expires_at?: number | null\n url?: string | null\n /** App deliveries. */\n target_app?: ShareTargetApp\n delivery_state?: ShareDeliveryState\n business_ref?: Record<string, unknown> | null\n handoff_at?: number\n reported_at?: number | null\n delivery_error?: string | null\n stats: ShareStatsTotals\n}\n\nexport interface ShareAuditEntry {\n action: string\n revision_before?: number | null\n revision_after?: number | null\n actor_user_id: string\n actor_app_id?: string | null\n source: string\n reason?: string | null\n outcome: string\n at: number\n}\n\nexport interface ShareDetail extends ShareInfo {\n /** The bucket binding that retains the snapshot (`zone/nfs-server/_/shares`); empty once released. */\n retention: { bucket_id: string; key: string; obj_id: string; version: number; bound_at: number }[]\n audit: ShareAuditEntry[]\n related?: ShareInfo\n}\n\nexport interface ShareCapabilities {\n actions: string[]\n access: ShareAccess[]\n expiry_presets: (number | null)[]\n default_expiry: number\n limits: { sync_threshold: number; max_total_size: number; max_items: number }\n targets: { app: ShareTargetApp; accepts: string | string[]; max_item_size: number; fallback: 'link' }[]\n blocked: { action: string; reason: string }[]\n share_base_url?: string | null\n public: { dir: string; base_url?: string | null }\n}\n\nexport interface ShareTargetFit {\n app: ShareTargetApp\n mode: 'attachment' | 'link'\n reason?: 'folder' | 'not_media' | 'too_large' | null\n kind: ShareContentKind\n}\n\nexport interface SharePlan {\n plan_fingerprint: string\n source: { path: string; name: string; kind: 'file' | 'dir'; mime?: string | null }\n estimate: { total_size: number; item_count: number; excluded: { path: string; reason: string }[] }\n warnings: string[]\n requires_task: boolean\n existing: ShareInfo[]\n source_public: { in_publish_dir: boolean; public_acl: boolean; public_url?: string | null }\n /** Folders with an index.html may be websites (going live is App Service's job). */\n site: { has_index_html: boolean }\n target_fit?: ShareTargetFit\n}\n\nexport interface SharePolicyInput {\n access: ShareAccess\n password?: string\n /** Unix seconds; `null` = does not expire; omitted = seven days. */\n expires_at?: number | null\n allow_download?: boolean\n}\n\nexport interface ShareHandoff {\n share_id: string\n obj_id: string\n name: string\n size: number\n mime?: string | null\n kind: ShareContentKind\n mode: 'attachment' | 'link'\n link_share_id?: string | null\n url?: string | null\n launch: { appId: ShareTargetApp; payload: Record<string, unknown> }\n}\n\nexport interface ShareOperation {\n operation_id: string\n intent: 'create_link' | 'app_handoff'\n state: ShareOperationState\n share_id?: string | null\n task_id?: string | null\n error?: NfspErrorBody | null\n progress: { done_bytes?: number; total_bytes?: number }\n created_at: number\n updated_at: number\n source_changed?: boolean\n obj_id?: string\n url?: string | null\n share?: ShareInfo\n handoff?: ShareHandoff | null\n}\n\nexport interface ShareListFilter {\n kind?: ShareKind\n status?: ShareStatus\n source?: string | Locator\n objId?: string\n q?: string\n includeHidden?: boolean\n limit?: number\n cursor?: string\n}\n\nexport interface ShareStatsPoint {\n t: number\n views: number\n downloads: number\n bytes: number\n visitors: number\n}\n\nexport interface ShareStatsResult {\n granularity: 'hour' | 'day'\n from: number\n to: number\n series: ShareStatsPoint[]\n totals: Omit<ShareStatsTotals, 'last_access_at'>\n /** `expiring_soon`: active links ending within `expiring_within` seconds. */\n summary: { active: number; expiring_soon: number; expiring_within: number }\n unavailable: string[]\n}\n\nexport interface PublicEntry {\n rel_path: string\n entry_kind: 'file' | 'dir' | 'symlink'\n link_target?: string | null\n link_ok?: boolean | null\n size?: number | null\n mtime?: number | null\n first_seen_at: number\n last_seen_at: number\n missing_since?: number | null\n url?: string | null\n}\n\nexport interface PublicListing {\n entries: PublicEntry[]\n last_scan_at: number\n scan_ok: boolean\n dir: string\n base_url?: string | null\n}\n\nconst newIdempotencyKey = (): string =>\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n\nexport type CollectionPatchOp =\n | {\n add_ref: {\n target_ref: WireRef\n name?: string\n position?: number\n parent_entry_ref?: string\n }\n }\n | { remove_entry: { entry_ref: string } }\n | { move_entries: { entry_refs: string[]; to_index: number } }\n | { create_group: { name: string; position?: number } }\n | { rename_group: { entry_ref: string; name: string } }\n\nexport interface WatchEvent {\n /** `resync` | `container_changed` | `meta_changed` (extensible). */\n event: string\n /** Parsed JSON payload of the SSE `data:` line. */\n data: Record<string, unknown>\n /** SSE `id:` (the server_rev), when present. */\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Structured NFSP error body (§8 plus implementation extensions). */\nexport interface NfspErrorBody {\n code: string\n message: string\n [k: string]: unknown\n}\n\nexport class NfspError extends Error {\n readonly code: string\n readonly httpStatus: number\n /** Extra structured fields (reason, holder_session, obj_id, expected, ...). */\n readonly details: Record<string, unknown>\n\n constructor(body: NfspErrorBody, httpStatus: number) {\n super(`${body.code}: ${body.message}`)\n this.name = 'NfspError'\n this.code = body.code\n this.httpStatus = httpStatus\n this.details = Object.fromEntries(Object.entries(body).filter(([key]) => key !== 'code' && key !== 'message'))\n }\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\nexport interface NfspClientOptions {\n sessionToken?: () => Promise<string | null>\n /** e.g. `http://127.0.0.1:3260` — no trailing slash needed. */\n baseUrl: string\n /** Override fetch (tests, custom auth wrappers). Defaults to global fetch. */\n fetch?: typeof fetch\n /** tus PATCH chunk size; server guidance is 4–16 MB. Default 8 MB. */\n uploadChunkSize?: number\n}\n\ninterface EnvelopeExtras {\n at?: Locator\n want?: WantGroup[]\n args?: Record<string, unknown>\n}\n\nexport class NfspClient {\n private readonly sessionToken?: () => Promise<string | null>\n readonly baseUrl: string\n private readonly fetchFn: typeof fetch\n private readonly chunkSize: number\n private session: string | null = null\n private seq = 0\n private helloResult: HelloResult | null = null\n\n constructor(opts: NfspClientOptions) {\n this.sessionToken = opts.sessionToken\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n // Bind to globalThis: an unbound fetch throws \"Illegal invocation\".\n this.fetchFn = opts.fetch ?? ((input, init) => globalThis.fetch(input, init))\n this.chunkSize = opts.uploadChunkSize ?? 8 * 1024 * 1024\n }\n\n get sessionId(): string | null {\n return this.session\n }\n\n /** Features advertised by the server in hello (empty before hello). */\n get features(): string[] {\n return this.helloResult?.features ?? []\n }\n\n get limits(): HelloResult['limits'] | null {\n return this.helloResult?.limits ?? null\n }\n\n // ---------- session ----------\n\n async hello(clientFeatures?: string[]): Promise<HelloResult> {\n const args: Record<string, unknown> = { versions: [NFSP_VERSION] }\n if (clientFeatures) args.features = clientFeatures\n const r = (await this.post('hello', { args })) as HelloResult\n this.session = r.session\n this.seq = 0\n this.helloResult = r\n return r\n }\n\n async bye(): Promise<void> {\n await this.call('bye', {})\n this.session = null\n this.helloResult = null\n }\n\n // ---------- resolve / stat / list / batch ----------\n\n async resolve(at: LocatorLike, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('resolve', { at: toLocator(at), want })) as NodeInfo\n }\n\n /** stat with an optional child `name` step (server-side walk). */\n async stat(at: LocatorLike, opts?: { name?: string; want?: WantGroup[] }): Promise<NodeInfo> {\n return (await this.call('stat', {\n at: toLocator(at),\n want: opts?.want,\n args: opts?.name ? { name: opts.name } : {},\n })) as NodeInfo\n }\n\n async list(at: LocatorLike, opts?: ListOptions, want?: WantGroup[]): Promise<Listing> {\n return (await this.call('list', {\n at: toLocator(at),\n want,\n args: (opts ?? {}) as Record<string, unknown>,\n })) as Listing\n }\n\n async batch(\n start: LocatorLike,\n ops: BatchOp[],\n onError: 'abort' | 'continue' = 'abort',\n ): Promise<BatchResult> {\n return (await this.call('batch', {\n args: { start: toLocator(start), ops, on_error: onError },\n })) as BatchResult\n }\n\n // ---------- structure writes ----------\n\n /**\n * Ref form: create one child under `parent` (optional CAS via\n * `expectedRevision`). Path form: pass a dfs path string and no name —\n * behaves like `mkdir -p` (idempotent, no CAS).\n */\n async mkdir(\n parent: LocatorLike,\n name?: string,\n opts?: { expectedRevision?: string },\n ): Promise<{ ref: WireRef; existed: boolean; revision?: string }> {\n const args: Record<string, unknown> = {}\n if (name !== undefined) args.name = name\n if (opts?.expectedRevision) args.expected_revision = opts.expectedRevision\n return (await this.write('mkdir', { at: toLocator(parent), args })) as {\n ref: WireRef\n existed: boolean\n revision?: string\n }\n }\n\n async move(\n from: { parentRef: WireRef; name: string },\n to: { parentRef: WireRef; name: string },\n opts?: { expectedFromRevision?: string; expectedToRevision?: string },\n ): Promise<{ from_revision: string; to_revision: string }> {\n return (await this.write('move', {\n args: {\n from: { parent_ref: from.parentRef, name: from.name },\n to: { parent_ref: to.parentRef, name: to.name },\n expected_from_revision: opts?.expectedFromRevision,\n expected_to_revision: opts?.expectedToRevision,\n },\n })) as { from_revision: string; to_revision: string }\n }\n\n /** Destroys a native entry. Reference entries must use `unlink` instead. */\n async delete(\n parent: LocatorLike,\n name: string,\n opts?: { recursive?: boolean; expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('delete', {\n at: toLocator(parent),\n args: {\n name,\n recursive: opts?.recursive,\n expected_revision: opts?.expectedRevision,\n },\n })) as { revision: string }\n }\n\n async bindRef(\n parentRef: WireRef,\n name: string,\n targetRef: WireRef,\n opts?: { expectedRevision?: string },\n ): Promise<{ entry_ref: string; revision: string }> {\n return (await this.write('bind_ref', {\n args: {\n parent_ref: parentRef,\n name,\n target_ref: targetRef,\n expected_revision: opts?.expectedRevision,\n },\n })) as { entry_ref: string; revision: string }\n }\n\n /** Removes a reference entry (`be_*`) only; the target is never touched. */\n async unlink(\n entryRef: string,\n opts?: { expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('unlink', {\n args: { entry_ref: entryRef, expected_revision: opts?.expectedRevision },\n })) as { revision: string }\n }\n\n // ---------- content writes (open_write / tus / commit_file / probe) ----------\n\n async openWrite(\n target: { parentRef: WireRef; name: string; size?: number } | { ref: WireRef },\n ): Promise<OpenWriteResult> {\n const args: Record<string, unknown> =\n 'ref' in target\n ? { ref: target.ref }\n : { parent_ref: target.parentRef, name: target.name, size: target.size }\n return (await this.write('open_write', { args })) as OpenWriteResult\n }\n\n /** Current upload offset (tus HEAD) — resume point after interruption. */\n async uploadOffset(fbHandle: string): Promise<number> {\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/uploads/${fbHandle}`, {\n method: 'HEAD',\n headers: await this.authHeaders(),\n })\n if (!resp.ok) throw await this.httpError(resp)\n return Number(resp.headers.get('Upload-Offset') ?? '0')\n }\n\n /** One tus PATCH. Returns the new offset. */\n async uploadChunk(fbHandle: string, offset: number, chunk: Uint8Array): Promise<number> {\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/uploads/${fbHandle}`, {\n method: 'PATCH',\n headers: {\n ...await this.authHeaders(),\n 'Upload-Offset': String(offset),\n 'Content-Type': 'application/offset+octet-stream',\n },\n body: chunk as unknown as BodyInit,\n })\n if (resp.status !== 204) throw await this.httpError(resp)\n return Number(resp.headers.get('Upload-Offset') ?? String(offset + chunk.byteLength))\n }\n\n /** Uploads a whole buffer in chunks, resuming from the server's offset. */\n async uploadContent(\n fbHandle: string,\n content: Uint8Array,\n onProgress?: (sent: number, total: number) => void,\n ): Promise<void> {\n let offset = await this.uploadOffset(fbHandle)\n while (offset < content.byteLength) {\n const chunk = content.subarray(offset, Math.min(offset + this.chunkSize, content.byteLength))\n offset = await this.uploadChunk(fbHandle, offset, chunk)\n onProgress?.(offset, content.byteLength)\n }\n }\n\n async abortWrite(leaseId: string): Promise<void> {\n await this.write('abort_write', { args: { lease_id: leaseId } })\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const token = await this.sessionToken?.()\n return token ? { Authorization: `Bearer ${token}` } : {}\n }\n\n async commitFile(\n parent: LocatorLike,\n name: string,\n source: { fbHandle: string; leaseId?: string } | { hash: string },\n opts?: { overwrite?: boolean; expectedRevision?: string },\n ): Promise<CommitResult> {\n const args: Record<string, unknown> = {\n name,\n overwrite: opts?.overwrite,\n expected_revision: opts?.expectedRevision,\n }\n if ('fbHandle' in source) {\n args.fb_handle = source.fbHandle\n if (source.leaseId) args.lease_id = source.leaseId\n } else {\n args.hash = source.hash\n }\n return (await this.write('commit_file', { at: toLocator(parent), args })) as CommitResult\n }\n\n async commitObject(\n parent: LocatorLike,\n name: string,\n objId: string,\n opts?: { overwrite?: boolean; expectedRevision?: string; leaseId?: string },\n ): Promise<CommitObjectResult> {\n return await this.write('commit_object', {\n at: toLocator(parent),\n args: { name, obj_id: objId, overwrite: opts?.overwrite, expected_revision: opts?.expectedRevision, lease_id: opts?.leaseId },\n }) as CommitObjectResult\n }\n\n /** Which of these digests must be uploaded (§ probe / 秒传). */\n async probe(digests: { hash: string; size?: number }[]): Promise<{ missing: { hash: string; size?: number }[] }> {\n return (await this.call('probe', { args: { digests } })) as {\n missing: { hash: string; size?: number }[]\n }\n }\n\n /**\n * Convenience: open_write + chunked tus upload + commit_file.\n * Overwrites an existing file only when `opts.overwrite` is set.\n */\n async uploadFile(\n parentRef: WireRef,\n name: string,\n content: Uint8Array,\n opts?: { overwrite?: boolean; onProgress?: (sent: number, total: number) => void },\n ): Promise<CommitResult> {\n const ow = await this.openWrite({ parentRef, name, size: content.byteLength })\n await this.uploadContent(ow.fb_handle, content, opts?.onProgress)\n return this.commitFile({ ref: parentRef }, name, {\n fbHandle: ow.fb_handle,\n leaseId: ow.lease.lease_id,\n }, { overwrite: opts?.overwrite })\n }\n\n // ---------- data plane read ----------\n\n /**\n * URL for `GET /nfs/v1/read/{node_id}` (usable in <img>, <a download>, …).\n * It carries no token: same-origin requests authenticate with the SSO cookie,\n * anything else only reads public paths.\n */\n readUrl(nodeId: string, opts?: { download?: boolean; name?: string }): string {\n const params = new URLSearchParams()\n if (opts?.download) params.set('download', '1')\n if (opts?.name) params.set('name', opts.name)\n const qs = params.size > 0 ? `?${params.toString()}` : ''\n return `${this.baseUrl}/nfs/v1/read/${encodeURIComponent(nodeId)}${qs}`\n }\n\n /** Reads file content; `range` is inclusive byte positions. */\n async readFile(\n nodeId: string,\n opts?: { range?: { start: number; end?: number }; ifNoneMatch?: string },\n ): Promise<Response> {\n const headers: Record<string, string> = await this.authHeaders()\n if (opts?.range) {\n headers.Range = `bytes=${opts.range.start}-${opts.range.end ?? ''}`\n }\n if (opts?.ifNoneMatch) headers['If-None-Match'] = opts.ifNoneMatch\n const resp = await this.fetchFn(this.readUrl(nodeId), { headers, cache: 'no-store' })\n if (!resp.ok && resp.status !== 304) throw await this.httpError(resp)\n return resp\n }\n\n // ---------- meta ----------\n\n async getMeta(target: LocatorLike, ns?: string[]): Promise<{ records: MetaRecord[] }> {\n const loc = toLocator(target)\n const args: Record<string, unknown> = { ns }\n if (loc.ref) args.ref = loc.ref\n return (await this.call('get_meta', { at: loc, args })) as { records: MetaRecord[] }\n }\n\n /** v1: only `ns: \"user\"` records are writable. */\n async setMeta(\n target: LocatorLike,\n records: { ns: string; key: string; value: unknown; visibility?: string }[],\n ): Promise<{ updated: number }> {\n const loc = toLocator(target)\n const args: Record<string, unknown> = { records }\n if (loc.ref) args.ref = loc.ref\n return (await this.write('set_meta', { at: loc, args })) as { updated: number }\n }\n\n // ---------- search ----------\n\n async search(\n q: string,\n opts?: { limit?: number; cursor?: string; scope?: LocatorLike; modes?: string[] },\n want?: WantGroup[],\n ): Promise<SearchResult> {\n return (await this.call('search', {\n want,\n args: {\n q,\n limit: opts?.limit,\n cursor: opts?.cursor,\n scope: opts?.scope !== undefined ? toLocator(opts.scope) : undefined,\n modes: opts?.modes,\n },\n })) as SearchResult\n }\n\n // ---------- views & collections ----------\n\n async openView(viewId: string, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('open_view', { want, args: { view_id: viewId } })) as NodeInfo\n }\n\n async createCollection(\n title: string,\n collectionId?: string,\n want?: WantGroup[],\n ): Promise<NodeInfo> {\n return (await this.write('create_collection', {\n want,\n args: { title, collection_id: collectionId },\n })) as NodeInfo\n }\n\n async openCollection(collectionId: string, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('open_collection', {\n want,\n args: { collection_id: collectionId },\n })) as NodeInfo\n }\n\n async collectionPatch(\n ref: WireRef,\n ops: CollectionPatchOp[],\n opts?: { expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('collection_patch', {\n args: { ref, ops, expected_revision: opts?.expectedRevision },\n })) as { revision: string }\n }\n\n // ---------- grants ----------\n\n async grant(\n subtree: LocatorLike,\n opts?: { ops?: string[]; ttl?: number; audience?: string; maxUses?: number },\n ): Promise<GrantResult> {\n return (await this.write('grant', {\n args: {\n subtree: toLocator(subtree),\n ops: opts?.ops,\n ttl: opts?.ttl,\n audience: opts?.audience,\n max_uses: opts?.maxUses,\n },\n })) as GrantResult\n }\n\n async revoke(capId: string): Promise<{ revoked: string }> {\n return (await this.write('revoke', { args: { cap_id: capId } })) as { revoked: string }\n }\n\n // ---------- acl ----------\n\n async getAcl(at: LocatorLike): Promise<AclInfo> {\n return (await this.call('get_acl', { at: toLocator(at) })) as AclInfo\n }\n\n /** Replaces the node's own record; `inherit: true` with no entries removes it. */\n async setAcl(\n at: LocatorLike,\n acl: { inherit: boolean; entries: AclEntry[] },\n opts?: { expectedRevision?: string },\n ): Promise<{ path: string; revision: string }> {\n return (await this.write('set_acl', {\n at: toLocator(at),\n args: { inherit: acl.inherit, entries: acl.entries, expected_revision: opts?.expectedRevision },\n })) as { path: string; revision: string }\n }\n\n /** Every ACL record the caller may manage (all of them for the zone owner). */\n async listAcls(): Promise<{ records: AclRecordInfo[]; zone_owner: string | null; is_admin: boolean }> {\n return (await this.call('list_acls', {})) as { records: AclRecordInfo[]; zone_owner: string | null; is_admin: boolean }\n }\n\n // ---------- shares (`share.*`) ----------\n\n async shareCapabilities(): Promise<ShareCapabilities> {\n return (await this.call('share.capabilities', {})) as ShareCapabilities\n }\n\n /** Side-effect free: size, exclusions, existing links, Public state, target fit. */\n async sharePlan(\n source: LocatorLike,\n opts?: { intent?: 'create_link' | 'app_handoff'; target?: ShareTargetApp },\n ): Promise<SharePlan> {\n return (await this.call('share.plan', {\n args: { source: toLocator(source), intent: opts?.intent, target: opts?.target },\n })) as SharePlan\n }\n\n /** Snapshots the source and creates a link. Large sources return `state: 'running'`. */\n async shareCreate(\n source: LocatorLike,\n policy: SharePolicyInput,\n opts?: { title?: string; idempotencyKey?: string; planFingerprint?: string; sourceHint?: 'gui' | 'cli' | 'agent' },\n ): Promise<ShareOperation> {\n return (await this.write('share.create', {\n args: {\n source: toLocator(source),\n policy,\n title: opts?.title,\n idempotency_key: opts?.idempotencyKey ?? newIdempotencyKey(),\n plan_fingerprint: opts?.planFingerprint,\n source_hint: opts?.sourceHint,\n },\n })) as ShareOperation\n }\n\n async shareOperation(operationId: string, opts?: { cancel?: boolean }): Promise<ShareOperation> {\n return (await this.call('share.operation', {\n args: { operation_id: operationId, cancel: opts?.cancel },\n })) as ShareOperation\n }\n\n /** Polls a running operation until it settles. */\n async waitShareOperation(\n op: ShareOperation,\n opts?: { intervalMs?: number; signal?: AbortSignal; onProgress?: (op: ShareOperation) => void },\n ): Promise<ShareOperation> {\n let cur = op\n while (cur.state === 'running' || cur.state === 'preparing') {\n if (opts?.signal?.aborted) throw new DOMException('aborted', 'AbortError')\n await new Promise((r) => setTimeout(r, opts?.intervalMs ?? 1000))\n cur = await this.shareOperation(cur.operation_id)\n opts?.onProgress?.(cur)\n }\n return cur\n }\n\n async shareGet(shareId: string): Promise<ShareDetail> {\n return (await this.call('share.get', { args: { share_id: shareId } })) as ShareDetail\n }\n\n async shareList(filter?: ShareListFilter): Promise<{ items: ShareInfo[]; next_cursor?: string | null }> {\n const source = filter?.source === undefined || typeof filter.source === 'string' ? filter?.source : toLocator(filter.source)\n return (await this.call('share.list', {\n args: {\n kind: filter?.kind,\n status: filter?.status,\n source,\n obj_id: filter?.objId,\n q: filter?.q,\n include_hidden: filter?.includeHidden,\n limit: filter?.limit,\n cursor: filter?.cursor,\n },\n })) as { items: ShareInfo[]; next_cursor?: string | null }\n }\n\n /** `password: null` removes it, `expires_at: null` = no expiry. REVISION_CONFLICT carries `current`. */\n async shareUpdate(\n shareId: string,\n revision: number,\n changes: { title?: string; expires_at?: number | null; password?: string | null; allow_download?: boolean; access?: ShareAccess },\n reason?: string,\n ): Promise<{ revision: number; share: ShareInfo }> {\n return (await this.write('share.update', {\n args: { share_id: shareId, revision, changes, reason },\n })) as { revision: number; share: ShareInfo }\n }\n\n async shareStop(shareId: string, revision: number, reason?: string): Promise<{ status: ShareStatus; revision: number }> {\n return (await this.write('share.stop', {\n args: { share_id: shareId, revision, reason },\n })) as { status: ShareStatus; revision: number }\n }\n\n async shareHide(shareId: string, hidden: boolean): Promise<{ hidden: boolean; revision: number }> {\n return (await this.write('share.hide', { args: { share_id: shareId, hidden } })) as { hidden: boolean; revision: number }\n }\n\n /** Snapshot for an app target; the result's `handoff.launch` opens the app. */\n async shareAppHandoff(\n source: LocatorLike,\n targetApp: ShareTargetApp,\n opts?: { title?: string; idempotencyKey?: string },\n ): Promise<ShareOperation> {\n return (await this.write('share.app.handoff', {\n args: {\n source: toLocator(source),\n target_app: targetApp,\n title: opts?.title,\n idempotency_key: opts?.idempotencyKey ?? newIdempotencyKey(),\n },\n })) as ShareOperation\n }\n\n /** What the target app did; older facts (by `observedAt`, ms) are refused. */\n async shareAppReport(\n shareId: string,\n deliveryState: Exclude<ShareDeliveryState, 'handed_off'>,\n opts?: { businessRef?: Record<string, unknown>; error?: string; observedAt?: number },\n ): Promise<{ accepted: boolean; delivery_state: string }> {\n return (await this.write('share.app.report', {\n args: {\n share_id: shareId,\n delivery_state: deliveryState,\n business_ref: opts?.businessRef,\n error: opts?.error,\n observed_at: opts?.observedAt ?? Date.now(),\n },\n })) as { accepted: boolean; delivery_state: string }\n }\n\n async sharePublicList(opts?: { refresh?: boolean }): Promise<PublicListing> {\n return (await this.call('share.public.list', { args: { refresh: opts?.refresh } })) as PublicListing\n }\n\n async shareStats(opts?: { shareId?: string; from?: number; to?: number; granularity?: 'hour' | 'day' }): Promise<ShareStatsResult> {\n return (await this.call('share.stats', {\n args: { share_id: opts?.shareId, from: opts?.from, to: opts?.to, granularity: opts?.granularity },\n })) as ShareStatsResult\n }\n\n // ---------- watch (SSE) ----------\n\n /**\n * Opens the watch stream. `tokens` filters to specific containers using the\n * `watch_token` returned by list. The stream is lossy by contract: on any\n * `resync` event, re-list watched containers.\n */\n watch(opts?: { tokens?: string[]; signal?: AbortSignal }): AsyncIterableIterator<WatchEvent> & {\n close: () => void\n } {\n if (!this.session) throw new Error('watch requires an active session; call hello() first')\n const params = new URLSearchParams({ session: this.session })\n if (opts?.tokens?.length) params.set('tokens', opts.tokens.join(','))\n const url = `${this.baseUrl}/nfs/v1/watch?${params.toString()}`\n const controller = new AbortController()\n if (opts?.signal) {\n opts.signal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n const fetchFn = this.fetchFn\n const authHeaders = () => this.authHeaders()\n\n async function* events(): AsyncIterableIterator<WatchEvent> {\n const resp = await fetchFn(url, {\n headers: { ...await authHeaders(), Accept: 'text/event-stream' },\n signal: controller.signal,\n })\n if (!resp.ok || !resp.body) {\n throw new Error(`watch failed: HTTP ${resp.status}`)\n }\n const reader = resp.body.getReader()\n const decoder = new TextDecoder()\n let buf = ''\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) return\n buf += decoder.decode(value, { stream: true })\n // SSE frames are separated by a blank line.\n for (;;) {\n const sep = buf.indexOf('\\n\\n')\n if (sep < 0) break\n const frame = buf.slice(0, sep)\n buf = buf.slice(sep + 2)\n let event = 'message'\n let id: string | undefined\n const dataLines: string[] = []\n for (const line of frame.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice(6).trim()\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim())\n else if (line.startsWith('id:')) id = line.slice(3).trim()\n // comment lines (`:keep-alive`) are ignored\n }\n if (dataLines.length === 0 && event === 'message') continue\n let data: Record<string, unknown> = {}\n if (dataLines.length > 0) {\n try {\n data = JSON.parse(dataLines.join('\\n')) as Record<string, unknown>\n } catch {\n data = { raw: dataLines.join('\\n') }\n }\n }\n yield { event, data, id }\n }\n }\n } finally {\n reader.cancel().catch(() => {})\n }\n }\n\n const it = events() as AsyncIterableIterator<WatchEvent> & { close: () => void }\n it.close = () => controller.abort()\n return it\n }\n\n // ---------- envelope plumbing ----------\n\n /** Read-path call: no seq. Throws NfspError on `ok:false`. */\n async call(method: string, extras: EnvelopeExtras): Promise<unknown> {\n return this.post(method, this.envelope(extras))\n }\n\n /**\n * Write-path call: assigns the next seq once and reuses it across network\n * retries so the server's replay window guarantees exactly-once.\n */\n async write(method: string, extras: EnvelopeExtras, retries = 2): Promise<unknown> {\n const seq = ++this.seq\n const body = { ...this.envelope(extras), seq }\n let lastErr: unknown\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await this.post(method, body)\n } catch (e) {\n // Only network-level failures are retried (same seq → replay-safe).\n // NfspError means the server answered: propagate immediately.\n if (e instanceof NfspError) throw e\n lastErr = e\n }\n }\n throw lastErr\n }\n\n private envelope(extras: EnvelopeExtras): Record<string, unknown> {\n const body: Record<string, unknown> = { args: pruneUndefined(extras.args ?? {}) }\n if (this.session) body.session = this.session\n if (extras.at) body.at = pruneUndefined(extras.at as unknown as Record<string, unknown>)\n if (extras.want) body.want = extras.want\n return body\n }\n\n private async post(method: string, body: Record<string, unknown>): Promise<unknown> {\n const token = await this.sessionToken?.()\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/${method}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },\n body: JSON.stringify(body),\n })\n let parsed: { ok?: boolean; result?: unknown; error?: NfspErrorBody }\n try {\n parsed = (await resp.json()) as typeof parsed\n } catch {\n throw new NfspError(\n { code: 'INTERNAL', message: `non-JSON response (HTTP ${resp.status})` },\n resp.status,\n )\n }\n if (parsed.ok !== true) {\n throw new NfspError(\n parsed.error ?? { code: 'INTERNAL', message: 'malformed error envelope' },\n resp.status,\n )\n }\n return parsed.result\n }\n\n private async httpError(resp: Response): Promise<NfspError> {\n try {\n const parsed = (await resp.json()) as { error?: NfspErrorBody }\n if (parsed.error) return new NfspError(parsed.error, resp.status)\n } catch {\n // fall through\n }\n return new NfspError({ code: 'INTERNAL', message: `HTTP ${resp.status}` }, resp.status)\n }\n}\n\n/** Drops undefined values so they don't serialize as JSON nulls. */\nconst pruneUndefined = (obj: Record<string, unknown>): Record<string, unknown> => {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k] = v\n }\n return out\n}\n"],"names":["NFSP_VERSION","toLocator","at","includes","uri","realm","path","ref","newIdempotencyKey","crypto","randomUUID","Date","now","toString","Math","random","slice","NfspError","Error","constructor","body","httpStatus","super","code","message","this","name","details","Object","fromEntries","entries","filter","key","pruneUndefined","obj","out","k","v","opts","session","seq","helloResult","sessionToken","baseUrl","replace","fetchFn","fetch","input","init","globalThis","chunkSize","uploadChunkSize","sessionId","features","_a","limits","hello","clientFeatures","args","versions","r","post","bye","call","resolve","want","stat","list","batch","start","ops","onError","on_error","mkdir","parent","expectedRevision","expected_revision","write","move","from","to","parent_ref","parentRef","expected_from_revision","expectedFromRevision","expected_to_revision","expectedToRevision","recursive","bindRef","targetRef","target_ref","unlink","entryRef","entry_ref","openWrite","target","size","uploadOffset","fbHandle","resp","method","headers","authHeaders","ok","httpError","Number","get","uploadChunk","offset","chunk","String","status","byteLength","uploadContent","content","onProgress","subarray","min","abortWrite","leaseId","lease_id","token","Authorization","commitFile","source","overwrite","fb_handle","hash","commitObject","objId","obj_id","probe","digests","uploadFile","ow","lease","readUrl","nodeId","params","URLSearchParams","download","set","qs","encodeURIComponent","readFile","range","Range","end","ifNoneMatch","cache","getMeta","ns","loc","setMeta","records","search","q","limit","cursor","scope","modes","openView","viewId","view_id","createCollection","title","collectionId","collection_id","openCollection","collectionPatch","grant","subtree","ttl","audience","max_uses","maxUses","revoke","capId","cap_id","getAcl","setAcl","acl","inherit","listAcls","shareCapabilities","sharePlan","intent","shareCreate","policy","idempotency_key","idempotencyKey","plan_fingerprint","planFingerprint","source_hint","sourceHint","shareOperation","operationId","operation_id","cancel","waitShareOperation","op","cur","state","signal","aborted","DOMException","Promise","setTimeout","intervalMs","_b","shareGet","shareId","share_id","shareList","kind","include_hidden","includeHidden","shareUpdate","revision","changes","reason","shareStop","shareHide","hidden","shareAppHandoff","targetApp","target_app","shareAppReport","deliveryState","delivery_state","business_ref","businessRef","error","observed_at","observedAt","sharePublicList","refresh","shareStats","granularity","watch","tokens","length","join","url","controller","AbortController","addEventListener","abort","once","it","async","Accept","reader","getReader","decoder","TextDecoder","buf","done","value","read","decode","stream","sep","indexOf","frame","id","event","dataLines","line","split","startsWith","trim","push","data","JSON","parse","raw","catch","events","close","extras","envelope","retries","lastErr","attempt","e","stringify","parsed","json","result","gen","type","node_id"],"mappings":"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"}
1
+ {"version":3,"file":"nfsp.cjs","sources":["../src/nfsp.ts"],"sourcesContent":["/**\n * NFSP v0 client for nfs_server (buckyos/src/frame/nfs_server).\n *\n * Speaks the control plane (`POST /nfs/v1/{method}`), the data plane\n * (`GET /nfs/v1/read/{node_id}`), the minimal tus upload area\n * (`PATCH|HEAD /nfs/v1/uploads/{fb}`) and the watch SSE stream\n * (`GET /nfs/v1/watch`).\n *\n * Protocol references:\n * - cyfs-ndn/doc/NamedFileSystem_Protocol_v0.md (NFSP v0)\n * - buckyos/product/bucky_file/nfs_server.md\n * - buckyos/src/frame/nfs_server/README.md (v1 降级契约)\n *\n * Client contract highlights (nfs_server README §4.2):\n * - `revision` is an opaque equality token: only compare, never order.\n * - Write ops require `seq` (exactly-once); the client auto-assigns one per\n * write and replays with the SAME seq on network retry.\n * - Leases are advisory versus server-local bypass writers: commit may fail\n * with TARGET_MISMATCH{reason:\"bypass_modified\"} — surface, don't retry.\n * - watch is lossy: any `resync` event means \"re-list what you care about\".\n *\n * Uses only fetch/WHATWG streams, so it runs in the browser and in Node ≥ 18.\n */\n\n// ---------------------------------------------------------------------------\n// Wire types\n// ---------------------------------------------------------------------------\n\nexport const NFSP_VERSION = 'nfsp/0'\n\n/** `{\"type\":\"live\",...}` or `{\"type\":\"object\",...}` (NFSP §3.1.2). */\nexport type LiveRef = { type: 'live'; node_id: string; gen?: number }\nexport type ObjectRef = { type: 'object'; obj_id: string; inner_path?: string }\nexport type WireRef = LiveRef | ObjectRef\n\nexport const liveRef = (nodeId: string, gen = 0): LiveRef => ({\n type: 'live',\n node_id: nodeId,\n gen,\n})\n\n/** The `at` locator: ref > uri > path (NFSP §3.4). */\nexport interface Locator {\n realm?: string\n path?: string\n uri?: string\n ref?: WireRef\n}\n\n/** Sugar: accept a dfs path string, a `xxx://` uri, a WireRef or a Locator. */\nexport type LocatorLike = string | WireRef | Locator\n\nexport const toLocator = (at: LocatorLike): Locator => {\n if (typeof at === 'string') {\n if (at.includes('://')) return { uri: at }\n return { realm: 'dfs', path: at }\n }\n if ('type' in at) return { ref: at }\n return at\n}\n\nexport type WantGroup = 'base' | 'ident' | 'access' | 'meta'\n\nexport type NodeKind = 'dir' | 'file' | 'symlink' | 'view' | 'collection' | 'group'\n\n/**\n * The caller's effective permission on a node. `traverse` = only the way to\n * something shared deeper down: listable, but only those children are shown.\n */\nexport type AccessLevel = 'none' | 'traverse' | 'read' | 'write' | 'manage'\n\n/** `user:<user_id>`, `zone_users` (any signed-in user) or `public` (anyone, read only). */\nexport type AclPrincipal = `user:${string}` | 'zone_users' | 'public'\n\nexport interface AclEntry {\n principal: AclPrincipal\n level: Exclude<AccessLevel, 'none' | 'traverse'>\n}\n\n/** `get_acl` result; the entry lists only appear for callers who may manage. */\nexport interface AclInfo {\n path: string\n access_level: AccessLevel\n can_manage: boolean\n revision: string\n inherit?: boolean\n entries?: AclEntry[]\n /** Grants arriving from ancestors, with the directory they come from. */\n inherited?: (AclEntry & { from: string })[]\n /** Built-in rules (zone owner, home owner) that no record can remove. */\n builtin?: (AclEntry & { reason: 'zone_owner' | 'home_owner' })[]\n updated_by?: string\n updated_at?: number\n}\n\nexport interface AclRecordInfo {\n path: string\n inherit: boolean\n entries: AclEntry[]\n home_owner: string | null\n updated_by: string\n updated_at: number\n revision: string\n /** The path no longer exists (removed outside the protocol). */\n missing: boolean\n}\n\nexport interface Capabilities {\n list: boolean\n read: boolean\n accepts_content: boolean\n accepts_references: boolean\n remove_semantics: 'destroy' | 'unlink' | 'none'\n ordered: boolean\n}\n\n/** resolve/stat result (fields beyond `base` appear per the `want` mask). */\nexport interface NodeInfo {\n kind: NodeKind\n state: string\n ref: WireRef\n copy_ref?: WireRef | null\n capabilities: Capabilities\n access_level?: AccessLevel\n revision?: string\n locations?: unknown[]\n // base\n name?: string\n size?: number\n mtime?: number\n ctime?: number\n flags?: string[]\n // ident\n node_id?: string\n gen?: number\n etag?: string\n obj_id?: string\n // access\n access_urls?: { kind: string; url: string }[]\n // meta\n meta_summary?: Record<string, number>\n // view / collection extras\n view_id?: string\n collection_id?: string\n title?: string\n origin?: string\n stale?: boolean\n}\n\nexport type EntryBinding = 'native' | 'reference' | 'member' | 'derived' | 'projection'\n\n/** Compact target of a listing entry (not a full NodeInfo). */\nexport interface EntryTarget {\n ref: WireRef\n copy_ref?: WireRef | null\n kind: NodeKind\n /** Attribute groups per the `want` mask: base → size/mtime/flags, ident → etag/obj_id, access → access_urls. */\n attrs?: {\n size?: number\n mtime?: number\n flags?: string[]\n etag?: string\n obj_id?: string\n access_urls?: { kind: string; url: string }[]\n } & Record<string, unknown>\n /** `stale` when a referenced target no longer resolves, `denied` when the caller may not see it. */\n target_state?: string\n access_level?: AccessLevel\n}\n\nexport interface Entry {\n name: string\n binding: EntryBinding\n entry_ref?: string\n target: EntryTarget\n canonical_path?: string\n context?: { count?: number; provenance?: Record<string, unknown> } & Record<string, unknown>\n}\n\nexport interface Listing {\n container: NodeInfo\n entries: Entry[]\n truncated?: boolean\n next_cursor?: string\n revision_changed?: boolean\n watch_token?: string\n /** Same-name virtual bindings shadowed by native entries (`native_shadow`). */\n conflicts?: { name: string; reason: string; entry_ref?: string; target?: EntryTarget }[]\n}\n\nexport interface ListOptions {\n cursor?: string\n limit?: number\n order?: 'name' | 'mtime' | 'size' | 'manual'\n filter?: { kind?: NodeKind[]; name_glob?: string }\n}\n\nexport interface HelloResult {\n version: string\n session: string\n features: string[]\n limits: { max_batch: number; max_list: number; replay_window: number; attr_ttl_ms: number }\n realms: { id: string; writable: boolean }[]\n /** Who this session acts as (`admin` = the zone owner or a trusted service). */\n identity?: { user_id: string | null; role: 'admin' | 'user' | 'anonymous' }\n}\n\nexport type BatchOp =\n | { m: 'walk'; args: { name?: string; names?: string[]; entry_ref?: string } }\n | { m: 'stat' | 'resolve'; args?: { name?: string }; want?: WantGroup[] }\n | { m: 'list'; args?: ListOptions; want?: WantGroup[] }\n\nexport interface BatchResult {\n completed: number\n results: ({ ok: true; result: unknown } | { ok: false; error: NfspErrorBody })[]\n}\n\nexport interface OpenWriteResult {\n fb_handle: string\n upload_url: string\n lease: { lease_id: string; seq: number; ttl_ms: number }\n target: { path: string; exists: boolean }\n}\n\nexport interface CommitResult {\n ref: WireRef\n entry_ref: string\n revision: string\n obj: { sha256: string; size: number }\n}\n\nexport interface CommitObjectResult {\n ref: WireRef\n entry_ref: string\n revision: string\n obj_id: string\n kind: 'file' | 'dir'\n obj: { size: number; sha256?: string }\n}\n\nexport interface MetaRecord {\n ns: string\n key: string\n value: unknown\n source?: unknown\n confidence?: number\n anchor?: string\n visibility?: string\n}\n\nexport interface SearchHit {\n copy_ref?: WireRef | null\n match_source: string\n canonical_path: string\n explain?: Record<string, unknown>\n [k: string]: unknown\n}\n\nexport interface SearchResult {\n hits: SearchHit[]\n partial: boolean\n sources: { mode: string; state: string; took_ms?: number; reason?: string }[]\n next_cursor?: string\n}\n\nexport interface GrantResult {\n cap_id: string\n token: string\n subtree: string\n ops: string[]\n expires_at?: number\n}\n\n// ---------- shares (`share.*`, nfs-server share module) ----------\n\nexport type ShareAccess = 'holder' | 'password'\nexport type ShareStatus = 'active' | 'stopping' | 'stopped' | 'expired' | 'failed'\nexport type ShareKind = 'link' | 'app_delivery'\nexport type ShareTargetApp = 'messagehub' | 'homestation'\nexport type ShareContentKind = 'image' | 'video' | 'audio' | 'file' | 'folder'\nexport type ShareDeliveryState = 'handed_off' | 'confirmed' | 'canceled' | 'failed' | 'unknown'\nexport type ShareOperationState = 'preparing' | 'running' | 'completed' | 'failed' | 'canceled'\n\nexport interface ShareStatsTotals {\n views: number\n downloads: number\n bytes: number\n visitors: number\n last_access_at?: number | null\n}\n\nexport interface ShareInfo {\n share_id: string\n kind: ShareKind\n title: string\n status: ShareStatus\n status_reason?: string | null\n hidden: boolean\n revision: number\n created_at: number\n updated_at: number\n stopped_at?: number | null\n owner_user_id: string\n created_by_app?: string | null\n related_share_id?: string | null\n obj_id: string\n obj_type?: string\n name?: string\n mime?: string | null\n total_size?: number\n item_count?: number\n content_kind?: ShareContentKind\n excluded?: { path: string; reason: string }[]\n prepared_at?: number\n source_path: string\n retained: boolean\n /** Link shares. `url` is only returned to the owner. */\n access?: ShareAccess\n has_password?: boolean\n allow_download?: boolean\n expires_at?: number | null\n url?: string | null\n /** App deliveries. */\n target_app?: ShareTargetApp\n delivery_state?: ShareDeliveryState\n business_ref?: Record<string, unknown> | null\n handoff_at?: number\n reported_at?: number | null\n delivery_error?: string | null\n stats: ShareStatsTotals\n}\n\nexport interface ShareAuditEntry {\n action: string\n revision_before?: number | null\n revision_after?: number | null\n actor_user_id: string\n actor_app_id?: string | null\n source: string\n reason?: string | null\n outcome: string\n at: number\n}\n\nexport interface ShareDetail extends ShareInfo {\n /** The bucket binding that retains the snapshot (`zone/nfs-server/_/shares`); empty once released. */\n retention: { bucket_id: string; key: string; obj_id: string; version: number; bound_at: number }[]\n audit: ShareAuditEntry[]\n related?: ShareInfo\n}\n\nexport interface ShareCapabilities {\n actions: string[]\n access: ShareAccess[]\n expiry_presets: (number | null)[]\n default_expiry: number\n limits: { sync_threshold: number; max_total_size: number; max_items: number }\n targets: { app: ShareTargetApp; accepts: string | string[]; max_item_size: number; fallback: 'link' }[]\n blocked: { action: string; reason: string }[]\n share_base_url?: string | null\n public: { dir: string; base_url?: string | null }\n}\n\nexport interface ShareTargetFit {\n app: ShareTargetApp\n mode: 'attachment' | 'link'\n reason?: 'folder' | 'not_media' | 'too_large' | null\n kind: ShareContentKind\n}\n\nexport interface SharePlan {\n plan_fingerprint: string\n source: { path: string; name: string; kind: 'file' | 'dir'; mime?: string | null }\n estimate: { total_size: number; item_count: number; excluded: { path: string; reason: string }[] }\n warnings: string[]\n requires_task: boolean\n existing: ShareInfo[]\n source_public: { in_publish_dir: boolean; public_acl: boolean; public_url?: string | null }\n /** Folders with an index.html may be websites (going live is App Service's job). */\n site: { has_index_html: boolean }\n target_fit?: ShareTargetFit\n}\n\nexport interface SharePolicyInput {\n access: ShareAccess\n password?: string\n /** Unix seconds; `null` = does not expire; omitted = seven days. */\n expires_at?: number | null\n allow_download?: boolean\n}\n\nexport interface ShareHandoff {\n share_id: string\n obj_id: string\n name: string\n size: number\n mime?: string | null\n kind: ShareContentKind\n mode: 'attachment' | 'link'\n link_share_id?: string | null\n url?: string | null\n launch: { appId: ShareTargetApp; payload: Record<string, unknown> }\n}\n\nexport interface ShareOperation {\n operation_id: string\n intent: 'create_link' | 'app_handoff'\n state: ShareOperationState\n share_id?: string | null\n task_id?: string | null\n error?: NfspErrorBody | null\n progress: { done_bytes?: number; total_bytes?: number }\n created_at: number\n updated_at: number\n source_changed?: boolean\n obj_id?: string\n url?: string | null\n share?: ShareInfo\n handoff?: ShareHandoff | null\n}\n\nexport interface ShareListFilter {\n kind?: ShareKind\n status?: ShareStatus\n source?: string | Locator\n objId?: string\n q?: string\n includeHidden?: boolean\n limit?: number\n cursor?: string\n}\n\nexport interface ShareStatsPoint {\n t: number\n views: number\n downloads: number\n bytes: number\n visitors: number\n}\n\nexport interface ShareStatsResult {\n granularity: 'hour' | 'day'\n from: number\n to: number\n series: ShareStatsPoint[]\n totals: Omit<ShareStatsTotals, 'last_access_at'>\n /** `expiring_soon`: active links ending within `expiring_within` seconds. */\n summary: { active: number; expiring_soon: number; expiring_within: number }\n unavailable: string[]\n}\n\nexport interface PublicEntry {\n rel_path: string\n entry_kind: 'file' | 'dir' | 'symlink'\n link_target?: string | null\n link_ok?: boolean | null\n size?: number | null\n mtime?: number | null\n first_seen_at: number\n last_seen_at: number\n missing_since?: number | null\n url?: string | null\n}\n\nexport interface PublicListing {\n entries: PublicEntry[]\n last_scan_at: number\n scan_ok: boolean\n dir: string\n base_url?: string | null\n}\n\nconst newIdempotencyKey = (): string =>\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n\nexport type CollectionPatchOp =\n | {\n add_ref: {\n target_ref: WireRef\n name?: string\n position?: number\n parent_entry_ref?: string\n }\n }\n | { remove_entry: { entry_ref: string } }\n | { move_entries: { entry_refs: string[]; to_index: number } }\n | { create_group: { name: string; position?: number } }\n | { rename_group: { entry_ref: string; name: string } }\n\nexport interface WatchEvent {\n /** `resync` | `container_changed` | `meta_changed` (extensible). */\n event: string\n /** Parsed JSON payload of the SSE `data:` line. */\n data: Record<string, unknown>\n /** SSE `id:` (the server_rev), when present. */\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Structured NFSP error body (§8 plus implementation extensions). */\nexport interface NfspErrorBody {\n code: string\n message: string\n [k: string]: unknown\n}\n\nexport class NfspError extends Error {\n readonly code: string\n readonly httpStatus: number\n /** Extra structured fields (reason, holder_session, obj_id, expected, ...). */\n readonly details: Record<string, unknown>\n\n constructor(body: NfspErrorBody, httpStatus: number) {\n super(`${body.code}: ${body.message}`)\n this.name = 'NfspError'\n this.code = body.code\n this.httpStatus = httpStatus\n this.details = Object.fromEntries(Object.entries(body).filter(([key]) => key !== 'code' && key !== 'message'))\n }\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\nexport interface NfspClientOptions {\n sessionToken?: () => Promise<string | null>\n /** e.g. `http://127.0.0.1:3260` — no trailing slash needed. */\n baseUrl: string\n /** Override fetch (tests, custom auth wrappers). Defaults to global fetch. */\n fetch?: typeof fetch\n /** tus PATCH chunk size; server guidance is 4–16 MB. Default 8 MB. */\n uploadChunkSize?: number\n}\n\ninterface EnvelopeExtras {\n at?: Locator\n want?: WantGroup[]\n args?: Record<string, unknown>\n}\n\nexport class NfspClient {\n private readonly sessionToken?: () => Promise<string | null>\n readonly baseUrl: string\n private readonly fetchFn: typeof fetch\n private readonly chunkSize: number\n private session: string | null = null\n private seq = 0\n private helloResult: HelloResult | null = null\n\n constructor(opts: NfspClientOptions) {\n this.sessionToken = opts.sessionToken\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n // Bind to globalThis: an unbound fetch throws \"Illegal invocation\".\n this.fetchFn = opts.fetch ?? ((input, init) => globalThis.fetch(input, init))\n this.chunkSize = opts.uploadChunkSize ?? 8 * 1024 * 1024\n }\n\n get sessionId(): string | null {\n return this.session\n }\n\n /** Features advertised by the server in hello (empty before hello). */\n get features(): string[] {\n return this.helloResult?.features ?? []\n }\n\n get limits(): HelloResult['limits'] | null {\n return this.helloResult?.limits ?? null\n }\n\n // ---------- session ----------\n\n async hello(clientFeatures?: string[]): Promise<HelloResult> {\n const args: Record<string, unknown> = { versions: [NFSP_VERSION] }\n if (clientFeatures) args.features = clientFeatures\n const r = (await this.post('hello', { args })) as HelloResult\n this.session = r.session\n this.seq = 0\n this.helloResult = r\n return r\n }\n\n async bye(): Promise<void> {\n await this.call('bye', {})\n this.session = null\n this.helloResult = null\n }\n\n // ---------- resolve / stat / list / batch ----------\n\n async resolve(at: LocatorLike, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('resolve', { at: toLocator(at), want })) as NodeInfo\n }\n\n /** stat with an optional child `name` step (server-side walk). */\n async stat(at: LocatorLike, opts?: { name?: string; want?: WantGroup[] }): Promise<NodeInfo> {\n return (await this.call('stat', {\n at: toLocator(at),\n want: opts?.want,\n args: opts?.name ? { name: opts.name } : {},\n })) as NodeInfo\n }\n\n async list(at: LocatorLike, opts?: ListOptions, want?: WantGroup[]): Promise<Listing> {\n return (await this.call('list', {\n at: toLocator(at),\n want,\n args: (opts ?? {}) as Record<string, unknown>,\n })) as Listing\n }\n\n async batch(\n start: LocatorLike,\n ops: BatchOp[],\n onError: 'abort' | 'continue' = 'abort',\n ): Promise<BatchResult> {\n return (await this.call('batch', {\n args: { start: toLocator(start), ops, on_error: onError },\n })) as BatchResult\n }\n\n // ---------- structure writes ----------\n\n /**\n * Ref form: create one child under `parent` (optional CAS via\n * `expectedRevision`). Path form: pass a dfs path string and no name —\n * behaves like `mkdir -p` (idempotent, no CAS).\n */\n async mkdir(\n parent: LocatorLike,\n name?: string,\n opts?: { expectedRevision?: string },\n ): Promise<{ ref: WireRef; existed: boolean; revision?: string }> {\n const args: Record<string, unknown> = {}\n if (name !== undefined) args.name = name\n if (opts?.expectedRevision) args.expected_revision = opts.expectedRevision\n return (await this.write('mkdir', { at: toLocator(parent), args })) as {\n ref: WireRef\n existed: boolean\n revision?: string\n }\n }\n\n async move(\n from: { parentRef: WireRef; name: string },\n to: { parentRef: WireRef; name: string },\n opts?: { expectedFromRevision?: string; expectedToRevision?: string },\n ): Promise<{ from_revision: string; to_revision: string }> {\n return (await this.write('move', {\n args: {\n from: { parent_ref: from.parentRef, name: from.name },\n to: { parent_ref: to.parentRef, name: to.name },\n expected_from_revision: opts?.expectedFromRevision,\n expected_to_revision: opts?.expectedToRevision,\n },\n })) as { from_revision: string; to_revision: string }\n }\n\n /** Destroys a native entry. Reference entries must use `unlink` instead. */\n async delete(\n parent: LocatorLike,\n name: string,\n opts?: { recursive?: boolean; expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('delete', {\n at: toLocator(parent),\n args: {\n name,\n recursive: opts?.recursive,\n expected_revision: opts?.expectedRevision,\n },\n })) as { revision: string }\n }\n\n async bindRef(\n parentRef: WireRef,\n name: string,\n targetRef: WireRef,\n opts?: { expectedRevision?: string },\n ): Promise<{ entry_ref: string; revision: string }> {\n return (await this.write('bind_ref', {\n args: {\n parent_ref: parentRef,\n name,\n target_ref: targetRef,\n expected_revision: opts?.expectedRevision,\n },\n })) as { entry_ref: string; revision: string }\n }\n\n /** Removes a reference entry (`be_*`) only; the target is never touched. */\n async unlink(\n entryRef: string,\n opts?: { expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('unlink', {\n args: { entry_ref: entryRef, expected_revision: opts?.expectedRevision },\n })) as { revision: string }\n }\n\n // ---------- content writes (open_write / tus / commit_file / probe) ----------\n\n async openWrite(\n target: { parentRef: WireRef; name: string; size?: number } | { ref: WireRef },\n ): Promise<OpenWriteResult> {\n const args: Record<string, unknown> =\n 'ref' in target\n ? { ref: target.ref }\n : { parent_ref: target.parentRef, name: target.name, size: target.size }\n return (await this.write('open_write', { args })) as OpenWriteResult\n }\n\n /** Current upload offset (tus HEAD) — resume point after interruption. */\n async uploadOffset(fbHandle: string): Promise<number> {\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/uploads/${fbHandle}`, {\n method: 'HEAD',\n headers: await this.authHeaders(),\n })\n if (!resp.ok) throw await this.httpError(resp)\n return Number(resp.headers.get('Upload-Offset') ?? '0')\n }\n\n /** One tus PATCH. Returns the new offset. */\n async uploadChunk(fbHandle: string, offset: number, chunk: Uint8Array): Promise<number> {\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/uploads/${fbHandle}`, {\n method: 'PATCH',\n headers: {\n ...await this.authHeaders(),\n 'Upload-Offset': String(offset),\n 'Content-Type': 'application/offset+octet-stream',\n },\n body: chunk as unknown as BodyInit,\n })\n if (resp.status !== 204) throw await this.httpError(resp)\n return Number(resp.headers.get('Upload-Offset') ?? String(offset + chunk.byteLength))\n }\n\n /** Uploads a whole buffer in chunks, resuming from the server's offset. */\n async uploadContent(\n fbHandle: string,\n content: Uint8Array,\n onProgress?: (sent: number, total: number) => void,\n ): Promise<void> {\n let offset = await this.uploadOffset(fbHandle)\n while (offset < content.byteLength) {\n const chunk = content.subarray(offset, Math.min(offset + this.chunkSize, content.byteLength))\n offset = await this.uploadChunk(fbHandle, offset, chunk)\n onProgress?.(offset, content.byteLength)\n }\n }\n\n async abortWrite(leaseId: string): Promise<void> {\n await this.write('abort_write', { args: { lease_id: leaseId } })\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const token = await this.sessionToken?.()\n return token ? { Authorization: `Bearer ${token}` } : {}\n }\n\n async commitFile(\n parent: LocatorLike,\n name: string,\n source: { fbHandle: string; leaseId?: string } | { hash: string },\n opts?: { overwrite?: boolean; expectedRevision?: string },\n ): Promise<CommitResult> {\n const args: Record<string, unknown> = {\n name,\n overwrite: opts?.overwrite,\n expected_revision: opts?.expectedRevision,\n }\n if ('fbHandle' in source) {\n args.fb_handle = source.fbHandle\n if (source.leaseId) args.lease_id = source.leaseId\n } else {\n args.hash = source.hash\n }\n return (await this.write('commit_file', { at: toLocator(parent), args })) as CommitResult\n }\n\n async commitObject(\n parent: LocatorLike,\n name: string,\n objId: string,\n opts?: { overwrite?: boolean; expectedRevision?: string; leaseId?: string },\n ): Promise<CommitObjectResult> {\n return await this.write('commit_object', {\n at: toLocator(parent),\n args: { name, obj_id: objId, overwrite: opts?.overwrite, expected_revision: opts?.expectedRevision, lease_id: opts?.leaseId },\n }) as CommitObjectResult\n }\n\n /** Which of these digests must be uploaded (§ probe / 秒传). */\n async probe(digests: { hash: string; size?: number }[]): Promise<{ missing: { hash: string; size?: number }[] }> {\n return (await this.call('probe', { args: { digests } })) as {\n missing: { hash: string; size?: number }[]\n }\n }\n\n /**\n * Convenience: open_write + chunked tus upload + commit_file.\n * Overwrites an existing file only when `opts.overwrite` is set.\n */\n async uploadFile(\n parentRef: WireRef,\n name: string,\n content: Uint8Array,\n opts?: { overwrite?: boolean; onProgress?: (sent: number, total: number) => void },\n ): Promise<CommitResult> {\n const ow = await this.openWrite({ parentRef, name, size: content.byteLength })\n await this.uploadContent(ow.fb_handle, content, opts?.onProgress)\n return this.commitFile({ ref: parentRef }, name, {\n fbHandle: ow.fb_handle,\n leaseId: ow.lease.lease_id,\n }, { overwrite: opts?.overwrite })\n }\n\n // ---------- data plane read ----------\n\n /**\n * URL for `GET /nfs/v1/read/{node_id}` (usable in <img>, <a download>, …).\n * It carries no token: same-origin requests authenticate with the SSO cookie,\n * anything else only reads public paths.\n */\n readUrl(nodeId: string, opts?: { download?: boolean; name?: string }): string {\n const params = new URLSearchParams()\n if (opts?.download) params.set('download', '1')\n if (opts?.name) params.set('name', opts.name)\n const qs = params.size > 0 ? `?${params.toString()}` : ''\n return `${this.baseUrl}/nfs/v1/read/${encodeURIComponent(nodeId)}${qs}`\n }\n\n /** Reads file content; `range` is inclusive byte positions. */\n async readFile(\n nodeId: string,\n opts?: { range?: { start: number; end?: number }; ifNoneMatch?: string },\n ): Promise<Response> {\n const headers: Record<string, string> = await this.authHeaders()\n if (opts?.range) {\n headers.Range = `bytes=${opts.range.start}-${opts.range.end ?? ''}`\n }\n if (opts?.ifNoneMatch) headers['If-None-Match'] = opts.ifNoneMatch\n const resp = await this.fetchFn(this.readUrl(nodeId), { headers, cache: 'no-store' })\n if (!resp.ok && resp.status !== 304) throw await this.httpError(resp)\n return resp\n }\n\n // ---------- meta ----------\n\n async getMeta(target: LocatorLike, ns?: string[]): Promise<{ records: MetaRecord[] }> {\n const loc = toLocator(target)\n const args: Record<string, unknown> = { ns }\n if (loc.ref) args.ref = loc.ref\n return (await this.call('get_meta', { at: loc, args })) as { records: MetaRecord[] }\n }\n\n /** v1: only `ns: \"user\"` records are writable. */\n async setMeta(\n target: LocatorLike,\n records: { ns: string; key: string; value: unknown; visibility?: string }[],\n ): Promise<{ updated: number }> {\n const loc = toLocator(target)\n const args: Record<string, unknown> = { records }\n if (loc.ref) args.ref = loc.ref\n return (await this.write('set_meta', { at: loc, args })) as { updated: number }\n }\n\n // ---------- search ----------\n\n async search(\n q: string,\n opts?: { limit?: number; cursor?: string; scope?: LocatorLike; modes?: string[] },\n want?: WantGroup[],\n ): Promise<SearchResult> {\n return (await this.call('search', {\n want,\n args: {\n q,\n limit: opts?.limit,\n cursor: opts?.cursor,\n scope: opts?.scope !== undefined ? toLocator(opts.scope) : undefined,\n modes: opts?.modes,\n },\n })) as SearchResult\n }\n\n // ---------- views & collections ----------\n\n async openView(viewId: string, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('open_view', { want, args: { view_id: viewId } })) as NodeInfo\n }\n\n async createCollection(\n title: string,\n collectionId?: string,\n want?: WantGroup[],\n ): Promise<NodeInfo> {\n return (await this.write('create_collection', {\n want,\n args: { title, collection_id: collectionId },\n })) as NodeInfo\n }\n\n async openCollection(collectionId: string, want?: WantGroup[]): Promise<NodeInfo> {\n return (await this.call('open_collection', {\n want,\n args: { collection_id: collectionId },\n })) as NodeInfo\n }\n\n async collectionPatch(\n ref: WireRef,\n ops: CollectionPatchOp[],\n opts?: { expectedRevision?: string },\n ): Promise<{ revision: string }> {\n return (await this.write('collection_patch', {\n args: { ref, ops, expected_revision: opts?.expectedRevision },\n })) as { revision: string }\n }\n\n // ---------- grants ----------\n\n async grant(\n subtree: LocatorLike,\n opts?: { ops?: string[]; ttl?: number; audience?: string; maxUses?: number },\n ): Promise<GrantResult> {\n return (await this.write('grant', {\n args: {\n subtree: toLocator(subtree),\n ops: opts?.ops,\n ttl: opts?.ttl,\n audience: opts?.audience,\n max_uses: opts?.maxUses,\n },\n })) as GrantResult\n }\n\n async revoke(capId: string): Promise<{ revoked: string }> {\n return (await this.write('revoke', { args: { cap_id: capId } })) as { revoked: string }\n }\n\n // ---------- acl ----------\n\n async getAcl(at: LocatorLike): Promise<AclInfo> {\n return (await this.call('get_acl', { at: toLocator(at) })) as AclInfo\n }\n\n /** Replaces the node's own record; `inherit: true` with no entries removes it. */\n async setAcl(\n at: LocatorLike,\n acl: { inherit: boolean; entries: AclEntry[] },\n opts?: { expectedRevision?: string },\n ): Promise<{ path: string; revision: string }> {\n return (await this.write('set_acl', {\n at: toLocator(at),\n args: { inherit: acl.inherit, entries: acl.entries, expected_revision: opts?.expectedRevision },\n })) as { path: string; revision: string }\n }\n\n /** Every ACL record the caller may manage (all of them for the zone owner). */\n async listAcls(): Promise<{ records: AclRecordInfo[]; zone_owner: string | null; is_admin: boolean }> {\n return (await this.call('list_acls', {})) as { records: AclRecordInfo[]; zone_owner: string | null; is_admin: boolean }\n }\n\n // ---------- shares (`share.*`) ----------\n\n async shareCapabilities(): Promise<ShareCapabilities> {\n return (await this.call('share.capabilities', {})) as ShareCapabilities\n }\n\n /** Side-effect free: size, exclusions, existing links, Public state, target fit. */\n async sharePlan(\n source: LocatorLike,\n opts?: { intent?: 'create_link' | 'app_handoff'; target?: ShareTargetApp },\n ): Promise<SharePlan> {\n return (await this.call('share.plan', {\n args: { source: toLocator(source), intent: opts?.intent, target: opts?.target },\n })) as SharePlan\n }\n\n /** Snapshots the source and creates a link. Large sources return `state: 'running'`. */\n async shareCreate(\n source: LocatorLike,\n policy: SharePolicyInput,\n opts?: { title?: string; idempotencyKey?: string; planFingerprint?: string; sourceHint?: 'gui' | 'cli' | 'agent' },\n ): Promise<ShareOperation> {\n return (await this.write('share.create', {\n args: {\n source: toLocator(source),\n policy,\n title: opts?.title,\n idempotency_key: opts?.idempotencyKey ?? newIdempotencyKey(),\n plan_fingerprint: opts?.planFingerprint,\n source_hint: opts?.sourceHint,\n },\n })) as ShareOperation\n }\n\n async shareOperation(operationId: string, opts?: { cancel?: boolean }): Promise<ShareOperation> {\n return (await this.call('share.operation', {\n args: { operation_id: operationId, cancel: opts?.cancel },\n })) as ShareOperation\n }\n\n /** Polls a running operation until it settles. */\n async waitShareOperation(\n op: ShareOperation,\n opts?: { intervalMs?: number; signal?: AbortSignal; onProgress?: (op: ShareOperation) => void },\n ): Promise<ShareOperation> {\n let cur = op\n while (cur.state === 'running' || cur.state === 'preparing') {\n if (opts?.signal?.aborted) throw new DOMException('aborted', 'AbortError')\n await new Promise((r) => setTimeout(r, opts?.intervalMs ?? 1000))\n cur = await this.shareOperation(cur.operation_id)\n opts?.onProgress?.(cur)\n }\n return cur\n }\n\n async shareGet(shareId: string): Promise<ShareDetail> {\n return (await this.call('share.get', { args: { share_id: shareId } })) as ShareDetail\n }\n\n async shareList(filter?: ShareListFilter): Promise<{ items: ShareInfo[]; next_cursor?: string | null }> {\n const source = filter?.source === undefined || typeof filter.source === 'string' ? filter?.source : toLocator(filter.source)\n return (await this.call('share.list', {\n args: {\n kind: filter?.kind,\n status: filter?.status,\n source,\n obj_id: filter?.objId,\n q: filter?.q,\n include_hidden: filter?.includeHidden,\n limit: filter?.limit,\n cursor: filter?.cursor,\n },\n })) as { items: ShareInfo[]; next_cursor?: string | null }\n }\n\n /** `password: null` removes it, `expires_at: null` = no expiry. REVISION_CONFLICT carries `current`. */\n async shareUpdate(\n shareId: string,\n revision: number,\n changes: { title?: string; expires_at?: number | null; password?: string | null; allow_download?: boolean; access?: ShareAccess },\n reason?: string,\n ): Promise<{ revision: number; share: ShareInfo }> {\n return (await this.write('share.update', {\n args: { share_id: shareId, revision, changes, reason },\n })) as { revision: number; share: ShareInfo }\n }\n\n async shareStop(shareId: string, revision: number, reason?: string): Promise<{ status: ShareStatus; revision: number }> {\n return (await this.write('share.stop', {\n args: { share_id: shareId, revision, reason },\n })) as { status: ShareStatus; revision: number }\n }\n\n async shareHide(shareId: string, hidden: boolean): Promise<{ hidden: boolean; revision: number }> {\n return (await this.write('share.hide', { args: { share_id: shareId, hidden } })) as { hidden: boolean; revision: number }\n }\n\n /** Snapshot for an app target; the result's `handoff.launch` opens the app. */\n async shareAppHandoff(\n source: LocatorLike,\n targetApp: ShareTargetApp,\n opts?: { title?: string; idempotencyKey?: string },\n ): Promise<ShareOperation> {\n return (await this.write('share.app.handoff', {\n args: {\n source: toLocator(source),\n target_app: targetApp,\n title: opts?.title,\n idempotency_key: opts?.idempotencyKey ?? newIdempotencyKey(),\n },\n })) as ShareOperation\n }\n\n /** What the target app did; older facts (by `observedAt`, ms) are refused. */\n async shareAppReport(\n shareId: string,\n deliveryState: Exclude<ShareDeliveryState, 'handed_off'>,\n opts?: { businessRef?: Record<string, unknown>; error?: string; observedAt?: number },\n ): Promise<{ accepted: boolean; delivery_state: string }> {\n return (await this.write('share.app.report', {\n args: {\n share_id: shareId,\n delivery_state: deliveryState,\n business_ref: opts?.businessRef,\n error: opts?.error,\n observed_at: opts?.observedAt ?? Date.now(),\n },\n })) as { accepted: boolean; delivery_state: string }\n }\n\n async sharePublicList(opts?: { refresh?: boolean }): Promise<PublicListing> {\n return (await this.call('share.public.list', { args: { refresh: opts?.refresh } })) as PublicListing\n }\n\n async shareStats(opts?: { shareId?: string; from?: number; to?: number; granularity?: 'hour' | 'day' }): Promise<ShareStatsResult> {\n return (await this.call('share.stats', {\n args: { share_id: opts?.shareId, from: opts?.from, to: opts?.to, granularity: opts?.granularity },\n })) as ShareStatsResult\n }\n\n // ---------- watch (SSE) ----------\n\n /**\n * Opens the watch stream. `tokens` filters to specific containers using the\n * `watch_token` returned by list. The stream is lossy by contract: on any\n * `resync` event, re-list watched containers.\n */\n watch(opts?: { tokens?: string[]; signal?: AbortSignal }): AsyncIterableIterator<WatchEvent> & {\n close: () => void\n } {\n if (!this.session) throw new Error('watch requires an active session; call hello() first')\n const params = new URLSearchParams({ session: this.session })\n if (opts?.tokens?.length) params.set('tokens', opts.tokens.join(','))\n const url = `${this.baseUrl}/nfs/v1/watch?${params.toString()}`\n const controller = new AbortController()\n if (opts?.signal) {\n opts.signal.addEventListener('abort', () => controller.abort(), { once: true })\n }\n const fetchFn = this.fetchFn\n const authHeaders = () => this.authHeaders()\n\n async function* events(): AsyncIterableIterator<WatchEvent> {\n const resp = await fetchFn(url, {\n headers: { ...await authHeaders(), Accept: 'text/event-stream' },\n signal: controller.signal,\n })\n if (!resp.ok || !resp.body) {\n throw new Error(`watch failed: HTTP ${resp.status}`)\n }\n const reader = resp.body.getReader()\n const decoder = new TextDecoder()\n let buf = ''\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) return\n buf += decoder.decode(value, { stream: true })\n // SSE frames are separated by a blank line.\n for (;;) {\n const sep = buf.indexOf('\\n\\n')\n if (sep < 0) break\n const frame = buf.slice(0, sep)\n buf = buf.slice(sep + 2)\n let event = 'message'\n let id: string | undefined\n const dataLines: string[] = []\n for (const line of frame.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice(6).trim()\n else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim())\n else if (line.startsWith('id:')) id = line.slice(3).trim()\n // comment lines (`:keep-alive`) are ignored\n }\n if (dataLines.length === 0 && event === 'message') continue\n let data: Record<string, unknown> = {}\n if (dataLines.length > 0) {\n try {\n data = JSON.parse(dataLines.join('\\n')) as Record<string, unknown>\n } catch {\n data = { raw: dataLines.join('\\n') }\n }\n }\n yield { event, data, id }\n }\n }\n } finally {\n reader.cancel().catch(() => {})\n }\n }\n\n const it = events() as AsyncIterableIterator<WatchEvent> & { close: () => void }\n it.close = () => controller.abort()\n return it\n }\n\n // ---------- envelope plumbing ----------\n\n /** Read-path call: no seq. Throws NfspError on `ok:false`. */\n async call(method: string, extras: EnvelopeExtras): Promise<unknown> {\n return this.post(method, this.envelope(extras))\n }\n\n /**\n * Write-path call: assigns the next seq once and reuses it across network\n * retries so the server's replay window guarantees exactly-once.\n */\n async write(method: string, extras: EnvelopeExtras, retries = 2): Promise<unknown> {\n const seq = ++this.seq\n const body = { ...this.envelope(extras), seq }\n let lastErr: unknown\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await this.post(method, body)\n } catch (e) {\n // Only network-level failures are retried (same seq → replay-safe).\n // NfspError means the server answered: propagate immediately.\n if (e instanceof NfspError) throw e\n lastErr = e\n }\n }\n throw lastErr\n }\n\n private envelope(extras: EnvelopeExtras): Record<string, unknown> {\n const body: Record<string, unknown> = { args: pruneUndefined(extras.args ?? {}) }\n if (this.session) body.session = this.session\n if (extras.at) body.at = pruneUndefined(extras.at as unknown as Record<string, unknown>)\n if (extras.want) body.want = extras.want\n return body\n }\n\n private async post(method: string, body: Record<string, unknown>): Promise<unknown> {\n const token = await this.sessionToken?.()\n const resp = await this.fetchFn(`${this.baseUrl}/nfs/v1/${method}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },\n body: JSON.stringify(body),\n })\n let parsed: { ok?: boolean; result?: unknown; error?: NfspErrorBody }\n try {\n parsed = (await resp.json()) as typeof parsed\n } catch {\n throw new NfspError(\n { code: 'INTERNAL', message: `non-JSON response (HTTP ${resp.status})` },\n resp.status,\n )\n }\n if (parsed.ok !== true) {\n throw new NfspError(\n parsed.error ?? { code: 'INTERNAL', message: 'malformed error envelope' },\n resp.status,\n )\n }\n return parsed.result\n }\n\n private async httpError(resp: Response): Promise<NfspError> {\n try {\n const parsed = (await resp.json()) as { error?: NfspErrorBody }\n if (parsed.error) return new NfspError(parsed.error, resp.status)\n } catch {\n // fall through\n }\n return new NfspError({ code: 'INTERNAL', message: `HTTP ${resp.status}` }, resp.status)\n }\n}\n\n/** Drops undefined values so they don't serialize as JSON nulls. */\nconst pruneUndefined = (obj: Record<string, unknown>): Record<string, unknown> => {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k] = v\n }\n return out\n}\n"],"names":["NFSP_VERSION","toLocator","at","includes","uri","realm","path","ref","newIdempotencyKey","crypto","randomUUID","Date","now","toString","Math","random","slice","NfspError","Error","constructor","body","httpStatus","super","code","message","this","name","details","Object","fromEntries","entries","filter","key","pruneUndefined","obj","out","k","v","opts","session","seq","helloResult","sessionToken","baseUrl","replace","fetchFn","fetch","input","init","globalThis","chunkSize","uploadChunkSize","sessionId","features","_a","limits","hello","clientFeatures","args","versions","r","post","bye","call","resolve","want","stat","list","batch","start","ops","onError","on_error","mkdir","parent","expectedRevision","expected_revision","write","move","from","to","parent_ref","parentRef","expected_from_revision","expectedFromRevision","expected_to_revision","expectedToRevision","recursive","bindRef","targetRef","target_ref","unlink","entryRef","entry_ref","openWrite","target","size","uploadOffset","fbHandle","resp","method","headers","authHeaders","ok","httpError","Number","get","uploadChunk","offset","chunk","String","status","byteLength","uploadContent","content","onProgress","subarray","min","abortWrite","leaseId","lease_id","token","Authorization","commitFile","source","overwrite","fb_handle","hash","commitObject","objId","obj_id","probe","digests","uploadFile","ow","lease","readUrl","nodeId","params","URLSearchParams","download","set","qs","encodeURIComponent","readFile","range","Range","end","ifNoneMatch","cache","getMeta","ns","loc","setMeta","records","search","q","limit","cursor","scope","modes","openView","viewId","view_id","createCollection","title","collectionId","collection_id","openCollection","collectionPatch","grant","subtree","ttl","audience","max_uses","maxUses","revoke","capId","cap_id","getAcl","setAcl","acl","inherit","listAcls","shareCapabilities","sharePlan","intent","shareCreate","policy","idempotency_key","idempotencyKey","plan_fingerprint","planFingerprint","source_hint","sourceHint","shareOperation","operationId","operation_id","cancel","waitShareOperation","op","cur","state","signal","aborted","DOMException","Promise","setTimeout","intervalMs","_b","shareGet","shareId","share_id","shareList","kind","include_hidden","includeHidden","shareUpdate","revision","changes","reason","shareStop","shareHide","hidden","shareAppHandoff","targetApp","target_app","shareAppReport","deliveryState","delivery_state","business_ref","businessRef","error","observed_at","observedAt","sharePublicList","refresh","shareStats","granularity","watch","tokens","length","join","url","controller","AbortController","addEventListener","abort","once","it","async","Accept","reader","getReader","decoder","TextDecoder","buf","done","value","read","decode","stream","sep","indexOf","frame","id","event","dataLines","line","split","startsWith","trim","push","data","JSON","parse","raw","catch","events","close","extras","envelope","retries","lastErr","attempt","e","stringify","parsed","json","result","gen","type","node_id"],"mappings":"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"}
package/dist/nfsp.d.ts CHANGED
@@ -126,7 +126,7 @@ export interface NodeInfo {
126
126
  origin?: string;
127
127
  stale?: boolean;
128
128
  }
129
- export type EntryBinding = 'native' | 'reference' | 'member' | 'derived';
129
+ export type EntryBinding = 'native' | 'reference' | 'member' | 'derived' | 'projection';
130
130
  /** Compact target of a listing entry (not a full NodeInfo). */
131
131
  export interface EntryTarget {
132
132
  ref: WireRef;