@nestm/storage 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +20 -0
- package/README.md +161 -0
- package/SECURITY.md +27 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.d.ts +37 -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 +258 -0
- package/dist/ai-sdk/ai-sdk-file-workflow-tools.js.map +1 -0
- package/dist/ai-sdk/index.d.ts +1 -0
- package/dist/ai-sdk/index.d.ts.map +1 -1
- package/dist/ai-sdk/index.js +1 -0
- package/dist/ai-sdk/index.js.map +1 -1
- 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 +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -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 +104 -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.d.ts +33 -0
- package/dist/core/storage-text.d.ts.map +1 -0
- package/dist/core/storage-text.js +98 -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/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.map +1 -1
- package/dist/files-sdk/provider/index.js +4 -0
- package/dist/files-sdk/provider/index.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +3 -0
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/storage-file-catalog.types.d.ts +65 -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 +440 -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 +258 -0
- package/dist/workspace/storage-file-workflow.protection.js.map +1 -0
- package/dist/workspace/storage-file-workflow.types.d.ts +128 -0
- package/dist/workspace/storage-file-workflow.types.d.ts.map +1 -0
- package/dist/workspace/storage-file-workflow.types.js +8 -0
- package/dist/workspace/storage-file-workflow.types.js.map +1 -0
- package/package.json +17 -4
|
@@ -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 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { nmf1EncryptedFileSize, NMF1_FORMAT_MAX_PLAINTEXT_BYTES, } from '@nestm/crypto/files';
|
|
3
|
+
import { StorageError } from '../storage.error.js';
|
|
4
|
+
import { StorageStagedContentStore, } from '../core/storage-staged-content.js';
|
|
5
|
+
import { storageBytesStream, storageInteger } from '../core/storage-streams.js';
|
|
6
|
+
/** Coordinates immutable ciphertext I/O and authenticated plaintext accounting. */
|
|
7
|
+
export class StorageEncryptedContentStore {
|
|
8
|
+
#options;
|
|
9
|
+
#ciphertext;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.#options = {
|
|
12
|
+
...options,
|
|
13
|
+
allowedProviders: Object.freeze([...options.allowedProviders]),
|
|
14
|
+
};
|
|
15
|
+
this.#ciphertext = new StorageStagedContentStore({
|
|
16
|
+
client: options.client,
|
|
17
|
+
key: options.key,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async write(scope, body, options = {}) {
|
|
21
|
+
const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;
|
|
22
|
+
storageInteger(maxBytes, 'maxBytes');
|
|
23
|
+
const signal = options.signal;
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const writeSignal = signal === undefined
|
|
27
|
+
? controller.signal
|
|
28
|
+
: AbortSignal.any([signal, controller.signal]);
|
|
29
|
+
const id = randomUUID();
|
|
30
|
+
const hash = createHash('sha256');
|
|
31
|
+
let plaintextSize = 0;
|
|
32
|
+
const plaintext = body.pipeThrough(new TransformStream({
|
|
33
|
+
transform(chunk, stream) {
|
|
34
|
+
plaintextSize += chunk.byteLength;
|
|
35
|
+
if (!Number.isSafeInteger(plaintextSize) || plaintextSize > maxBytes)
|
|
36
|
+
throw new StorageError('Encrypted content exceeds its byte budget.', { code: 'LIMIT_EXCEEDED' });
|
|
37
|
+
hash.update(chunk);
|
|
38
|
+
stream.enqueue(chunk);
|
|
39
|
+
},
|
|
40
|
+
}), { signal: writeSignal });
|
|
41
|
+
let encryption;
|
|
42
|
+
let upload;
|
|
43
|
+
let stored;
|
|
44
|
+
let prepared = false;
|
|
45
|
+
let aad;
|
|
46
|
+
try {
|
|
47
|
+
aad = this.#options.aad(scope, id);
|
|
48
|
+
encryption = await this.#options.cipher.encrypt(plaintext, {
|
|
49
|
+
aad,
|
|
50
|
+
...(this.#options.provider === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { provider: this.#options.provider }),
|
|
53
|
+
signal: writeSignal,
|
|
54
|
+
});
|
|
55
|
+
const protection = {
|
|
56
|
+
headerBytes: encryption.headerBytes,
|
|
57
|
+
detachedKey: encryption.detachedKey,
|
|
58
|
+
wrappingContextDigest: encryption.wrappingContextDigest,
|
|
59
|
+
};
|
|
60
|
+
await this.#options.metadata.prepare(scope, id, protection);
|
|
61
|
+
prepared = true;
|
|
62
|
+
writeSignal.throwIfAborted();
|
|
63
|
+
const maximum = nmf1EncryptedFileSize(BigInt(maxBytes) < NMF1_FORMAT_MAX_PLAINTEXT_BYTES
|
|
64
|
+
? BigInt(maxBytes)
|
|
65
|
+
: NMF1_FORMAT_MAX_PLAINTEXT_BYTES);
|
|
66
|
+
if (maximum > BigInt(Number.MAX_SAFE_INTEGER))
|
|
67
|
+
throw new StorageError('Ciphertext exceeds safe storage accounting.', {
|
|
68
|
+
code: 'INVALID_ARGUMENT',
|
|
69
|
+
});
|
|
70
|
+
upload = this.#ciphertext
|
|
71
|
+
.writeReserved(scope, id, encryption.encrypted, {
|
|
72
|
+
signal: writeSignal,
|
|
73
|
+
maxBytes: Number(maximum),
|
|
74
|
+
})
|
|
75
|
+
.then((receipt) => {
|
|
76
|
+
stored = receipt;
|
|
77
|
+
return receipt;
|
|
78
|
+
});
|
|
79
|
+
const [ciphertext, summary] = await Promise.all([
|
|
80
|
+
upload,
|
|
81
|
+
encryption.completion,
|
|
82
|
+
]);
|
|
83
|
+
signal?.throwIfAborted();
|
|
84
|
+
const size = Number(summary.plaintextBytes);
|
|
85
|
+
if (!Number.isSafeInteger(size) ||
|
|
86
|
+
size !== plaintextSize ||
|
|
87
|
+
size > maxBytes ||
|
|
88
|
+
summary.ciphertextBytes !== BigInt(ciphertext.size) ||
|
|
89
|
+
summary.ciphertextSha256 !== ciphertext.sha256)
|
|
90
|
+
throw new StorageError('The encrypted body was not fully acknowledged.', { code: 'PROVIDER' });
|
|
91
|
+
const contentDigest = hash.digest('hex');
|
|
92
|
+
await this.#options.metadata.complete(scope, {
|
|
93
|
+
id,
|
|
94
|
+
protection,
|
|
95
|
+
ciphertextBody: ciphertext,
|
|
96
|
+
size,
|
|
97
|
+
contentDigest,
|
|
98
|
+
});
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
payloadId: id,
|
|
101
|
+
size,
|
|
102
|
+
sha256: contentDigest,
|
|
103
|
+
etag: ciphertext.etag,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
controller.abort(error);
|
|
108
|
+
await encryption?.cancel(error);
|
|
109
|
+
await Promise.allSettled([upload, encryption?.completion]);
|
|
110
|
+
if (prepared)
|
|
111
|
+
await this.#options.metadata
|
|
112
|
+
.discard(scope, id, stored)
|
|
113
|
+
.catch(() => undefined);
|
|
114
|
+
throw protectedFailure(signal);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
aad?.fill(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
read(scope, expected, options) {
|
|
121
|
+
return this.readById(scope, expected.payloadId, options, expected);
|
|
122
|
+
}
|
|
123
|
+
async readById(scope, id, options = {}, expected) {
|
|
124
|
+
const signal = options.signal;
|
|
125
|
+
let aad;
|
|
126
|
+
try {
|
|
127
|
+
signal?.throwIfAborted();
|
|
128
|
+
const record = await this.#options.metadata.require(scope, id);
|
|
129
|
+
if (record.id !== id ||
|
|
130
|
+
record.ciphertextBody.payloadId !== id ||
|
|
131
|
+
(expected !== undefined &&
|
|
132
|
+
(expected.etag !== record.ciphertextBody.etag ||
|
|
133
|
+
expected.sha256 !== record.contentDigest ||
|
|
134
|
+
expected.size !== record.size)))
|
|
135
|
+
throw new StorageError('The encrypted content receipt changed.', {
|
|
136
|
+
code: 'CONFLICT',
|
|
137
|
+
});
|
|
138
|
+
storageInteger(record.size, 'size');
|
|
139
|
+
aad = this.#options.aad(scope, id);
|
|
140
|
+
const input = {
|
|
141
|
+
aad,
|
|
142
|
+
detachedKey: record.protection.detachedKey,
|
|
143
|
+
allowedProviders: this.#options.allowedProviders,
|
|
144
|
+
expectedHeaderBytes: record.protection.headerBytes,
|
|
145
|
+
expectedPlaintextBytes: BigInt(record.size),
|
|
146
|
+
expectedCiphertextBytes: BigInt(record.ciphertextBody.size),
|
|
147
|
+
...(signal === undefined ? {} : { signal }),
|
|
148
|
+
};
|
|
149
|
+
if (options.range !== undefined) {
|
|
150
|
+
const start = options.range.start;
|
|
151
|
+
const end = options.range.end ?? record.size - 1;
|
|
152
|
+
if (!Number.isSafeInteger(start) ||
|
|
153
|
+
start < 0 ||
|
|
154
|
+
!Number.isSafeInteger(end) ||
|
|
155
|
+
end < start ||
|
|
156
|
+
end >= record.size)
|
|
157
|
+
throw new StorageError('The requested content range is invalid.', {
|
|
158
|
+
code: 'INVALID_ARGUMENT',
|
|
159
|
+
});
|
|
160
|
+
const bytes = await this.#options.cipher.decryptRange((range, readSignal) => this.#ciphertext.read(scope, record.ciphertextBody, {
|
|
161
|
+
signal: readSignal,
|
|
162
|
+
range: {
|
|
163
|
+
start: Number(range.start),
|
|
164
|
+
...(range.end === undefined ? {} : { end: Number(range.end) }),
|
|
165
|
+
},
|
|
166
|
+
}), {
|
|
167
|
+
...input,
|
|
168
|
+
offset: BigInt(start),
|
|
169
|
+
length: end - start + 1,
|
|
170
|
+
maxRangeBytes: end - start + 1,
|
|
171
|
+
});
|
|
172
|
+
return storageBytesStream(bytes);
|
|
173
|
+
}
|
|
174
|
+
const decrypted = await this.#options.cipher.decrypt(await this.#ciphertext.read(scope, record.ciphertextBody, options), { ...input, expectedCiphertextSha256: record.ciphertextBody.sha256 });
|
|
175
|
+
const hash = createHash('sha256');
|
|
176
|
+
let size = 0;
|
|
177
|
+
return decrypted.plaintext.pipeThrough(new TransformStream({
|
|
178
|
+
transform(chunk, stream) {
|
|
179
|
+
size += chunk.byteLength;
|
|
180
|
+
hash.update(chunk);
|
|
181
|
+
stream.enqueue(chunk);
|
|
182
|
+
},
|
|
183
|
+
async flush() {
|
|
184
|
+
try {
|
|
185
|
+
await decrypted.verification;
|
|
186
|
+
if (size !== record.size ||
|
|
187
|
+
hash.digest('hex') !== record.contentDigest)
|
|
188
|
+
throw protectedFailure(signal);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
throw protectedFailure(signal);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
}), signal === undefined ? {} : { signal });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (error instanceof StorageError &&
|
|
198
|
+
['NOT_FOUND', 'CONFLICT', 'INVALID_ARGUMENT'].includes(error.code))
|
|
199
|
+
throw error;
|
|
200
|
+
throw protectedFailure(signal);
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
aad?.fill(0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function protectedFailure(signal) {
|
|
208
|
+
return new StorageError(signal?.aborted
|
|
209
|
+
? 'The encrypted content operation was cancelled.'
|
|
210
|
+
: 'The encrypted content could not be authenticated.', { code: signal?.aborted ? 'ABORTED' : 'PROVIDER' });
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=storage-encrypted-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-encrypted-content.js","sourceRoot":"","sources":["../../src/crypto/storage-encrypted-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport {\n type FileCipherEngine,\n nmf1EncryptedFileSize,\n NMF1_FORMAT_MAX_PLAINTEXT_BYTES,\n type DetachedFileKey,\n type FileEncryptResult,\n} from '@nestm/crypto/files';\nimport { StorageError } from '../storage.error.js';\nimport type { StorageClient } from '../storage.client.js';\nimport {\n StorageStagedContentStore,\n type StorageStagedContent,\n type StorageStagedBody,\n type StorageStagedReadOptions,\n type StorageStagedWriteOptions,\n} from '../core/storage-staged-content.js';\nimport { storageBytesStream, storageInteger } from '../core/storage-streams.js';\n\n/** Detached encryption metadata. Hosts own serialization, key policy and access control. */\nexport interface StorageEncryptedContentProtection {\n readonly headerBytes: Uint8Array;\n readonly detachedKey: DetachedFileKey;\n readonly wrappingContextDigest: string;\n}\nexport interface StorageEncryptedContentRecord {\n readonly id: string;\n readonly protection: StorageEncryptedContentProtection;\n readonly ciphertextBody: StorageStagedBody;\n readonly size: number;\n readonly contentDigest: string;\n}\nexport interface StorageEncryptedContentPersistence<Scope> {\n /** Atomic create-only reservation; persist before consuming any ciphertext. */\n prepare(\n scope: Scope,\n id: string,\n protection: StorageEncryptedContentProtection,\n ): Promise<void>;\n /** Atomically make the prepared body readable after both streams acknowledge completion. */\n complete(scope: Scope, record: StorageEncryptedContentRecord): Promise<void>;\n require(scope: Scope, id: string): Promise<StorageEncryptedContentRecord>;\n /**\n * Retire only after proving reference eligibility, then clean the exact object.\n * An absent receipt means upload completion may be uncertain: retain cleanup inventory.\n * Called only after this writer successfully prepared its fresh identity.\n */\n discard(\n scope: Scope,\n id: string,\n ciphertextBody?: StorageStagedBody,\n ): Promise<void>;\n}\nexport interface StorageEncryptedContentStoreOptions<Scope> {\n readonly client: StorageClient;\n /** Caller owns the engine and its shutdown lifecycle. */\n readonly cipher: FileCipherEngine;\n readonly key: (scope: Scope, payloadId: string) => string;\n /** Return a fresh context buffer; this store clears it after the engine copies it. */\n readonly aad: (scope: Scope, payloadId: string) => Uint8Array;\n readonly allowedProviders: readonly string[];\n readonly provider?: string;\n readonly metadata: StorageEncryptedContentPersistence<Scope>;\n}\n\n/** Coordinates immutable ciphertext I/O and authenticated plaintext accounting. */\nexport class StorageEncryptedContentStore<\n Scope,\n> implements StorageStagedContent<Scope> {\n readonly #options: StorageEncryptedContentStoreOptions<Scope>;\n readonly #ciphertext: StorageStagedContentStore<Scope>;\n constructor(options: StorageEncryptedContentStoreOptions<Scope>) {\n this.#options = {\n ...options,\n allowedProviders: Object.freeze([...options.allowedProviders]),\n };\n this.#ciphertext = new StorageStagedContentStore({\n client: options.client,\n key: options.key,\n });\n }\n\n async write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;\n storageInteger(maxBytes, 'maxBytes');\n const signal = options.signal;\n signal?.throwIfAborted();\n const controller = new AbortController();\n const writeSignal =\n signal === undefined\n ? controller.signal\n : AbortSignal.any([signal, controller.signal]);\n const id = randomUUID();\n const hash = createHash('sha256');\n let plaintextSize = 0;\n const plaintext = body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, stream) {\n plaintextSize += chunk.byteLength;\n if (!Number.isSafeInteger(plaintextSize) || plaintextSize > maxBytes)\n throw new StorageError(\n 'Encrypted content exceeds its byte budget.',\n { code: 'LIMIT_EXCEEDED' },\n );\n hash.update(chunk);\n stream.enqueue(chunk);\n },\n }),\n { signal: writeSignal },\n );\n let encryption: FileEncryptResult | undefined;\n let upload: Promise<StorageStagedBody> | undefined;\n let stored: StorageStagedBody | undefined;\n let prepared = false;\n let aad: Uint8Array | undefined;\n try {\n aad = this.#options.aad(scope, id);\n encryption = await this.#options.cipher.encrypt(plaintext, {\n aad,\n ...(this.#options.provider === undefined\n ? {}\n : { provider: this.#options.provider }),\n signal: writeSignal,\n });\n const protection = {\n headerBytes: encryption.headerBytes,\n detachedKey: encryption.detachedKey,\n wrappingContextDigest: encryption.wrappingContextDigest,\n };\n await this.#options.metadata.prepare(scope, id, protection);\n prepared = true;\n writeSignal.throwIfAborted();\n const maximum = nmf1EncryptedFileSize(\n BigInt(maxBytes) < NMF1_FORMAT_MAX_PLAINTEXT_BYTES\n ? BigInt(maxBytes)\n : NMF1_FORMAT_MAX_PLAINTEXT_BYTES,\n );\n if (maximum > BigInt(Number.MAX_SAFE_INTEGER))\n throw new StorageError('Ciphertext exceeds safe storage accounting.', {\n code: 'INVALID_ARGUMENT',\n });\n upload = this.#ciphertext\n .writeReserved(scope, id, encryption.encrypted, {\n signal: writeSignal,\n maxBytes: Number(maximum),\n })\n .then((receipt) => {\n stored = receipt;\n return receipt;\n });\n const [ciphertext, summary] = await Promise.all([\n upload,\n encryption.completion,\n ]);\n signal?.throwIfAborted();\n const size = Number(summary.plaintextBytes);\n if (\n !Number.isSafeInteger(size) ||\n size !== plaintextSize ||\n size > maxBytes ||\n summary.ciphertextBytes !== BigInt(ciphertext.size) ||\n summary.ciphertextSha256 !== ciphertext.sha256\n )\n throw new StorageError(\n 'The encrypted body was not fully acknowledged.',\n { code: 'PROVIDER' },\n );\n const contentDigest = hash.digest('hex');\n await this.#options.metadata.complete(scope, {\n id,\n protection,\n ciphertextBody: ciphertext,\n size,\n contentDigest,\n });\n return Object.freeze({\n payloadId: id,\n size,\n sha256: contentDigest,\n etag: ciphertext.etag,\n });\n } catch (error: unknown) {\n controller.abort(error);\n await encryption?.cancel(error);\n await Promise.allSettled([upload, encryption?.completion]);\n if (prepared)\n await this.#options.metadata\n .discard(scope, id, stored)\n .catch(() => undefined);\n throw protectedFailure(signal);\n } finally {\n aad?.fill(0);\n }\n }\n\n read(\n scope: Scope,\n expected: StorageStagedBody,\n options?: StorageStagedReadOptions,\n ): Promise<ReadableStream<Uint8Array>> {\n return this.readById(scope, expected.payloadId, options, expected);\n }\n\n async readById(\n scope: Scope,\n id: string,\n options: StorageStagedReadOptions = {},\n expected?: StorageStagedBody,\n ): Promise<ReadableStream<Uint8Array>> {\n const signal = options.signal;\n let aad: Uint8Array | undefined;\n try {\n signal?.throwIfAborted();\n const record = await this.#options.metadata.require(scope, id);\n if (\n record.id !== id ||\n record.ciphertextBody.payloadId !== id ||\n (expected !== undefined &&\n (expected.etag !== record.ciphertextBody.etag ||\n expected.sha256 !== record.contentDigest ||\n expected.size !== record.size))\n )\n throw new StorageError('The encrypted content receipt changed.', {\n code: 'CONFLICT',\n });\n storageInteger(record.size, 'size');\n aad = this.#options.aad(scope, id);\n const input = {\n aad,\n detachedKey: record.protection.detachedKey,\n allowedProviders: this.#options.allowedProviders,\n expectedHeaderBytes: record.protection.headerBytes,\n expectedPlaintextBytes: BigInt(record.size),\n expectedCiphertextBytes: BigInt(record.ciphertextBody.size),\n ...(signal === undefined ? {} : { signal }),\n };\n if (options.range !== undefined) {\n const start = options.range.start;\n const end = options.range.end ?? record.size - 1;\n if (\n !Number.isSafeInteger(start) ||\n start < 0 ||\n !Number.isSafeInteger(end) ||\n end < start ||\n end >= record.size\n )\n throw new StorageError('The requested content range is invalid.', {\n code: 'INVALID_ARGUMENT',\n });\n const bytes = await this.#options.cipher.decryptRange(\n (range, readSignal) =>\n this.#ciphertext.read(scope, record.ciphertextBody, {\n signal: readSignal,\n range: {\n start: Number(range.start),\n ...(range.end === undefined ? {} : { end: Number(range.end) }),\n },\n }),\n {\n ...input,\n offset: BigInt(start),\n length: end - start + 1,\n maxRangeBytes: end - start + 1,\n },\n );\n return storageBytesStream(bytes);\n }\n const decrypted = await this.#options.cipher.decrypt(\n await this.#ciphertext.read(scope, record.ciphertextBody, options),\n { ...input, expectedCiphertextSha256: record.ciphertextBody.sha256 },\n );\n const hash = createHash('sha256');\n let size = 0;\n return decrypted.plaintext.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, stream) {\n size += chunk.byteLength;\n hash.update(chunk);\n stream.enqueue(chunk);\n },\n async flush() {\n try {\n await decrypted.verification;\n if (\n size !== record.size ||\n hash.digest('hex') !== record.contentDigest\n )\n throw protectedFailure(signal);\n } catch {\n throw protectedFailure(signal);\n }\n },\n }),\n signal === undefined ? {} : { signal },\n );\n } catch (error: unknown) {\n if (\n error instanceof StorageError &&\n ['NOT_FOUND', 'CONFLICT', 'INVALID_ARGUMENT'].includes(error.code)\n )\n throw error;\n throw protectedFailure(signal);\n } finally {\n aad?.fill(0);\n }\n }\n}\n\nfunction protectedFailure(signal?: AbortSignal): StorageError {\n return new StorageError(\n signal?.aborted\n ? 'The encrypted content operation was cancelled.'\n : 'The encrypted content could not be authenticated.',\n { code: signal?.aborted ? 'ABORTED' : 'PROVIDER' },\n );\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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createStoredFile, FilesError, } from 'files-sdk';
|
|
2
|
+
import { memory } from 'files-sdk/memory';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
const decorated = new WeakSet();
|
|
5
|
+
/**
|
|
6
|
+
* Reuses files-sdk buffering/metadata; comparisons and Map replacement share a
|
|
7
|
+
* synchronous linearization point AFTER body consumption. raw stays host-only.
|
|
8
|
+
*/
|
|
9
|
+
export function withMemoryConditionalOperations(adapter) {
|
|
10
|
+
if (decorated.has(adapter))
|
|
11
|
+
return adapter;
|
|
12
|
+
decorated.add(adapter);
|
|
13
|
+
// Upstream memory uses a quoted 32-bit content checksum. Give every publication
|
|
14
|
+
// (ordinary, copy, resumable or conditional) a canonical SHA-256 identity at
|
|
15
|
+
// the Map's synchronous set boundary; uploads return this same entry object.
|
|
16
|
+
const set = adapter.raw.set.bind(adapter.raw);
|
|
17
|
+
Object.defineProperty(adapter.raw, 'set', {
|
|
18
|
+
value: (key, entry) => {
|
|
19
|
+
entry.etag = createHash('sha256').update(entry.bytes).digest('hex');
|
|
20
|
+
return set(key, entry);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
for (const [key, entry] of adapter.raw)
|
|
24
|
+
adapter.raw.set(key, entry);
|
|
25
|
+
const upload = async (key, body, expectedEtag, options) => {
|
|
26
|
+
options?.signal?.throwIfAborted();
|
|
27
|
+
// An isolated upstream store performs its usual body conversion and hashing.
|
|
28
|
+
const staged = memory();
|
|
29
|
+
const result = await staged.upload(key, body, options);
|
|
30
|
+
options?.signal?.throwIfAborted();
|
|
31
|
+
const current = adapter.raw.get(key);
|
|
32
|
+
if (expectedEtag === null
|
|
33
|
+
? current !== undefined
|
|
34
|
+
: current?.etag !== expectedEtag)
|
|
35
|
+
throw new FilesError('Conflict', 'Memory object condition did not match.');
|
|
36
|
+
// No await/callback between the comparison and publication, including when
|
|
37
|
+
// ordinary upstream writes race this conditional operation.
|
|
38
|
+
adapter.raw.set(key, staged.raw.get(key));
|
|
39
|
+
return { ...result, etag: staged.raw.get(key).etag };
|
|
40
|
+
};
|
|
41
|
+
const conditional = {
|
|
42
|
+
create: (key, body, options) => upload(key, body, null, options),
|
|
43
|
+
replace: (key, body, etag, options) => upload(key, body, etag, options),
|
|
44
|
+
exactRead: async (key, etag, options) => {
|
|
45
|
+
options?.signal?.throwIfAborted();
|
|
46
|
+
const entry = adapter.raw.get(key);
|
|
47
|
+
if (entry === undefined || entry.etag !== etag)
|
|
48
|
+
throw new FilesError('Conflict', 'Memory object condition did not match.');
|
|
49
|
+
const range = options?.range;
|
|
50
|
+
const bytes = entry.bytes.slice(range?.start ?? 0, range?.end === undefined ? undefined : range.end + 1);
|
|
51
|
+
return createStoredFile({
|
|
52
|
+
key,
|
|
53
|
+
etag: entry.etag,
|
|
54
|
+
size: bytes.byteLength,
|
|
55
|
+
type: entry.contentType,
|
|
56
|
+
lastModified: entry.lastModified,
|
|
57
|
+
...(entry.metadata === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { metadata: { ...entry.metadata } }),
|
|
60
|
+
}, { kind: 'buffer', data: bytes });
|
|
61
|
+
},
|
|
62
|
+
delete: async (key, etag, options) => {
|
|
63
|
+
options?.signal?.throwIfAborted();
|
|
64
|
+
const entry = adapter.raw.get(key);
|
|
65
|
+
if (entry === undefined || entry.etag !== etag)
|
|
66
|
+
throw new FilesError('Conflict', 'Memory object condition did not match.');
|
|
67
|
+
adapter.raw.delete(key);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return Object.assign(adapter, { conditional: Object.freeze(conditional) });
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/files-sdk/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createStoredFile,\n FilesError,\n type AdapterConditionalOperations,\n type AdapterUploadOptions,\n type Body,\n} from 'files-sdk';\nimport { memory, type MemoryAdapter, type MemoryEntry } from 'files-sdk/memory';\nimport { createHash } from 'node:crypto';\n\nconst decorated = new WeakSet<MemoryAdapter>();\n\n/**\n * Reuses files-sdk buffering/metadata; comparisons and Map replacement share a\n * synchronous linearization point AFTER body consumption. raw stays host-only.\n */\nexport function withMemoryConditionalOperations(\n adapter: MemoryAdapter,\n): MemoryAdapter {\n if (decorated.has(adapter)) return adapter;\n decorated.add(adapter);\n // Upstream memory uses a quoted 32-bit content checksum. Give every publication\n // (ordinary, copy, resumable or conditional) a canonical SHA-256 identity at\n // the Map's synchronous set boundary; uploads return this same entry object.\n const set = adapter.raw.set.bind(adapter.raw);\n Object.defineProperty(adapter.raw, 'set', {\n value: (key: string, entry: MemoryEntry) => {\n entry.etag = createHash('sha256').update(entry.bytes).digest('hex');\n return set(key, entry);\n },\n });\n for (const [key, entry] of adapter.raw) adapter.raw.set(key, entry);\n const upload = async (\n key: string,\n body: Body,\n expectedEtag: string | null,\n options?: AdapterUploadOptions,\n ) => {\n options?.signal?.throwIfAborted();\n // An isolated upstream store performs its usual body conversion and hashing.\n const staged = memory();\n const result = await staged.upload(key, body, options);\n options?.signal?.throwIfAborted();\n const current = adapter.raw.get(key);\n if (\n expectedEtag === null\n ? current !== undefined\n : current?.etag !== expectedEtag\n )\n throw new FilesError(\n 'Conflict',\n 'Memory object condition did not match.',\n );\n // No await/callback between the comparison and publication, including when\n // ordinary upstream writes race this conditional operation.\n adapter.raw.set(key, staged.raw.get(key)!);\n return { ...result, etag: staged.raw.get(key)!.etag };\n };\n const conditional: AdapterConditionalOperations = {\n create: (key, body, options) => upload(key, body, null, options),\n replace: (key, body, etag, options) => upload(key, body, etag, options),\n exactRead: async (key, etag, options) => {\n options?.signal?.throwIfAborted();\n const entry = adapter.raw.get(key);\n if (entry === undefined || entry.etag !== etag)\n throw new FilesError(\n 'Conflict',\n 'Memory object condition did not match.',\n );\n const range = options?.range;\n const bytes = entry.bytes.slice(\n range?.start ?? 0,\n range?.end === undefined ? undefined : range.end + 1,\n );\n return createStoredFile(\n {\n key,\n etag: entry.etag,\n size: bytes.byteLength,\n type: entry.contentType,\n lastModified: entry.lastModified,\n ...(entry.metadata === undefined\n ? {}\n : { metadata: { ...entry.metadata } }),\n },\n { kind: 'buffer', data: bytes },\n );\n },\n delete: async (key, etag, options) => {\n options?.signal?.throwIfAborted();\n const entry = adapter.raw.get(key);\n if (entry === undefined || entry.etag !== etag)\n throw new FilesError(\n 'Conflict',\n 'Memory object condition did not match.',\n );\n adapter.raw.delete(key);\n },\n };\n return Object.assign(adapter, { conditional: Object.freeze(conditional) });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/files-sdk/provider/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAKL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAa,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMnE,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;AAEvE,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,qBAAqB,CAAC,OAAO,CAAC,EAC9B,SAAS,CACV;IACC,wFAAwF;IACxF,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,qBAAqB,CAAC,CAoBhC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/files-sdk/provider/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAKL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAa,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMnE,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;AAEvE,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CACxD,qBAAqB,CAAC,OAAO,CAAC,EAC9B,SAAS,CACV;IACC,wFAAwF;IACxF,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,qBAAqB,CAAC,CAoBhC;AA2HD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,mBAAmB,CAE7E;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,IAAI,SAAS,QAAQ,EAAE,CAE1D;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAErE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjE;AAED,6EAA6E;AAC7E,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvE;AAED,YAAY,EACV,MAAM,IAAI,qBAAqB,EAC/B,QAAQ,IAAI,mBAAmB,GAChC,MAAM,qBAAqB,CAAC"}
|
|
@@ -62,6 +62,10 @@ async function resolveAdapter(provider, config, s3ProviderProfile) {
|
|
|
62
62
|
throw mapFilesSdkError(error);
|
|
63
63
|
}
|
|
64
64
|
const { adapter } = resolved.files;
|
|
65
|
+
if (provider === 'memory') {
|
|
66
|
+
const { withMemoryConditionalOperations } = await import('../memory.js');
|
|
67
|
+
return withMemoryConditionalOperations(adapter);
|
|
68
|
+
}
|
|
65
69
|
if (provider === 'fs') {
|
|
66
70
|
const { withFsConditionalMutation } = await import('../fs/index.js');
|
|
67
71
|
return withFsConditionalMutation(adapter);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/files-sdk/provider/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Adapter } from 'files-sdk';\nimport { loadFiles, type LoadFilesOptions } from 'files-sdk/loader';\nimport {\n PROVIDER_NAMES,\n getProvider,\n getSecretEnvVars,\n listEnvVars,\n type EnvVar,\n type Provider,\n type ProviderSlug,\n} from 'files-sdk/providers';\n\nimport { StorageError, StorageErrorCode } from '../../storage.error.js';\nimport {\n createFilesSdkDriver,\n mapFilesSdkError,\n type FilesSdkDriverOptions,\n type FilesSdkStorageDriver,\n} from '../files-sdk.driver.js';\nimport type { S3Adapter, S3ProviderProfile } from '../s3/index.js';\nimport {\n recordS3ConditionalRequestPermission,\n recordS3ConstructionMetadata,\n} from '../s3/construction-metadata.js';\n\n/** Slug of a storage provider this package can build a driver for. */\nexport type StorageProviderName = ProviderSlug;\n\n/**\n * Flat, provider-specific settings — `bucket` and `region` for an object store,\n * `root` for the filesystem, `accountName` and `container` for Azure, and so\n * on. Every provider reads what it needs and ignores the rest, so one config\n * shape serves a deployment that switches providers by name.\n *\n * Credentials may be omitted for any provider whose SDK resolves its own chain\n * (an IAM role, Application Default Credentials, a shared profile).\n */\nexport type StorageProviderConfig = Omit<LoadFilesOptions, 'provider'>;\n\nexport interface ProviderStorageDriverOptions extends Omit<\n FilesSdkDriverOptions<Adapter>,\n 'adapter'\n> {\n /** Which provider to build. Validate untrusted input with {@link isStorageProvider}. */\n provider: StorageProviderName;\n config?: StorageProviderConfig;\n /** Verified operation profile for the `s3` slug, especially custom endpoints. */\n s3ProviderProfile?: S3ProviderProfile;\n}\n\n/**\n * Builds a {@link FilesSdkStorageDriver} for a provider named at runtime,\n * importing that provider's adapter — and only that one — on demand. A\n * deployment selects its store with a string (`'s3'`, `'gcs'`, `'azure'`,\n * `'r2'`, `'fs'`, …) and installs one native SDK, instead of the application\n * hard-coding a driver per backend.\n *\n * The `s3` slug additionally gets the exact verified provider profile and\n * signed-policy capabilities {@link createS3StorageDriver} attaches. Providers\n * not backed by the AWS S3 SDK expose what their adapter declares;\n * noncanonical S3-backed providers default to an unverified, read-only profile.\n * Import `@nestm/storage/files-sdk/s3` directly when the provider is known at\n * build time and the extra indirection buys nothing.\n *\n * @throws StorageError `INVALID_ARGUMENT` for an unknown slug, and whatever the\n * adapter reports (mapped) when required config or credentials are missing.\n */\nexport async function createProviderStorageDriver(\n options: ProviderStorageDriverOptions,\n): Promise<FilesSdkStorageDriver> {\n const { provider, config, s3ProviderProfile, ...filesOptions } = options;\n if (!isStorageProvider(provider)) {\n throw unknownProvider(provider);\n }\n\n // `loadFiles` owns the slug → adapter mapping and keeps the import lazy; the\n // client it returns is discarded so the caller's own driver options (prefix,\n // hooks, plugins, readonly, retries) apply to the instance the bridge builds.\n if (s3ProviderProfile !== undefined && provider !== 's3') {\n throw new StorageError(\n 's3ProviderProfile can only be used with the \"s3\" provider.',\n { code: StorageErrorCode.INVALID_ARGUMENT, permanent: true },\n );\n }\n const adapter = await resolveAdapter(provider, config, s3ProviderProfile);\n return createFilesSdkDriver({\n ...filesOptions,\n adapter,\n });\n}\n\nfunction isS3BackedAdapter(adapter: Adapter): adapter is S3Adapter {\n try {\n const raw = adapter.raw;\n if (typeof raw !== 'object' || raw === null) return false;\n const candidate = raw as {\n readonly config?: unknown;\n readonly send?: unknown;\n };\n return (\n typeof candidate.send === 'function' &&\n typeof candidate.config === 'object' &&\n candidate.config !== null &&\n (candidate.config as { readonly serviceId?: unknown }).serviceId === 'S3'\n );\n } catch {\n return false;\n }\n}\n\nasync function resolveAdapter(\n provider: StorageProviderName,\n config: StorageProviderConfig | undefined,\n s3ProviderProfile: S3ProviderProfile | undefined,\n): Promise<Adapter> {\n const loaderConfig = withVerifiedS3ConditionalOptIn(\n provider,\n config,\n s3ProviderProfile,\n );\n let resolved;\n try {\n resolved = await loadFiles({ ...loaderConfig, provider });\n } catch (error) {\n throw mapFilesSdkError(error);\n }\n const { adapter } = resolved.files;\n if (provider === 'fs') {\n const { withFsConditionalMutation } = await import('../fs/index.js');\n return withFsConditionalMutation(\n adapter as Parameters<typeof withFsConditionalMutation>[0],\n );\n }\n if (!isS3BackedAdapter(adapter)) {\n return adapter;\n }\n // Every AWS-SDK-backed wrapper gets fail-closed S3 provenance and endpoint\n // hardening. Only the canonical `s3` slug may infer native AWS or accept an\n // explicit verified profile; named compatibles remain unverified/read-only.\n const { withS3Capabilities } = await import('../s3/index.js');\n const configJsonPublicBaseUrl = config?.configJson?.publicBaseUrl;\n const publicBaseUrl =\n config?.publicBaseUrl ??\n (typeof configJsonPublicBaseUrl === 'string'\n ? configJsonPublicBaseUrl\n : undefined);\n const s3Adapter = adapter as Parameters<typeof withS3Capabilities>[0];\n if (provider === 's3') {\n recordS3ConditionalRequestPermission(\n s3Adapter.raw,\n config?.configJson?.conditional !== false,\n );\n }\n recordS3ConstructionMetadata(s3Adapter.raw, {\n publicBaseUrlConfigured:\n provider !== 's3' ||\n config?.publicBaseUrl !== undefined ||\n configJsonPublicBaseUrl !== undefined,\n });\n return withS3Capabilities(s3Adapter, {\n ...(typeof config?.endpoint === 'string' && {\n endpoint: config.endpoint,\n }),\n ...(publicBaseUrl !== undefined && {\n publicBaseUrl,\n }),\n ...(s3ProviderProfile !== undefined && {\n providerProfile: s3ProviderProfile,\n }),\n });\n}\n\nfunction withVerifiedS3ConditionalOptIn(\n provider: StorageProviderName,\n config: StorageProviderConfig | undefined,\n s3ProviderProfile: S3ProviderProfile | undefined,\n): StorageProviderConfig | undefined {\n const configJsonEndpoint = config?.configJson?.endpoint;\n const hasCustomEndpoint =\n config?.endpoint !== undefined || configJsonEndpoint !== undefined;\n if (\n provider !== 's3' ||\n s3ProviderProfile === undefined ||\n config === undefined ||\n !hasCustomEndpoint ||\n config.configJson?.conditional !== undefined\n ) {\n return config;\n }\n\n // files-sdk requires this construction-time opt-in before it exposes native\n // conditional primitives for a custom endpoint. The branded provider profile\n // still narrows the exposed operations in withS3Capabilities below.\n return {\n ...config,\n configJson: { ...config.configJson, conditional: true },\n };\n}\n\nfunction unknownProvider(provider: string): StorageError {\n return new StorageError(\n `Unknown storage provider: ${JSON.stringify(provider)}. Known providers: ${PROVIDER_NAMES.join(', ')}.`,\n { code: StorageErrorCode.INVALID_ARGUMENT, permanent: true },\n );\n}\n\n/** Narrows an untrusted string — an env var, a config file — to a known slug. */\nexport function isStorageProvider(value: string): value is StorageProviderName {\n return getProvider(value) !== undefined;\n}\n\n/** Every provider that can be named, sorted by slug. */\nexport function listStorageProviders(): readonly Provider[] {\n return PROVIDER_NAMES.flatMap((slug) => getProvider(slug) ?? []);\n}\n\n/** One provider's display name, description, native SDKs, and env contract. */\nexport function getStorageProvider(slug: string): Provider | undefined {\n return getProvider(slug);\n}\n\n/**\n * Every environment variable a provider reads, flattened across required, all\n * credential modes, and optional. Useful for validating a deployment's config\n * before the first upload rather than at it.\n */\nexport function listStorageProviderEnvVars(slug: string): EnvVar[] {\n return listEnvVars(slug);\n}\n\n/** The subset of {@link listStorageProviderEnvVars} that carries secrets. */\nexport function listStorageProviderSecretEnvVars(slug: string): EnvVar[] {\n return getSecretEnvVars(slug);\n}\n\nexport type {\n EnvVar as StorageProviderEnvVar,\n Provider as StorageProviderInfo,\n} from 'files-sdk/providers';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/files-sdk/provider/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Adapter } from 'files-sdk';\nimport { loadFiles, type LoadFilesOptions } from 'files-sdk/loader';\nimport {\n PROVIDER_NAMES,\n getProvider,\n getSecretEnvVars,\n listEnvVars,\n type EnvVar,\n type Provider,\n type ProviderSlug,\n} from 'files-sdk/providers';\n\nimport { StorageError, StorageErrorCode } from '../../storage.error.js';\nimport {\n createFilesSdkDriver,\n mapFilesSdkError,\n type FilesSdkDriverOptions,\n type FilesSdkStorageDriver,\n} from '../files-sdk.driver.js';\nimport type { S3Adapter, S3ProviderProfile } from '../s3/index.js';\nimport {\n recordS3ConditionalRequestPermission,\n recordS3ConstructionMetadata,\n} from '../s3/construction-metadata.js';\n\n/** Slug of a storage provider this package can build a driver for. */\nexport type StorageProviderName = ProviderSlug;\n\n/**\n * Flat, provider-specific settings — `bucket` and `region` for an object store,\n * `root` for the filesystem, `accountName` and `container` for Azure, and so\n * on. Every provider reads what it needs and ignores the rest, so one config\n * shape serves a deployment that switches providers by name.\n *\n * Credentials may be omitted for any provider whose SDK resolves its own chain\n * (an IAM role, Application Default Credentials, a shared profile).\n */\nexport type StorageProviderConfig = Omit<LoadFilesOptions, 'provider'>;\n\nexport interface ProviderStorageDriverOptions extends Omit<\n FilesSdkDriverOptions<Adapter>,\n 'adapter'\n> {\n /** Which provider to build. Validate untrusted input with {@link isStorageProvider}. */\n provider: StorageProviderName;\n config?: StorageProviderConfig;\n /** Verified operation profile for the `s3` slug, especially custom endpoints. */\n s3ProviderProfile?: S3ProviderProfile;\n}\n\n/**\n * Builds a {@link FilesSdkStorageDriver} for a provider named at runtime,\n * importing that provider's adapter — and only that one — on demand. A\n * deployment selects its store with a string (`'s3'`, `'gcs'`, `'azure'`,\n * `'r2'`, `'fs'`, …) and installs one native SDK, instead of the application\n * hard-coding a driver per backend.\n *\n * The `s3` slug additionally gets the exact verified provider profile and\n * signed-policy capabilities {@link createS3StorageDriver} attaches. Providers\n * not backed by the AWS S3 SDK expose what their adapter declares;\n * noncanonical S3-backed providers default to an unverified, read-only profile.\n * Import `@nestm/storage/files-sdk/s3` directly when the provider is known at\n * build time and the extra indirection buys nothing.\n *\n * @throws StorageError `INVALID_ARGUMENT` for an unknown slug, and whatever the\n * adapter reports (mapped) when required config or credentials are missing.\n */\nexport async function createProviderStorageDriver(\n options: ProviderStorageDriverOptions,\n): Promise<FilesSdkStorageDriver> {\n const { provider, config, s3ProviderProfile, ...filesOptions } = options;\n if (!isStorageProvider(provider)) {\n throw unknownProvider(provider);\n }\n\n // `loadFiles` owns the slug → adapter mapping and keeps the import lazy; the\n // client it returns is discarded so the caller's own driver options (prefix,\n // hooks, plugins, readonly, retries) apply to the instance the bridge builds.\n if (s3ProviderProfile !== undefined && provider !== 's3') {\n throw new StorageError(\n 's3ProviderProfile can only be used with the \"s3\" provider.',\n { code: StorageErrorCode.INVALID_ARGUMENT, permanent: true },\n );\n }\n const adapter = await resolveAdapter(provider, config, s3ProviderProfile);\n return createFilesSdkDriver({\n ...filesOptions,\n adapter,\n });\n}\n\nfunction isS3BackedAdapter(adapter: Adapter): adapter is S3Adapter {\n try {\n const raw = adapter.raw;\n if (typeof raw !== 'object' || raw === null) return false;\n const candidate = raw as {\n readonly config?: unknown;\n readonly send?: unknown;\n };\n return (\n typeof candidate.send === 'function' &&\n typeof candidate.config === 'object' &&\n candidate.config !== null &&\n (candidate.config as { readonly serviceId?: unknown }).serviceId === 'S3'\n );\n } catch {\n return false;\n }\n}\n\nasync function resolveAdapter(\n provider: StorageProviderName,\n config: StorageProviderConfig | undefined,\n s3ProviderProfile: S3ProviderProfile | undefined,\n): Promise<Adapter> {\n const loaderConfig = withVerifiedS3ConditionalOptIn(\n provider,\n config,\n s3ProviderProfile,\n );\n let resolved;\n try {\n resolved = await loadFiles({ ...loaderConfig, provider });\n } catch (error) {\n throw mapFilesSdkError(error);\n }\n const { adapter } = resolved.files;\n if (provider === 'memory') {\n const { withMemoryConditionalOperations } = await import('../memory.js');\n return withMemoryConditionalOperations(\n adapter as Parameters<typeof withMemoryConditionalOperations>[0],\n );\n }\n if (provider === 'fs') {\n const { withFsConditionalMutation } = await import('../fs/index.js');\n return withFsConditionalMutation(\n adapter as Parameters<typeof withFsConditionalMutation>[0],\n );\n }\n if (!isS3BackedAdapter(adapter)) {\n return adapter;\n }\n // Every AWS-SDK-backed wrapper gets fail-closed S3 provenance and endpoint\n // hardening. Only the canonical `s3` slug may infer native AWS or accept an\n // explicit verified profile; named compatibles remain unverified/read-only.\n const { withS3Capabilities } = await import('../s3/index.js');\n const configJsonPublicBaseUrl = config?.configJson?.publicBaseUrl;\n const publicBaseUrl =\n config?.publicBaseUrl ??\n (typeof configJsonPublicBaseUrl === 'string'\n ? configJsonPublicBaseUrl\n : undefined);\n const s3Adapter = adapter as Parameters<typeof withS3Capabilities>[0];\n if (provider === 's3') {\n recordS3ConditionalRequestPermission(\n s3Adapter.raw,\n config?.configJson?.conditional !== false,\n );\n }\n recordS3ConstructionMetadata(s3Adapter.raw, {\n publicBaseUrlConfigured:\n provider !== 's3' ||\n config?.publicBaseUrl !== undefined ||\n configJsonPublicBaseUrl !== undefined,\n });\n return withS3Capabilities(s3Adapter, {\n ...(typeof config?.endpoint === 'string' && {\n endpoint: config.endpoint,\n }),\n ...(publicBaseUrl !== undefined && {\n publicBaseUrl,\n }),\n ...(s3ProviderProfile !== undefined && {\n providerProfile: s3ProviderProfile,\n }),\n });\n}\n\nfunction withVerifiedS3ConditionalOptIn(\n provider: StorageProviderName,\n config: StorageProviderConfig | undefined,\n s3ProviderProfile: S3ProviderProfile | undefined,\n): StorageProviderConfig | undefined {\n const configJsonEndpoint = config?.configJson?.endpoint;\n const hasCustomEndpoint =\n config?.endpoint !== undefined || configJsonEndpoint !== undefined;\n if (\n provider !== 's3' ||\n s3ProviderProfile === undefined ||\n config === undefined ||\n !hasCustomEndpoint ||\n config.configJson?.conditional !== undefined\n ) {\n return config;\n }\n\n // files-sdk requires this construction-time opt-in before it exposes native\n // conditional primitives for a custom endpoint. The branded provider profile\n // still narrows the exposed operations in withS3Capabilities below.\n return {\n ...config,\n configJson: { ...config.configJson, conditional: true },\n };\n}\n\nfunction unknownProvider(provider: string): StorageError {\n return new StorageError(\n `Unknown storage provider: ${JSON.stringify(provider)}. Known providers: ${PROVIDER_NAMES.join(', ')}.`,\n { code: StorageErrorCode.INVALID_ARGUMENT, permanent: true },\n );\n}\n\n/** Narrows an untrusted string — an env var, a config file — to a known slug. */\nexport function isStorageProvider(value: string): value is StorageProviderName {\n return getProvider(value) !== undefined;\n}\n\n/** Every provider that can be named, sorted by slug. */\nexport function listStorageProviders(): readonly Provider[] {\n return PROVIDER_NAMES.flatMap((slug) => getProvider(slug) ?? []);\n}\n\n/** One provider's display name, description, native SDKs, and env contract. */\nexport function getStorageProvider(slug: string): Provider | undefined {\n return getProvider(slug);\n}\n\n/**\n * Every environment variable a provider reads, flattened across required, all\n * credential modes, and optional. Useful for validating a deployment's config\n * before the first upload rather than at it.\n */\nexport function listStorageProviderEnvVars(slug: string): EnvVar[] {\n return listEnvVars(slug);\n}\n\n/** The subset of {@link listStorageProviderEnvVars} that carries secrets. */\nexport function listStorageProviderSecretEnvVars(slug: string): EnvVar[] {\n return getSecretEnvVars(slug);\n}\n\nexport type {\n EnvVar as StorageProviderEnvVar,\n Provider as StorageProviderInfo,\n} from 'files-sdk/providers';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CACtD,qBAAqB,CAAC,aAAa,CAAC,EACpC,SAAS,CACV;IACC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,aAAa,CAMf;AAED,OAAO,EACL,qCAAqC,EACrC,KAAK,sCAAsC,EAC3C,KAAK,8BAA8B,EACnC,KAAK,oCAAoC,EACzC,KAAK,iCAAiC,EACtC,KAAK,iCAAiC,GACvC,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/testing/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { withMemoryConditionalOperations } from '../files-sdk/memory.js';
|
|
1
2
|
import { createFilesSdkDriver, } from '../files-sdk/files-sdk.driver.js';
|
|
2
3
|
import { memory, } from 'files-sdk/memory';
|
|
3
4
|
export function createMemoryStorageDriver(options = {}) {
|
|
4
5
|
const { adapter, ...filesOptions } = options;
|
|
5
6
|
return createFilesSdkDriver({
|
|
6
7
|
...filesOptions,
|
|
7
|
-
adapter: memory(adapter),
|
|
8
|
+
adapter: withMemoryConditionalOperations(memory(adapter)),
|
|
8
9
|
});
|
|
9
10
|
}
|
|
10
11
|
export { createStorageProviderConformanceCases, } from './provider-conformance.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,GAErB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,MAAM,GAGP,MAAM,kBAAkB,CAAC;AAS1B,MAAM,UAAU,yBAAyB,CACvC,OAAO,GAA+B,EAAE;IAExC,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC;IAC7C,OAAO,oBAAoB,CAAC;QAC1B,GAAG,YAAY;QACf,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EACL,oBAAoB,GAErB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,MAAM,GAGP,MAAM,kBAAkB,CAAC;AAS1B,MAAM,UAAU,yBAAyB,CACvC,OAAO,GAA+B,EAAE;IAExC,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC;IAC7C,OAAO,oBAAoB,CAAC;QAC1B,GAAG,YAAY;QACf,OAAO,EAAE,+BAA+B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;KAC1D,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EACL,qCAAqC,GAMtC,MAAM,2BAA2B,CAAC","sourcesContent":["import type { StorageDriver } from '../storage.driver.js';\nimport { withMemoryConditionalOperations } from '../files-sdk/memory.js';\nimport {\n createFilesSdkDriver,\n type FilesSdkDriverOptions,\n} from '../files-sdk/files-sdk.driver.js';\nimport {\n memory,\n type MemoryAdapter,\n type MemoryAdapterOptions,\n} from 'files-sdk/memory';\n\nexport interface MemoryStorageDriverOptions extends Omit<\n FilesSdkDriverOptions<MemoryAdapter>,\n 'adapter'\n> {\n adapter?: MemoryAdapterOptions;\n}\n\nexport function createMemoryStorageDriver(\n options: MemoryStorageDriverOptions = {},\n): StorageDriver {\n const { adapter, ...filesOptions } = options;\n return createFilesSdkDriver({\n ...filesOptions,\n adapter: withMemoryConditionalOperations(memory(adapter)),\n });\n}\n\nexport {\n createStorageProviderConformanceCases,\n type StorageProviderConformanceCapabilities,\n type StorageProviderConformanceCase,\n type StorageProviderConformanceCaseResult,\n type StorageProviderConformanceFixture,\n type StorageProviderConformanceOptions,\n} from './provider-conformance.js';\n\nexport type { MemoryAdapterOptions, MemorySeed } from 'files-sdk/memory';\n"]}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export { StorageWorkspaceError, isStorageWorkspaceError, } from './storage-workspace.error.js';
|
|
2
|
+
export { StorageFileWorkflow } from './storage-file-workflow.js';
|
|
3
|
+
export { DEFAULT_STORAGE_FILE_WORKFLOW_LIMITS } from './storage-file-workflow.types.js';
|
|
4
|
+
export type * from './storage-file-workflow.types.js';
|
|
5
|
+
export type * from './storage-file-catalog.types.js';
|
|
6
|
+
export { protectStorageFileWorkflowWorkspace, protectStorageFileWorkflowOperation, getStorageFileWorkflow, getStorageFileCatalog, type StorageFileWorkflowProtection, type StorageFileWorkflowWorkspace, type ProtectStorageFileWorkflowWorkspaceOptions, } from './storage-file-workflow.protection.js';
|
|
2
7
|
export { Aes256GcmStorageWorkspaceCursorCodec, STORAGE_WORKSPACE_CURSOR_VERSION, STORAGE_WORKSPACE_MAX_CURSOR_BYTES, type Aes256GcmStorageWorkspaceCursorCodecOptions, type StorageWorkspaceCursorCodec, type StorageWorkspaceCursorConfiguration, type StorageWorkspaceCursorEncodeOptions, } from './storage-workspace.cursor.js';
|
|
3
8
|
export { createStorageWorkspace, mountStorageWorkspace, } from './storage-workspace.js';
|
|
4
9
|
export { DEFAULT_STORAGE_WORKSPACE_LIMITS, STORAGE_WORKSPACE_PERMISSIONS, type MountStorageWorkspaceOptions, type StorageWorkspaceBody, type StorageWorkspaceByteFile, type StorageWorkspaceCopyOptions, type StorageWorkspaceDeleteOptions, type StorageWorkspace, type StorageWorkspaceDirectory, type StorageWorkspaceEntry, type StorageWorkspaceFile, type StorageWorkspaceLimits, type StorageWorkspaceListOptions, type StorageWorkspaceMountOptions, type StorageWorkspaceMutationOptions, type StorageWorkspaceOverwriteOptions, type StorageWorkspacePage, type StorageWorkspacePermission, type StorageWorkspaceReadOptions, type StorageWorkspaceSearchMatch, type StorageWorkspaceSearchOptions, type StorageWorkspaceTextFile, type StorageWorkspaceUnconditionalDeleteOptions, type StorageWorkspaceWriteOptions, } from './storage-workspace.types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,kCAAkC,EAClC,KAAK,2CAA2C,EAChD,KAAK,2BAA2B,EAChC,KAAK,mCAAmC,EACxC,KAAK,mCAAmC,GACzC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,KAAK,4BAA4B,EACjC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,0CAA0C,EAC/C,KAAK,4BAA4B,GAClC,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,oCAAoC,EAAE,MAAM,kCAAkC,CAAC;AACxF,mBAAmB,kCAAkC,CAAC;AACtD,mBAAmB,iCAAiC,CAAC;AACrD,OAAO,EACL,mCAAmC,EACnC,mCAAmC,EACnC,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EACjC,KAAK,0CAA0C,GAChD,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,oCAAoC,EACpC,gCAAgC,EAChC,kCAAkC,EAClC,KAAK,2CAA2C,EAChD,KAAK,2BAA2B,EAChC,KAAK,mCAAmC,EACxC,KAAK,mCAAmC,GACzC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gCAAgC,EAChC,6BAA6B,EAC7B,KAAK,4BAA4B,EACjC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,0CAA0C,EAC/C,KAAK,4BAA4B,GAClC,MAAM,8BAA8B,CAAC"}
|
package/dist/workspace/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { StorageWorkspaceError, isStorageWorkspaceError, } from './storage-workspace.error.js';
|
|
2
|
+
export { StorageFileWorkflow } from './storage-file-workflow.js';
|
|
3
|
+
export { DEFAULT_STORAGE_FILE_WORKFLOW_LIMITS } from './storage-file-workflow.types.js';
|
|
4
|
+
export { protectStorageFileWorkflowWorkspace, protectStorageFileWorkflowOperation, getStorageFileWorkflow, getStorageFileCatalog, } from './storage-file-workflow.protection.js';
|
|
2
5
|
export { Aes256GcmStorageWorkspaceCursorCodec, STORAGE_WORKSPACE_CURSOR_VERSION, STORAGE_WORKSPACE_MAX_CURSOR_BYTES, } from './storage-workspace.cursor.js';
|
|
3
6
|
export { createStorageWorkspace, mountStorageWorkspace, } from './storage-workspace.js';
|
|
4
7
|
export { DEFAULT_STORAGE_WORKSPACE_LIMITS, STORAGE_WORKSPACE_PERMISSIONS, } from './storage-workspace.types.js';
|