@nestm/storage 0.1.0-alpha.10 → 0.1.0-alpha.11

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 (62) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +153 -0
  3. package/SECURITY.md +27 -0
  4. package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts +37 -0
  5. package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts.map +1 -0
  6. package/dist/ai-sdk/ai-sdk-file-workflow-tools.js +258 -0
  7. package/dist/ai-sdk/ai-sdk-file-workflow-tools.js.map +1 -0
  8. package/dist/ai-sdk/index.d.ts +1 -0
  9. package/dist/ai-sdk/index.d.ts.map +1 -1
  10. package/dist/ai-sdk/index.js +1 -0
  11. package/dist/ai-sdk/index.js.map +1 -1
  12. package/dist/bytes/index.d.ts +30 -0
  13. package/dist/bytes/index.d.ts.map +1 -0
  14. package/dist/bytes/index.js +96 -0
  15. package/dist/bytes/index.js.map +1 -0
  16. package/dist/core/index.d.ts +3 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +3 -0
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/storage-staged-content.d.ts +37 -0
  21. package/dist/core/storage-staged-content.d.ts.map +1 -0
  22. package/dist/core/storage-staged-content.js +94 -0
  23. package/dist/core/storage-staged-content.js.map +1 -0
  24. package/dist/core/storage-streams.d.ts +19 -0
  25. package/dist/core/storage-streams.d.ts.map +1 -0
  26. package/dist/core/storage-streams.js +95 -0
  27. package/dist/core/storage-streams.js.map +1 -0
  28. package/dist/core/storage-text.d.ts +33 -0
  29. package/dist/core/storage-text.d.ts.map +1 -0
  30. package/dist/core/storage-text.js +98 -0
  31. package/dist/core/storage-text.js.map +1 -0
  32. package/dist/files-sdk/memory.d.ts +7 -0
  33. package/dist/files-sdk/memory.d.ts.map +1 -0
  34. package/dist/files-sdk/memory.js +72 -0
  35. package/dist/files-sdk/memory.js.map +1 -0
  36. package/dist/files-sdk/provider/index.d.ts.map +1 -1
  37. package/dist/files-sdk/provider/index.js +4 -0
  38. package/dist/files-sdk/provider/index.js.map +1 -1
  39. package/dist/testing/index.d.ts.map +1 -1
  40. package/dist/testing/index.js +2 -1
  41. package/dist/testing/index.js.map +1 -1
  42. package/dist/workspace/index.d.ts +5 -0
  43. package/dist/workspace/index.d.ts.map +1 -1
  44. package/dist/workspace/index.js +3 -0
  45. package/dist/workspace/index.js.map +1 -1
  46. package/dist/workspace/storage-file-catalog.types.d.ts +65 -0
  47. package/dist/workspace/storage-file-catalog.types.d.ts.map +1 -0
  48. package/dist/workspace/storage-file-catalog.types.js +2 -0
  49. package/dist/workspace/storage-file-catalog.types.js.map +1 -0
  50. package/dist/workspace/storage-file-workflow.d.ts +7 -0
  51. package/dist/workspace/storage-file-workflow.d.ts.map +1 -0
  52. package/dist/workspace/storage-file-workflow.js +440 -0
  53. package/dist/workspace/storage-file-workflow.js.map +1 -0
  54. package/dist/workspace/storage-file-workflow.protection.d.ts +31 -0
  55. package/dist/workspace/storage-file-workflow.protection.d.ts.map +1 -0
  56. package/dist/workspace/storage-file-workflow.protection.js +258 -0
  57. package/dist/workspace/storage-file-workflow.protection.js.map +1 -0
  58. package/dist/workspace/storage-file-workflow.types.d.ts +128 -0
  59. package/dist/workspace/storage-file-workflow.types.d.ts.map +1 -0
  60. package/dist/workspace/storage-file-workflow.types.js +8 -0
  61. package/dist/workspace/storage-file-workflow.types.js.map +1 -0
  62. package/package.json +5 -1
@@ -0,0 +1,96 @@
1
+ /** Browser-safe bounded byte helpers. No Node, framework, provider or AI imports. */
2
+ export async function sha256StorageBytes(bytes, options) {
3
+ budget(options.maxBytes);
4
+ options.signal?.throwIfAborted();
5
+ if (bytes.byteLength > options.maxBytes)
6
+ throw new RangeError('Chunk exceeds the byte budget.');
7
+ const hash = await crypto.subtle.digest('SHA-256', bytes.slice());
8
+ options.signal?.throwIfAborted();
9
+ return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, '0')).join('');
10
+ }
11
+ /** Defers only a valid incomplete suffix (at most 3 bytes), never malformed data. */
12
+ export function trimStorageUtf8Chunk(bytes, options) {
13
+ let end = bytes.byteLength;
14
+ if (!options.final && end > 0) {
15
+ let start = end - 1;
16
+ while (start >= 0 && (bytes[start] & 0xc0) === 0x80)
17
+ start--;
18
+ if (start >= 0) {
19
+ const lead = bytes[start];
20
+ const width = lead >= 0xc2 && lead <= 0xdf
21
+ ? 2
22
+ : lead >= 0xe0 && lead <= 0xef
23
+ ? 3
24
+ : lead >= 0xf0 && lead <= 0xf4
25
+ ? 4
26
+ : 1;
27
+ if (end - start < width) {
28
+ new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes, { stream: true });
29
+ end = start;
30
+ }
31
+ }
32
+ }
33
+ const result = bytes.subarray(0, end);
34
+ new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(result);
35
+ return result;
36
+ }
37
+ export function encodeStorageBase64(bytes, options) {
38
+ budget(options.maxBytes);
39
+ if (bytes.byteLength > options.maxBytes)
40
+ throw new RangeError('Chunk exceeds the byte budget.');
41
+ let text = '';
42
+ for (let offset = 0; offset < bytes.byteLength; offset += 8192)
43
+ text += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
44
+ return btoa(text);
45
+ }
46
+ /** Verify receipt bounds, ordering and actual local bytes before skipping a chunk. */
47
+ export async function verifyStorageChunkReceipt(blob, receipt, options) {
48
+ budget(options.maxBytes);
49
+ options.signal?.throwIfAborted();
50
+ if (!Number.isSafeInteger(options.offset) ||
51
+ options.offset < 0 ||
52
+ receipt.offset !== options.offset ||
53
+ !Number.isSafeInteger(receipt.size) ||
54
+ receipt.size < 1 ||
55
+ receipt.size > options.maxBytes ||
56
+ !Number.isSafeInteger(receipt.offset + receipt.size) ||
57
+ receipt.offset + receipt.size > blob.size ||
58
+ !/^[0-9a-f]{64}$/u.test(receipt.sha256))
59
+ throw new TypeError('Invalid or out-of-order chunk receipt.');
60
+ const bytes = new Uint8Array(await blob
61
+ .slice(receipt.offset, receipt.offset + receipt.size)
62
+ .arrayBuffer());
63
+ if ((await sha256StorageBytes(bytes, options)) !== receipt.sha256)
64
+ throw new Error('The selected bytes differ from the accepted chunk.');
65
+ return receipt.offset + receipt.size;
66
+ }
67
+ /** Bounded UTF-8 validation; filename/MIME classification remains host policy. */
68
+ export async function isStorageUtf8Blob(blob, options) {
69
+ budget(options.chunkBytes);
70
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
71
+ try {
72
+ options.signal?.throwIfAborted();
73
+ for (let offset = 0; offset < blob.size; offset += options.chunkBytes) {
74
+ options.signal?.throwIfAborted();
75
+ const bytes = await blob
76
+ .slice(offset, offset + options.chunkBytes)
77
+ .arrayBuffer();
78
+ options.signal?.throwIfAborted();
79
+ const text = decoder.decode(bytes, { stream: true });
80
+ if (options.rejectNul && text.includes('\0'))
81
+ return false;
82
+ }
83
+ decoder.decode();
84
+ return true;
85
+ }
86
+ catch (error) {
87
+ if (options.signal?.aborted)
88
+ throw error;
89
+ return false;
90
+ }
91
+ }
92
+ function budget(value) {
93
+ if (!Number.isSafeInteger(value) || value < 1)
94
+ throw new RangeError('Byte budget must be a positive safe integer.');
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bytes/index.ts"],"names":[],"mappings":"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","sourcesContent":["/** Browser-safe bounded byte helpers. No Node, framework, provider or AI imports. */\nexport async function sha256StorageBytes(\n bytes: Uint8Array,\n options: {\n readonly maxBytes: number;\n readonly signal?: AbortSignal | undefined;\n },\n): Promise<string> {\n budget(options.maxBytes);\n options.signal?.throwIfAborted();\n if (bytes.byteLength > options.maxBytes)\n throw new RangeError('Chunk exceeds the byte budget.');\n const hash = await crypto.subtle.digest('SHA-256', bytes.slice());\n options.signal?.throwIfAborted();\n return Array.from(new Uint8Array(hash), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\n/** Defers only a valid incomplete suffix (at most 3 bytes), never malformed data. */\nexport function trimStorageUtf8Chunk(\n bytes: Uint8Array,\n options: { readonly final: boolean },\n): Uint8Array {\n let end = bytes.byteLength;\n if (!options.final && end > 0) {\n let start = end - 1;\n while (start >= 0 && (bytes[start]! & 0xc0) === 0x80) start--;\n if (start >= 0) {\n const lead = bytes[start]!;\n const width =\n lead >= 0xc2 && lead <= 0xdf\n ? 2\n : lead >= 0xe0 && lead <= 0xef\n ? 3\n : lead >= 0xf0 && lead <= 0xf4\n ? 4\n : 1;\n if (end - start < width) {\n new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(\n bytes,\n { stream: true },\n );\n end = start;\n }\n }\n }\n const result = bytes.subarray(0, end);\n new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(result);\n return result;\n}\n\nexport function encodeStorageBase64(\n bytes: Uint8Array,\n options: { readonly maxBytes: number },\n): string {\n budget(options.maxBytes);\n if (bytes.byteLength > options.maxBytes)\n throw new RangeError('Chunk exceeds the byte budget.');\n let text = '';\n for (let offset = 0; offset < bytes.byteLength; offset += 8192)\n text += String.fromCharCode(...bytes.subarray(offset, offset + 8192));\n return btoa(text);\n}\n\nexport interface StorageChunkIntegrityReceipt {\n readonly offset: number;\n readonly size: number;\n readonly sha256: string;\n}\n/** Verify receipt bounds, ordering and actual local bytes before skipping a chunk. */\nexport async function verifyStorageChunkReceipt(\n blob: Blob,\n receipt: StorageChunkIntegrityReceipt,\n options: {\n readonly offset: number;\n readonly maxBytes: number;\n readonly signal?: AbortSignal | undefined;\n },\n): Promise<number> {\n budget(options.maxBytes);\n options.signal?.throwIfAborted();\n if (\n !Number.isSafeInteger(options.offset) ||\n options.offset < 0 ||\n receipt.offset !== options.offset ||\n !Number.isSafeInteger(receipt.size) ||\n receipt.size < 1 ||\n receipt.size > options.maxBytes ||\n !Number.isSafeInteger(receipt.offset + receipt.size) ||\n receipt.offset + receipt.size > blob.size ||\n !/^[0-9a-f]{64}$/u.test(receipt.sha256)\n )\n throw new TypeError('Invalid or out-of-order chunk receipt.');\n const bytes = new Uint8Array(\n await blob\n .slice(receipt.offset, receipt.offset + receipt.size)\n .arrayBuffer(),\n );\n if ((await sha256StorageBytes(bytes, options)) !== receipt.sha256)\n throw new Error('The selected bytes differ from the accepted chunk.');\n return receipt.offset + receipt.size;\n}\n\n/** Bounded UTF-8 validation; filename/MIME classification remains host policy. */\nexport async function isStorageUtf8Blob(\n blob: Blob,\n options: {\n readonly chunkBytes: number;\n readonly rejectNul?: boolean;\n readonly signal?: AbortSignal | undefined;\n },\n): Promise<boolean> {\n budget(options.chunkBytes);\n const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });\n try {\n options.signal?.throwIfAborted();\n for (let offset = 0; offset < blob.size; offset += options.chunkBytes) {\n options.signal?.throwIfAborted();\n const bytes = await blob\n .slice(offset, offset + options.chunkBytes)\n .arrayBuffer();\n options.signal?.throwIfAborted();\n const text = decoder.decode(bytes, { stream: true });\n if (options.rejectNul && text.includes('\\0')) return false;\n }\n decoder.decode();\n return true;\n } catch (error) {\n if (options.signal?.aborted) throw error;\n return false;\n }\n}\nfunction budget(value: number) {\n if (!Number.isSafeInteger(value) || value < 1)\n throw new RangeError('Byte budget must be a positive safe integer.');\n}\n"]}
@@ -3,4 +3,7 @@ export type { StorageDriver } from '../storage.driver.js';
3
3
  export { StorageError, StorageErrorCode, isStorageError, normalizeStorageError, type StorageErrorOptions, } from '../storage.error.js';
4
4
  export { StorageUploadControl, type StorageResumableToken, type StorageUploadStatus, } from '../storage-upload-control.js';
5
5
  export type * from '../storage.types.js';
6
+ export { searchStorageText, applyStorageTextEdit, type StorageTextSearchOptions, type StorageTextSearchResult, type StorageTextEdit, } from './storage-text.js';
7
+ export { collectStorageBytes, storageBytesStream, readStorageTextWindow, type StorageTextWindow, type StorageTextWindowOptions, type StorageRangeReader, } from './storage-streams.js';
8
+ export { StorageStagedContentStore, type StorageStagedBody, type StorageStagedContent, type StorageStagedContentStoreOptions, type StorageStagedReadOptions, type StorageStagedWriteOptions, } from './storage-staged-content.js';
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,GACzB,MAAM,8BAA8B,CAAC;AACtC,mBAAmB,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,iBAAiB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,GACzB,MAAM,8BAA8B,CAAC;AACtC,mBAAmB,qBAAqB,CAAC;AACzC,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,EACrC,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,6BAA6B,CAAC"}
@@ -1,4 +1,7 @@
1
1
  export { DEFAULT_BUFFER_LIMIT, StorageClient, } from '../storage.client.js';
2
2
  export { StorageError, StorageErrorCode, isStorageError, normalizeStorageError, } from '../storage.error.js';
3
3
  export { StorageUploadControl, } from '../storage-upload-control.js';
4
+ export { searchStorageText, applyStorageTextEdit, } from './storage-text.js';
5
+ export { collectStorageBytes, storageBytesStream, readStorageTextWindow, } from './storage-streams.js';
6
+ export { StorageStagedContentStore, } from './storage-staged-content.js';
4
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,qBAAqB,GAEtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,GAGrB,MAAM,8BAA8B,CAAC","sourcesContent":["export {\n DEFAULT_BUFFER_LIMIT,\n StorageClient,\n type StorageFileHandle,\n} from '../storage.client.js';\nexport type { StorageDriver } from '../storage.driver.js';\nexport {\n StorageError,\n StorageErrorCode,\n isStorageError,\n normalizeStorageError,\n type StorageErrorOptions,\n} from '../storage.error.js';\nexport {\n StorageUploadControl,\n type StorageResumableToken,\n type StorageUploadStatus,\n} from '../storage-upload-control.js';\nexport type * from '../storage.types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,qBAAqB,GAEtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,GAGrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,iBAAiB,EACjB,oBAAoB,GAIrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GAItB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,yBAAyB,GAM1B,MAAM,6BAA6B,CAAC","sourcesContent":["export {\n DEFAULT_BUFFER_LIMIT,\n StorageClient,\n type StorageFileHandle,\n} from '../storage.client.js';\nexport type { StorageDriver } from '../storage.driver.js';\nexport {\n StorageError,\n StorageErrorCode,\n isStorageError,\n normalizeStorageError,\n type StorageErrorOptions,\n} from '../storage.error.js';\nexport {\n StorageUploadControl,\n type StorageResumableToken,\n type StorageUploadStatus,\n} from '../storage-upload-control.js';\nexport type * from '../storage.types.js';\nexport {\n searchStorageText,\n applyStorageTextEdit,\n type StorageTextSearchOptions,\n type StorageTextSearchResult,\n type StorageTextEdit,\n} from './storage-text.js';\nexport {\n collectStorageBytes,\n storageBytesStream,\n readStorageTextWindow,\n type StorageTextWindow,\n type StorageTextWindowOptions,\n type StorageRangeReader,\n} from './storage-streams.js';\nexport {\n StorageStagedContentStore,\n type StorageStagedBody,\n type StorageStagedContent,\n type StorageStagedContentStoreOptions,\n type StorageStagedReadOptions,\n type StorageStagedWriteOptions,\n} from './storage-staged-content.js';\n"]}
@@ -0,0 +1,37 @@
1
+ import type { StorageClient } from '../storage.client.js';
2
+ import type { StorageByteRange } from '../storage.types.js';
3
+ export interface StorageStagedBody {
4
+ readonly payloadId: string;
5
+ readonly size: number;
6
+ readonly sha256: string;
7
+ readonly etag: string;
8
+ }
9
+ export interface StorageStagedWriteOptions {
10
+ readonly signal?: AbortSignal | undefined;
11
+ readonly maxBytes?: number | undefined;
12
+ }
13
+ export interface StorageStagedReadOptions {
14
+ readonly signal?: AbortSignal | undefined;
15
+ readonly range?: StorageByteRange | undefined;
16
+ }
17
+ export interface StorageStagedContent<Scope> {
18
+ write(scope: Scope, body: ReadableStream<Uint8Array>, options?: StorageStagedWriteOptions): Promise<StorageStagedBody>;
19
+ read(scope: Scope, body: StorageStagedBody, options?: StorageStagedReadOptions): Promise<ReadableStream<Uint8Array>>;
20
+ }
21
+ export interface StorageStagedContentStoreOptions<Scope> {
22
+ readonly client: StorageClient;
23
+ /** Trusted, injective scope mapping; never accept this function from a model. */
24
+ readonly key: (scope: Scope, payloadId: string) => string;
25
+ }
26
+ /** Immutable create-only bodies. Retention eligibility and references remain host-owned. */
27
+ export declare class StorageStagedContentStore<Scope> implements StorageStagedContent<Scope> {
28
+ #private;
29
+ constructor(options: StorageStagedContentStoreOptions<Scope>);
30
+ write(scope: Scope, body: ReadableStream<Uint8Array>, options?: StorageStagedWriteOptions): Promise<StorageStagedBody>;
31
+ read(scope: Scope, body: StorageStagedBody, options?: StorageStagedReadOptions): Promise<ReadableStream<Uint8Array>>;
32
+ /** Host must prove no durable/in-flight reference can be created before removal. */
33
+ remove(scope: Scope, body: StorageStagedBody, options?: {
34
+ readonly signal?: AbortSignal;
35
+ }): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=storage-staged-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-staged-content.d.ts","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AACD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AACD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC/C;AACD,MAAM,WAAW,oBAAoB,CAAC,KAAK;IACzC,KAAK,CACH,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,IAAI,CACF,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;CACxC;AACD,MAAM,WAAW,gCAAgC,CAAC,KAAK;IACrD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D;AAED,4FAA4F;AAC5F,qBAAa,yBAAyB,CACpC,KAAK,CACL,YAAW,oBAAoB,CAAC,KAAK,CAAC;;IAGtC,YAAY,OAAO,EAAE,gCAAgC,CAAC,KAAK,CAAC,EAG3D;IACK,KAAK,CACT,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAgE5B;IACK,IAAI,CACR,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAgBrC;IACD,oFAAoF;IAC9E,MAAM,CACV,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,IAAI,CAAC,CAMf;CACF"}
@@ -0,0 +1,94 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { StorageError } from '../storage.error.js';
3
+ import { storageInteger } from './storage-streams.js';
4
+ /** Immutable create-only bodies. Retention eligibility and references remain host-owned. */
5
+ export class StorageStagedContentStore {
6
+ #client;
7
+ #key;
8
+ constructor(options) {
9
+ this.#client = options.client;
10
+ this.#key = options.key;
11
+ }
12
+ async write(scope, body, options = {}) {
13
+ const { signal } = options;
14
+ const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;
15
+ storageInteger(maxBytes, 'maxBytes');
16
+ const capabilities = this.#client.capabilities;
17
+ if (!capabilities.conditionalCreate?.resultEtag ||
18
+ !capabilities.conditionalRead?.etag) {
19
+ await body.cancel().catch(() => { });
20
+ throw new StorageError('Staged content requires native create-only writes and exact ETag reads.', { code: 'NOT_SUPPORTED' });
21
+ }
22
+ const payloadId = randomUUID();
23
+ const hash = createHash('sha256');
24
+ let size = 0;
25
+ let completed = false;
26
+ const measured = body.pipeThrough(new TransformStream({
27
+ transform(chunk, controller) {
28
+ signal?.throwIfAborted();
29
+ size += chunk.byteLength;
30
+ if (!Number.isSafeInteger(size) || size > maxBytes)
31
+ throw new StorageError('Staged body exceeds its byte budget.', {
32
+ code: 'LIMIT_EXCEEDED',
33
+ });
34
+ hash.update(chunk);
35
+ controller.enqueue(chunk);
36
+ },
37
+ flush() {
38
+ completed = true;
39
+ },
40
+ }), signal === undefined ? {} : { signal });
41
+ try {
42
+ signal?.throwIfAborted();
43
+ const result = await this.#client.uploadConditional(this.#key(scope, payloadId), measured, {
44
+ condition: { type: 'create' },
45
+ contentType: 'application/octet-stream',
46
+ retries: 0,
47
+ ...(signal === undefined ? {} : { signal }),
48
+ });
49
+ if (!completed || result.size !== size || result.etag === undefined)
50
+ throw new StorageError('Provider did not acknowledge the complete staged body.', { code: 'PROVIDER', applied: true });
51
+ return Object.freeze({
52
+ payloadId,
53
+ size,
54
+ sha256: hash.digest('hex'),
55
+ etag: result.etag,
56
+ });
57
+ }
58
+ catch (error) {
59
+ await measured.cancel(error).catch(() => { });
60
+ throw error;
61
+ }
62
+ }
63
+ async read(scope, body, options = {}) {
64
+ validateBody(body);
65
+ options.signal?.throwIfAborted();
66
+ if (options.range !== undefined && !this.#client.capabilities.rangeRead)
67
+ throw new StorageError('Provider does not support byte ranges.', {
68
+ code: 'NOT_SUPPORTED',
69
+ });
70
+ const result = await this.#client.downloadConditional(this.#key(scope, body.payloadId), {
71
+ condition: { etag: body.etag },
72
+ ...(options.range === undefined ? {} : { range: options.range }),
73
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
74
+ });
75
+ return result.body;
76
+ }
77
+ /** Host must prove no durable/in-flight reference can be created before removal. */
78
+ async remove(scope, body, options = {}) {
79
+ validateBody(body);
80
+ await this.#client.deleteConditional(this.#key(scope, body.payloadId), {
81
+ condition: { etag: body.etag },
82
+ ...options,
83
+ });
84
+ }
85
+ }
86
+ function validateBody(body) {
87
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(body.payloadId) ||
88
+ !/^[0-9a-f]{64}$/u.test(body.sha256))
89
+ throw new StorageError('Invalid staged body receipt.', {
90
+ code: 'INVALID_ARGUMENT',
91
+ });
92
+ storageInteger(body.size, 'size');
93
+ }
94
+ //# sourceMappingURL=storage-staged-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-staged-content.js","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { StorageError } from '../storage.error.js';\nimport type { StorageClient } from '../storage.client.js';\nimport type { StorageByteRange } from '../storage.types.js';\nimport { storageInteger } from './storage-streams.js';\n\nexport interface StorageStagedBody {\n readonly payloadId: string;\n readonly size: number;\n readonly sha256: string;\n readonly etag: string;\n}\nexport interface StorageStagedWriteOptions {\n readonly signal?: AbortSignal | undefined;\n readonly maxBytes?: number | undefined;\n}\nexport interface StorageStagedReadOptions {\n readonly signal?: AbortSignal | undefined;\n readonly range?: StorageByteRange | undefined;\n}\nexport interface StorageStagedContent<Scope> {\n write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options?: StorageStagedWriteOptions,\n ): Promise<StorageStagedBody>;\n read(\n scope: Scope,\n body: StorageStagedBody,\n options?: StorageStagedReadOptions,\n ): Promise<ReadableStream<Uint8Array>>;\n}\nexport interface StorageStagedContentStoreOptions<Scope> {\n readonly client: StorageClient;\n /** Trusted, injective scope mapping; never accept this function from a model. */\n readonly key: (scope: Scope, payloadId: string) => string;\n}\n\n/** Immutable create-only bodies. Retention eligibility and references remain host-owned. */\nexport class StorageStagedContentStore<\n Scope,\n> implements StorageStagedContent<Scope> {\n readonly #client: StorageClient;\n readonly #key: (scope: Scope, payloadId: string) => string;\n constructor(options: StorageStagedContentStoreOptions<Scope>) {\n this.#client = options.client;\n this.#key = options.key;\n }\n async write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n const { signal } = options;\n const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;\n storageInteger(maxBytes, 'maxBytes');\n const capabilities = this.#client.capabilities;\n if (\n !capabilities.conditionalCreate?.resultEtag ||\n !capabilities.conditionalRead?.etag\n ) {\n await body.cancel().catch(() => {});\n throw new StorageError(\n 'Staged content requires native create-only writes and exact ETag reads.',\n { code: 'NOT_SUPPORTED' },\n );\n }\n const payloadId = randomUUID();\n const hash = createHash('sha256');\n let size = 0;\n let completed = false;\n const measured = body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n signal?.throwIfAborted();\n size += chunk.byteLength;\n if (!Number.isSafeInteger(size) || size > maxBytes)\n throw new StorageError('Staged body exceeds its byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n hash.update(chunk);\n controller.enqueue(chunk);\n },\n flush() {\n completed = true;\n },\n }),\n signal === undefined ? {} : { signal },\n );\n try {\n signal?.throwIfAborted();\n const result = await this.#client.uploadConditional(\n this.#key(scope, payloadId),\n measured,\n {\n condition: { type: 'create' },\n contentType: 'application/octet-stream',\n retries: 0,\n ...(signal === undefined ? {} : { signal }),\n },\n );\n if (!completed || result.size !== size || result.etag === undefined)\n throw new StorageError(\n 'Provider did not acknowledge the complete staged body.',\n { code: 'PROVIDER', applied: true },\n );\n return Object.freeze({\n payloadId,\n size,\n sha256: hash.digest('hex'),\n etag: result.etag,\n });\n } catch (error) {\n await measured.cancel(error).catch(() => {});\n throw error;\n }\n }\n async read(\n scope: Scope,\n body: StorageStagedBody,\n options: StorageStagedReadOptions = {},\n ): Promise<ReadableStream<Uint8Array>> {\n validateBody(body);\n options.signal?.throwIfAborted();\n if (options.range !== undefined && !this.#client.capabilities.rangeRead)\n throw new StorageError('Provider does not support byte ranges.', {\n code: 'NOT_SUPPORTED',\n });\n const result = await this.#client.downloadConditional(\n this.#key(scope, body.payloadId),\n {\n condition: { etag: body.etag },\n ...(options.range === undefined ? {} : { range: options.range }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n },\n );\n return result.body;\n }\n /** Host must prove no durable/in-flight reference can be created before removal. */\n async remove(\n scope: Scope,\n body: StorageStagedBody,\n options: { readonly signal?: AbortSignal } = {},\n ): Promise<void> {\n validateBody(body);\n await this.#client.deleteConditional(this.#key(scope, body.payloadId), {\n condition: { etag: body.etag },\n ...options,\n });\n }\n}\nfunction validateBody(body: StorageStagedBody): void {\n if (\n !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(\n body.payloadId,\n ) ||\n !/^[0-9a-f]{64}$/u.test(body.sha256)\n )\n throw new StorageError('Invalid staged body receipt.', {\n code: 'INVALID_ARGUMENT',\n });\n storageInteger(body.size, 'size');\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import type { StorageByteRange } from '../storage.types.js';
2
+ export declare function storageBytesStream(bytes: Uint8Array): ReadableStream<Uint8Array>;
3
+ /** Explicitly buffers no more than maxBytes and cancels the source on failure/abort. */
4
+ export declare function collectStorageBytes(stream: ReadableStream<Uint8Array>, maxBytes: number, signal?: AbortSignal): Promise<Uint8Array>;
5
+ export interface StorageTextWindow {
6
+ readonly content: string | null;
7
+ readonly offset: number;
8
+ readonly nextOffset: number | null;
9
+ }
10
+ export interface StorageTextWindowOptions {
11
+ readonly size: number;
12
+ readonly offset?: number | undefined;
13
+ readonly maxBytes: number;
14
+ readonly signal?: AbortSignal | undefined;
15
+ }
16
+ export type StorageRangeReader = (range: Required<StorageByteRange>, signal?: AbortSignal) => Promise<ReadableStream<Uint8Array>>;
17
+ /** Byte offsets; inclusive provider ranges. Never trims malformed UTF-8 at EOF. */
18
+ export declare function readStorageTextWindow(read: StorageRangeReader, options: StorageTextWindowOptions): Promise<StorageTextWindow>;
19
+ //# sourceMappingURL=storage-streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-streams.d.ts","sourceRoot":"","sources":["../../src/core/storage-streams.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,UAAU,GAChB,cAAc,CAAC,UAAU,CAAC,CAO5B;AAED,wFAAwF;AACxF,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAkCrB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AACD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AACD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EACjC,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;AAEzC,mFAAmF;AACnF,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA0C5B"}
@@ -0,0 +1,95 @@
1
+ import { StorageError } from '../storage.error.js';
2
+ import { trimStorageUtf8Chunk } from '../bytes/index.js';
3
+ export function storageBytesStream(bytes) {
4
+ return new ReadableStream({
5
+ start(controller) {
6
+ controller.enqueue(bytes);
7
+ controller.close();
8
+ },
9
+ });
10
+ }
11
+ /** Explicitly buffers no more than maxBytes and cancels the source on failure/abort. */
12
+ export async function collectStorageBytes(stream, maxBytes, signal) {
13
+ storageInteger(maxBytes, 'maxBytes');
14
+ const reader = stream.getReader();
15
+ const abort = () => {
16
+ void reader.cancel(signal?.reason).catch(() => { });
17
+ };
18
+ signal?.addEventListener('abort', abort, { once: true });
19
+ const chunks = [];
20
+ let size = 0;
21
+ try {
22
+ signal?.throwIfAborted();
23
+ for (;;) {
24
+ const next = await reader.read();
25
+ signal?.throwIfAborted();
26
+ if (next.done)
27
+ break;
28
+ size += next.value.byteLength;
29
+ if (!Number.isSafeInteger(size) || size > maxBytes)
30
+ throw new StorageError('The stream exceeds its byte budget.', {
31
+ code: 'LIMIT_EXCEEDED',
32
+ });
33
+ chunks.push(next.value.slice());
34
+ }
35
+ const result = new Uint8Array(size);
36
+ let offset = 0;
37
+ for (const chunk of chunks) {
38
+ result.set(chunk, offset);
39
+ offset += chunk.byteLength;
40
+ }
41
+ return result;
42
+ }
43
+ finally {
44
+ signal?.removeEventListener('abort', abort);
45
+ await reader.cancel().catch(() => { });
46
+ reader.releaseLock();
47
+ }
48
+ }
49
+ /** Byte offsets; inclusive provider ranges. Never trims malformed UTF-8 at EOF. */
50
+ export async function readStorageTextWindow(read, options) {
51
+ const { size, maxBytes, signal } = options;
52
+ const offset = options.offset ?? 0;
53
+ storageInteger(size, 'size');
54
+ storageInteger(offset, 'offset');
55
+ storageInteger(maxBytes, 'maxBytes', 4);
56
+ if (offset > size)
57
+ throw new StorageError('Offset exceeds the body size.', {
58
+ code: 'INVALID_ARGUMENT',
59
+ });
60
+ signal?.throwIfAborted();
61
+ if (offset === size)
62
+ return { content: '', offset, nextOffset: null };
63
+ const length = Math.min(maxBytes, size - offset);
64
+ const bytes = await collectStorageBytes(await read({ start: offset, end: offset + length - 1 }, signal), length, signal);
65
+ if (bytes.byteLength !== length)
66
+ throw new StorageError('The range response is incomplete.', {
67
+ code: 'PROVIDER',
68
+ });
69
+ try {
70
+ const complete = trimStorageUtf8Chunk(bytes, {
71
+ final: offset + length === size,
72
+ });
73
+ const end = complete.byteLength;
74
+ const content = new TextDecoder('utf-8', {
75
+ fatal: true,
76
+ ignoreBOM: true,
77
+ }).decode(complete);
78
+ return {
79
+ content,
80
+ offset,
81
+ nextOffset: offset + end < size ? offset + end : null,
82
+ };
83
+ }
84
+ catch {
85
+ throw new StorageError('The window is not valid UTF-8 or its offset splits a character.', { code: 'INVALID_ARGUMENT' });
86
+ }
87
+ }
88
+ /** @internal */
89
+ export function storageInteger(value, name, minimum = 0) {
90
+ if (!Number.isSafeInteger(value) || value < minimum)
91
+ throw new StorageError(`${name} must be a safe integer >= ${minimum}.`, {
92
+ code: 'INVALID_ARGUMENT',
93
+ });
94
+ }
95
+ //# sourceMappingURL=storage-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-streams.js","sourceRoot":"","sources":["../../src/core/storage-streams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,UAAU,kBAAkB,CAChC,KAAiB;IAEjB,OAAO,IAAI,cAAc,CAAC;QACxB,KAAK,CAAC,UAAU;YACd,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAkC,EAClC,QAAgB,EAChB,MAAoB;IAEpB,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC;IACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,SAAS,CAAC;YACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,IAAI;gBAAE,MAAM;YACrB,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,QAAQ;gBAChD,MAAM,IAAI,YAAY,CAAC,qCAAqC,EAAE;oBAC5D,IAAI,EAAE,gBAAgB;iBACvB,CAAC,CAAC;YACL,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;QAC7B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAkBD,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAwB,EACxB,OAAiC;IAEjC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IACnC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7B,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjC,cAAc,CAAC,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACxC,IAAI,MAAM,GAAG,IAAI;QACf,MAAM,IAAI,YAAY,CAAC,+BAA+B,EAAE;YACtD,IAAI,EAAE,kBAAkB;SACzB,CAAC,CAAC;IACL,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,MAAM,mBAAmB,CACrC,MAAM,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAC/D,MAAM,EACN,MAAM,CACP,CAAC;IACF,IAAI,KAAK,CAAC,UAAU,KAAK,MAAM;QAC7B,MAAM,IAAI,YAAY,CAAC,mCAAmC,EAAE;YAC1D,IAAI,EAAE,UAAU;SACjB,CAAC,CAAC;IACL,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,EAAE;YAC3C,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI;SAChC,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE;YACvC,KAAK,EAAE,IAAI;YACX,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpB,OAAO;YACL,OAAO;YACP,MAAM;YACN,UAAU,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI;SACtD,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CACpB,iEAAiE,EACjE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAC7B,CAAC;IACJ,CAAC;AACH,CAAC;AAED,gBAAgB;AAChB,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,IAAY,EAAE,OAAO,GAAG,CAAC;IACrE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,OAAO;QACjD,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,8BAA8B,OAAO,GAAG,EAAE;YACtE,IAAI,EAAE,kBAAkB;SACzB,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { StorageError } from '../storage.error.js';\nimport type { StorageByteRange } from '../storage.types.js';\nimport { trimStorageUtf8Chunk } from '../bytes/index.js';\n\nexport function storageBytesStream(\n bytes: Uint8Array,\n): ReadableStream<Uint8Array> {\n return new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n}\n\n/** Explicitly buffers no more than maxBytes and cancels the source on failure/abort. */\nexport async function collectStorageBytes(\n stream: ReadableStream<Uint8Array>,\n maxBytes: number,\n signal?: AbortSignal,\n): Promise<Uint8Array> {\n storageInteger(maxBytes, 'maxBytes');\n const reader = stream.getReader();\n const abort = () => {\n void reader.cancel(signal?.reason).catch(() => {});\n };\n signal?.addEventListener('abort', abort, { once: true });\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n signal?.throwIfAborted();\n for (;;) {\n const next = await reader.read();\n signal?.throwIfAborted();\n if (next.done) break;\n size += next.value.byteLength;\n if (!Number.isSafeInteger(size) || size > maxBytes)\n throw new StorageError('The stream exceeds its byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n chunks.push(next.value.slice());\n }\n const result = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return result;\n } finally {\n signal?.removeEventListener('abort', abort);\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n\nexport interface StorageTextWindow {\n readonly content: string | null;\n readonly offset: number;\n readonly nextOffset: number | null;\n}\nexport interface StorageTextWindowOptions {\n readonly size: number;\n readonly offset?: number | undefined;\n readonly maxBytes: number;\n readonly signal?: AbortSignal | undefined;\n}\nexport type StorageRangeReader = (\n range: Required<StorageByteRange>,\n signal?: AbortSignal,\n) => Promise<ReadableStream<Uint8Array>>;\n\n/** Byte offsets; inclusive provider ranges. Never trims malformed UTF-8 at EOF. */\nexport async function readStorageTextWindow(\n read: StorageRangeReader,\n options: StorageTextWindowOptions,\n): Promise<StorageTextWindow> {\n const { size, maxBytes, signal } = options;\n const offset = options.offset ?? 0;\n storageInteger(size, 'size');\n storageInteger(offset, 'offset');\n storageInteger(maxBytes, 'maxBytes', 4);\n if (offset > size)\n throw new StorageError('Offset exceeds the body size.', {\n code: 'INVALID_ARGUMENT',\n });\n signal?.throwIfAborted();\n if (offset === size) return { content: '', offset, nextOffset: null };\n const length = Math.min(maxBytes, size - offset);\n const bytes = await collectStorageBytes(\n await read({ start: offset, end: offset + length - 1 }, signal),\n length,\n signal,\n );\n if (bytes.byteLength !== length)\n throw new StorageError('The range response is incomplete.', {\n code: 'PROVIDER',\n });\n try {\n const complete = trimStorageUtf8Chunk(bytes, {\n final: offset + length === size,\n });\n const end = complete.byteLength;\n const content = new TextDecoder('utf-8', {\n fatal: true,\n ignoreBOM: true,\n }).decode(complete);\n return {\n content,\n offset,\n nextOffset: offset + end < size ? offset + end : null,\n };\n } catch {\n throw new StorageError(\n 'The window is not valid UTF-8 or its offset splits a character.',\n { code: 'INVALID_ARGUMENT' },\n );\n }\n}\n\n/** @internal */\nexport function storageInteger(value: number, name: string, minimum = 0): void {\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new StorageError(`${name} must be a safe integer >= ${minimum}.`, {\n code: 'INVALID_ARGUMENT',\n });\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { type StorageRangeReader } from './storage-streams.js';
2
+ export interface StorageTextSearchOptions {
3
+ readonly size: number;
4
+ readonly query: string;
5
+ readonly offset?: number | undefined;
6
+ readonly maxScanBytes: number;
7
+ readonly maxMatches: number;
8
+ readonly maxSnippetCharacters: number;
9
+ readonly maxReadBytes: number;
10
+ readonly signal?: AbortSignal | undefined;
11
+ }
12
+ export interface StorageTextSearchResult {
13
+ readonly matches: readonly {
14
+ readonly offset: number;
15
+ readonly text: string;
16
+ }[];
17
+ readonly nextOffset: number | null;
18
+ }
19
+ /** Literal, non-overlapping matches. Continuations retain cross-window candidates. */
20
+ export declare function searchStorageText(read: StorageRangeReader, options: StorageTextSearchOptions): Promise<StorageTextSearchResult>;
21
+ export type StorageTextEdit = {
22
+ readonly kind: 'append';
23
+ readonly text: string;
24
+ } | {
25
+ readonly kind: 'replace';
26
+ readonly oldText: string;
27
+ readonly newText: string;
28
+ };
29
+ /** Whole-text operation; callers must also bound the original buffered read. */
30
+ export declare function applyStorageTextEdit(content: string, change: StorageTextEdit, options: {
31
+ readonly maxBytes: number;
32
+ }): string;
33
+ //# sourceMappingURL=storage-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-text.d.ts","sourceRoot":"","sources":["../../src/core/storage-text.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AACD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,EAAE,CAAC;IACJ,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,sFAAsF;AACtF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAoElC;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,gFAAgF;AAChF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrC,MAAM,CAiCR"}
@@ -0,0 +1,98 @@
1
+ import { StorageError } from '../storage.error.js';
2
+ import { readStorageTextWindow, storageInteger, } from './storage-streams.js';
3
+ /** Literal, non-overlapping matches. Continuations retain cross-window candidates. */
4
+ export async function searchStorageText(read, options) {
5
+ const { query, size, signal } = options;
6
+ const encode = (text) => new TextEncoder().encode(text).byteLength;
7
+ if (query.length === 0 ||
8
+ query.length > 256 ||
9
+ /[\uD800-\uDFFF]/u.test(query))
10
+ throw new StorageError('Query must contain 1–256 well-formed characters.', {
11
+ code: 'INVALID_ARGUMENT',
12
+ });
13
+ storageInteger(size, 'size');
14
+ storageInteger(options.maxReadBytes, 'maxReadBytes', 4);
15
+ storageInteger(options.maxScanBytes, 'maxScanBytes', encode(query) + 4);
16
+ storageInteger(options.maxMatches, 'maxMatches', 1);
17
+ storageInteger(options.maxSnippetCharacters, 'maxSnippetCharacters', 1);
18
+ let cursor = options.offset ?? 0;
19
+ storageInteger(cursor, 'offset');
20
+ if (cursor > size)
21
+ throw new StorageError('Offset exceeds body size.', {
22
+ code: 'INVALID_ARGUMENT',
23
+ });
24
+ let pending = '';
25
+ let pendingOffset = cursor;
26
+ let scanned = 0;
27
+ const matches = [];
28
+ signal?.throwIfAborted();
29
+ while (cursor < size && options.maxScanBytes - scanned >= 4) {
30
+ const maxBytes = Math.min(options.maxReadBytes, options.maxScanBytes - scanned);
31
+ const page = await readStorageTextWindow(read, {
32
+ size,
33
+ offset: cursor,
34
+ maxBytes,
35
+ signal,
36
+ });
37
+ scanned += Math.min(maxBytes, size - cursor);
38
+ const text = pending + page.content;
39
+ const safeEnd = page.nextOffset === null
40
+ ? text.length
41
+ : Math.max(0, text.length - query.length + 1);
42
+ let at = 0;
43
+ for (;;) {
44
+ const found = text.indexOf(query, at);
45
+ if (found < 0 || found >= safeEnd)
46
+ break;
47
+ matches.push({
48
+ offset: pendingOffset + encode(text.slice(0, found)),
49
+ text: Array.from(text.slice(found))
50
+ .slice(0, options.maxSnippetCharacters)
51
+ .join(''),
52
+ });
53
+ at = found + query.length;
54
+ if (matches.length === options.maxMatches) {
55
+ const next = pendingOffset + encode(text.slice(0, at));
56
+ return { matches, nextOffset: next < size ? next : null };
57
+ }
58
+ }
59
+ let consumed = Math.max(at, safeEnd);
60
+ if (consumed > 0 && /[\uDC00-\uDFFF]/u.test(text.charAt(consumed)))
61
+ consumed--;
62
+ pendingOffset += encode(text.slice(0, consumed));
63
+ pending = text.slice(consumed);
64
+ cursor = page.nextOffset ?? size;
65
+ }
66
+ return { matches, nextOffset: cursor < size ? pendingOffset : null };
67
+ }
68
+ /** Whole-text operation; callers must also bound the original buffered read. */
69
+ export function applyStorageTextEdit(content, change, options) {
70
+ storageInteger(options.maxBytes, 'maxBytes');
71
+ const strings = change.kind === 'append'
72
+ ? [content, change.text]
73
+ : [content, change.oldText, change.newText];
74
+ if (strings.some((value) => /[\uD800-\uDFFF]/u.test(value)))
75
+ throw new StorageError('Text must be well-formed UTF-8.', {
76
+ code: 'INVALID_ARGUMENT',
77
+ });
78
+ let result;
79
+ if (change.kind === 'append')
80
+ result = content + change.text;
81
+ else {
82
+ const first = content.indexOf(change.oldText);
83
+ if (change.oldText.length === 0 ||
84
+ first < 0 ||
85
+ content.indexOf(change.oldText, first + 1) >= 0)
86
+ throw new StorageError('The replacement target must match exactly once.', { code: 'CONFLICT' });
87
+ result =
88
+ content.slice(0, first) +
89
+ change.newText +
90
+ content.slice(first + change.oldText.length);
91
+ }
92
+ if (new TextEncoder().encode(result).byteLength > options.maxBytes)
93
+ throw new StorageError('Edited content exceeds the byte budget.', {
94
+ code: 'LIMIT_EXCEEDED',
95
+ });
96
+ return result;
97
+ }
98
+ //# sourceMappingURL=storage-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-text.js","sourceRoot":"","sources":["../../src/core/storage-text.ts"],"names":[],"mappings":"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","sourcesContent":["import { StorageError } from '../storage.error.js';\nimport {\n readStorageTextWindow,\n storageInteger,\n type StorageRangeReader,\n} from './storage-streams.js';\n\nexport interface StorageTextSearchOptions {\n readonly size: number;\n readonly query: string;\n readonly offset?: number | undefined;\n readonly maxScanBytes: number;\n readonly maxMatches: number;\n readonly maxSnippetCharacters: number;\n readonly maxReadBytes: number;\n readonly signal?: AbortSignal | undefined;\n}\nexport interface StorageTextSearchResult {\n readonly matches: readonly {\n readonly offset: number;\n readonly text: string;\n }[];\n readonly nextOffset: number | null;\n}\n\n/** Literal, non-overlapping matches. Continuations retain cross-window candidates. */\nexport async function searchStorageText(\n read: StorageRangeReader,\n options: StorageTextSearchOptions,\n): Promise<StorageTextSearchResult> {\n const { query, size, signal } = options;\n const encode = (text: string) => new TextEncoder().encode(text).byteLength;\n if (\n query.length === 0 ||\n query.length > 256 ||\n /[\\uD800-\\uDFFF]/u.test(query)\n )\n throw new StorageError('Query must contain 1–256 well-formed characters.', {\n code: 'INVALID_ARGUMENT',\n });\n storageInteger(size, 'size');\n storageInteger(options.maxReadBytes, 'maxReadBytes', 4);\n storageInteger(options.maxScanBytes, 'maxScanBytes', encode(query) + 4);\n storageInteger(options.maxMatches, 'maxMatches', 1);\n storageInteger(options.maxSnippetCharacters, 'maxSnippetCharacters', 1);\n let cursor = options.offset ?? 0;\n storageInteger(cursor, 'offset');\n if (cursor > size)\n throw new StorageError('Offset exceeds body size.', {\n code: 'INVALID_ARGUMENT',\n });\n let pending = '';\n let pendingOffset = cursor;\n let scanned = 0;\n const matches: { offset: number; text: string }[] = [];\n signal?.throwIfAborted();\n while (cursor < size && options.maxScanBytes - scanned >= 4) {\n const maxBytes = Math.min(\n options.maxReadBytes,\n options.maxScanBytes - scanned,\n );\n const page = await readStorageTextWindow(read, {\n size,\n offset: cursor,\n maxBytes,\n signal,\n });\n scanned += Math.min(maxBytes, size - cursor);\n const text = pending + page.content;\n const safeEnd =\n page.nextOffset === null\n ? text.length\n : Math.max(0, text.length - query.length + 1);\n let at = 0;\n for (;;) {\n const found = text.indexOf(query, at);\n if (found < 0 || found >= safeEnd) break;\n matches.push({\n offset: pendingOffset + encode(text.slice(0, found)),\n text: Array.from(text.slice(found))\n .slice(0, options.maxSnippetCharacters)\n .join(''),\n });\n at = found + query.length;\n if (matches.length === options.maxMatches) {\n const next = pendingOffset + encode(text.slice(0, at));\n return { matches, nextOffset: next < size ? next : null };\n }\n }\n let consumed = Math.max(at, safeEnd);\n if (consumed > 0 && /[\\uDC00-\\uDFFF]/u.test(text.charAt(consumed)))\n consumed--;\n pendingOffset += encode(text.slice(0, consumed));\n pending = text.slice(consumed);\n cursor = page.nextOffset ?? size;\n }\n return { matches, nextOffset: cursor < size ? pendingOffset : null };\n}\n\nexport type StorageTextEdit =\n | { readonly kind: 'append'; readonly text: string }\n | {\n readonly kind: 'replace';\n readonly oldText: string;\n readonly newText: string;\n };\n\n/** Whole-text operation; callers must also bound the original buffered read. */\nexport function applyStorageTextEdit(\n content: string,\n change: StorageTextEdit,\n options: { readonly maxBytes: number },\n): string {\n storageInteger(options.maxBytes, 'maxBytes');\n const strings =\n change.kind === 'append'\n ? [content, change.text]\n : [content, change.oldText, change.newText];\n if (strings.some((value) => /[\\uD800-\\uDFFF]/u.test(value)))\n throw new StorageError('Text must be well-formed UTF-8.', {\n code: 'INVALID_ARGUMENT',\n });\n let result: string;\n if (change.kind === 'append') result = content + change.text;\n else {\n const first = content.indexOf(change.oldText);\n if (\n change.oldText.length === 0 ||\n first < 0 ||\n content.indexOf(change.oldText, first + 1) >= 0\n )\n throw new StorageError(\n 'The replacement target must match exactly once.',\n { code: 'CONFLICT' },\n );\n result =\n content.slice(0, first) +\n change.newText +\n content.slice(first + change.oldText.length);\n }\n if (new TextEncoder().encode(result).byteLength > options.maxBytes)\n throw new StorageError('Edited content exceeds the byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n return result;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { type MemoryAdapter } from 'files-sdk/memory';
2
+ /**
3
+ * Reuses files-sdk buffering/metadata; comparisons and Map replacement share a
4
+ * synchronous linearization point AFTER body consumption. raw stays host-only.
5
+ */
6
+ export declare function withMemoryConditionalOperations(adapter: MemoryAdapter): MemoryAdapter;
7
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/files-sdk/memory.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,KAAK,aAAa,EAAoB,MAAM,kBAAkB,CAAC;AAKhF;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,aAAa,GACrB,aAAa,CAkFf"}