@nestm/storage 0.1.0-alpha.2 → 0.1.0-alpha.20
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.
- package/CHANGELOG.md +320 -0
- package/README.md +975 -29
- package/SECURITY.md +128 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts +53 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts.map +1 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.js +370 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.js.map +1 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts +91 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.d.ts.map +1 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js +497 -0
- package/dist/ai-sdk/ai-sdk-workspace-tools.js.map +1 -0
- package/dist/ai-sdk/index.d.ts +3 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/index.js +3 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/bytes/index.d.ts +30 -0
- package/dist/bytes/index.d.ts.map +1 -0
- package/dist/bytes/index.js +96 -0
- package/dist/bytes/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/storage-staged-content.d.ts +39 -0
- package/dist/core/storage-staged-content.d.ts.map +1 -0
- package/dist/core/storage-staged-content.js +109 -0
- package/dist/core/storage-staged-content.js.map +1 -0
- package/dist/core/storage-streams.d.ts +19 -0
- package/dist/core/storage-streams.d.ts.map +1 -0
- package/dist/core/storage-streams.js +95 -0
- package/dist/core/storage-streams.js.map +1 -0
- package/dist/core/storage-text-edit.d.ts +38 -0
- package/dist/core/storage-text-edit.d.ts.map +1 -0
- package/dist/core/storage-text-edit.js +86 -0
- package/dist/core/storage-text-edit.js.map +1 -0
- package/dist/core/storage-text-stream-edit.d.ts +8 -0
- package/dist/core/storage-text-stream-edit.d.ts.map +1 -0
- package/dist/core/storage-text-stream-edit.js +114 -0
- package/dist/core/storage-text-stream-edit.js.map +1 -0
- package/dist/core/storage-text.d.ts +22 -0
- package/dist/core/storage-text.d.ts.map +1 -0
- package/dist/core/storage-text.js +69 -0
- package/dist/core/storage-text.js.map +1 -0
- package/dist/crypto/index.d.ts +2 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +2 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/storage-encrypted-content.d.ts +49 -0
- package/dist/crypto/storage-encrypted-content.d.ts.map +1 -0
- package/dist/crypto/storage-encrypted-content.js +212 -0
- package/dist/crypto/storage-encrypted-content.js.map +1 -0
- package/dist/files-sdk/azure/azure-conditional.d.ts +5 -0
- package/dist/files-sdk/azure/azure-conditional.d.ts.map +1 -0
- package/dist/files-sdk/azure/azure-conditional.js +184 -0
- package/dist/files-sdk/azure/azure-conditional.js.map +1 -0
- package/dist/files-sdk/azure/index.d.ts +10 -0
- package/dist/files-sdk/azure/index.d.ts.map +1 -0
- package/dist/files-sdk/azure/index.js +34 -0
- package/dist/files-sdk/azure/index.js.map +1 -0
- package/dist/files-sdk/files-sdk.driver.d.ts +85 -3
- package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -1
- package/dist/files-sdk/files-sdk.driver.js +1437 -37
- package/dist/files-sdk/files-sdk.driver.js.map +1 -1
- package/dist/files-sdk/fs/index.d.ts +42 -0
- package/dist/files-sdk/fs/index.d.ts.map +1 -0
- package/dist/files-sdk/fs/index.js +1117 -0
- package/dist/files-sdk/fs/index.js.map +1 -0
- package/dist/files-sdk/index.d.ts +1 -1
- package/dist/files-sdk/index.d.ts.map +1 -1
- package/dist/files-sdk/index.js.map +1 -1
- package/dist/files-sdk/memory.d.ts +7 -0
- package/dist/files-sdk/memory.d.ts.map +1 -0
- package/dist/files-sdk/memory.js +72 -0
- package/dist/files-sdk/memory.js.map +1 -0
- package/dist/files-sdk/provider/index.d.ts +58 -0
- package/dist/files-sdk/provider/index.d.ts.map +1 -0
- package/dist/files-sdk/provider/index.js +169 -0
- package/dist/files-sdk/provider/index.js.map +1 -0
- package/dist/files-sdk/s3/construction-metadata.d.ts +9 -0
- package/dist/files-sdk/s3/construction-metadata.d.ts.map +1 -0
- package/dist/files-sdk/s3/construction-metadata.js +24 -0
- package/dist/files-sdk/s3/construction-metadata.js.map +1 -0
- package/dist/files-sdk/s3/index.d.ts +54 -0
- package/dist/files-sdk/s3/index.d.ts.map +1 -0
- package/dist/files-sdk/s3/index.js +1505 -0
- package/dist/files-sdk/s3/index.js.map +1 -0
- package/dist/gateway/index.d.ts +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/storage-gateway-fastify-parser.d.ts.map +1 -1
- package/dist/gateway/storage-gateway-fastify-parser.js.map +1 -1
- package/dist/gateway/storage-gateway.controller.d.ts +12 -11
- package/dist/gateway/storage-gateway.controller.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.controller.js +240 -70
- package/dist/gateway/storage-gateway.controller.js.map +1 -1
- package/dist/gateway/storage-gateway.guard.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.guard.js.map +1 -1
- package/dist/gateway/storage-gateway.module.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.module.js +60 -1
- package/dist/gateway/storage-gateway.module.js.map +1 -1
- package/dist/gateway/storage-gateway.tokens.d.ts +1 -0
- package/dist/gateway/storage-gateway.tokens.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.tokens.js +1 -0
- package/dist/gateway/storage-gateway.tokens.js.map +1 -1
- package/dist/gateway/storage-gateway.types.d.ts +53 -10
- package/dist/gateway/storage-gateway.types.d.ts.map +1 -1
- package/dist/gateway/storage-gateway.types.js.map +1 -1
- package/dist/inject-storage.decorator.js.map +1 -1
- package/dist/storage-etag.d.ts +13 -0
- package/dist/storage-etag.d.ts.map +1 -0
- package/dist/storage-etag.js +31 -0
- package/dist/storage-etag.js.map +1 -0
- package/dist/storage-upload-control.d.ts.map +1 -1
- package/dist/storage.client.d.ts +14 -1
- package/dist/storage.client.d.ts.map +1 -1
- package/dist/storage.client.js +204 -2
- package/dist/storage.client.js.map +1 -1
- package/dist/storage.driver.d.ts +28 -1
- package/dist/storage.driver.d.ts.map +1 -1
- package/dist/storage.driver.js.map +1 -1
- package/dist/storage.error.d.ts +17 -10
- package/dist/storage.error.d.ts.map +1 -1
- package/dist/storage.error.js +47 -1
- package/dist/storage.error.js.map +1 -1
- package/dist/storage.module.d.ts.map +1 -1
- package/dist/storage.module.js.map +1 -1
- package/dist/storage.service.d.ts.map +1 -1
- package/dist/storage.service.js +28 -3
- package/dist/storage.service.js.map +1 -1
- package/dist/storage.tokens.js.map +1 -1
- package/dist/storage.types.d.ts +150 -1
- package/dist/storage.types.d.ts.map +1 -1
- package/dist/storage.types.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/provider-conformance.d.ts +65 -0
- package/dist/testing/provider-conformance.d.ts.map +1 -0
- package/dist/testing/provider-conformance.js +869 -0
- package/dist/testing/provider-conformance.js.map +1 -0
- package/dist/workspace/index.d.ts +12 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +10 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/storage-catalog-checkout.d.ts +7 -0
- package/dist/workspace/storage-catalog-checkout.d.ts.map +1 -0
- package/dist/workspace/storage-catalog-checkout.js +13 -0
- package/dist/workspace/storage-catalog-checkout.js.map +1 -0
- package/dist/workspace/storage-draft-stream.d.ts +3 -0
- package/dist/workspace/storage-draft-stream.d.ts.map +1 -0
- package/dist/workspace/storage-draft-stream.js +57 -0
- package/dist/workspace/storage-draft-stream.js.map +1 -0
- package/dist/workspace/storage-file-catalog.types.d.ts +73 -0
- package/dist/workspace/storage-file-catalog.types.d.ts.map +1 -0
- package/dist/workspace/storage-file-catalog.types.js +2 -0
- package/dist/workspace/storage-file-catalog.types.js.map +1 -0
- package/dist/workspace/storage-file-workflow.d.ts +7 -0
- package/dist/workspace/storage-file-workflow.d.ts.map +1 -0
- package/dist/workspace/storage-file-workflow.js +657 -0
- package/dist/workspace/storage-file-workflow.js.map +1 -0
- package/dist/workspace/storage-file-workflow.protection.d.ts +31 -0
- package/dist/workspace/storage-file-workflow.protection.d.ts.map +1 -0
- package/dist/workspace/storage-file-workflow.protection.js +339 -0
- package/dist/workspace/storage-file-workflow.protection.js.map +1 -0
- package/dist/workspace/storage-file-workflow.types.d.ts +184 -0
- package/dist/workspace/storage-file-workflow.types.d.ts.map +1 -0
- package/dist/workspace/storage-file-workflow.types.js +10 -0
- package/dist/workspace/storage-file-workflow.types.js.map +1 -0
- package/dist/workspace/storage-text-draft.d.ts +21 -0
- package/dist/workspace/storage-text-draft.d.ts.map +1 -0
- package/dist/workspace/storage-text-draft.js +77 -0
- package/dist/workspace/storage-text-draft.js.map +1 -0
- package/dist/workspace/storage-working-files.d.ts +34 -0
- package/dist/workspace/storage-working-files.d.ts.map +1 -0
- package/dist/workspace/storage-working-files.js +326 -0
- package/dist/workspace/storage-working-files.js.map +1 -0
- package/dist/workspace/storage-workspace.cursor.d.ts +71 -0
- package/dist/workspace/storage-workspace.cursor.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.cursor.js +408 -0
- package/dist/workspace/storage-workspace.cursor.js.map +1 -0
- package/dist/workspace/storage-workspace.d.ts +6 -0
- package/dist/workspace/storage-workspace.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.d.ts +32 -0
- package/dist/workspace/storage-workspace.error.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.error.js +75 -0
- package/dist/workspace/storage-workspace.error.js.map +1 -0
- package/dist/workspace/storage-workspace.js +849 -0
- package/dist/workspace/storage-workspace.js.map +1 -0
- package/dist/workspace/storage-workspace.path.d.ts +9 -0
- package/dist/workspace/storage-workspace.path.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.path.js +68 -0
- package/dist/workspace/storage-workspace.path.js.map +1 -0
- package/dist/workspace/storage-workspace.types.d.ts +123 -0
- package/dist/workspace/storage-workspace.types.d.ts.map +1 -0
- package/dist/workspace/storage-workspace.types.js +22 -0
- package/dist/workspace/storage-workspace.types.js.map +1 -0
- package/package.json +99 -20
|
@@ -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":"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","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,38 @@
|
|
|
1
|
+
import { StorageError } from '../storage.error.js';
|
|
2
|
+
export type StorageTextChange = {
|
|
3
|
+
readonly kind: 'append';
|
|
4
|
+
readonly text: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'replace';
|
|
7
|
+
readonly oldText: string;
|
|
8
|
+
readonly newText: string;
|
|
9
|
+
};
|
|
10
|
+
export type StorageTextEdit = StorageTextChange | {
|
|
11
|
+
readonly kind: 'batch';
|
|
12
|
+
readonly changes: readonly StorageTextChange[];
|
|
13
|
+
};
|
|
14
|
+
export interface StorageTextContext {
|
|
15
|
+
/** UTF-8 byte position and one-based UTF-16 line/column of the snippet start. */
|
|
16
|
+
readonly offset: number;
|
|
17
|
+
readonly line: number;
|
|
18
|
+
readonly column: number;
|
|
19
|
+
readonly text: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StorageTextEditDiagnostic {
|
|
22
|
+
readonly editIndex: number;
|
|
23
|
+
readonly reason: 'empty_target' | 'missing_target' | 'ambiguous_target';
|
|
24
|
+
/** Bounded source data from the intermediate buffer; no edits were persisted. */
|
|
25
|
+
readonly contexts: readonly StorageTextContext[];
|
|
26
|
+
}
|
|
27
|
+
export declare class StorageTextEditConflict extends StorageError {
|
|
28
|
+
readonly diagnostic: StorageTextEditDiagnostic;
|
|
29
|
+
constructor(diagnostic: StorageTextEditDiagnostic);
|
|
30
|
+
}
|
|
31
|
+
/** A bounded source window. Coordinates count UTF-16 columns, like JS parsers. */
|
|
32
|
+
export declare function storageTextContext(content: string, index: number): StorageTextContext;
|
|
33
|
+
/** Sequential exact edits in memory. The host persists only the complete result. */
|
|
34
|
+
export declare function applyStorageTextEdit(content: string, edit: StorageTextEdit, options: {
|
|
35
|
+
readonly maxBytes: number;
|
|
36
|
+
readonly maxEdits?: number;
|
|
37
|
+
}): string;
|
|
38
|
+
//# sourceMappingURL=storage-text-edit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-text-edit.d.ts","sourceRoot":"","sources":["../../src/core/storage-text-edit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,MAAM,MAAM,iBAAiB,GACzB;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;AACN,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD,CAAC;AACN,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,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,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IACxE,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD;AACD,qBAAa,uBAAwB,SAAQ,YAAY;IACvD,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;IAC/C,YAAY,UAAU,EAAE,yBAAyB,EAMhD;CACF;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,kBAAkB,CAcpB;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,MAAM,CA6DR"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { StorageError } from '../storage.error.js';
|
|
2
|
+
import { storageInteger } from './storage-streams.js';
|
|
3
|
+
export class StorageTextEditConflict extends StorageError {
|
|
4
|
+
diagnostic;
|
|
5
|
+
constructor(diagnostic) {
|
|
6
|
+
super('The replacement target must match exactly once. No edits were applied.', { code: 'CONFLICT' });
|
|
7
|
+
this.diagnostic = diagnostic;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** A bounded source window. Coordinates count UTF-16 columns, like JS parsers. */
|
|
11
|
+
export function storageTextContext(content, index) {
|
|
12
|
+
storageInteger(index, 'index');
|
|
13
|
+
index = Math.min(index, content.length);
|
|
14
|
+
let start = Math.max(0, index - 100);
|
|
15
|
+
if (/[\uDC00-\uDFFF]/u.test(content.charAt(start)))
|
|
16
|
+
start++;
|
|
17
|
+
const before = content.slice(0, start);
|
|
18
|
+
return {
|
|
19
|
+
offset: new TextEncoder().encode(before).byteLength,
|
|
20
|
+
line: before.split('\n').length,
|
|
21
|
+
column: start - before.lastIndexOf('\n'),
|
|
22
|
+
text: Array.from(content.slice(start, start + 640))
|
|
23
|
+
.slice(0, 320)
|
|
24
|
+
.join(''),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Sequential exact edits in memory. The host persists only the complete result. */
|
|
28
|
+
export function applyStorageTextEdit(content, edit, options) {
|
|
29
|
+
storageInteger(options.maxBytes, 'maxBytes');
|
|
30
|
+
const changes = edit.kind === 'batch' ? edit.changes : [edit];
|
|
31
|
+
storageInteger(options.maxEdits ?? 64, 'maxEdits', 1);
|
|
32
|
+
if (changes.length < 1 || changes.length > (options.maxEdits ?? 64))
|
|
33
|
+
throw new StorageError('Edit batch exceeds the operation limit.', {
|
|
34
|
+
code: 'LIMIT_EXCEEDED',
|
|
35
|
+
});
|
|
36
|
+
const validate = (text) => {
|
|
37
|
+
if (/[\uD800-\uDFFF]/u.test(text))
|
|
38
|
+
throw new StorageError('Text must be well-formed UTF-8.', {
|
|
39
|
+
code: 'INVALID_ARGUMENT',
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
validate(content);
|
|
43
|
+
for (const [editIndex, change] of changes.entries()) {
|
|
44
|
+
if (change.kind === 'append') {
|
|
45
|
+
validate(change.text);
|
|
46
|
+
content += change.text;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
validate(change.oldText);
|
|
50
|
+
validate(change.newText);
|
|
51
|
+
const first = content.indexOf(change.oldText);
|
|
52
|
+
const second = first < 0 ? -1 : content.indexOf(change.oldText, first + 1);
|
|
53
|
+
if (change.oldText.length === 0 || first < 0 || second >= 0) {
|
|
54
|
+
const reason = change.oldText.length === 0
|
|
55
|
+
? 'empty_target'
|
|
56
|
+
: first < 0
|
|
57
|
+
? 'missing_target'
|
|
58
|
+
: 'ambiguous_target';
|
|
59
|
+
// An anchor helps locate stale whitespace/context; it never relaxes matching.
|
|
60
|
+
const anchor = change.oldText
|
|
61
|
+
.split('\n')
|
|
62
|
+
.find((line) => line.trim().length > 0)
|
|
63
|
+
?.trim()
|
|
64
|
+
.slice(0, 80) ?? '';
|
|
65
|
+
const near = anchor.length === 0 ? 0 : Math.max(0, content.indexOf(anchor));
|
|
66
|
+
throw new StorageTextEditConflict({
|
|
67
|
+
editIndex,
|
|
68
|
+
reason,
|
|
69
|
+
contexts: (reason === 'ambiguous_target'
|
|
70
|
+
? [first, second]
|
|
71
|
+
: [near]).map((at) => storageTextContext(content, at)),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
content =
|
|
75
|
+
content.slice(0, first) +
|
|
76
|
+
change.newText +
|
|
77
|
+
content.slice(first + change.oldText.length);
|
|
78
|
+
}
|
|
79
|
+
if (new TextEncoder().encode(content).byteLength > options.maxBytes)
|
|
80
|
+
throw new StorageError('Edited content exceeds the byte budget.', {
|
|
81
|
+
code: 'LIMIT_EXCEEDED',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return content;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=storage-text-edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-text-edit.js","sourceRoot":"","sources":["../../src/core/storage-text-edit.ts"],"names":[],"mappings":"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","sourcesContent":["import { StorageError } from '../storage.error.js';\nimport { storageInteger } from './storage-streams.js';\n\nexport type StorageTextChange =\n | { readonly kind: 'append'; readonly text: string }\n | {\n readonly kind: 'replace';\n readonly oldText: string;\n readonly newText: string;\n };\nexport type StorageTextEdit =\n | StorageTextChange\n | {\n readonly kind: 'batch';\n readonly changes: readonly StorageTextChange[];\n };\nexport interface StorageTextContext {\n /** UTF-8 byte position and one-based UTF-16 line/column of the snippet start. */\n readonly offset: number;\n readonly line: number;\n readonly column: number;\n readonly text: string;\n}\nexport interface StorageTextEditDiagnostic {\n readonly editIndex: number;\n readonly reason: 'empty_target' | 'missing_target' | 'ambiguous_target';\n /** Bounded source data from the intermediate buffer; no edits were persisted. */\n readonly contexts: readonly StorageTextContext[];\n}\nexport class StorageTextEditConflict extends StorageError {\n readonly diagnostic: StorageTextEditDiagnostic;\n constructor(diagnostic: StorageTextEditDiagnostic) {\n super(\n 'The replacement target must match exactly once. No edits were applied.',\n { code: 'CONFLICT' },\n );\n this.diagnostic = diagnostic;\n }\n}\n\n/** A bounded source window. Coordinates count UTF-16 columns, like JS parsers. */\nexport function storageTextContext(\n content: string,\n index: number,\n): StorageTextContext {\n storageInteger(index, 'index');\n index = Math.min(index, content.length);\n let start = Math.max(0, index - 100);\n if (/[\\uDC00-\\uDFFF]/u.test(content.charAt(start))) start++;\n const before = content.slice(0, start);\n return {\n offset: new TextEncoder().encode(before).byteLength,\n line: before.split('\\n').length,\n column: start - before.lastIndexOf('\\n'),\n text: Array.from(content.slice(start, start + 640))\n .slice(0, 320)\n .join(''),\n };\n}\n\n/** Sequential exact edits in memory. The host persists only the complete result. */\nexport function applyStorageTextEdit(\n content: string,\n edit: StorageTextEdit,\n options: { readonly maxBytes: number; readonly maxEdits?: number },\n): string {\n storageInteger(options.maxBytes, 'maxBytes');\n const changes = edit.kind === 'batch' ? edit.changes : [edit];\n storageInteger(options.maxEdits ?? 64, 'maxEdits', 1);\n if (changes.length < 1 || changes.length > (options.maxEdits ?? 64))\n throw new StorageError('Edit batch exceeds the operation limit.', {\n code: 'LIMIT_EXCEEDED',\n });\n const validate = (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 };\n validate(content);\n for (const [editIndex, change] of changes.entries()) {\n if (change.kind === 'append') {\n validate(change.text);\n content += change.text;\n } else {\n validate(change.oldText);\n validate(change.newText);\n const first = content.indexOf(change.oldText);\n const second =\n first < 0 ? -1 : content.indexOf(change.oldText, first + 1);\n if (change.oldText.length === 0 || first < 0 || second >= 0) {\n const reason =\n change.oldText.length === 0\n ? 'empty_target'\n : first < 0\n ? 'missing_target'\n : 'ambiguous_target';\n // An anchor helps locate stale whitespace/context; it never relaxes matching.\n const anchor =\n change.oldText\n .split('\\n')\n .find((line) => line.trim().length > 0)\n ?.trim()\n .slice(0, 80) ?? '';\n const near =\n anchor.length === 0 ? 0 : Math.max(0, content.indexOf(anchor));\n throw new StorageTextEditConflict({\n editIndex,\n reason,\n contexts: (reason === 'ambiguous_target'\n ? [first, second]\n : [near]\n ).map((at) => storageTextContext(content, at)),\n });\n }\n content =\n content.slice(0, first) +\n change.newText +\n content.slice(first + change.oldText.length);\n }\n if (new TextEncoder().encode(content).byteLength > options.maxBytes)\n throw new StorageError('Edited content exceeds the byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n }\n return content;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type StorageTextChange } from './storage-text-edit.js';
|
|
2
|
+
/** Exact ordered edits with bounded lookbehind. The consumer must stage before committing. */
|
|
3
|
+
export declare function editStorageTextStream(source: ReadableStream<Uint8Array>, changes: readonly StorageTextChange[], options: {
|
|
4
|
+
readonly maxEditBytes: number;
|
|
5
|
+
readonly maxEdits?: number;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
}): ReadableStream<Uint8Array>;
|
|
8
|
+
//# sourceMappingURL=storage-text-stream-edit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-text-stream-edit.d.ts","sourceRoot":"","sources":["../../src/core/storage-text-stream-edit.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAGhC,8FAA8F;AAC9F,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,EAAE,SAAS,iBAAiB,EAAE,EACrC,OAAO,EAAE;IACP,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,GACA,cAAc,CAAC,UAAU,CAAC,CAwH5B"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { StorageError } from '../storage.error.js';
|
|
2
|
+
import { StorageTextEditConflict, storageTextContext, } from './storage-text-edit.js';
|
|
3
|
+
import { storageInteger } from './storage-streams.js';
|
|
4
|
+
/** Exact ordered edits with bounded lookbehind. The consumer must stage before committing. */
|
|
5
|
+
export function editStorageTextStream(source, changes, options) {
|
|
6
|
+
storageInteger(options.maxEditBytes, 'maxEditBytes', 1);
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
if (!changes.length || changes.length > (options.maxEdits ?? 64))
|
|
9
|
+
throw new StorageError('Edit batch exceeds the operation limit.', {
|
|
10
|
+
code: 'LIMIT_EXCEEDED',
|
|
11
|
+
});
|
|
12
|
+
let size = 0;
|
|
13
|
+
for (const [editIndex, change] of changes.entries()) {
|
|
14
|
+
for (const text of change.kind === 'append'
|
|
15
|
+
? [change.text]
|
|
16
|
+
: [change.oldText, change.newText]) {
|
|
17
|
+
if (/[\uD800-\uDFFF]/u.test(text))
|
|
18
|
+
throw new StorageError('Text must be well-formed UTF-8.', {
|
|
19
|
+
code: 'INVALID_ARGUMENT',
|
|
20
|
+
});
|
|
21
|
+
size += encoder.encode(text).byteLength;
|
|
22
|
+
}
|
|
23
|
+
if (change.kind === 'replace' && !change.oldText.length)
|
|
24
|
+
throw new StorageTextEditConflict({
|
|
25
|
+
editIndex,
|
|
26
|
+
reason: 'empty_target',
|
|
27
|
+
contexts: [],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (size > options.maxEditBytes)
|
|
31
|
+
throw new StorageError('Edit text exceeds the per-operation limit.', {
|
|
32
|
+
code: 'LIMIT_EXCEEDED',
|
|
33
|
+
});
|
|
34
|
+
let result = source;
|
|
35
|
+
for (const [editIndex, change] of changes.entries()) {
|
|
36
|
+
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
37
|
+
let pending = '';
|
|
38
|
+
let consumed = 0;
|
|
39
|
+
let byteOffset = 0;
|
|
40
|
+
let line = 1;
|
|
41
|
+
let column = 1;
|
|
42
|
+
let skipUntil = 0;
|
|
43
|
+
let matches = 0;
|
|
44
|
+
const contexts = [];
|
|
45
|
+
let firstContext;
|
|
46
|
+
const process = (text, final, output) => {
|
|
47
|
+
options.signal?.throwIfAborted();
|
|
48
|
+
pending += text;
|
|
49
|
+
firstContext ??= pending.length
|
|
50
|
+
? storageTextContext(pending, 0)
|
|
51
|
+
: undefined;
|
|
52
|
+
const target = change.kind === 'replace' ? change.oldText : '';
|
|
53
|
+
let end = final || !target.length
|
|
54
|
+
? pending.length
|
|
55
|
+
: Math.max(0, pending.length - target.length + 1);
|
|
56
|
+
if (end < pending.length && /[\uDC00-\uDFFF]/u.test(pending.charAt(end)))
|
|
57
|
+
end--;
|
|
58
|
+
let start = Math.min(end, Math.max(0, skipUntil - consumed));
|
|
59
|
+
if (change.kind === 'replace') {
|
|
60
|
+
for (let found = pending.indexOf(target); found >= 0 && found < end; found = pending.indexOf(target, found + 1)) {
|
|
61
|
+
const context = storageTextContext(pending, found);
|
|
62
|
+
contexts.push({
|
|
63
|
+
...context,
|
|
64
|
+
offset: byteOffset + context.offset,
|
|
65
|
+
line: line + context.line - 1,
|
|
66
|
+
column: context.line === 1 ? column + context.column - 1 : context.column,
|
|
67
|
+
});
|
|
68
|
+
if (++matches > 1)
|
|
69
|
+
throw new StorageTextEditConflict({
|
|
70
|
+
editIndex,
|
|
71
|
+
reason: 'ambiguous_target',
|
|
72
|
+
contexts,
|
|
73
|
+
});
|
|
74
|
+
if (found > start)
|
|
75
|
+
output.enqueue(encoder.encode(pending.slice(start, found)));
|
|
76
|
+
if (change.newText)
|
|
77
|
+
output.enqueue(encoder.encode(change.newText));
|
|
78
|
+
skipUntil = consumed + found + target.length;
|
|
79
|
+
start = Math.min(end, skipUntil - consumed);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (start < end)
|
|
83
|
+
output.enqueue(encoder.encode(pending.slice(start, end)));
|
|
84
|
+
const prefix = pending.slice(0, end);
|
|
85
|
+
const lastNewline = prefix.lastIndexOf('\n');
|
|
86
|
+
line += prefix.split('\n').length - 1;
|
|
87
|
+
column =
|
|
88
|
+
lastNewline < 0 ? column + prefix.length : prefix.length - lastNewline;
|
|
89
|
+
byteOffset += encoder.encode(prefix).byteLength;
|
|
90
|
+
consumed += end;
|
|
91
|
+
pending = pending.slice(end);
|
|
92
|
+
if (final) {
|
|
93
|
+
if (change.kind === 'replace' && matches !== 1)
|
|
94
|
+
throw new StorageTextEditConflict({
|
|
95
|
+
editIndex,
|
|
96
|
+
reason: 'missing_target',
|
|
97
|
+
contexts: firstContext ? [firstContext] : [],
|
|
98
|
+
});
|
|
99
|
+
if (change.kind === 'append' && change.text)
|
|
100
|
+
output.enqueue(encoder.encode(change.text));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
result = result.pipeThrough(new TransformStream({
|
|
104
|
+
transform(chunk, output) {
|
|
105
|
+
process(decoder.decode(chunk, { stream: true }), false, output);
|
|
106
|
+
},
|
|
107
|
+
flush(output) {
|
|
108
|
+
process(decoder.decode(), true, output);
|
|
109
|
+
},
|
|
110
|
+
}), options.signal ? { signal: options.signal } : {});
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=storage-text-stream-edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-text-stream-edit.js","sourceRoot":"","sources":["../../src/core/storage-text-stream-edit.ts"],"names":[],"mappings":"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","sourcesContent":["import { StorageError } from '../storage.error.js';\nimport {\n StorageTextEditConflict,\n storageTextContext,\n type StorageTextChange,\n} from './storage-text-edit.js';\nimport { storageInteger } from './storage-streams.js';\n\n/** Exact ordered edits with bounded lookbehind. The consumer must stage before committing. */\nexport function editStorageTextStream(\n source: ReadableStream<Uint8Array>,\n changes: readonly StorageTextChange[],\n options: {\n readonly maxEditBytes: number;\n readonly maxEdits?: number;\n readonly signal?: AbortSignal;\n },\n): ReadableStream<Uint8Array> {\n storageInteger(options.maxEditBytes, 'maxEditBytes', 1);\n const encoder = new TextEncoder();\n if (!changes.length || changes.length > (options.maxEdits ?? 64))\n throw new StorageError('Edit batch exceeds the operation limit.', {\n code: 'LIMIT_EXCEEDED',\n });\n let size = 0;\n for (const [editIndex, change] of changes.entries()) {\n for (const text of change.kind === 'append'\n ? [change.text]\n : [change.oldText, change.newText]) {\n if (/[\\uD800-\\uDFFF]/u.test(text))\n throw new StorageError('Text must be well-formed UTF-8.', {\n code: 'INVALID_ARGUMENT',\n });\n size += encoder.encode(text).byteLength;\n }\n if (change.kind === 'replace' && !change.oldText.length)\n throw new StorageTextEditConflict({\n editIndex,\n reason: 'empty_target',\n contexts: [],\n });\n }\n if (size > options.maxEditBytes)\n throw new StorageError('Edit text exceeds the per-operation limit.', {\n code: 'LIMIT_EXCEEDED',\n });\n let result = source;\n for (const [editIndex, change] of changes.entries()) {\n const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });\n let pending = '';\n let consumed = 0;\n let byteOffset = 0;\n let line = 1;\n let column = 1;\n let skipUntil = 0;\n let matches = 0;\n const contexts: ReturnType<typeof storageTextContext>[] = [];\n let firstContext: ReturnType<typeof storageTextContext> | undefined;\n const process = (\n text: string,\n final: boolean,\n output: TransformStreamDefaultController<Uint8Array>,\n ) => {\n options.signal?.throwIfAborted();\n pending += text;\n firstContext ??= pending.length\n ? storageTextContext(pending, 0)\n : undefined;\n const target = change.kind === 'replace' ? change.oldText : '';\n let end =\n final || !target.length\n ? pending.length\n : Math.max(0, pending.length - target.length + 1);\n if (end < pending.length && /[\\uDC00-\\uDFFF]/u.test(pending.charAt(end)))\n end--;\n let start = Math.min(end, Math.max(0, skipUntil - consumed));\n if (change.kind === 'replace') {\n for (\n let found = pending.indexOf(target);\n found >= 0 && found < end;\n found = pending.indexOf(target, found + 1)\n ) {\n const context = storageTextContext(pending, found);\n contexts.push({\n ...context,\n offset: byteOffset + context.offset,\n line: line + context.line - 1,\n column:\n context.line === 1 ? column + context.column - 1 : context.column,\n });\n if (++matches > 1)\n throw new StorageTextEditConflict({\n editIndex,\n reason: 'ambiguous_target',\n contexts,\n });\n if (found > start)\n output.enqueue(encoder.encode(pending.slice(start, found)));\n if (change.newText) output.enqueue(encoder.encode(change.newText));\n skipUntil = consumed + found + target.length;\n start = Math.min(end, skipUntil - consumed);\n }\n }\n if (start < end)\n output.enqueue(encoder.encode(pending.slice(start, end)));\n const prefix = pending.slice(0, end);\n const lastNewline = prefix.lastIndexOf('\\n');\n line += prefix.split('\\n').length - 1;\n column =\n lastNewline < 0 ? column + prefix.length : prefix.length - lastNewline;\n byteOffset += encoder.encode(prefix).byteLength;\n consumed += end;\n pending = pending.slice(end);\n if (final) {\n if (change.kind === 'replace' && matches !== 1)\n throw new StorageTextEditConflict({\n editIndex,\n reason: 'missing_target',\n contexts: firstContext ? [firstContext] : [],\n });\n if (change.kind === 'append' && change.text)\n output.enqueue(encoder.encode(change.text));\n }\n };\n result = result.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, output) {\n process(decoder.decode(chunk, { stream: true }), false, output);\n },\n flush(output) {\n process(decoder.decode(), true, output);\n },\n }),\n options.signal ? { signal: options.signal } : {},\n );\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 { applyStorageTextEdit, type StorageTextEdit, } from './storage-text-edit.js';
|
|
22
|
+
//# 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,OAAO,EACL,oBAAoB,EACpB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
export { applyStorageTextEdit, } from './storage-text-edit.js';
|
|
69
|
+
//# 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 {\n applyStorageTextEdit,\n type StorageTextEdit,\n} from './storage-text-edit.js';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,KAAK,mCAAmC,EACxC,KAAK,iCAAiC,EACtC,KAAK,6BAA6B,EAClC,KAAK,kCAAkC,GACxC,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,GAK7B,MAAM,gCAAgC,CAAC","sourcesContent":["export {\n StorageEncryptedContentStore,\n type StorageEncryptedContentStoreOptions,\n type StorageEncryptedContentProtection,\n type StorageEncryptedContentRecord,\n type StorageEncryptedContentPersistence,\n} from './storage-encrypted-content.js';\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type FileCipherEngine, type DetachedFileKey } from '@nestm/crypto/files';
|
|
2
|
+
import type { StorageClient } from '../storage.client.js';
|
|
3
|
+
import { type StorageStagedContent, type StorageStagedBody, type StorageStagedReadOptions, type StorageStagedWriteOptions } from '../core/storage-staged-content.js';
|
|
4
|
+
/** Detached encryption metadata. Hosts own serialization, key policy and access control. */
|
|
5
|
+
export interface StorageEncryptedContentProtection {
|
|
6
|
+
readonly headerBytes: Uint8Array;
|
|
7
|
+
readonly detachedKey: DetachedFileKey;
|
|
8
|
+
readonly wrappingContextDigest: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StorageEncryptedContentRecord {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly protection: StorageEncryptedContentProtection;
|
|
13
|
+
readonly ciphertextBody: StorageStagedBody;
|
|
14
|
+
readonly size: number;
|
|
15
|
+
readonly contentDigest: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StorageEncryptedContentPersistence<Scope> {
|
|
18
|
+
/** Atomic create-only reservation; persist before consuming any ciphertext. */
|
|
19
|
+
prepare(scope: Scope, id: string, protection: StorageEncryptedContentProtection): Promise<void>;
|
|
20
|
+
/** Atomically make the prepared body readable after both streams acknowledge completion. */
|
|
21
|
+
complete(scope: Scope, record: StorageEncryptedContentRecord): Promise<void>;
|
|
22
|
+
require(scope: Scope, id: string): Promise<StorageEncryptedContentRecord>;
|
|
23
|
+
/**
|
|
24
|
+
* Retire only after proving reference eligibility, then clean the exact object.
|
|
25
|
+
* An absent receipt means upload completion may be uncertain: retain cleanup inventory.
|
|
26
|
+
* Called only after this writer successfully prepared its fresh identity.
|
|
27
|
+
*/
|
|
28
|
+
discard(scope: Scope, id: string, ciphertextBody?: StorageStagedBody): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface StorageEncryptedContentStoreOptions<Scope> {
|
|
31
|
+
readonly client: StorageClient;
|
|
32
|
+
/** Caller owns the engine and its shutdown lifecycle. */
|
|
33
|
+
readonly cipher: FileCipherEngine;
|
|
34
|
+
readonly key: (scope: Scope, payloadId: string) => string;
|
|
35
|
+
/** Return a fresh context buffer; this store clears it after the engine copies it. */
|
|
36
|
+
readonly aad: (scope: Scope, payloadId: string) => Uint8Array;
|
|
37
|
+
readonly allowedProviders: readonly string[];
|
|
38
|
+
readonly provider?: string;
|
|
39
|
+
readonly metadata: StorageEncryptedContentPersistence<Scope>;
|
|
40
|
+
}
|
|
41
|
+
/** Coordinates immutable ciphertext I/O and authenticated plaintext accounting. */
|
|
42
|
+
export declare class StorageEncryptedContentStore<Scope> implements StorageStagedContent<Scope> {
|
|
43
|
+
#private;
|
|
44
|
+
constructor(options: StorageEncryptedContentStoreOptions<Scope>);
|
|
45
|
+
write(scope: Scope, body: ReadableStream<Uint8Array>, options?: StorageStagedWriteOptions): Promise<StorageStagedBody>;
|
|
46
|
+
read(scope: Scope, expected: StorageStagedBody, options?: StorageStagedReadOptions): Promise<ReadableStream<Uint8Array>>;
|
|
47
|
+
readById(scope: Scope, id: string, options?: StorageStagedReadOptions, expected?: StorageStagedBody): Promise<ReadableStream<Uint8Array>>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=storage-encrypted-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-encrypted-content.d.ts","sourceRoot":"","sources":["../../src/crypto/storage-encrypted-content.ts"],"names":[],"mappings":"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"}
|