@nestm/storage 0.1.0-alpha.17 → 0.1.0-alpha.18

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +21 -1
  3. package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts +2 -0
  4. package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts.map +1 -1
  5. package/dist/ai-sdk/ai-sdk-file-workflow-tools.js +19 -0
  6. package/dist/ai-sdk/ai-sdk-file-workflow-tools.js.map +1 -1
  7. package/dist/ai-sdk/index.d.ts +1 -1
  8. package/dist/ai-sdk/index.d.ts.map +1 -1
  9. package/dist/ai-sdk/index.js +1 -1
  10. package/dist/ai-sdk/index.js.map +1 -1
  11. package/dist/core/index.d.ts +1 -0
  12. package/dist/core/index.d.ts.map +1 -1
  13. package/dist/core/index.js +1 -0
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/storage-text-stream-edit.d.ts +8 -0
  16. package/dist/core/storage-text-stream-edit.d.ts.map +1 -0
  17. package/dist/core/storage-text-stream-edit.js +114 -0
  18. package/dist/core/storage-text-stream-edit.js.map +1 -0
  19. package/dist/files-sdk/provider/index.d.ts.map +1 -1
  20. package/dist/files-sdk/provider/index.js +14 -0
  21. package/dist/files-sdk/provider/index.js.map +1 -1
  22. package/dist/workspace/index.d.ts +1 -0
  23. package/dist/workspace/index.d.ts.map +1 -1
  24. package/dist/workspace/index.js +1 -0
  25. package/dist/workspace/index.js.map +1 -1
  26. package/dist/workspace/storage-catalog-checkout.d.ts +1 -1
  27. package/dist/workspace/storage-catalog-checkout.d.ts.map +1 -1
  28. package/dist/workspace/storage-catalog-checkout.js +49 -35
  29. package/dist/workspace/storage-catalog-checkout.js.map +1 -1
  30. package/dist/workspace/storage-draft-stream.d.ts +3 -0
  31. package/dist/workspace/storage-draft-stream.d.ts.map +1 -0
  32. package/dist/workspace/storage-draft-stream.js +57 -0
  33. package/dist/workspace/storage-draft-stream.js.map +1 -0
  34. package/dist/workspace/storage-file-workflow.d.ts.map +1 -1
  35. package/dist/workspace/storage-file-workflow.js +96 -10
  36. package/dist/workspace/storage-file-workflow.js.map +1 -1
  37. package/dist/workspace/storage-file-workflow.protection.d.ts.map +1 -1
  38. package/dist/workspace/storage-file-workflow.protection.js +10 -0
  39. package/dist/workspace/storage-file-workflow.protection.js.map +1 -1
  40. package/dist/workspace/storage-file-workflow.types.d.ts +25 -1
  41. package/dist/workspace/storage-file-workflow.types.d.ts.map +1 -1
  42. package/dist/workspace/storage-file-workflow.types.js.map +1 -1
  43. package/dist/workspace/storage-text-draft.d.ts +2 -1
  44. package/dist/workspace/storage-text-draft.d.ts.map +1 -1
  45. package/dist/workspace/storage-text-draft.js +15 -18
  46. package/dist/workspace/storage-text-draft.js.map +1 -1
  47. package/dist/workspace/storage-working-files.d.ts +34 -0
  48. package/dist/workspace/storage-working-files.d.ts.map +1 -0
  49. package/dist/workspace/storage-working-files.js +291 -0
  50. package/dist/workspace/storage-working-files.js.map +1 -0
  51. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"storage-file-workflow.types.js","sourceRoot":"","sources":["../../src/workspace/storage-file-workflow.types.ts"],"names":[],"mappings":"AAmBA,MAAM,CAAC,MAAM,oCAAoC,GAC/C,MAAM,CAAC,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO;IACtB,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,EAAE;IACf,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC9B,QAAQ,EAAE,EAAE;CACb,CAAC,CAAC","sourcesContent":["import type {\n StorageStagedBody,\n StorageStagedContent,\n} from '../core/storage-staged-content.js';\nimport type { StorageTextChange } from '../core/storage-text-edit.js';\nimport type { StorageTextWindow } from '../core/storage-streams.js';\n\nexport type StorageFileWorkflowPermission = 'read' | 'write' | 'commit';\nexport type StorageFileWorkflowMutation = 'create' | 'replace';\nexport interface StorageFileWorkflowLimits {\n maxChunkBytes: number;\n maxReadBytes: number;\n maxPageSize: number;\n maxCommitFiles: number;\n maxPathBytes: number;\n /** Buffered text staging/editing ceiling; chunked binary workflows remain streaming. */\n maxTextBytes: number;\n maxEdits: number;\n}\nexport const DEFAULT_STORAGE_FILE_WORKFLOW_LIMITS: Readonly<StorageFileWorkflowLimits> =\n Object.freeze({\n maxChunkBytes: 262_144,\n maxReadBytes: 4096,\n maxPageSize: 64,\n maxCommitFiles: 20,\n maxPathBytes: 1024,\n maxTextBytes: 16 * 1024 * 1024,\n maxEdits: 64,\n });\nexport interface StorageFileWorkflowOperation {\n readonly signal?: AbortSignal | undefined;\n}\nexport interface StorageFileDraftBegin extends StorageFileWorkflowOperation {\n readonly path: string;\n readonly idempotencyKey: string;\n readonly text: boolean;\n readonly expectedEtag?: string | undefined;\n}\nexport interface StorageFileDraftStageText extends Omit<\n StorageFileDraftBegin,\n 'text'\n> {\n readonly content: string;\n}\nexport interface StorageFileDraftReviseText extends StorageFileDraftRequest {\n readonly expectedSize: number;\n readonly idempotencyKey: string;\n readonly changes: readonly StorageTextChange[];\n}\nexport interface StorageFileDraftRequest extends StorageFileWorkflowOperation {\n readonly draftId: string;\n}\nexport interface StorageFileDraftPageRequest extends StorageFileWorkflowOperation {\n readonly offset?: number | undefined;\n}\nexport interface StorageFileDraftAppend extends StorageFileDraftRequest {\n readonly offset: number;\n readonly bytes: Uint8Array;\n}\nexport interface StorageFileDraftCommit {\n readonly draftId: string;\n readonly size: number;\n readonly sha256?: string | undefined;\n}\nexport interface StorageFileWorkflowCommit extends StorageFileWorkflowOperation {\n readonly drafts: readonly StorageFileDraftCommit[];\n}\nexport interface StorageFileDraft<Receipt> {\n readonly id: string;\n readonly path: string;\n readonly expectedEtag: string | null;\n /** Predecessor checkpoint; null for a new draft or catalog checkout. */\n readonly sourceDraftId: string | null;\n readonly text: boolean;\n readonly status: 'open' | 'sealed' | 'committed' | 'cancelled';\n readonly size: number;\n readonly result: Receipt | null;\n readonly createdAt: string;\n}\nexport interface StorageFileDraftRecord<\n Receipt,\n> extends StorageFileDraft<Receipt> {\n readonly idempotencyKey: string;\n readonly requestFingerprint: string;\n /** Whole-file verified body; persisted only with successful head commit. */\n readonly body: StorageStagedBody | null;\n}\nexport interface StorageFilePartReceipt {\n readonly offset: number;\n readonly size: number;\n readonly sha256: string;\n}\nexport interface StorageFilePartRecord extends StorageFilePartReceipt {\n readonly body: StorageStagedBody;\n}\nexport interface StorageFileWorkflowPage<Item> {\n readonly items: readonly Item[];\n readonly nextOffset: number | null;\n}\nexport interface StorageFileHeadChange<Receipt> {\n readonly draft: StorageFileDraftRecord<Receipt>;\n readonly body: StorageStagedBody;\n}\n\n/** All methods execute in the SAME host transaction. Records are detached values. */\nexport interface StorageFileWorkflowTransaction<Receipt> {\n findDraftByKey(key: string): Promise<StorageFileDraftRecord<Receipt> | null>;\n getDraft(id: string): Promise<StorageFileDraftRecord<Receipt> | null>;\n saveDraft(draft: StorageFileDraftRecord<Receipt>): Promise<void>;\n /** Stable order, at most limit entries; scoped to the authorized principal. */\n listDrafts(\n offset: number,\n limit: number,\n ): Promise<readonly StorageFileDraftRecord<Receipt>[]>;\n /** Ordered by offset; include the part containing offset, then later parts. */\n listParts(\n draftId: string,\n offset: number,\n limit: number,\n ): Promise<readonly StorageFilePartRecord[]>;\n putPart(draftId: string, part: StorageFilePartRecord): Promise<void>;\n /**\n * Compare every expected head/create predicate and update all heads atomically.\n * Return one receipt per change, in the supplied order. Must join this transaction;\n * never implement this as serial provider writes or independent transactions.\n * Paths arrive sorted to permit consistent lock ordering.\n */\n commitHeads(\n changes: readonly StorageFileHeadChange<Receipt>[],\n ): Promise<readonly Receipt[]>;\n}\nexport interface StorageFileWorkflowPersistence<Scope, Receipt> {\n /**\n * Reauthorize scope on EVERY call; lock/serialize its draft/idempotency state\n * before work begins, including across replicas. Roll back ALL callback effects\n * on rejection. Resolve only after durable commit. Do not retry the callback.\n * Check signal before committing; once committed, return its result even if\n * signal subsequently aborts. Lost responses are reconciled by replay.\n */\n transaction<Result>(\n scope: Scope,\n options: StorageFileWorkflowOperation & {\n readonly permission: StorageFileWorkflowPermission;\n },\n work: (\n transaction: StorageFileWorkflowTransaction<Receipt>,\n ) => Promise<Result>,\n ): Promise<Result>;\n}\nexport interface StorageFileWorkflowOptions<Scope, Receipt> {\n readonly persistence: StorageFileWorkflowPersistence<Scope, Receipt>;\n readonly content: StorageStagedContent<Scope>;\n}\nexport interface MountStorageFileWorkflowOptions extends StorageFileWorkflowOperation {\n readonly permissions?: Iterable<StorageFileWorkflowPermission>;\n /** Checked against each persisted draft on append/cancel/commit, including replay. */\n readonly mutations?: Iterable<StorageFileWorkflowMutation>;\n readonly limits?: Partial<StorageFileWorkflowLimits>;\n}\n/** A host-scope-bound capability. Scope, provider keys and staged bodies stay private. */\nexport interface StorageFileWorkflowCapability<Receipt = unknown> {\n readonly kind: 'storage-file-workflow';\n readonly version: 1;\n readonly limits: Readonly<StorageFileWorkflowLimits>;\n allows(permission: StorageFileWorkflowPermission): boolean;\n /** Narrow only; cannot change scope, widen grants/limits, or detach parent abort. */\n restrict(\n options: MountStorageFileWorkflowOptions,\n ): StorageFileWorkflowCapability<Receipt>;\n /** Atomically save a bounded text buffer as a sealed, resumable checkpoint. */\n stageText(\n input: StorageFileDraftStageText,\n ): Promise<StorageFileDraft<Receipt>>;\n /** Save a new sealed checkpoint; preserve the source and original head precondition. */\n reviseText(\n input: StorageFileDraftReviseText,\n ): Promise<StorageFileDraft<Receipt>>;\n begin(input: StorageFileDraftBegin): Promise<StorageFileDraft<Receipt>>;\n list(\n input?: StorageFileDraftPageRequest,\n ): Promise<StorageFileWorkflowPage<StorageFileDraft<Receipt>>>;\n read(\n input: StorageFileDraftRequest & StorageFileDraftPageRequest,\n ): Promise<StorageFileDraft<Receipt> & StorageTextWindow>;\n /** Buffer one exact text revision within maxTextBytes for host validation. */\n readText(\n input: StorageFileDraftRequest & { readonly expectedSize: number },\n ): Promise<StorageFileDraft<Receipt> & { readonly content: string }>;\n append(input: StorageFileDraftAppend): Promise<StorageFileDraft<Receipt>>;\n parts(\n input: StorageFileDraftRequest & StorageFileDraftPageRequest,\n ): Promise<StorageFileWorkflowPage<StorageFilePartReceipt>>;\n cancel(input: StorageFileDraftRequest): Promise<StorageFileDraft<Receipt>>;\n commit(input: StorageFileWorkflowCommit): Promise<readonly Receipt[]>;\n}\n"]}
1
+ {"version":3,"file":"storage-file-workflow.types.js","sourceRoot":"","sources":["../../src/workspace/storage-file-workflow.types.ts"],"names":[],"mappings":"AAoBA,MAAM,CAAC,MAAM,oCAAoC,GAC/C,MAAM,CAAC,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO;IACtB,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,EAAE;IACf,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC9B,QAAQ,EAAE,EAAE;CACb,CAAC,CAAC","sourcesContent":["import type {\n StorageStagedBody,\n StorageStagedContent,\n} from '../core/storage-staged-content.js';\nimport type { StorageTextChange } from '../core/storage-text-edit.js';\nimport type { StorageTextSearchResult } from '../core/storage-text.js';\nimport type { StorageTextWindow } from '../core/storage-streams.js';\n\nexport type StorageFileWorkflowPermission = 'read' | 'write' | 'commit';\nexport type StorageFileWorkflowMutation = 'create' | 'replace';\nexport interface StorageFileWorkflowLimits {\n maxChunkBytes: number;\n maxReadBytes: number;\n maxPageSize: number;\n maxCommitFiles: number;\n maxPathBytes: number;\n /** Buffered input and host read ceiling; streamed file edits are not limited by total file size. */\n maxTextBytes: number;\n maxEdits: number;\n}\nexport const DEFAULT_STORAGE_FILE_WORKFLOW_LIMITS: Readonly<StorageFileWorkflowLimits> =\n Object.freeze({\n maxChunkBytes: 262_144,\n maxReadBytes: 4096,\n maxPageSize: 64,\n maxCommitFiles: 20,\n maxPathBytes: 1024,\n maxTextBytes: 16 * 1024 * 1024,\n maxEdits: 64,\n });\nexport interface StorageFileWorkflowOperation {\n readonly signal?: AbortSignal | undefined;\n}\nexport interface StorageFileDraftBegin extends StorageFileWorkflowOperation {\n readonly path: string;\n readonly idempotencyKey: string;\n readonly text: boolean;\n readonly expectedEtag?: string | undefined;\n}\nexport interface StorageFileDraftStageText extends Omit<\n StorageFileDraftBegin,\n 'text'\n> {\n readonly content: string;\n}\nexport interface StorageFileDraftStageStream extends StorageFileDraftBegin {\n /** Stable trusted source identity used to detect conflicting command replay. */\n readonly contentIdentity: string;\n /** Open only after authorization and replay lookup; bytes are never exposed to the model. */\n readonly body: () => ReadableStream<Uint8Array>;\n readonly sourceDraftId?: string | undefined;\n readonly expectedSize?: number | undefined;\n}\nexport interface StorageFileDraftReviseText extends StorageFileDraftRequest {\n /** Optional host request identity, included in replay validation. */\n readonly requestIdentity?: string | undefined;\n readonly expectedSize: number;\n readonly idempotencyKey: string;\n readonly changes: readonly StorageTextChange[];\n}\nexport interface StorageFileDraftRequest extends StorageFileWorkflowOperation {\n readonly draftId: string;\n}\nexport interface StorageFileDraftPageRequest extends StorageFileWorkflowOperation {\n readonly offset?: number | undefined;\n}\nexport interface StorageFileDraftAppend extends StorageFileDraftRequest {\n readonly offset: number;\n readonly bytes: Uint8Array;\n}\nexport interface StorageFileDraftCommit {\n readonly draftId: string;\n readonly size: number;\n readonly sha256?: string | undefined;\n}\nexport interface StorageFileWorkflowCommit extends StorageFileWorkflowOperation {\n readonly drafts: readonly StorageFileDraftCommit[];\n}\nexport interface StorageFileDraft<Receipt> {\n readonly id: string;\n readonly path: string;\n readonly expectedEtag: string | null;\n /** Predecessor checkpoint; null for a new draft or catalog checkout. */\n readonly sourceDraftId: string | null;\n readonly text: boolean;\n readonly status: 'open' | 'sealed' | 'committed' | 'cancelled';\n readonly size: number;\n readonly result: Receipt | null;\n readonly createdAt: string;\n}\nexport interface StorageFileDraftRecord<\n Receipt,\n> extends StorageFileDraft<Receipt> {\n readonly idempotencyKey: string;\n readonly requestFingerprint: string;\n /** Whole-file verified body; persisted only with successful head commit. */\n readonly body: StorageStagedBody | null;\n}\nexport interface StorageFilePartReceipt {\n readonly offset: number;\n readonly size: number;\n readonly sha256: string;\n}\nexport interface StorageFilePartRecord extends StorageFilePartReceipt {\n readonly body: StorageStagedBody;\n}\nexport interface StorageFileWorkflowPage<Item> {\n readonly items: readonly Item[];\n readonly nextOffset: number | null;\n}\nexport interface StorageFileHeadChange<Receipt> {\n readonly draft: StorageFileDraftRecord<Receipt>;\n readonly body: StorageStagedBody;\n}\n\n/** All methods execute in the SAME host transaction. Records are detached values. */\nexport interface StorageFileWorkflowTransaction<Receipt> {\n findDraftByKey(key: string): Promise<StorageFileDraftRecord<Receipt> | null>;\n getDraft(id: string): Promise<StorageFileDraftRecord<Receipt> | null>;\n saveDraft(draft: StorageFileDraftRecord<Receipt>): Promise<void>;\n /** Stable order, at most limit entries; scoped to the authorized principal. */\n listDrafts(\n offset: number,\n limit: number,\n ): Promise<readonly StorageFileDraftRecord<Receipt>[]>;\n /** Ordered by offset; include the part containing offset, then later parts. */\n listParts(\n draftId: string,\n offset: number,\n limit: number,\n ): Promise<readonly StorageFilePartRecord[]>;\n putPart(draftId: string, part: StorageFilePartRecord): Promise<void>;\n /**\n * Compare every expected head/create predicate and update all heads atomically.\n * Return one receipt per change, in the supplied order. Must join this transaction;\n * never implement this as serial provider writes or independent transactions.\n * Paths arrive sorted to permit consistent lock ordering.\n */\n commitHeads(\n changes: readonly StorageFileHeadChange<Receipt>[],\n ): Promise<readonly Receipt[]>;\n}\nexport interface StorageFileWorkflowPersistence<Scope, Receipt> {\n /**\n * Reauthorize scope on EVERY call; lock/serialize its draft/idempotency state\n * before work begins, including across replicas. Roll back ALL callback effects\n * on rejection. Resolve only after durable commit. Do not retry the callback.\n * Check signal before committing; once committed, return its result even if\n * signal subsequently aborts. Lost responses are reconciled by replay.\n */\n transaction<Result>(\n scope: Scope,\n options: StorageFileWorkflowOperation & {\n readonly permission: StorageFileWorkflowPermission;\n },\n work: (\n transaction: StorageFileWorkflowTransaction<Receipt>,\n ) => Promise<Result>,\n ): Promise<Result>;\n}\nexport interface StorageFileWorkflowOptions<Scope, Receipt> {\n readonly persistence: StorageFileWorkflowPersistence<Scope, Receipt>;\n readonly content: StorageStagedContent<Scope>;\n}\nexport interface MountStorageFileWorkflowOptions extends StorageFileWorkflowOperation {\n readonly permissions?: Iterable<StorageFileWorkflowPermission>;\n /** Checked against each persisted draft on append/cancel/commit, including replay. */\n readonly mutations?: Iterable<StorageFileWorkflowMutation>;\n readonly limits?: Partial<StorageFileWorkflowLimits>;\n}\n/** A host-scope-bound capability. Scope, provider keys and staged bodies stay private. */\nexport interface StorageFileWorkflowCapability<Receipt = unknown> {\n readonly kind: 'storage-file-workflow';\n readonly version: 1;\n readonly limits: Readonly<StorageFileWorkflowLimits>;\n allows(permission: StorageFileWorkflowPermission): boolean;\n /** Narrow only; cannot change scope, widen grants/limits, or detach parent abort. */\n restrict(\n options: MountStorageFileWorkflowOptions,\n ): StorageFileWorkflowCapability<Receipt>;\n /** Atomically save a bounded text buffer as a sealed, resumable checkpoint. */\n stageText(\n input: StorageFileDraftStageText,\n ): Promise<StorageFileDraft<Receipt>>;\n /** Stage arbitrary-size text or binary source in bounded, integrity-checked chunks. */\n stageStream(\n input: StorageFileDraftStageStream,\n ): Promise<StorageFileDraft<Receipt>>;\n /** Save a new sealed checkpoint; preserve the source and original head precondition. */\n reviseText(\n input: StorageFileDraftReviseText,\n ): Promise<StorageFileDraft<Receipt>>;\n /** Recover a host command receipt without reopening its source. */\n lookup(\n input: StorageFileWorkflowOperation & { readonly idempotencyKey: string },\n ): Promise<StorageFileDraft<Receipt> | null>;\n begin(input: StorageFileDraftBegin): Promise<StorageFileDraft<Receipt>>;\n list(\n input?: StorageFileDraftPageRequest,\n ): Promise<StorageFileWorkflowPage<StorageFileDraft<Receipt>>>;\n read(\n input: StorageFileDraftRequest & StorageFileDraftPageRequest,\n ): Promise<StorageFileDraft<Receipt> & StorageTextWindow>;\n searchText(\n input: StorageFileDraftRequest &\n StorageFileDraftPageRequest & {\n readonly expectedSize: number;\n readonly query: string;\n },\n ): Promise<\n StorageTextSearchResult & { readonly path: string; readonly etag: string }\n >;\n /** Buffer one exact text revision within maxTextBytes for host validation. */\n readText(\n input: StorageFileDraftRequest & { readonly expectedSize: number },\n ): Promise<StorageFileDraft<Receipt> & { readonly content: string }>;\n append(input: StorageFileDraftAppend): Promise<StorageFileDraft<Receipt>>;\n parts(\n input: StorageFileDraftRequest & StorageFileDraftPageRequest,\n ): Promise<StorageFileWorkflowPage<StorageFilePartReceipt>>;\n cancel(input: StorageFileDraftRequest): Promise<StorageFileDraft<Receipt>>;\n commit(input: StorageFileWorkflowCommit): Promise<readonly Receipt[]>;\n}\n"]}
@@ -11,7 +11,8 @@ export declare function stageStorageTextDraft<Scope, Receipt>(options: {
11
11
  readonly requireMutation: (expectedEtag: string | null) => void;
12
12
  readonly transaction: <T>(work: (tx: StorageFileWorkflowTransaction<Receipt>) => Promise<T>) => Promise<T>;
13
13
  readonly load: () => Promise<{
14
- content: string;
14
+ content: string | ReadableStream<Uint8Array>;
15
+ text?: boolean;
15
16
  path: string;
16
17
  expectedEtag: string | null;
17
18
  source: StorageFileDraftRecord<Receipt> | null;
@@ -1 +1 @@
1
- {"version":3,"file":"storage-text-draft.d.ts","sourceRoot":"","sources":["../../src/workspace/storage-text-draft.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EACV,sBAAsB,EAEtB,yBAAyB,EACzB,8BAA8B,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,6FAA6F;AAC7F,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;IACnE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,yBAAyB,CAAC,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,EACtB,IAAI,EAAE,CAAC,EAAE,EAAE,8BAA8B,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAC9D,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;KAChD,CAAC,CAAC;CACJ,GAAG,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAuF3C"}
1
+ {"version":3,"file":"storage-text-draft.d.ts","sourceRoot":"","sources":["../../src/workspace/storage-text-draft.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EACV,sBAAsB,EAEtB,yBAAyB,EACzB,8BAA8B,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,6FAA6F;AAC7F,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;IACnE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,yBAAyB,CAAC,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,EACtB,IAAI,EAAE,CAAC,EAAE,EAAE,8BAA8B,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAC9D,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC;QAC3B,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;KAChD,CAAC,CAAC;CACJ,GAAG,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CA4F3C"}
@@ -1,4 +1,5 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
+ import { chunkStorageDraftStream } from './storage-draft-stream.js';
2
3
  import { StorageError } from '../storage.error.js';
3
4
  import { storageBytesStream } from '../core/storage-streams.js';
4
5
  /** Stage bytes outside the host transaction, then atomically publish all part references. */
@@ -18,32 +19,28 @@ export async function stageStorageTextDraft(options) {
18
19
  return prior;
19
20
  const loaded = await options.load();
20
21
  options.requireMutation(loaded.expectedEtag);
21
- if (/[\uD800-\uDFFF]/u.test(loaded.content))
22
+ if (typeof loaded.content === 'string' &&
23
+ /[\uD800-\uDFFF]/u.test(loaded.content))
22
24
  throw new StorageError('Text must be well-formed UTF-8.', {
23
25
  code: 'INVALID_ARGUMENT',
24
26
  });
25
- const bytes = new TextEncoder().encode(loaded.content);
26
- if (bytes.byteLength > limits.maxTextBytes)
27
- throw new StorageError('Text checkpoint exceeds the buffered text limit.', {
28
- code: 'LIMIT_EXCEEDED',
29
- });
30
- if (limits.maxChunkBytes < 4)
31
- throw new StorageError('Text checkpoints require a chunk limit of at least four bytes.', { code: 'LIMIT_EXCEEDED' });
27
+ const source = typeof loaded.content === 'string'
28
+ ? storageBytesStream(new TextEncoder().encode(loaded.content))
29
+ : loaded.content;
32
30
  const parts = [];
33
- for (let offset = 0; offset < bytes.length;) {
31
+ let size = 0;
32
+ for await (const chunk of chunkStorageDraftStream(source, limits.maxChunkBytes, loaded.text ?? true, signal)) {
34
33
  signal.throwIfAborted();
35
- let end = Math.min(bytes.length, offset + limits.maxChunkBytes);
36
- while (end < bytes.length && (bytes[end] & 0xc0) === 0x80)
37
- end--;
38
- const chunk = bytes.subarray(offset, end);
39
34
  const body = await options.content.write(options.scope, storageBytesStream(chunk), { maxBytes: limits.maxChunkBytes, signal });
40
35
  if (body.size !== chunk.length ||
41
36
  body.sha256 !== createHash('sha256').update(chunk).digest('hex'))
42
- throw new StorageError('Invalid text chunk receipt.', {
37
+ throw new StorageError('Invalid draft chunk receipt.', {
43
38
  code: 'PROVIDER',
44
39
  });
45
- parts.push({ offset, size: body.size, sha256: body.sha256, body });
46
- offset = end;
40
+ parts.push({ offset: size, size: body.size, sha256: body.sha256, body });
41
+ size += chunk.length;
42
+ if (!Number.isSafeInteger(size))
43
+ throw new StorageError('Draft size exceeds supported integer precision.', { code: 'LIMIT_EXCEEDED' });
47
44
  }
48
45
  return transaction(async (tx) => {
49
46
  const concurrent = await replay(tx);
@@ -62,9 +59,9 @@ export async function stageStorageTextDraft(options) {
62
59
  path: loaded.path,
63
60
  expectedEtag: loaded.expectedEtag,
64
61
  sourceDraftId: loaded.source?.id ?? null,
65
- text: true,
62
+ text: loaded.text ?? true,
66
63
  status: 'sealed',
67
- size: bytes.length,
64
+ size,
68
65
  result: null,
69
66
  createdAt: new Date().toISOString(),
70
67
  idempotencyKey: options.idempotencyKey,
@@ -1 +1 @@
1
- {"version":3,"file":"storage-text-draft.js","sourceRoot":"","sources":["../../src/workspace/storage-text-draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { StorageError } from '../storage.error.js';\nimport { storageBytesStream } from '../core/storage-streams.js';\nimport type { StorageStagedContent } from '../core/storage-staged-content.js';\nimport type {\n StorageFileDraftRecord,\n StorageFilePartRecord,\n StorageFileWorkflowLimits,\n StorageFileWorkflowTransaction,\n} from './storage-file-workflow.types.js';\n\n/** Stage bytes outside the host transaction, then atomically publish all part references. */\nexport async function stageStorageTextDraft<Scope, Receipt>(options: {\n readonly scope: Scope;\n readonly signal: AbortSignal;\n readonly limits: Readonly<StorageFileWorkflowLimits>;\n readonly content: StorageStagedContent<Scope>;\n readonly idempotencyKey: string;\n readonly fingerprint: string;\n readonly requireMutation: (expectedEtag: string | null) => void;\n readonly transaction: <T>(\n work: (tx: StorageFileWorkflowTransaction<Receipt>) => Promise<T>,\n ) => Promise<T>;\n readonly load: () => Promise<{\n content: string;\n path: string;\n expectedEtag: string | null;\n source: StorageFileDraftRecord<Receipt> | null;\n }>;\n}): Promise<StorageFileDraftRecord<Receipt>> {\n const { transaction, signal, limits } = options;\n const replay = async (tx: StorageFileWorkflowTransaction<Receipt>) => {\n const prior = await tx.findDraftByKey(options.idempotencyKey);\n if (prior !== null) {\n options.requireMutation(prior.expectedEtag);\n if (prior.requestFingerprint !== options.fingerprint)\n throw new StorageError(\n 'Draft key was already used for different input.',\n { code: 'CONFLICT' },\n );\n }\n return prior;\n };\n const prior = await transaction(replay);\n if (prior !== null) return prior;\n const loaded = await options.load();\n options.requireMutation(loaded.expectedEtag);\n if (/[\\uD800-\\uDFFF]/u.test(loaded.content))\n throw new StorageError('Text must be well-formed UTF-8.', {\n code: 'INVALID_ARGUMENT',\n });\n const bytes = new TextEncoder().encode(loaded.content);\n if (bytes.byteLength > limits.maxTextBytes)\n throw new StorageError('Text checkpoint exceeds the buffered text limit.', {\n code: 'LIMIT_EXCEEDED',\n });\n if (limits.maxChunkBytes < 4)\n throw new StorageError(\n 'Text checkpoints require a chunk limit of at least four bytes.',\n { code: 'LIMIT_EXCEEDED' },\n );\n const parts: StorageFilePartRecord[] = [];\n for (let offset = 0; offset < bytes.length;) {\n signal.throwIfAborted();\n let end = Math.min(bytes.length, offset + limits.maxChunkBytes);\n while (end < bytes.length && (bytes[end]! & 0xc0) === 0x80) end--;\n const chunk = bytes.subarray(offset, end);\n const body = await options.content.write(\n options.scope,\n storageBytesStream(chunk),\n { maxBytes: limits.maxChunkBytes, signal },\n );\n if (\n body.size !== chunk.length ||\n body.sha256 !== createHash('sha256').update(chunk).digest('hex')\n )\n throw new StorageError('Invalid text chunk receipt.', {\n code: 'PROVIDER',\n });\n parts.push({ offset, size: body.size, sha256: body.sha256, body });\n offset = end;\n }\n return transaction(async (tx) => {\n const concurrent = await replay(tx);\n if (concurrent !== null) return concurrent;\n if (loaded.source !== null) {\n const current = await tx.getDraft(loaded.source.id);\n if (\n current === null ||\n current.size !== loaded.source.size ||\n !['open', 'sealed'].includes(current.status)\n )\n throw new StorageError(\n 'The source draft changed while editing. Read its latest size.',\n { code: 'CONFLICT' },\n );\n await tx.saveDraft({ ...current, status: 'sealed' });\n }\n const draft: StorageFileDraftRecord<Receipt> = {\n id: randomUUID(),\n path: loaded.path,\n expectedEtag: loaded.expectedEtag,\n sourceDraftId: loaded.source?.id ?? null,\n text: true,\n status: 'sealed',\n size: bytes.length,\n result: null,\n createdAt: new Date().toISOString(),\n idempotencyKey: options.idempotencyKey,\n requestFingerprint: options.fingerprint,\n body: null,\n };\n await tx.saveDraft(draft);\n for (const part of parts) await tx.putPart(draft.id, part);\n return draft;\n });\n}\n"]}
1
+ {"version":3,"file":"storage-text-draft.js","sourceRoot":"","sources":["../../src/workspace/storage-text-draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAShE,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAiB,OAkB3D;IACC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAChD,MAAM,MAAM,GAAG,KAAK,EAAE,EAA2C,EAAE,EAAE;QACnE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC9D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5C,IAAI,KAAK,CAAC,kBAAkB,KAAK,OAAO,CAAC,WAAW;gBAClD,MAAM,IAAI,YAAY,CACpB,iDAAiD,EACjD,EAAE,IAAI,EAAE,UAAU,EAAE,CACrB,CAAC;QACN,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACpC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7C,IACE,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAClC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAEvC,MAAM,IAAI,YAAY,CAAC,iCAAiC,EAAE;YACxD,IAAI,EAAE,kBAAkB;SACzB,CAAC,CAAC;IACL,MAAM,MAAM,GACV,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAChC,CAAC,CAAC,kBAAkB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9D,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACrB,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,uBAAuB,CAC/C,MAAM,EACN,MAAM,CAAC,aAAa,EACpB,MAAM,CAAC,IAAI,IAAI,IAAI,EACnB,MAAM,CACP,EAAE,CAAC;QACF,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CACtC,OAAO,CAAC,KAAK,EACb,kBAAkB,CAAC,KAAK,CAAC,EACzB,EAAE,QAAQ,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAC3C,CAAC;QACF,IACE,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;YAC1B,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAEhE,MAAM,IAAI,YAAY,CAAC,8BAA8B,EAAE;gBACrD,IAAI,EAAE,UAAU;aACjB,CAAC,CAAC;QACL,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;YAC7B,MAAM,IAAI,YAAY,CACpB,iDAAiD,EACjD,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAC3B,CAAC;IACN,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QAC9B,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,UAAU,CAAC;QAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACpD,IACE,OAAO,KAAK,IAAI;gBAChB,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI;gBACnC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;gBAE5C,MAAM,IAAI,YAAY,CACpB,+DAA+D,EAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,CACrB,CAAC;YACJ,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,KAAK,GAAoC;YAC7C,EAAE,EAAE,UAAU,EAAE;YAChB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI;YACxC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;YACzB,MAAM,EAAE,QAAQ;YAChB,IAAI;YACJ,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,WAAW;YACvC,IAAI,EAAE,IAAI;SACX,CAAC;QACF,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC3D,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { chunkStorageDraftStream } from './storage-draft-stream.js';\nimport { StorageError } from '../storage.error.js';\nimport { storageBytesStream } from '../core/storage-streams.js';\nimport type { StorageStagedContent } from '../core/storage-staged-content.js';\nimport type {\n StorageFileDraftRecord,\n StorageFilePartRecord,\n StorageFileWorkflowLimits,\n StorageFileWorkflowTransaction,\n} from './storage-file-workflow.types.js';\n\n/** Stage bytes outside the host transaction, then atomically publish all part references. */\nexport async function stageStorageTextDraft<Scope, Receipt>(options: {\n readonly scope: Scope;\n readonly signal: AbortSignal;\n readonly limits: Readonly<StorageFileWorkflowLimits>;\n readonly content: StorageStagedContent<Scope>;\n readonly idempotencyKey: string;\n readonly fingerprint: string;\n readonly requireMutation: (expectedEtag: string | null) => void;\n readonly transaction: <T>(\n work: (tx: StorageFileWorkflowTransaction<Receipt>) => Promise<T>,\n ) => Promise<T>;\n readonly load: () => Promise<{\n content: string | ReadableStream<Uint8Array>;\n text?: boolean;\n path: string;\n expectedEtag: string | null;\n source: StorageFileDraftRecord<Receipt> | null;\n }>;\n}): Promise<StorageFileDraftRecord<Receipt>> {\n const { transaction, signal, limits } = options;\n const replay = async (tx: StorageFileWorkflowTransaction<Receipt>) => {\n const prior = await tx.findDraftByKey(options.idempotencyKey);\n if (prior !== null) {\n options.requireMutation(prior.expectedEtag);\n if (prior.requestFingerprint !== options.fingerprint)\n throw new StorageError(\n 'Draft key was already used for different input.',\n { code: 'CONFLICT' },\n );\n }\n return prior;\n };\n const prior = await transaction(replay);\n if (prior !== null) return prior;\n const loaded = await options.load();\n options.requireMutation(loaded.expectedEtag);\n if (\n typeof loaded.content === 'string' &&\n /[\\uD800-\\uDFFF]/u.test(loaded.content)\n )\n throw new StorageError('Text must be well-formed UTF-8.', {\n code: 'INVALID_ARGUMENT',\n });\n const source =\n typeof loaded.content === 'string'\n ? storageBytesStream(new TextEncoder().encode(loaded.content))\n : loaded.content;\n const parts: StorageFilePartRecord[] = [];\n let size = 0;\n for await (const chunk of chunkStorageDraftStream(\n source,\n limits.maxChunkBytes,\n loaded.text ?? true,\n signal,\n )) {\n signal.throwIfAborted();\n const body = await options.content.write(\n options.scope,\n storageBytesStream(chunk),\n { maxBytes: limits.maxChunkBytes, signal },\n );\n if (\n body.size !== chunk.length ||\n body.sha256 !== createHash('sha256').update(chunk).digest('hex')\n )\n throw new StorageError('Invalid draft chunk receipt.', {\n code: 'PROVIDER',\n });\n parts.push({ offset: size, size: body.size, sha256: body.sha256, body });\n size += chunk.length;\n if (!Number.isSafeInteger(size))\n throw new StorageError(\n 'Draft size exceeds supported integer precision.',\n { code: 'LIMIT_EXCEEDED' },\n );\n }\n return transaction(async (tx) => {\n const concurrent = await replay(tx);\n if (concurrent !== null) return concurrent;\n if (loaded.source !== null) {\n const current = await tx.getDraft(loaded.source.id);\n if (\n current === null ||\n current.size !== loaded.source.size ||\n !['open', 'sealed'].includes(current.status)\n )\n throw new StorageError(\n 'The source draft changed while editing. Read its latest size.',\n { code: 'CONFLICT' },\n );\n await tx.saveDraft({ ...current, status: 'sealed' });\n }\n const draft: StorageFileDraftRecord<Receipt> = {\n id: randomUUID(),\n path: loaded.path,\n expectedEtag: loaded.expectedEtag,\n sourceDraftId: loaded.source?.id ?? null,\n text: loaded.text ?? true,\n status: 'sealed',\n size,\n result: null,\n createdAt: new Date().toISOString(),\n idempotencyKey: options.idempotencyKey,\n requestFingerprint: options.fingerprint,\n body: null,\n };\n await tx.saveDraft(draft);\n for (const part of parts) await tx.putPart(draft.id, part);\n return draft;\n });\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import type { StorageFileCatalogCapability, StorageCatalogFile, StorageCatalogPath, StorageCatalogCommand } from './storage-file-catalog.types.js';
2
+ import type { StorageFileDraft, StorageFileWorkflowCapability, StorageFileWorkflowOperation } from './storage-file-workflow.types.js';
3
+ export interface StorageWorkingFile extends StorageCatalogFile {
4
+ readonly operation: 'created' | 'updated';
5
+ readonly pending: true;
6
+ }
7
+ export interface StorageWorkingFileReceipt {
8
+ readonly path: string;
9
+ readonly etag: string;
10
+ readonly size: number;
11
+ }
12
+ /** A path-based editing view over durable checkpoints. Saved heads change only on commit. */
13
+ export declare class StorageWorkingFiles<Receipt extends StorageWorkingFileReceipt> {
14
+ readonly files: StorageFileCatalogCapability<Receipt>;
15
+ readonly workflow: StorageFileWorkflowCapability<Receipt>;
16
+ readonly catalog: StorageFileCatalogCapability<Receipt | StorageWorkingFile>;
17
+ constructor(files: StorageFileCatalogCapability<Receipt>, workflow: StorageFileWorkflowCapability<Receipt>);
18
+ write(input: StorageCatalogCommand & {
19
+ readonly content: string;
20
+ }): Promise<Receipt | StorageWorkingFile>;
21
+ /** Resolve an exact candidate (including committed replay) or the unique unfinished leaf. */
22
+ draft(input: StorageCatalogPath): Promise<StorageFileDraft<Receipt> | null>;
23
+ /** Discovery derives from persisted lineage, so process restarts need no local registry. */
24
+ pending(input: StorageFileWorkflowOperation): Promise<StorageFileDraft<Receipt>[]>;
25
+ private page;
26
+ private saved;
27
+ private result;
28
+ private receipt;
29
+ private textLimit;
30
+ private requireWrite;
31
+ private denied;
32
+ private conflict;
33
+ }
34
+ //# sourceMappingURL=storage-working-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-working-files.d.ts","sourceRoot":"","sources":["../../src/workspace/storage-working-files.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,4BAA4B,EAC5B,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EAEtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,gBAAgB,EAChB,6BAA6B,EAC7B,4BAA4B,EAE7B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;CACxB;AACD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,6FAA6F;AAC7F,qBAAa,mBAAmB,CAAC,OAAO,SAAS,yBAAyB;IAGtE,QAAQ,CAAC,KAAK,EAAE,4BAA4B,CAAC,OAAO,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,6BAA6B,CAAC,OAAO,CAAC;IAH3D,QAAQ,CAAC,OAAO,EAAE,4BAA4B,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAAC;IAC7E,YACW,KAAK,EAAE,4BAA4B,CAAC,OAAO,CAAC,EAC5C,QAAQ,EAAE,6BAA6B,CAAC,OAAO,CAAC,EAsF1D;IAEK,KAAK,CACT,KAAK,EAAE,qBAAqB,GAAG;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAC1D,OAAO,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAkDvC;IAED,6FAA6F;IACvF,KAAK,CACT,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAyB3C;IAED,4FAA4F;IACtF,OAAO,CACX,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC,CAyBtC;YAEa,IAAI;IAuDlB,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,YAAY;IAGpB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,QAAQ;CAMjB"}
@@ -0,0 +1,291 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { StorageError, isStorageError } from '../storage.error.js';
3
+ import { storageBytesStream, storageInteger } from '../core/storage-streams.js';
4
+ import { checkoutStorageCatalogText } from './storage-catalog-checkout.js';
5
+ /** A path-based editing view over durable checkpoints. Saved heads change only on commit. */
6
+ export class StorageWorkingFiles {
7
+ files;
8
+ workflow;
9
+ catalog;
10
+ constructor(files, workflow) {
11
+ this.files = files;
12
+ this.workflow = workflow;
13
+ this.catalog = Object.freeze({
14
+ ...files,
15
+ list: (input) => this.page(input, 'list'),
16
+ search: (input) => this.page(input, 'search'),
17
+ stat: async (input) => {
18
+ const draft = await this.draft(input);
19
+ return draft && draft.status !== 'committed'
20
+ ? this.receipt(draft)
21
+ : files.stat(this.saved(input, draft));
22
+ },
23
+ readWindow: async (input) => {
24
+ const draft = await this.draft(input);
25
+ if (!draft || draft.status === 'committed')
26
+ return files.readWindow(this.saved(input, draft));
27
+ const page = await workflow.read({
28
+ draftId: draft.id,
29
+ offset: input.offset,
30
+ signal: input.signal,
31
+ });
32
+ if (page.size !== draft.size)
33
+ this.conflict();
34
+ return {
35
+ ...this.receipt(draft),
36
+ content: page.content,
37
+ offset: page.offset,
38
+ nextOffset: page.nextOffset,
39
+ totalBytes: page.size,
40
+ };
41
+ },
42
+ searchContent: async (input) => {
43
+ const draft = await this.draft(input);
44
+ if (!draft || draft.status === 'committed')
45
+ return files.searchContent(this.saved(input, draft));
46
+ return workflow.searchText({
47
+ draftId: draft.id,
48
+ expectedSize: draft.size,
49
+ query: input.query,
50
+ offset: input.offset,
51
+ signal: input.signal,
52
+ });
53
+ },
54
+ write: (input) => this.write(input),
55
+ edit: async (input) => {
56
+ this.requireWrite();
57
+ const changes = input.change.kind === 'batch' ? input.change.changes : [input.change];
58
+ this.textLimit(changes
59
+ .map((change) => change.kind === 'append'
60
+ ? change.text
61
+ : change.oldText + change.newText)
62
+ .join(''));
63
+ const prior = await workflow.lookup({
64
+ idempotencyKey: input.commandId,
65
+ signal: input.signal,
66
+ });
67
+ let source = prior?.sourceDraftId
68
+ ? await workflow.read({
69
+ draftId: prior.sourceDraftId,
70
+ signal: input.signal,
71
+ })
72
+ : await this.draft(input);
73
+ if (!source || (!prior && source.status === 'committed'))
74
+ source = await checkoutStorageCatalogText(files, workflow, {
75
+ ...this.saved(input, source),
76
+ commandId: `checkout:${input.commandId}`,
77
+ });
78
+ const result = await workflow.reviseText({
79
+ draftId: source.id,
80
+ expectedSize: source.size,
81
+ changes,
82
+ requestIdentity: JSON.stringify([input.path, input.expectedEtag]),
83
+ idempotencyKey: input.commandId,
84
+ signal: input.signal,
85
+ });
86
+ // The workflow fingerprint includes the source, but the host also fences its public path.
87
+ if (result.path !== input.path)
88
+ this.conflict();
89
+ return this.result(result);
90
+ },
91
+ });
92
+ }
93
+ async write(input) {
94
+ this.requireWrite();
95
+ this.textLimit(input.content);
96
+ const prior = await this.workflow.lookup({
97
+ idempotencyKey: input.commandId,
98
+ signal: input.signal,
99
+ });
100
+ let source = prior?.sourceDraftId
101
+ ? await this.workflow.read({
102
+ draftId: prior.sourceDraftId,
103
+ signal: input.signal,
104
+ })
105
+ : null;
106
+ let originalEtag = prior?.expectedEtag ?? undefined;
107
+ if (!prior) {
108
+ source = await this.draft(input);
109
+ const saved = this.saved(input, source);
110
+ if (input.expectedEtag === undefined) {
111
+ if (source)
112
+ this.conflict();
113
+ try {
114
+ await this.files.stat(input);
115
+ this.conflict();
116
+ }
117
+ catch (error) {
118
+ if (!isStorageError(error) || error.code !== 'NOT_FOUND')
119
+ throw error;
120
+ }
121
+ }
122
+ else if (!source || source.status === 'committed') {
123
+ const file = await this.files.stat(saved);
124
+ if (file.etag !== saved.expectedEtag)
125
+ this.conflict();
126
+ }
127
+ if (source?.status === 'committed')
128
+ source = null;
129
+ originalEtag = source
130
+ ? (source.expectedEtag ?? undefined)
131
+ : saved.expectedEtag;
132
+ }
133
+ const bytes = new TextEncoder().encode(input.content);
134
+ const result = await this.workflow.stageStream({
135
+ path: input.path,
136
+ text: true,
137
+ idempotencyKey: input.commandId,
138
+ expectedEtag: originalEtag,
139
+ contentIdentity: createHash('sha256')
140
+ .update(JSON.stringify([input.expectedEtag ?? null, input.content]))
141
+ .digest('hex'),
142
+ body: () => storageBytesStream(bytes),
143
+ signal: input.signal,
144
+ ...(source
145
+ ? { sourceDraftId: source.id, expectedSize: source.size }
146
+ : {}),
147
+ });
148
+ return this.result(result);
149
+ }
150
+ /** Resolve an exact candidate (including committed replay) or the unique unfinished leaf. */
151
+ async draft(input) {
152
+ if (!this.files.allows('read'))
153
+ this.denied();
154
+ if (input.expectedEtag !== undefined) {
155
+ try {
156
+ const draft = await this.workflow.read({
157
+ draftId: input.expectedEtag,
158
+ signal: input.signal,
159
+ });
160
+ if (draft.path !== input.path || draft.status === 'cancelled')
161
+ this.conflict();
162
+ return draft;
163
+ }
164
+ catch (error) {
165
+ if (!isStorageError(error) || error.code !== 'NOT_FOUND')
166
+ throw error;
167
+ return null;
168
+ }
169
+ }
170
+ const matches = (await this.pending(input)).filter((draft) => draft.path === input.path);
171
+ if (matches.length > 1)
172
+ throw new StorageError('Several unfinished revisions exist. Read an exact listed ETag before editing.', { code: 'CONFLICT' });
173
+ return matches[0] ?? null;
174
+ }
175
+ /** Discovery derives from persisted lineage, so process restarts need no local registry. */
176
+ async pending(input) {
177
+ if (!this.files.allows('read'))
178
+ this.denied();
179
+ const drafts = [];
180
+ let offset = 0;
181
+ do {
182
+ const page = await this.workflow.list({ offset, signal: input.signal });
183
+ drafts.push(...page.items);
184
+ if (page.nextOffset !== null && page.nextOffset <= offset)
185
+ throw new StorageError('Invalid draft pagination.', {
186
+ code: 'PROVIDER',
187
+ });
188
+ offset = page.nextOffset;
189
+ } while (offset !== null);
190
+ const predecessors = new Set(drafts.flatMap((draft) => draft.status !== 'cancelled' && draft.sourceDraftId
191
+ ? [draft.sourceDraftId]
192
+ : []));
193
+ return drafts.filter((draft) => ['open', 'sealed'].includes(draft.status) &&
194
+ !predecessors.has(draft.id));
195
+ }
196
+ async page(input, mode) {
197
+ const offset = input.offset ?? 0;
198
+ storageInteger(offset, 'offset', 0);
199
+ const pending = await this.pending(input);
200
+ const paths = new Set(pending.map((draft) => draft.path));
201
+ const items = [];
202
+ const savedByPath = new Map();
203
+ let cursor = 0;
204
+ do {
205
+ const page = mode === 'list'
206
+ ? await this.files.list({ ...input, offset: cursor })
207
+ : await this.files.search({
208
+ ...input,
209
+ query: input.query,
210
+ offset: cursor,
211
+ });
212
+ for (const file of page.items)
213
+ savedByPath.set(file.path, file);
214
+ items.push(...page.items.filter((file) => !paths.has(file.path)));
215
+ if (page.nextOffset !== null && page.nextOffset <= cursor)
216
+ throw new StorageError('Invalid catalog pagination.', {
217
+ code: 'PROVIDER',
218
+ });
219
+ cursor = page.nextOffset;
220
+ } while (cursor !== null);
221
+ const directory = input.directory?.replace(/\/$/u, '');
222
+ items.push(...pending
223
+ .filter((draft) => mode === 'search'
224
+ ? draft.path.toLowerCase().includes(input.query.toLowerCase())
225
+ : !directory ||
226
+ directory === '.' ||
227
+ draft.path.startsWith(`${directory}/`))
228
+ .map((draft) => ({
229
+ ...savedByPath.get(draft.path),
230
+ ...this.receipt(draft),
231
+ })));
232
+ items.sort((a, b) => a.path.localeCompare(b.path) || a.etag.localeCompare(b.etag));
233
+ const next = offset + this.files.limits.maxPageSize;
234
+ return {
235
+ items: items.slice(offset, next),
236
+ nextOffset: next < items.length ? next : null,
237
+ };
238
+ }
239
+ saved(input, draft) {
240
+ if (draft?.status !== 'committed')
241
+ return input;
242
+ if (!draft.result || draft.result.path !== input.path)
243
+ this.conflict();
244
+ return { ...input, expectedEtag: draft.result.etag };
245
+ }
246
+ result(draft) {
247
+ if (draft.status === 'cancelled')
248
+ this.conflict();
249
+ if (draft.status === 'committed') {
250
+ if (!draft.result)
251
+ this.conflict();
252
+ return draft.result;
253
+ }
254
+ return this.receipt(draft);
255
+ }
256
+ receipt(draft) {
257
+ return {
258
+ path: draft.path,
259
+ fileId: draft.id,
260
+ etag: draft.id,
261
+ size: draft.size,
262
+ contentType: draft.text ? 'text/plain' : 'application/octet-stream',
263
+ operation: draft.expectedEtag === null ? 'created' : 'updated',
264
+ pending: true,
265
+ };
266
+ }
267
+ textLimit(text) {
268
+ if (/[\uD800-\uDFFF]/u.test(text))
269
+ throw new StorageError('Text must be well-formed UTF-8.', {
270
+ code: 'INVALID_ARGUMENT',
271
+ });
272
+ if (new TextEncoder().encode(text).byteLength >
273
+ this.files.limits.maxWriteBytes)
274
+ throw new StorageError('Write exceeds the per-call text limit.', {
275
+ code: 'LIMIT_EXCEEDED',
276
+ });
277
+ }
278
+ requireWrite() {
279
+ if (!this.files.allows('write'))
280
+ this.denied();
281
+ }
282
+ denied() {
283
+ throw new StorageError('Working file operation is not permitted.', {
284
+ code: 'UNAUTHORIZED',
285
+ });
286
+ }
287
+ conflict() {
288
+ throw new StorageError('The working file revision changed. Read the current file before editing.', { code: 'CONFLICT' });
289
+ }
290
+ }
291
+ //# sourceMappingURL=storage-working-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-working-files.js","sourceRoot":"","sources":["../../src/workspace/storage-working-files.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { StorageError, isStorageError } from '../storage.error.js';\nimport { storageBytesStream, storageInteger } from '../core/storage-streams.js';\nimport { checkoutStorageCatalogText } from './storage-catalog-checkout.js';\nimport type {\n StorageFileCatalogCapability,\n StorageCatalogFile,\n StorageCatalogPath,\n StorageCatalogCommand,\n StorageCatalogPage,\n} from './storage-file-catalog.types.js';\nimport type {\n StorageFileDraft,\n StorageFileWorkflowCapability,\n StorageFileWorkflowOperation,\n StorageFileWorkflowPage,\n} from './storage-file-workflow.types.js';\n\nexport interface StorageWorkingFile extends StorageCatalogFile {\n readonly operation: 'created' | 'updated';\n readonly pending: true;\n}\nexport interface StorageWorkingFileReceipt {\n readonly path: string;\n readonly etag: string;\n readonly size: number;\n}\n\n/** A path-based editing view over durable checkpoints. Saved heads change only on commit. */\nexport class StorageWorkingFiles<Receipt extends StorageWorkingFileReceipt> {\n readonly catalog: StorageFileCatalogCapability<Receipt | StorageWorkingFile>;\n constructor(\n readonly files: StorageFileCatalogCapability<Receipt>,\n readonly workflow: StorageFileWorkflowCapability<Receipt>,\n ) {\n this.catalog = Object.freeze<\n StorageFileCatalogCapability<Receipt | StorageWorkingFile>\n >({\n ...files,\n list: (input) => this.page(input, 'list'),\n search: (input) => this.page(input, 'search'),\n stat: async (input) => {\n const draft = await this.draft(input);\n return draft && draft.status !== 'committed'\n ? this.receipt(draft)\n : files.stat(this.saved(input, draft));\n },\n readWindow: async (input) => {\n const draft = await this.draft(input);\n if (!draft || draft.status === 'committed')\n return files.readWindow(this.saved(input, draft));\n const page = await workflow.read({\n draftId: draft.id,\n offset: input.offset,\n signal: input.signal,\n });\n if (page.size !== draft.size) this.conflict();\n return {\n ...this.receipt(draft),\n content: page.content,\n offset: page.offset,\n nextOffset: page.nextOffset,\n totalBytes: page.size,\n };\n },\n searchContent: async (input) => {\n const draft = await this.draft(input);\n if (!draft || draft.status === 'committed')\n return files.searchContent(this.saved(input, draft));\n return workflow.searchText({\n draftId: draft.id,\n expectedSize: draft.size,\n query: input.query,\n offset: input.offset,\n signal: input.signal,\n });\n },\n write: (input) => this.write(input),\n edit: async (input) => {\n this.requireWrite();\n const changes =\n input.change.kind === 'batch' ? input.change.changes : [input.change];\n this.textLimit(\n changes\n .map((change) =>\n change.kind === 'append'\n ? change.text\n : change.oldText + change.newText,\n )\n .join(''),\n );\n const prior = await workflow.lookup({\n idempotencyKey: input.commandId,\n signal: input.signal,\n });\n let source: StorageFileDraft<Receipt> | null = prior?.sourceDraftId\n ? await workflow.read({\n draftId: prior.sourceDraftId,\n signal: input.signal,\n })\n : await this.draft(input);\n if (!source || (!prior && source.status === 'committed'))\n source = await checkoutStorageCatalogText(files, workflow, {\n ...this.saved(input, source),\n commandId: `checkout:${input.commandId}`,\n });\n const result = await workflow.reviseText({\n draftId: source.id,\n expectedSize: source.size,\n changes,\n requestIdentity: JSON.stringify([input.path, input.expectedEtag]),\n idempotencyKey: input.commandId,\n signal: input.signal,\n });\n // The workflow fingerprint includes the source, but the host also fences its public path.\n if (result.path !== input.path) this.conflict();\n return this.result(result);\n },\n });\n }\n\n async write(\n input: StorageCatalogCommand & { readonly content: string },\n ): Promise<Receipt | StorageWorkingFile> {\n this.requireWrite();\n this.textLimit(input.content);\n const prior = await this.workflow.lookup({\n idempotencyKey: input.commandId,\n signal: input.signal,\n });\n let source: StorageFileDraft<Receipt> | null = prior?.sourceDraftId\n ? await this.workflow.read({\n draftId: prior.sourceDraftId,\n signal: input.signal,\n })\n : null;\n let originalEtag = prior?.expectedEtag ?? undefined;\n if (!prior) {\n source = await this.draft(input);\n const saved = this.saved(input, source);\n if (input.expectedEtag === undefined) {\n if (source) this.conflict();\n try {\n await this.files.stat(input);\n this.conflict();\n } catch (error) {\n if (!isStorageError(error) || error.code !== 'NOT_FOUND') throw error;\n }\n } else if (!source || source.status === 'committed') {\n const file = await this.files.stat(saved);\n if (file.etag !== saved.expectedEtag) this.conflict();\n }\n if (source?.status === 'committed') source = null;\n originalEtag = source\n ? (source.expectedEtag ?? undefined)\n : saved.expectedEtag;\n }\n const bytes = new TextEncoder().encode(input.content);\n const result = await this.workflow.stageStream({\n path: input.path,\n text: true,\n idempotencyKey: input.commandId,\n expectedEtag: originalEtag,\n contentIdentity: createHash('sha256')\n .update(JSON.stringify([input.expectedEtag ?? null, input.content]))\n .digest('hex'),\n body: () => storageBytesStream(bytes),\n signal: input.signal,\n ...(source\n ? { sourceDraftId: source.id, expectedSize: source.size }\n : {}),\n });\n return this.result(result);\n }\n\n /** Resolve an exact candidate (including committed replay) or the unique unfinished leaf. */\n async draft(\n input: StorageCatalogPath,\n ): Promise<StorageFileDraft<Receipt> | null> {\n if (!this.files.allows('read')) this.denied();\n if (input.expectedEtag !== undefined) {\n try {\n const draft = await this.workflow.read({\n draftId: input.expectedEtag,\n signal: input.signal,\n });\n if (draft.path !== input.path || draft.status === 'cancelled')\n this.conflict();\n return draft;\n } catch (error) {\n if (!isStorageError(error) || error.code !== 'NOT_FOUND') throw error;\n return null;\n }\n }\n const matches = (await this.pending(input)).filter(\n (draft) => draft.path === input.path,\n );\n if (matches.length > 1)\n throw new StorageError(\n 'Several unfinished revisions exist. Read an exact listed ETag before editing.',\n { code: 'CONFLICT' },\n );\n return matches[0] ?? null;\n }\n\n /** Discovery derives from persisted lineage, so process restarts need no local registry. */\n async pending(\n input: StorageFileWorkflowOperation,\n ): Promise<StorageFileDraft<Receipt>[]> {\n if (!this.files.allows('read')) this.denied();\n const drafts: StorageFileDraft<Receipt>[] = [];\n let offset: number | null = 0;\n do {\n const page = await this.workflow.list({ offset, signal: input.signal });\n drafts.push(...page.items);\n if (page.nextOffset !== null && page.nextOffset <= offset)\n throw new StorageError('Invalid draft pagination.', {\n code: 'PROVIDER',\n });\n offset = page.nextOffset;\n } while (offset !== null);\n const predecessors = new Set(\n drafts.flatMap((draft) =>\n draft.status !== 'cancelled' && draft.sourceDraftId\n ? [draft.sourceDraftId]\n : [],\n ),\n );\n return drafts.filter(\n (draft) =>\n ['open', 'sealed'].includes(draft.status) &&\n !predecessors.has(draft.id),\n );\n }\n\n private async page(\n input: StorageCatalogPage & {\n directory?: string | undefined;\n query?: string;\n },\n mode: 'list' | 'search',\n ) {\n const offset = input.offset ?? 0;\n storageInteger(offset, 'offset', 0);\n const pending = await this.pending(input);\n const paths = new Set(pending.map((draft) => draft.path));\n const items: StorageCatalogFile[] = [];\n const savedByPath = new Map<string, StorageCatalogFile>();\n let cursor: number | null = 0;\n do {\n const page: StorageFileWorkflowPage<StorageCatalogFile> =\n mode === 'list'\n ? await this.files.list({ ...input, offset: cursor })\n : await this.files.search({\n ...input,\n query: input.query!,\n offset: cursor,\n });\n for (const file of page.items) savedByPath.set(file.path, file);\n items.push(...page.items.filter((file) => !paths.has(file.path)));\n if (page.nextOffset !== null && page.nextOffset <= cursor)\n throw new StorageError('Invalid catalog pagination.', {\n code: 'PROVIDER',\n });\n cursor = page.nextOffset;\n } while (cursor !== null);\n const directory = input.directory?.replace(/\\/$/u, '');\n items.push(\n ...pending\n .filter((draft) =>\n mode === 'search'\n ? draft.path.toLowerCase().includes(input.query!.toLowerCase())\n : !directory ||\n directory === '.' ||\n draft.path.startsWith(`${directory}/`),\n )\n .map((draft) => ({\n ...savedByPath.get(draft.path),\n ...this.receipt(draft),\n })),\n );\n items.sort(\n (a, b) => a.path.localeCompare(b.path) || a.etag.localeCompare(b.etag),\n );\n const next = offset + this.files.limits.maxPageSize;\n return {\n items: items.slice(offset, next),\n nextOffset: next < items.length ? next : null,\n };\n }\n private saved<T extends StorageCatalogPath>(\n input: T,\n draft: StorageFileDraft<Receipt> | null,\n ): T {\n if (draft?.status !== 'committed') return input;\n if (!draft.result || draft.result.path !== input.path) this.conflict();\n return { ...input, expectedEtag: draft.result.etag };\n }\n private result(\n draft: StorageFileDraft<Receipt>,\n ): Receipt | StorageWorkingFile {\n if (draft.status === 'cancelled') this.conflict();\n if (draft.status === 'committed') {\n if (!draft.result) this.conflict();\n return draft.result;\n }\n return this.receipt(draft);\n }\n private receipt(draft: StorageFileDraft<Receipt>): StorageWorkingFile {\n return {\n path: draft.path,\n fileId: draft.id,\n etag: draft.id,\n size: draft.size,\n contentType: draft.text ? 'text/plain' : 'application/octet-stream',\n operation: draft.expectedEtag === null ? 'created' : 'updated',\n pending: true,\n };\n }\n private textLimit(text: string) {\n if (/[\\uD800-\\uDFFF]/u.test(text))\n throw new StorageError('Text must be well-formed UTF-8.', {\n code: 'INVALID_ARGUMENT',\n });\n if (\n new TextEncoder().encode(text).byteLength >\n this.files.limits.maxWriteBytes\n )\n throw new StorageError('Write exceeds the per-call text limit.', {\n code: 'LIMIT_EXCEEDED',\n });\n }\n private requireWrite() {\n if (!this.files.allows('write')) this.denied();\n }\n private denied(): never {\n throw new StorageError('Working file operation is not permitted.', {\n code: 'UNAUTHORIZED',\n });\n }\n private conflict(): never {\n throw new StorageError(\n 'The working file revision changed. Read the current file before editing.',\n { code: 'CONFLICT' },\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestm/storage",
3
- "version": "0.1.0-alpha.17",
3
+ "version": "0.1.0-alpha.18",
4
4
  "description": "Framework-neutral storage clients, capability-scoped agent workspaces, NestJS 12 integration, and guarded HTTP access.",
5
5
  "license": "MIT",
6
6
  "author": "Kauan Guesser",
@@ -99,7 +99,7 @@
99
99
  "nestm"
100
100
  ],
101
101
  "dependencies": {
102
- "files-sdk": "2.3.0"
102
+ "files-sdk": "2.4.1"
103
103
  },
104
104
  "peerDependencies": {
105
105
  "@aws-sdk/client-s3": "^3.1079.0",