@nestm/storage 0.1.0-alpha.1 → 0.1.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +271 -0
- package/README.md +877 -25
- package/SECURITY.md +128 -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/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 +491 -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 +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/storage-staged-content.d.ts +37 -0
- package/dist/core/storage-staged-content.d.ts.map +1 -0
- package/dist/core/storage-staged-content.js +94 -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/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 +151 -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/index.d.ts +1 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.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 +15 -3
- 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 +10 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +8 -0
- package/dist/workspace/index.js.map +1 -0
- 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/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 +94 -20
|
@@ -1,29 +1,609 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
-
import { Files, FilesError, } from 'files-sdk';
|
|
3
|
-
import { StorageError, StorageErrorCode } from '../storage.error.js';
|
|
2
|
+
import { Files, FilesError, handlers, } from 'files-sdk';
|
|
3
|
+
import { StorageError, StorageErrorCode, isStorageError, } from '../storage.error.js';
|
|
4
|
+
import { isCanonicalStorageEtag, normalizeProviderStorageEtag, } from '../storage-etag.js';
|
|
4
5
|
import { getFilesSdkUploadControl } from '../storage-upload-control.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Detects caller policy that must not be bypassed by NestM-only conditional
|
|
8
|
+
* fallbacks that Files SDK cannot represent (versions, multipart completion,
|
|
9
|
+
* or a copy with only one side conditioned).
|
|
10
|
+
*/
|
|
11
|
+
function hasCallerFilesOperationPolicy(options) {
|
|
12
|
+
const hooks = options.hooks;
|
|
13
|
+
return ((options.plugins?.length ?? 0) > 0 ||
|
|
14
|
+
typeof hooks?.onAction === 'function' ||
|
|
15
|
+
typeof hooks?.onError === 'function' ||
|
|
16
|
+
typeof hooks?.onRetry === 'function' ||
|
|
17
|
+
(options.receipts !== undefined && options.receipts !== false));
|
|
18
|
+
}
|
|
19
|
+
const FILES_SDK_S3_RESERVED_EXTENSION_KEYS = [
|
|
20
|
+
'conditionalCopyDestination',
|
|
21
|
+
'conditionalCopySource',
|
|
22
|
+
'conditionalCreate',
|
|
23
|
+
'conditionalDelete',
|
|
24
|
+
'conditionalMultipartCompletion',
|
|
25
|
+
'conditionalRead',
|
|
26
|
+
'conditionalReplace',
|
|
27
|
+
'deleteConditional',
|
|
28
|
+
'downloadConditional',
|
|
29
|
+
'physicalKey',
|
|
30
|
+
'promote',
|
|
31
|
+
'signedDownloadPolicy',
|
|
32
|
+
'signedUploadPolicy',
|
|
33
|
+
'uploadConditional',
|
|
34
|
+
];
|
|
35
|
+
const FILES_SDK_S3_AUTHORITY_KEYS = [
|
|
36
|
+
...FILES_SDK_S3_RESERVED_EXTENSION_KEYS,
|
|
37
|
+
'bucket',
|
|
38
|
+
'conditional',
|
|
39
|
+
'copy',
|
|
40
|
+
'delete',
|
|
41
|
+
'deleteMany',
|
|
42
|
+
'download',
|
|
43
|
+
'exists',
|
|
44
|
+
'head',
|
|
45
|
+
'list',
|
|
46
|
+
'move',
|
|
47
|
+
'reportsUploadProgress',
|
|
48
|
+
'resumableUpload',
|
|
49
|
+
'signedUploadUrl',
|
|
50
|
+
'signedUrl',
|
|
51
|
+
'supportsCacheControl',
|
|
52
|
+
'supportsDelimiter',
|
|
53
|
+
'supportsMetadata',
|
|
54
|
+
'supportsRange',
|
|
55
|
+
'supportsServerSideCopy',
|
|
56
|
+
'upload',
|
|
57
|
+
'url',
|
|
58
|
+
];
|
|
59
|
+
const s3AdapterProvenance = new WeakMap();
|
|
60
|
+
const undecoratedS3Adapters = new WeakSet();
|
|
61
|
+
const s3AdapterAuthority = new WeakMap();
|
|
62
|
+
const s3MethodAuthority = new WeakMap();
|
|
63
|
+
const s3AuthorityState = new WeakMap();
|
|
64
|
+
const conflictingS3Authority = Symbol('conflictingS3Authority');
|
|
65
|
+
function adapterRawObject(adapter) {
|
|
66
|
+
try {
|
|
67
|
+
const raw = adapter.raw;
|
|
68
|
+
return (typeof raw === 'object' && raw !== null) ||
|
|
69
|
+
typeof raw === 'function'
|
|
70
|
+
? raw
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function s3AdapterAuthorityTokenOf(adapter) {
|
|
78
|
+
let resolved = s3AdapterAuthority.get(adapter);
|
|
79
|
+
const consider = (token) => {
|
|
80
|
+
if (token === undefined)
|
|
81
|
+
return true;
|
|
82
|
+
if (resolved !== undefined && resolved !== token)
|
|
83
|
+
return false;
|
|
84
|
+
resolved = token;
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
const candidate = adapter;
|
|
89
|
+
for (const key of FILES_SDK_S3_AUTHORITY_KEYS) {
|
|
90
|
+
if (!(key in candidate))
|
|
91
|
+
continue;
|
|
92
|
+
const value = candidate[key];
|
|
93
|
+
if (typeof value === 'function' &&
|
|
94
|
+
!consider(s3MethodAuthority.get(value))) {
|
|
95
|
+
return conflictingS3Authority;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return conflictingS3Authority;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function bindS3AdapterAuthorityMethods(adapter, token) {
|
|
105
|
+
const candidate = adapter;
|
|
106
|
+
for (const key of FILES_SDK_S3_AUTHORITY_KEYS) {
|
|
107
|
+
if (!(key in candidate))
|
|
108
|
+
continue;
|
|
109
|
+
const value = candidate[key];
|
|
110
|
+
if (typeof value !== 'function')
|
|
111
|
+
continue;
|
|
112
|
+
const existing = s3MethodAuthority.get(value);
|
|
113
|
+
if (existing !== undefined && existing !== token) {
|
|
114
|
+
throw new TypeError('S3 adapter methods have conflicting authority.');
|
|
115
|
+
}
|
|
116
|
+
s3MethodAuthority.set(value, token);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function attachS3AdapterAuthority(adapter, raw) {
|
|
120
|
+
const existing = s3AdapterAuthorityTokenOf(adapter);
|
|
121
|
+
if (existing === conflictingS3Authority) {
|
|
122
|
+
throw new TypeError('S3 adapter authority is inconsistent.');
|
|
123
|
+
}
|
|
124
|
+
const token = existing ?? Object.freeze({});
|
|
125
|
+
const state = s3AuthorityState.get(token);
|
|
126
|
+
if (state !== undefined && state.raw !== raw) {
|
|
127
|
+
throw new TypeError('S3 adapter authority raw client cannot be changed.');
|
|
128
|
+
}
|
|
129
|
+
if (state === undefined)
|
|
130
|
+
s3AuthorityState.set(token, { raw });
|
|
131
|
+
s3AdapterAuthority.set(adapter, token);
|
|
132
|
+
bindS3AdapterAuthorityMethods(adapter, token);
|
|
133
|
+
return token;
|
|
134
|
+
}
|
|
135
|
+
function setS3AdapterProvenance(target, adapter, provenance) {
|
|
136
|
+
const existing = s3AdapterProvenanceOfRaw(target);
|
|
137
|
+
if (existing !== undefined) {
|
|
138
|
+
if (existing.provenance !== provenance ||
|
|
139
|
+
mismatchedS3Authority(adapter, existing.surface) !== undefined) {
|
|
140
|
+
throw new TypeError('S3 adapter provenance cannot be changed.');
|
|
141
|
+
}
|
|
142
|
+
s3AdapterProvenance.set(target, existing);
|
|
143
|
+
return existing;
|
|
144
|
+
}
|
|
145
|
+
const record = Object.freeze({
|
|
146
|
+
provenance,
|
|
147
|
+
surface: s3AuthoritySnapshot(adapter),
|
|
148
|
+
});
|
|
149
|
+
s3AdapterProvenance.set(target, record);
|
|
150
|
+
return record;
|
|
151
|
+
}
|
|
152
|
+
function s3AdapterProvenanceOfRaw(raw) {
|
|
153
|
+
return s3AdapterProvenance.get(raw);
|
|
154
|
+
}
|
|
155
|
+
function s3AuthoritySnapshot(adapter) {
|
|
156
|
+
const candidate = adapter;
|
|
157
|
+
return Object.freeze(Object.fromEntries(FILES_SDK_S3_AUTHORITY_KEYS.map((key) => {
|
|
158
|
+
const present = key in candidate;
|
|
159
|
+
return [
|
|
160
|
+
key,
|
|
161
|
+
Object.freeze({
|
|
162
|
+
present,
|
|
163
|
+
value: present ? candidate[key] : undefined,
|
|
164
|
+
}),
|
|
165
|
+
];
|
|
166
|
+
})));
|
|
167
|
+
}
|
|
168
|
+
function mismatchedS3Authority(adapter, snapshot) {
|
|
169
|
+
const candidate = adapter;
|
|
170
|
+
try {
|
|
171
|
+
return FILES_SDK_S3_AUTHORITY_KEYS.find((key) => {
|
|
172
|
+
const present = key in candidate;
|
|
173
|
+
const expected = snapshot[key];
|
|
174
|
+
return (present !== expected.present ||
|
|
175
|
+
!Object.is(present ? candidate[key] : undefined, expected.value));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return FILES_SDK_S3_AUTHORITY_KEYS[0];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Rejects adapters already carrying reserved storage S3 extensions. */
|
|
183
|
+
export function assertFilesSdkS3AdapterHasNoReservedExtensions(adapter) {
|
|
184
|
+
const reserved = filesSdkS3ReservedExtensionOf(adapter);
|
|
185
|
+
if (reserved !== undefined) {
|
|
186
|
+
throw new TypeError(`S3 adapter already defines reserved extension "${reserved}".`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function filesSdkS3ReservedExtensionOf(adapter) {
|
|
190
|
+
const candidate = adapter;
|
|
191
|
+
try {
|
|
192
|
+
return FILES_SDK_S3_RESERVED_EXTENSION_KEYS.find((key) => key in candidate);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return FILES_SDK_S3_RESERVED_EXTENSION_KEYS[0];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Records that an S3 adapter was constructed but not provider-decorated. */
|
|
199
|
+
export function markFilesSdkS3AdapterUndecorated(adapter) {
|
|
200
|
+
const raw = adapterRawObject(adapter);
|
|
201
|
+
if (raw === undefined) {
|
|
202
|
+
throw new TypeError('S3 adapter must expose an object raw client.');
|
|
203
|
+
}
|
|
204
|
+
attachS3AdapterAuthority(adapter, raw);
|
|
205
|
+
undecoratedS3Adapters.add(raw);
|
|
206
|
+
}
|
|
207
|
+
/** Records the provider verification state of one decorated S3 adapter. */
|
|
208
|
+
export function markFilesSdkS3AdapterProvenance(adapter, provenance) {
|
|
209
|
+
const raw = adapterRawObject(adapter);
|
|
210
|
+
if (raw === undefined) {
|
|
211
|
+
throw new TypeError('S3 adapter must expose an object raw client.');
|
|
212
|
+
}
|
|
213
|
+
markFilesSdkS3RawClientProvenance(raw, adapter, provenance);
|
|
214
|
+
}
|
|
215
|
+
/** Records final provider verification state on one stable raw S3 client. */
|
|
216
|
+
export function markFilesSdkS3RawClientProvenance(raw, adapter, provenance) {
|
|
217
|
+
const authority = attachS3AdapterAuthority(adapter, raw);
|
|
218
|
+
const record = setS3AdapterProvenance(raw, adapter, provenance);
|
|
219
|
+
s3AuthorityState.set(authority, { raw, record });
|
|
220
|
+
bindS3AdapterAuthorityMethods(adapter, authority);
|
|
221
|
+
}
|
|
222
|
+
function isUndecoratedS3Raw(raw) {
|
|
223
|
+
return undecoratedS3Adapters.has(raw);
|
|
224
|
+
}
|
|
225
|
+
function isStructuralS3Raw(raw) {
|
|
226
|
+
try {
|
|
227
|
+
const candidate = raw;
|
|
228
|
+
return (typeof candidate.send === 'function' &&
|
|
229
|
+
typeof candidate.config === 'object' &&
|
|
230
|
+
candidate.config !== null &&
|
|
231
|
+
candidate.config.serviceId === 'S3');
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function conditionalCopyAdapterOf(adapter) {
|
|
238
|
+
if (typeof adapter !== 'object' ||
|
|
239
|
+
adapter === null ||
|
|
240
|
+
!('promote' in adapter)) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const candidate = adapter;
|
|
244
|
+
const source = candidate.conditionalCopySource;
|
|
245
|
+
const destination = candidate.conditionalCopyDestination;
|
|
246
|
+
if ((source === undefined && destination === undefined) ||
|
|
247
|
+
(source !== undefined &&
|
|
248
|
+
(typeof source.etag !== 'boolean' ||
|
|
249
|
+
typeof source.version !== 'boolean' ||
|
|
250
|
+
(source.requiresDestinationPredicate !== undefined &&
|
|
251
|
+
typeof source.requiresDestinationPredicate !== 'boolean'))) ||
|
|
252
|
+
(destination !== undefined &&
|
|
253
|
+
(typeof destination.create !== 'boolean' ||
|
|
254
|
+
typeof destination.replace !== 'boolean' ||
|
|
255
|
+
(destination.requiresSourcePredicate !== undefined &&
|
|
256
|
+
typeof destination.requiresSourcePredicate !== 'boolean') ||
|
|
257
|
+
typeof destination.atomicWithSource !== 'boolean')) ||
|
|
258
|
+
typeof candidate.promote !== 'function') {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return candidate;
|
|
262
|
+
}
|
|
263
|
+
function conditionalCopyCapabilities(direct, pipeline) {
|
|
264
|
+
const directSource = direct?.conditionalCopySource !== undefined &&
|
|
265
|
+
(direct.conditionalCopySource.etag || direct.conditionalCopySource.version)
|
|
266
|
+
? direct.conditionalCopySource
|
|
267
|
+
: undefined;
|
|
268
|
+
const directDestination = direct?.conditionalCopyDestination !== undefined &&
|
|
269
|
+
(direct.conditionalCopyDestination.create ||
|
|
270
|
+
direct.conditionalCopyDestination.replace)
|
|
271
|
+
? direct.conditionalCopyDestination
|
|
272
|
+
: undefined;
|
|
273
|
+
const pipelineSupported = pipeline.sourceEtag &&
|
|
274
|
+
pipeline.atomicSourceDestination &&
|
|
275
|
+
(pipeline.destinationCreate || pipeline.destinationReplace);
|
|
276
|
+
const pipelineCapabilities = pipelineSupported
|
|
277
|
+
? {
|
|
278
|
+
source: {
|
|
279
|
+
etag: true,
|
|
280
|
+
requiresDestinationPredicate: true,
|
|
281
|
+
version: false,
|
|
282
|
+
},
|
|
283
|
+
destination: {
|
|
284
|
+
atomicWithSource: true,
|
|
285
|
+
create: pipeline.destinationCreate,
|
|
286
|
+
replace: pipeline.destinationReplace,
|
|
287
|
+
requiresSourcePredicate: true,
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
: {};
|
|
291
|
+
if (directSource === undefined && directDestination === undefined) {
|
|
292
|
+
return pipelineCapabilities;
|
|
293
|
+
}
|
|
294
|
+
if (!pipelineSupported) {
|
|
295
|
+
return {
|
|
296
|
+
...(directSource !== undefined && { source: { ...directSource } }),
|
|
297
|
+
...(directDestination !== undefined && {
|
|
298
|
+
destination: { ...directDestination },
|
|
299
|
+
}),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Never splice unrelated one-sided direct declarations onto the paired
|
|
303
|
+
// Files surface. Without both direct halves there is no coherent route for
|
|
304
|
+
// the cross-product the public capability model would otherwise imply.
|
|
305
|
+
if (directSource === undefined || directDestination === undefined) {
|
|
306
|
+
return {
|
|
307
|
+
...(directSource !== undefined && { source: { ...directSource } }),
|
|
308
|
+
...(directDestination !== undefined && {
|
|
309
|
+
destination: { ...directDestination },
|
|
310
|
+
}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const sourcePredicates = [
|
|
314
|
+
...(directSource.etag || pipeline.sourceEtag ? ['etag'] : []),
|
|
315
|
+
...(directSource.version ? ['version'] : []),
|
|
316
|
+
];
|
|
317
|
+
const destinationPredicates = [
|
|
318
|
+
...(directDestination.create || pipeline.destinationCreate
|
|
319
|
+
? ['create']
|
|
320
|
+
: []),
|
|
321
|
+
...(directDestination.replace || pipeline.destinationReplace
|
|
322
|
+
? ['replace']
|
|
323
|
+
: []),
|
|
324
|
+
];
|
|
325
|
+
const supportsDirectSourceAlone = (predicate) => directSource[predicate] &&
|
|
326
|
+
directSource.requiresDestinationPredicate !== true;
|
|
327
|
+
const supportsDirectDestinationAlone = (predicate) => directDestination[predicate] &&
|
|
328
|
+
directDestination.requiresSourcePredicate !== true;
|
|
329
|
+
const sourceAlone = sourcePredicates.map(supportsDirectSourceAlone);
|
|
330
|
+
const destinationAlone = destinationPredicates.map(supportsDirectDestinationAlone);
|
|
331
|
+
// One shared dependency flag cannot express a union where only some source
|
|
332
|
+
// or destination predicate kinds require their counterpart.
|
|
333
|
+
if ((sourceAlone.some(Boolean) && !sourceAlone.every(Boolean)) ||
|
|
334
|
+
(destinationAlone.some(Boolean) && !destinationAlone.every(Boolean))) {
|
|
335
|
+
return {
|
|
336
|
+
source: { ...directSource },
|
|
337
|
+
destination: { ...directDestination },
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const everyPairIsAtomic = sourcePredicates.every((source) => destinationPredicates.every((destination) => {
|
|
341
|
+
const throughPipeline = source === 'etag' &&
|
|
342
|
+
(destination === 'create'
|
|
343
|
+
? pipeline.destinationCreate
|
|
344
|
+
: pipeline.destinationReplace);
|
|
345
|
+
const throughDirect = directSource[source] &&
|
|
346
|
+
directDestination[destination] &&
|
|
347
|
+
directDestination.atomicWithSource;
|
|
348
|
+
return throughPipeline || throughDirect;
|
|
349
|
+
}));
|
|
350
|
+
if (!everyPairIsAtomic) {
|
|
351
|
+
return {
|
|
352
|
+
source: { ...directSource },
|
|
353
|
+
destination: { ...directDestination },
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
source: {
|
|
358
|
+
etag: sourcePredicates.includes('etag'),
|
|
359
|
+
...(sourceAlone.every((supported) => !supported) && {
|
|
360
|
+
requiresDestinationPredicate: true,
|
|
361
|
+
}),
|
|
362
|
+
version: sourcePredicates.includes('version'),
|
|
363
|
+
},
|
|
364
|
+
destination: {
|
|
365
|
+
atomicWithSource: true,
|
|
366
|
+
create: destinationPredicates.includes('create'),
|
|
367
|
+
replace: destinationPredicates.includes('replace'),
|
|
368
|
+
...(destinationAlone.every((supported) => !supported) && {
|
|
369
|
+
requiresSourcePredicate: true,
|
|
370
|
+
}),
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function conditionalUploadAdapterOf(adapter) {
|
|
375
|
+
if (typeof adapter !== 'object' ||
|
|
376
|
+
adapter === null ||
|
|
377
|
+
!('uploadConditional' in adapter)) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
const candidate = adapter;
|
|
381
|
+
const create = candidate.conditionalCreate;
|
|
382
|
+
const replace = candidate.conditionalReplace;
|
|
383
|
+
const multipart = candidate.conditionalMultipartCompletion;
|
|
384
|
+
if ((create === undefined && replace === undefined) ||
|
|
385
|
+
(create !== undefined && typeof create.resultEtag !== 'boolean') ||
|
|
386
|
+
(replace !== undefined && typeof replace.resultEtag !== 'boolean') ||
|
|
387
|
+
(multipart !== undefined &&
|
|
388
|
+
(typeof multipart.create !== 'boolean' ||
|
|
389
|
+
typeof multipart.replace !== 'boolean')) ||
|
|
390
|
+
typeof candidate.uploadConditional !== 'function') {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
return candidate;
|
|
394
|
+
}
|
|
395
|
+
function conditionalDeleteAdapterOf(adapter) {
|
|
396
|
+
if (typeof adapter !== 'object' ||
|
|
397
|
+
adapter === null ||
|
|
398
|
+
!('conditionalDelete' in adapter) ||
|
|
399
|
+
!('deleteConditional' in adapter)) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const candidate = adapter;
|
|
403
|
+
if (candidate.conditionalDelete === undefined ||
|
|
404
|
+
typeof candidate.conditionalDelete.etag !== 'boolean' ||
|
|
405
|
+
typeof candidate.deleteConditional !== 'function') {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return candidate;
|
|
409
|
+
}
|
|
410
|
+
function conditionalReadAdapterOf(adapter) {
|
|
411
|
+
if (typeof adapter !== 'object' ||
|
|
412
|
+
adapter === null ||
|
|
413
|
+
!('conditionalRead' in adapter) ||
|
|
414
|
+
!('downloadConditional' in adapter)) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
const candidate = adapter;
|
|
418
|
+
if (candidate.conditionalRead === undefined ||
|
|
419
|
+
typeof candidate.conditionalRead.etag !== 'boolean' ||
|
|
420
|
+
typeof candidate.conditionalRead.version !== 'boolean' ||
|
|
421
|
+
typeof candidate.downloadConditional !== 'function') {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
return candidate;
|
|
425
|
+
}
|
|
426
|
+
function physicalKeyAdapterOf(adapter) {
|
|
427
|
+
if (typeof adapter !== 'object' ||
|
|
428
|
+
adapter === null ||
|
|
429
|
+
!('physicalKey' in adapter)) {
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
const candidate = adapter;
|
|
433
|
+
const maxBytes = candidate.physicalKey?.maxBytes;
|
|
434
|
+
if (!Number.isSafeInteger(maxBytes) || (maxBytes ?? 0) <= 0) {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
return candidate;
|
|
438
|
+
}
|
|
439
|
+
function signedUploadPolicyAdapterOf(adapter) {
|
|
440
|
+
if (typeof adapter !== 'object' ||
|
|
441
|
+
adapter === null ||
|
|
442
|
+
!('signedUploadPolicy' in adapter)) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
const candidate = adapter;
|
|
446
|
+
const capability = candidate.signedUploadPolicy;
|
|
447
|
+
if (capability === undefined ||
|
|
448
|
+
typeof capability.contentType !== 'boolean' ||
|
|
449
|
+
typeof capability.sizeRange !== 'boolean') {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
return candidate;
|
|
453
|
+
}
|
|
454
|
+
function signedDownloadPolicyAdapterOf(adapter) {
|
|
455
|
+
if (typeof adapter !== 'object' ||
|
|
456
|
+
adapter === null ||
|
|
457
|
+
!('signedDownloadPolicy' in adapter)) {
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
const candidate = adapter;
|
|
461
|
+
const capability = candidate.signedDownloadPolicy;
|
|
462
|
+
if (capability === undefined || typeof capability.expiresIn !== 'boolean') {
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
return candidate;
|
|
466
|
+
}
|
|
467
|
+
function isFilesErrorCode(value) {
|
|
468
|
+
return (value === 'NotFound' ||
|
|
469
|
+
value === 'Unauthorized' ||
|
|
470
|
+
value === 'Conflict' ||
|
|
471
|
+
value === 'ReadOnly' ||
|
|
472
|
+
value === 'Provider');
|
|
473
|
+
}
|
|
474
|
+
function isFilesErrorLike(error) {
|
|
475
|
+
if (error instanceof FilesError) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
if (!(error instanceof Error)) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
return (error.name === 'FilesError' &&
|
|
483
|
+
typeof error.message === 'string' &&
|
|
484
|
+
'code' in error &&
|
|
485
|
+
isFilesErrorCode(error.code) &&
|
|
486
|
+
'aborted' in error &&
|
|
487
|
+
typeof error.aborted === 'boolean' &&
|
|
488
|
+
'timedOut' in error &&
|
|
489
|
+
typeof error.timedOut === 'boolean' &&
|
|
490
|
+
'permanent' in error &&
|
|
491
|
+
typeof error.permanent === 'boolean' &&
|
|
492
|
+
(!('applied' in error) || typeof error.applied === 'boolean') &&
|
|
493
|
+
(!('appliedEtag' in error) || typeof error.appliedEtag === 'string'));
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function unwrapFilesError(error) {
|
|
500
|
+
const seen = new Set();
|
|
501
|
+
let current = error;
|
|
502
|
+
while (current.code === 'Provider' &&
|
|
503
|
+
!current.aborted &&
|
|
504
|
+
!current.timedOut &&
|
|
505
|
+
!current.permanent &&
|
|
506
|
+
current.applied !== true &&
|
|
507
|
+
isFilesErrorLike(current.cause) &&
|
|
508
|
+
current.message === current.cause.message &&
|
|
509
|
+
!seen.has(current.cause)) {
|
|
510
|
+
seen.add(current);
|
|
511
|
+
current = current.cause;
|
|
512
|
+
}
|
|
513
|
+
return current;
|
|
514
|
+
}
|
|
515
|
+
const PUBLIC_PROVIDER_ERROR_MESSAGES = Object.freeze({
|
|
516
|
+
[StorageErrorCode.NOT_FOUND]: 'Storage provider object was not found.',
|
|
517
|
+
[StorageErrorCode.UNAUTHORIZED]: 'Storage provider operation was unauthorized.',
|
|
518
|
+
[StorageErrorCode.CONFLICT]: 'Storage provider operation conflicted with current state.',
|
|
519
|
+
[StorageErrorCode.READ_ONLY]: 'Storage provider is read-only.',
|
|
520
|
+
[StorageErrorCode.INVALID_ARGUMENT]: 'Storage provider rejected an invalid argument.',
|
|
521
|
+
[StorageErrorCode.NOT_SUPPORTED]: 'Storage provider operation is not supported.',
|
|
522
|
+
[StorageErrorCode.ABORTED]: 'Storage provider operation was aborted.',
|
|
523
|
+
[StorageErrorCode.TIMEOUT]: 'Storage provider operation timed out.',
|
|
524
|
+
[StorageErrorCode.LIMIT_EXCEEDED]: 'Storage provider operation exceeded a limit.',
|
|
525
|
+
[StorageErrorCode.PROVIDER]: 'Storage provider operation failed.',
|
|
526
|
+
});
|
|
527
|
+
function sanitizedStorageError(error) {
|
|
528
|
+
return new StorageError(PUBLIC_PROVIDER_ERROR_MESSAGES[error.code], {
|
|
529
|
+
aborted: error.aborted,
|
|
530
|
+
applied: error.applied,
|
|
531
|
+
...(isCanonicalStorageEtag(error.appliedEtag) && {
|
|
532
|
+
appliedEtag: error.appliedEtag,
|
|
533
|
+
}),
|
|
534
|
+
code: error.code,
|
|
535
|
+
permanent: error.permanent,
|
|
536
|
+
timedOut: error.timedOut,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function filesErrorCodeOfStorage(error) {
|
|
540
|
+
switch (error.code) {
|
|
541
|
+
case StorageErrorCode.NOT_FOUND:
|
|
542
|
+
return 'NotFound';
|
|
543
|
+
case StorageErrorCode.UNAUTHORIZED:
|
|
544
|
+
return 'Unauthorized';
|
|
545
|
+
case StorageErrorCode.CONFLICT:
|
|
546
|
+
return error.permanent ? 'Conflict' : 'Provider';
|
|
547
|
+
case StorageErrorCode.READ_ONLY:
|
|
548
|
+
return 'ReadOnly';
|
|
549
|
+
default:
|
|
550
|
+
return 'Provider';
|
|
8
551
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
552
|
+
}
|
|
553
|
+
/** Converts NestM adapter failures before they enter the Files retry pipeline. */
|
|
554
|
+
export function mapStorageErrorToFilesSdkError(error) {
|
|
555
|
+
if (error instanceof FilesError)
|
|
556
|
+
return error;
|
|
557
|
+
if (!isStorageError(error))
|
|
558
|
+
return FilesError.wrap(error);
|
|
559
|
+
const sanitized = sanitizedStorageError(error);
|
|
560
|
+
return new FilesError(filesErrorCodeOfStorage(sanitized), sanitized.message, sanitized, {
|
|
561
|
+
aborted: sanitized.aborted,
|
|
562
|
+
applied: sanitized.applied,
|
|
563
|
+
...(isCanonicalStorageEtag(sanitized.appliedEtag) && {
|
|
564
|
+
appliedEtag: sanitized.appliedEtag,
|
|
565
|
+
}),
|
|
566
|
+
permanent: sanitized.permanent,
|
|
567
|
+
timedOut: sanitized.timedOut,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
export function mapFilesSdkError(error) {
|
|
571
|
+
if (isStorageError(error))
|
|
572
|
+
return sanitizedStorageError(error);
|
|
573
|
+
if (!isFilesErrorLike(error)) {
|
|
574
|
+
return new StorageError('Storage provider operation failed.', {
|
|
12
575
|
code: StorageErrorCode.PROVIDER,
|
|
13
576
|
});
|
|
14
577
|
}
|
|
15
|
-
|
|
16
|
-
|
|
578
|
+
const filesError = unwrapFilesError(error);
|
|
579
|
+
if (filesError.code === 'Provider' &&
|
|
580
|
+
!filesError.aborted &&
|
|
581
|
+
!filesError.timedOut &&
|
|
582
|
+
isStorageError(filesError.cause) &&
|
|
583
|
+
filesError.message === filesError.cause.message) {
|
|
584
|
+
const storageCause = filesError.cause;
|
|
585
|
+
return new StorageError(PUBLIC_PROVIDER_ERROR_MESSAGES[storageCause.code], {
|
|
586
|
+
aborted: filesError.aborted || storageCause.aborted,
|
|
587
|
+
applied: filesError.applied === true || storageCause.applied,
|
|
588
|
+
...(isCanonicalStorageEtag(filesError.appliedEtag)
|
|
589
|
+
? { appliedEtag: filesError.appliedEtag }
|
|
590
|
+
: isCanonicalStorageEtag(storageCause.appliedEtag)
|
|
591
|
+
? { appliedEtag: storageCause.appliedEtag }
|
|
592
|
+
: {}),
|
|
593
|
+
code: storageCause.code,
|
|
594
|
+
permanent: filesError.permanent || storageCause.permanent,
|
|
595
|
+
timedOut: filesError.timedOut || storageCause.timedOut,
|
|
596
|
+
});
|
|
17
597
|
}
|
|
18
598
|
let code;
|
|
19
|
-
if (
|
|
599
|
+
if (filesError.timedOut) {
|
|
20
600
|
code = StorageErrorCode.TIMEOUT;
|
|
21
601
|
}
|
|
22
|
-
else if (
|
|
602
|
+
else if (filesError.aborted) {
|
|
23
603
|
code = StorageErrorCode.ABORTED;
|
|
24
604
|
}
|
|
25
605
|
else {
|
|
26
|
-
switch (
|
|
606
|
+
switch (filesError.code) {
|
|
27
607
|
case 'NotFound':
|
|
28
608
|
code = StorageErrorCode.NOT_FOUND;
|
|
29
609
|
break;
|
|
@@ -37,18 +617,21 @@ function mapFilesError(error) {
|
|
|
37
617
|
code = StorageErrorCode.READ_ONLY;
|
|
38
618
|
break;
|
|
39
619
|
case 'Provider':
|
|
40
|
-
code = /(?:not supported|does not support|unsupported)/iu.test(
|
|
620
|
+
code = /(?:not supported|does not support|unsupported)/iu.test(filesError.message)
|
|
41
621
|
? StorageErrorCode.NOT_SUPPORTED
|
|
42
622
|
: StorageErrorCode.PROVIDER;
|
|
43
623
|
break;
|
|
44
624
|
}
|
|
45
625
|
}
|
|
46
|
-
return new StorageError(
|
|
47
|
-
aborted:
|
|
48
|
-
|
|
626
|
+
return new StorageError(PUBLIC_PROVIDER_ERROR_MESSAGES[code], {
|
|
627
|
+
aborted: filesError.aborted,
|
|
628
|
+
applied: filesError.applied === true,
|
|
629
|
+
...(isCanonicalStorageEtag(filesError.appliedEtag) && {
|
|
630
|
+
appliedEtag: filesError.appliedEtag,
|
|
631
|
+
}),
|
|
49
632
|
code,
|
|
50
|
-
permanent:
|
|
51
|
-
timedOut:
|
|
633
|
+
permanent: filesError.permanent,
|
|
634
|
+
timedOut: filesError.timedOut,
|
|
52
635
|
});
|
|
53
636
|
}
|
|
54
637
|
function mapRetryOptions(retries) {
|
|
@@ -60,7 +643,37 @@ function mapRetryOptions(retries) {
|
|
|
60
643
|
...(retries.backoff !== undefined && {
|
|
61
644
|
backoff: ({ attempt, error }) => retries.backoff?.({
|
|
62
645
|
attempt,
|
|
63
|
-
error:
|
|
646
|
+
error: mapFilesSdkError(error),
|
|
647
|
+
}) ?? 0,
|
|
648
|
+
}),
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function storageRetryOptions(retries) {
|
|
652
|
+
if (retries === undefined || typeof retries === 'number') {
|
|
653
|
+
return retries;
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
max: retries.max,
|
|
657
|
+
...(retries.backoff !== undefined && {
|
|
658
|
+
backoff: ({ attempt, error }) => retries.backoff?.({
|
|
659
|
+
attempt,
|
|
660
|
+
error: new FilesError(error.code === StorageErrorCode.NOT_FOUND
|
|
661
|
+
? 'NotFound'
|
|
662
|
+
: error.code === StorageErrorCode.UNAUTHORIZED
|
|
663
|
+
? 'Unauthorized'
|
|
664
|
+
: error.code === StorageErrorCode.CONFLICT
|
|
665
|
+
? 'Conflict'
|
|
666
|
+
: error.code === StorageErrorCode.READ_ONLY
|
|
667
|
+
? 'ReadOnly'
|
|
668
|
+
: 'Provider', error.message, error, {
|
|
669
|
+
aborted: error.aborted,
|
|
670
|
+
applied: error.applied,
|
|
671
|
+
...(isCanonicalStorageEtag(error.appliedEtag) && {
|
|
672
|
+
appliedEtag: error.appliedEtag,
|
|
673
|
+
}),
|
|
674
|
+
permanent: error.permanent,
|
|
675
|
+
timedOut: error.timedOut,
|
|
676
|
+
}),
|
|
64
677
|
}) ?? 0,
|
|
65
678
|
}),
|
|
66
679
|
};
|
|
@@ -114,7 +727,7 @@ function normalizeDownloadStream(source) {
|
|
|
114
727
|
await activeReader.cancel(reason);
|
|
115
728
|
}
|
|
116
729
|
catch (error) {
|
|
117
|
-
throw
|
|
730
|
+
throw mapFilesSdkError(error);
|
|
118
731
|
}
|
|
119
732
|
finally {
|
|
120
733
|
release();
|
|
@@ -137,7 +750,7 @@ function normalizeDownloadStream(source) {
|
|
|
137
750
|
}
|
|
138
751
|
catch (error) {
|
|
139
752
|
release();
|
|
140
|
-
controller.error(
|
|
753
|
+
controller.error(mapFilesSdkError(error));
|
|
141
754
|
}
|
|
142
755
|
},
|
|
143
756
|
});
|
|
@@ -166,6 +779,22 @@ function uploadOptions(options) {
|
|
|
166
779
|
}),
|
|
167
780
|
};
|
|
168
781
|
}
|
|
782
|
+
function conditionalUploadOptions(options) {
|
|
783
|
+
return {
|
|
784
|
+
...operationOptions(options),
|
|
785
|
+
...(options.cacheControl !== undefined && {
|
|
786
|
+
cacheControl: options.cacheControl,
|
|
787
|
+
}),
|
|
788
|
+
condition: options.condition,
|
|
789
|
+
...(options.contentType !== undefined && {
|
|
790
|
+
contentType: options.contentType,
|
|
791
|
+
}),
|
|
792
|
+
...(options.metadata !== undefined && { metadata: options.metadata }),
|
|
793
|
+
...(options.onProgress !== undefined && {
|
|
794
|
+
onProgress: options.onProgress,
|
|
795
|
+
}),
|
|
796
|
+
};
|
|
797
|
+
}
|
|
169
798
|
function downloadOptions(options) {
|
|
170
799
|
return {
|
|
171
800
|
...operationOptions(options),
|
|
@@ -227,10 +856,42 @@ function signedUploadOptions(options) {
|
|
|
227
856
|
...(options.minSize !== undefined && { minSize: options.minSize }),
|
|
228
857
|
};
|
|
229
858
|
}
|
|
230
|
-
function
|
|
859
|
+
function providerEtag(value, key, operation) {
|
|
860
|
+
if (value === undefined) {
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
const etag = normalizeProviderStorageEtag(value);
|
|
864
|
+
if (etag === undefined) {
|
|
865
|
+
throw new StorageError('Storage adapter returned an invalid ETag.', {
|
|
866
|
+
code: StorageErrorCode.PROVIDER,
|
|
867
|
+
key,
|
|
868
|
+
operation,
|
|
869
|
+
permanent: true,
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
return etag;
|
|
873
|
+
}
|
|
874
|
+
function invalidConditionalEtag(label, key, operation) {
|
|
875
|
+
return new StorageError(`${label} must be a canonical storage ETag.`, {
|
|
876
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
877
|
+
key,
|
|
878
|
+
operation,
|
|
879
|
+
permanent: true,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
function invalidConditionalArgument(message, key, operation) {
|
|
883
|
+
return new StorageError(message, {
|
|
884
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
885
|
+
key,
|
|
886
|
+
operation,
|
|
887
|
+
permanent: true,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
function metadataOf(file, operation) {
|
|
891
|
+
const etag = providerEtag(file.etag, file.key, operation);
|
|
231
892
|
return {
|
|
232
893
|
contentType: file.type,
|
|
233
|
-
...(
|
|
894
|
+
...(etag !== undefined && { etag }),
|
|
234
895
|
key: file.key,
|
|
235
896
|
...(file.lastModified !== undefined && {
|
|
236
897
|
lastModified: new Date(file.lastModified),
|
|
@@ -243,9 +904,10 @@ function metadataOf(file) {
|
|
|
243
904
|
};
|
|
244
905
|
}
|
|
245
906
|
function uploadResultOf(result) {
|
|
907
|
+
const etag = providerEtag(result.etag, result.key, 'upload');
|
|
246
908
|
return {
|
|
247
909
|
contentType: result.contentType,
|
|
248
|
-
...(
|
|
910
|
+
...(etag !== undefined && { etag }),
|
|
249
911
|
key: result.key,
|
|
250
912
|
...(result.lastModified !== undefined && {
|
|
251
913
|
lastModified: new Date(result.lastModified),
|
|
@@ -253,64 +915,726 @@ function uploadResultOf(result) {
|
|
|
253
915
|
size: result.size,
|
|
254
916
|
};
|
|
255
917
|
}
|
|
918
|
+
function conditionalUploadResultOf(result) {
|
|
919
|
+
try {
|
|
920
|
+
return uploadResultOf(result);
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
if (!isStorageError(error))
|
|
924
|
+
throw error;
|
|
925
|
+
throw new StorageError(error.message, {
|
|
926
|
+
applied: true,
|
|
927
|
+
code: error.code,
|
|
928
|
+
permanent: error.permanent,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function directConditionalUploadResultOf(result, logicalKey, physicalKey) {
|
|
933
|
+
let etag;
|
|
934
|
+
try {
|
|
935
|
+
etag = providerEtag(result.etag, logicalKey, 'upload');
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
if (!isStorageError(error))
|
|
939
|
+
throw error;
|
|
940
|
+
throw new StorageError(error.message, {
|
|
941
|
+
applied: true,
|
|
942
|
+
code: error.code,
|
|
943
|
+
key: logicalKey,
|
|
944
|
+
operation: 'upload',
|
|
945
|
+
permanent: true,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if (result.key !== physicalKey) {
|
|
949
|
+
throw new StorageError('Storage adapter returned an unexpected conditional upload key.', {
|
|
950
|
+
applied: true,
|
|
951
|
+
...(etag !== undefined && { appliedEtag: etag }),
|
|
952
|
+
code: StorageErrorCode.PROVIDER,
|
|
953
|
+
key: logicalKey,
|
|
954
|
+
operation: 'upload',
|
|
955
|
+
permanent: true,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
...result,
|
|
960
|
+
...(etag === undefined ? {} : { etag }),
|
|
961
|
+
key: logicalKey,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
function storageObjectOf(file) {
|
|
965
|
+
return {
|
|
966
|
+
...metadataOf(file, 'download'),
|
|
967
|
+
body: normalizeDownloadStream(file.stream()),
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
function normalizeFilesSdkPrefix(prefix) {
|
|
971
|
+
if (prefix === undefined)
|
|
972
|
+
return '';
|
|
973
|
+
if (typeof prefix !== 'string') {
|
|
974
|
+
throw new StorageError('prefix must be a string.', {
|
|
975
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
976
|
+
permanent: true,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
const normalized = prefix.replaceAll(/^\/+|(?<!\/)\/+$/gu, '');
|
|
980
|
+
if (normalized.length === 0 || normalized.includes('\0')) {
|
|
981
|
+
throw new StorageError('prefix must be a non-empty string without null bytes.', {
|
|
982
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
983
|
+
permanent: true,
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
if (normalized.split('/').some((segment) => segment === '.' || segment === '..')) {
|
|
987
|
+
throw new StorageError('prefix must not contain . or .. path segments.', {
|
|
988
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
989
|
+
permanent: true,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
return normalized;
|
|
993
|
+
}
|
|
994
|
+
function physicalPluginKey(prefix, key) {
|
|
995
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
996
|
+
throw new StorageError('The final plugin key must be a non-empty string.', {
|
|
997
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
998
|
+
permanent: true,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
if (key.includes('\0')) {
|
|
1002
|
+
throw new StorageError('The final plugin key must not contain null bytes.', {
|
|
1003
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1004
|
+
key,
|
|
1005
|
+
permanent: true,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const normalized = key.replace(/^\/+/u, '');
|
|
1009
|
+
if (normalized.split('/').some((segment) => segment === '.' || segment === '..')) {
|
|
1010
|
+
throw new StorageError('The final plugin key must not contain . or .. path segments.', {
|
|
1011
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1012
|
+
key,
|
|
1013
|
+
permanent: true,
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
logicalKey: key,
|
|
1018
|
+
physicalKey: prefix.length === 0 ? key : `${prefix}/${normalized}`,
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function assertPhysicalPluginBudget(physicalKey, maxBytes, logicalKey) {
|
|
1022
|
+
if (maxBytes === undefined ||
|
|
1023
|
+
new TextEncoder().encode(physicalKey).byteLength <= maxBytes) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
throw new StorageError(`The combined storage prefix and key exceed the provider's ${maxBytes}-byte physical-key limit.`, {
|
|
1027
|
+
code: StorageErrorCode.LIMIT_EXCEEDED,
|
|
1028
|
+
...(logicalKey !== undefined && { key: logicalKey }),
|
|
1029
|
+
permanent: true,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
function physicalKeyGuardPlugin(prefix, maxBytes) {
|
|
1033
|
+
const assertKey = (key) => {
|
|
1034
|
+
const resolved = physicalPluginKey(prefix, key);
|
|
1035
|
+
assertPhysicalPluginBudget(resolved.physicalKey, maxBytes, resolved.logicalKey);
|
|
1036
|
+
return resolved.logicalKey;
|
|
1037
|
+
};
|
|
1038
|
+
const assertListOptions = (options) => {
|
|
1039
|
+
if (options !== undefined &&
|
|
1040
|
+
(typeof options !== 'object' || options === null)) {
|
|
1041
|
+
throw new StorageError('The final plugin list options must be an object.', {
|
|
1042
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1043
|
+
permanent: true,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
const listPrefix = options
|
|
1047
|
+
?.prefix;
|
|
1048
|
+
if (listPrefix === undefined || listPrefix === '') {
|
|
1049
|
+
if (prefix.length > 0) {
|
|
1050
|
+
assertPhysicalPluginBudget(`${prefix}/`, maxBytes);
|
|
1051
|
+
}
|
|
1052
|
+
return options === undefined
|
|
1053
|
+
? undefined
|
|
1054
|
+
: (() => {
|
|
1055
|
+
const { prefix: _prefix, ...rest } = options;
|
|
1056
|
+
return Object.freeze({
|
|
1057
|
+
...rest,
|
|
1058
|
+
...(listPrefix === '' && { prefix: listPrefix }),
|
|
1059
|
+
});
|
|
1060
|
+
})();
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof listPrefix !== 'string') {
|
|
1063
|
+
throw new StorageError('The final plugin list prefix must be a string.', {
|
|
1064
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1065
|
+
permanent: true,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
const resolved = physicalPluginKey(prefix, listPrefix);
|
|
1069
|
+
assertPhysicalPluginBudget(resolved.physicalKey, maxBytes, resolved.logicalKey);
|
|
1070
|
+
return Object.freeze({
|
|
1071
|
+
...options,
|
|
1072
|
+
prefix: resolved.logicalKey,
|
|
1073
|
+
});
|
|
1074
|
+
};
|
|
1075
|
+
return {
|
|
1076
|
+
name: '@nestm/storage/physical-key-guard',
|
|
1077
|
+
wrap: handlers({
|
|
1078
|
+
copy: (operation, next) => {
|
|
1079
|
+
const from = assertKey(operation.from);
|
|
1080
|
+
const to = assertKey(operation.to);
|
|
1081
|
+
return next(Object.freeze({ ...operation, from, to }));
|
|
1082
|
+
},
|
|
1083
|
+
delete: (operation, next) => {
|
|
1084
|
+
const key = assertKey(operation.key);
|
|
1085
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1086
|
+
},
|
|
1087
|
+
download: (operation, next) => {
|
|
1088
|
+
const key = assertKey(operation.key);
|
|
1089
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1090
|
+
},
|
|
1091
|
+
exists: (operation, next) => {
|
|
1092
|
+
const key = assertKey(operation.key);
|
|
1093
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1094
|
+
},
|
|
1095
|
+
head: (operation, next) => {
|
|
1096
|
+
const key = assertKey(operation.key);
|
|
1097
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1098
|
+
},
|
|
1099
|
+
list: (operation, next) => {
|
|
1100
|
+
const options = assertListOptions(operation.options);
|
|
1101
|
+
return next(Object.freeze({
|
|
1102
|
+
...operation,
|
|
1103
|
+
...(options !== undefined && { options }),
|
|
1104
|
+
}));
|
|
1105
|
+
},
|
|
1106
|
+
move: (operation, next) => {
|
|
1107
|
+
const from = assertKey(operation.from);
|
|
1108
|
+
const to = assertKey(operation.to);
|
|
1109
|
+
return next(Object.freeze({ ...operation, from, to }));
|
|
1110
|
+
},
|
|
1111
|
+
signedUploadUrl: (operation, next) => {
|
|
1112
|
+
const key = assertKey(operation.key);
|
|
1113
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1114
|
+
},
|
|
1115
|
+
upload: (operation, next) => {
|
|
1116
|
+
const key = assertKey(operation.key);
|
|
1117
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1118
|
+
},
|
|
1119
|
+
url: (operation, next) => {
|
|
1120
|
+
const key = assertKey(operation.key);
|
|
1121
|
+
return next(Object.freeze({ ...operation, key }));
|
|
1122
|
+
},
|
|
1123
|
+
}),
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
256
1126
|
export class FilesSdkStorageDriver {
|
|
257
1127
|
#files;
|
|
258
1128
|
#name;
|
|
1129
|
+
#conditionalCopy;
|
|
1130
|
+
#conditionalDelete;
|
|
1131
|
+
#conditionalRead;
|
|
1132
|
+
#conditionalUpload;
|
|
1133
|
+
#directConditionalFallbackBlocked;
|
|
1134
|
+
#physicalKey;
|
|
1135
|
+
#prefix;
|
|
1136
|
+
#readOnly;
|
|
1137
|
+
#retries;
|
|
1138
|
+
#signal;
|
|
1139
|
+
#timeout;
|
|
1140
|
+
#signedUploadPolicy;
|
|
1141
|
+
#signedDownloadPolicy;
|
|
259
1142
|
constructor(options) {
|
|
260
|
-
|
|
261
|
-
|
|
1143
|
+
const raw = adapterRawObject(options.adapter);
|
|
1144
|
+
const rawProvenance = raw === undefined ? undefined : s3AdapterProvenanceOfRaw(raw);
|
|
1145
|
+
const authorityToken = s3AdapterAuthorityTokenOf(options.adapter);
|
|
1146
|
+
const authorityState = typeof authorityToken === 'object'
|
|
1147
|
+
? s3AuthorityState.get(authorityToken)
|
|
1148
|
+
: undefined;
|
|
1149
|
+
const authorityInvalid = authorityToken === conflictingS3Authority ||
|
|
1150
|
+
(authorityState !== undefined &&
|
|
1151
|
+
(authorityState.record === undefined ||
|
|
1152
|
+
raw === undefined ||
|
|
1153
|
+
(raw !== authorityState.raw &&
|
|
1154
|
+
rawProvenance !== authorityState.record)));
|
|
1155
|
+
const s3Provenance = authorityState?.record ?? rawProvenance;
|
|
1156
|
+
if (authorityInvalid ||
|
|
1157
|
+
(s3Provenance === undefined &&
|
|
1158
|
+
raw !== undefined &&
|
|
1159
|
+
(isUndecoratedS3Raw(raw) || isStructuralS3Raw(raw)))) {
|
|
1160
|
+
throw new StorageError('Raw S3 adapters must be configured with withS3Capabilities() or createS3StorageDriver().', {
|
|
1161
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1162
|
+
permanent: true,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
const mismatchedAuthority = s3Provenance === undefined
|
|
1166
|
+
? undefined
|
|
1167
|
+
: mismatchedS3Authority(options.adapter, s3Provenance.surface);
|
|
1168
|
+
if (mismatchedAuthority !== undefined) {
|
|
1169
|
+
throw new StorageError(`S3 adapter authority "${mismatchedAuthority}" does not match its verified provider profile.`, {
|
|
1170
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1171
|
+
permanent: true,
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
const adapterForFiles = s3Provenance === undefined
|
|
1175
|
+
? options.adapter
|
|
1176
|
+
: Object.freeze({ ...options.adapter });
|
|
1177
|
+
const copiedAuthorityMismatch = s3Provenance === undefined
|
|
1178
|
+
? undefined
|
|
1179
|
+
: mismatchedS3Authority(adapterForFiles, s3Provenance.surface);
|
|
1180
|
+
if (copiedAuthorityMismatch !== undefined) {
|
|
1181
|
+
throw new StorageError(`S3 adapter authority "${copiedAuthorityMismatch}" changed while it was being bound.`, {
|
|
1182
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1183
|
+
permanent: true,
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
const readOnly = options.readonly === true || s3Provenance?.provenance === 'unverified';
|
|
1187
|
+
// Evaluate caller policy before appending NestM's internal key guard below.
|
|
1188
|
+
this.#directConditionalFallbackBlocked =
|
|
1189
|
+
hasCallerFilesOperationPolicy(options);
|
|
1190
|
+
// Files retains the hooks object by reference. Snapshot it so an initially
|
|
1191
|
+
// inactive object cannot be mutated after this compatibility decision and
|
|
1192
|
+
// start observing only ordinary operations.
|
|
1193
|
+
const hooks = options.hooks === undefined
|
|
1194
|
+
? undefined
|
|
1195
|
+
: Object.freeze({ ...options.hooks });
|
|
1196
|
+
this.#physicalKey = physicalKeyAdapterOf(adapterForFiles);
|
|
1197
|
+
const guardedPrefix = normalizeFilesSdkPrefix(options.prefix);
|
|
1198
|
+
const plugins = [
|
|
1199
|
+
...(options.plugins ?? []),
|
|
1200
|
+
physicalKeyGuardPlugin(guardedPrefix, this.#physicalKey?.physicalKey.maxBytes),
|
|
1201
|
+
];
|
|
1202
|
+
this.#files = new Files({
|
|
1203
|
+
...options,
|
|
1204
|
+
adapter: adapterForFiles,
|
|
1205
|
+
...(hooks !== undefined && { hooks }),
|
|
1206
|
+
plugins,
|
|
1207
|
+
readonly: readOnly,
|
|
1208
|
+
});
|
|
1209
|
+
this.#name = adapterForFiles.name;
|
|
1210
|
+
this.#conditionalCopy = conditionalCopyAdapterOf(adapterForFiles);
|
|
1211
|
+
this.#conditionalDelete = conditionalDeleteAdapterOf(adapterForFiles);
|
|
1212
|
+
this.#conditionalRead = conditionalReadAdapterOf(adapterForFiles);
|
|
1213
|
+
this.#conditionalUpload = conditionalUploadAdapterOf(adapterForFiles);
|
|
1214
|
+
this.#prefix = this.#files.prefix;
|
|
1215
|
+
if (guardedPrefix !== this.#prefix) {
|
|
1216
|
+
throw new StorageError('Storage prefix normalization was inconsistent.', {
|
|
1217
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1218
|
+
permanent: true,
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
this.#assertPhysicalKeyBudget(this.#prefix);
|
|
1222
|
+
this.#readOnly = readOnly;
|
|
1223
|
+
this.#retries = storageRetryOptions(options.retries);
|
|
1224
|
+
this.#signal = options.signal;
|
|
1225
|
+
this.#timeout = options.timeout;
|
|
1226
|
+
this.#signedUploadPolicy = signedUploadPolicyAdapterOf(adapterForFiles);
|
|
1227
|
+
this.#signedDownloadPolicy = signedDownloadPolicyAdapterOf(adapterForFiles);
|
|
262
1228
|
}
|
|
263
1229
|
get name() {
|
|
264
1230
|
return this.#name;
|
|
265
1231
|
}
|
|
266
1232
|
get capabilities() {
|
|
267
1233
|
const capabilities = this.#files.capabilities;
|
|
1234
|
+
const conditional = capabilities.conditional;
|
|
1235
|
+
const directFallbackAllowed = !this.#directConditionalFallbackBlocked;
|
|
1236
|
+
const directCopy = directFallbackAllowed
|
|
1237
|
+
? this.#conditionalCopy
|
|
1238
|
+
: undefined;
|
|
1239
|
+
const pipelineCopy = conditional.copy;
|
|
1240
|
+
const copyCapabilities = conditionalCopyCapabilities(directCopy, pipelineCopy);
|
|
1241
|
+
const conditionalCopySource = copyCapabilities.source;
|
|
1242
|
+
const conditionalCopyDestination = copyCapabilities.destination;
|
|
1243
|
+
const directDelete = directFallbackAllowed
|
|
1244
|
+
? this.#conditionalDelete
|
|
1245
|
+
: undefined;
|
|
1246
|
+
const directRead = directFallbackAllowed
|
|
1247
|
+
? this.#conditionalRead
|
|
1248
|
+
: undefined;
|
|
1249
|
+
const directUpload = directFallbackAllowed
|
|
1250
|
+
? this.#conditionalUpload
|
|
1251
|
+
: undefined;
|
|
1252
|
+
const conditionalReadEtag = conditional.exactRead || directRead?.conditionalRead.etag === true;
|
|
1253
|
+
const conditionalReadVersion = directRead?.conditionalRead.version === true;
|
|
268
1254
|
return {
|
|
269
1255
|
cacheControl: capabilities.cacheControl,
|
|
270
1256
|
delimiter: capabilities.delimiter,
|
|
271
1257
|
metadata: capabilities.metadata,
|
|
272
1258
|
rangeRead: capabilities.rangeRead,
|
|
273
|
-
resumableUpload: capabilities.multipart,
|
|
274
|
-
serverSideCopy: capabilities.serverSideCopy,
|
|
1259
|
+
resumableUpload: !this.#readOnly && capabilities.multipart,
|
|
1260
|
+
serverSideCopy: !this.#readOnly && capabilities.serverSideCopy,
|
|
1261
|
+
...(!this.#readOnly &&
|
|
1262
|
+
conditionalCopySource !== undefined && {
|
|
1263
|
+
conditionalCopySource,
|
|
1264
|
+
}),
|
|
1265
|
+
...(!this.#readOnly &&
|
|
1266
|
+
conditionalCopyDestination !== undefined && {
|
|
1267
|
+
conditionalCopyDestination,
|
|
1268
|
+
}),
|
|
1269
|
+
...(!this.#readOnly &&
|
|
1270
|
+
(conditional.delete ||
|
|
1271
|
+
directDelete?.conditionalDelete.etag === true) && {
|
|
1272
|
+
conditionalDelete: { etag: true },
|
|
1273
|
+
}),
|
|
1274
|
+
...((conditionalReadEtag || conditionalReadVersion) && {
|
|
1275
|
+
conditionalRead: {
|
|
1276
|
+
etag: conditionalReadEtag,
|
|
1277
|
+
version: conditionalReadVersion,
|
|
1278
|
+
},
|
|
1279
|
+
}),
|
|
1280
|
+
...(!this.#readOnly &&
|
|
1281
|
+
(conditional.create ||
|
|
1282
|
+
directUpload?.conditionalCreate !== undefined) && {
|
|
1283
|
+
conditionalCreate: {
|
|
1284
|
+
resultEtag: conditional.create ||
|
|
1285
|
+
directUpload?.conditionalCreate?.resultEtag === true,
|
|
1286
|
+
},
|
|
1287
|
+
}),
|
|
1288
|
+
...(!this.#readOnly &&
|
|
1289
|
+
(conditional.replace ||
|
|
1290
|
+
directUpload?.conditionalReplace !== undefined) && {
|
|
1291
|
+
conditionalReplace: {
|
|
1292
|
+
resultEtag: conditional.replace ||
|
|
1293
|
+
directUpload?.conditionalReplace?.resultEtag === true,
|
|
1294
|
+
},
|
|
1295
|
+
}),
|
|
1296
|
+
...(!this.#readOnly &&
|
|
1297
|
+
directUpload?.conditionalMultipartCompletion !== undefined && {
|
|
1298
|
+
conditionalMultipartCompletion: {
|
|
1299
|
+
...directUpload.conditionalMultipartCompletion,
|
|
1300
|
+
},
|
|
1301
|
+
}),
|
|
1302
|
+
...(this.#physicalKey !== undefined && {
|
|
1303
|
+
physicalKey: { ...this.#physicalKey.physicalKey },
|
|
1304
|
+
}),
|
|
275
1305
|
signedDownload: { ...capabilities.signedUrl },
|
|
276
|
-
|
|
277
|
-
|
|
1306
|
+
...(this.#signedDownloadPolicy !== undefined && {
|
|
1307
|
+
signedDownloadPolicy: {
|
|
1308
|
+
...this.#signedDownloadPolicy.signedDownloadPolicy,
|
|
1309
|
+
},
|
|
1310
|
+
}),
|
|
1311
|
+
signedUpload: this.#readOnly ? false : 'runtime',
|
|
1312
|
+
...(this.#signedUploadPolicy !== undefined &&
|
|
1313
|
+
!this.#readOnly && {
|
|
1314
|
+
signedUploadPolicy: {
|
|
1315
|
+
...this.#signedUploadPolicy.signedUploadPolicy,
|
|
1316
|
+
},
|
|
1317
|
+
}),
|
|
1318
|
+
nativeUploadProgress: !this.#readOnly && capabilities.uploadProgress,
|
|
278
1319
|
};
|
|
279
1320
|
}
|
|
280
1321
|
async upload(key, body, options) {
|
|
1322
|
+
this.#assertLogicalKey(key);
|
|
281
1323
|
return this.#call(async () => uploadResultOf(await this.#files.upload(key, mapBody(body), uploadOptions(options))));
|
|
282
1324
|
}
|
|
1325
|
+
uploadConditional(key, body, options) {
|
|
1326
|
+
const condition = options
|
|
1327
|
+
?.condition;
|
|
1328
|
+
if (typeof condition !== 'object' ||
|
|
1329
|
+
condition === null ||
|
|
1330
|
+
!('type' in condition) ||
|
|
1331
|
+
(condition.type !== 'create' && condition.type !== 'replace')) {
|
|
1332
|
+
return Promise.reject(invalidConditionalArgument('condition.type must be "create" or "replace".', key, 'upload'));
|
|
1333
|
+
}
|
|
1334
|
+
if (condition.type === 'replace' &&
|
|
1335
|
+
!isCanonicalStorageEtag('etag' in condition ? condition.etag : undefined)) {
|
|
1336
|
+
return Promise.reject(invalidConditionalEtag('condition.etag', key, 'upload'));
|
|
1337
|
+
}
|
|
1338
|
+
this.#assertLogicalKey(key);
|
|
1339
|
+
if (this.#readOnly) {
|
|
1340
|
+
return Promise.reject(new StorageError(`Cannot call uploadConditional() on a read-only storage adapter.`, {
|
|
1341
|
+
code: StorageErrorCode.READ_ONLY,
|
|
1342
|
+
key,
|
|
1343
|
+
operation: 'upload',
|
|
1344
|
+
permanent: true,
|
|
1345
|
+
}));
|
|
1346
|
+
}
|
|
1347
|
+
const adapter = this.#conditionalUpload;
|
|
1348
|
+
const capability = condition.type === 'create'
|
|
1349
|
+
? adapter?.conditionalCreate
|
|
1350
|
+
: adapter?.conditionalReplace;
|
|
1351
|
+
const multipartRequested = options.multipart !== undefined && options.multipart !== false;
|
|
1352
|
+
const controlRequested = options.control !== undefined;
|
|
1353
|
+
const pipelineSupported = !multipartRequested &&
|
|
1354
|
+
!controlRequested &&
|
|
1355
|
+
(condition.type === 'create'
|
|
1356
|
+
? this.#files.capabilities.conditional.create
|
|
1357
|
+
: this.#files.capabilities.conditional.replace);
|
|
1358
|
+
if (pipelineSupported) {
|
|
1359
|
+
return this.#call(async () => conditionalUploadResultOf(await this.#files.upload(key, mapBody(body), conditionalUploadOptions(options))));
|
|
1360
|
+
}
|
|
1361
|
+
const multipartSupported = !multipartRequested ||
|
|
1362
|
+
(condition.type === 'create'
|
|
1363
|
+
? adapter?.conditionalMultipartCompletion?.create === true
|
|
1364
|
+
: adapter?.conditionalMultipartCompletion?.replace === true);
|
|
1365
|
+
const directFallbackSupported = adapter !== undefined && capability !== undefined && multipartSupported;
|
|
1366
|
+
if (directFallbackSupported && this.#directConditionalFallbackBlocked) {
|
|
1367
|
+
return Promise.reject(this.#conditionalFallbackPolicyError(key, 'upload'));
|
|
1368
|
+
}
|
|
1369
|
+
if (!directFallbackSupported) {
|
|
1370
|
+
return Promise.reject(new StorageError(`Storage adapter "${this.#name}" does not support conditional upload.`, {
|
|
1371
|
+
code: StorageErrorCode.NOT_SUPPORTED,
|
|
1372
|
+
key,
|
|
1373
|
+
operation: 'upload',
|
|
1374
|
+
permanent: true,
|
|
1375
|
+
}));
|
|
1376
|
+
}
|
|
1377
|
+
const mergedOptions = this.#conditionalOptions(options);
|
|
1378
|
+
return this.#call(async () => {
|
|
1379
|
+
const physicalKey = this.#path(key);
|
|
1380
|
+
const result = await adapter.uploadConditional(physicalKey, mapBody(body), mergedOptions);
|
|
1381
|
+
return directConditionalUploadResultOf(result, key, physicalKey);
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
283
1384
|
async download(key, options) {
|
|
1385
|
+
this.#assertLogicalKey(key);
|
|
284
1386
|
return this.#call(async () => {
|
|
285
1387
|
const file = await this.#files.download(key, downloadOptions(options));
|
|
286
1388
|
return {
|
|
287
|
-
...metadataOf(file),
|
|
1389
|
+
...metadataOf(file, 'download'),
|
|
288
1390
|
body: normalizeDownloadStream(file.stream()),
|
|
289
1391
|
};
|
|
290
1392
|
});
|
|
291
1393
|
}
|
|
1394
|
+
downloadConditional(key, options) {
|
|
1395
|
+
const condition = options
|
|
1396
|
+
?.condition;
|
|
1397
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
1398
|
+
return Promise.reject(invalidConditionalArgument('conditional read requires a condition object.', key, 'download'));
|
|
1399
|
+
}
|
|
1400
|
+
const etag = 'etag' in condition ? condition.etag : undefined;
|
|
1401
|
+
const version = 'version' in condition ? condition.version : undefined;
|
|
1402
|
+
if (etag !== undefined && !isCanonicalStorageEtag(etag)) {
|
|
1403
|
+
return Promise.reject(invalidConditionalEtag('condition.etag', key, 'download'));
|
|
1404
|
+
}
|
|
1405
|
+
if (version !== undefined &&
|
|
1406
|
+
(typeof version !== 'string' || version.length === 0)) {
|
|
1407
|
+
return Promise.reject(invalidConditionalArgument('condition.version must be a non-empty string.', key, 'download'));
|
|
1408
|
+
}
|
|
1409
|
+
if (etag === undefined && version === undefined) {
|
|
1410
|
+
return Promise.reject(invalidConditionalArgument('conditional read requires an etag, version, or both.', key, 'download'));
|
|
1411
|
+
}
|
|
1412
|
+
this.#assertLogicalKey(key);
|
|
1413
|
+
const pipelineSupported = etag !== undefined &&
|
|
1414
|
+
version === undefined &&
|
|
1415
|
+
this.#files.capabilities.conditional.exactRead;
|
|
1416
|
+
if (pipelineSupported) {
|
|
1417
|
+
return this.#call(async () => storageObjectOf(await this.#files.download(key, {
|
|
1418
|
+
...downloadOptions(options),
|
|
1419
|
+
condition: { etag },
|
|
1420
|
+
})));
|
|
1421
|
+
}
|
|
1422
|
+
const adapter = this.#conditionalRead;
|
|
1423
|
+
const directFallbackSupported = adapter !== undefined &&
|
|
1424
|
+
(etag === undefined || adapter.conditionalRead.etag) &&
|
|
1425
|
+
(version === undefined || adapter.conditionalRead.version);
|
|
1426
|
+
if (directFallbackSupported && this.#directConditionalFallbackBlocked) {
|
|
1427
|
+
return Promise.reject(this.#conditionalFallbackPolicyError(key, 'download'));
|
|
1428
|
+
}
|
|
1429
|
+
if (!directFallbackSupported) {
|
|
1430
|
+
return Promise.reject(new StorageError(`Storage adapter "${this.#name}" does not support conditional read.`, {
|
|
1431
|
+
code: StorageErrorCode.NOT_SUPPORTED,
|
|
1432
|
+
key,
|
|
1433
|
+
operation: 'download',
|
|
1434
|
+
permanent: true,
|
|
1435
|
+
}));
|
|
1436
|
+
}
|
|
1437
|
+
return this.#call(async () => {
|
|
1438
|
+
const physicalKey = this.#path(key);
|
|
1439
|
+
const object = await adapter.downloadConditional(physicalKey, this.#conditionalOptions(options));
|
|
1440
|
+
if (object.key !== physicalKey) {
|
|
1441
|
+
await object.body.cancel().catch(() => undefined);
|
|
1442
|
+
throw new StorageError('Storage adapter returned an unexpected conditional download key.', {
|
|
1443
|
+
code: StorageErrorCode.PROVIDER,
|
|
1444
|
+
key,
|
|
1445
|
+
operation: 'download',
|
|
1446
|
+
permanent: true,
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
let etag;
|
|
1450
|
+
try {
|
|
1451
|
+
etag = providerEtag(object.etag, key, 'download');
|
|
1452
|
+
}
|
|
1453
|
+
catch (error) {
|
|
1454
|
+
await object.body.cancel(error).catch(() => undefined);
|
|
1455
|
+
throw error;
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
...object,
|
|
1459
|
+
body: normalizeDownloadStream(object.body),
|
|
1460
|
+
...(etag === undefined ? {} : { etag }),
|
|
1461
|
+
key,
|
|
1462
|
+
name: key.split('/').at(-1) ?? key,
|
|
1463
|
+
};
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
292
1466
|
async head(key, options) {
|
|
293
|
-
|
|
1467
|
+
this.#assertLogicalKey(key);
|
|
1468
|
+
return this.#call(async () => metadataOf(await this.#files.head(key, operationOptions(options)), 'head'));
|
|
294
1469
|
}
|
|
295
1470
|
exists(key, options) {
|
|
1471
|
+
this.#assertLogicalKey(key);
|
|
296
1472
|
return this.#call(() => this.#files.exists(key, operationOptions(options)));
|
|
297
1473
|
}
|
|
298
1474
|
delete(key, options) {
|
|
1475
|
+
this.#assertLogicalKey(key);
|
|
299
1476
|
return this.#call(async () => {
|
|
300
1477
|
await this.#files.delete(key, operationOptions(options));
|
|
301
1478
|
});
|
|
302
1479
|
}
|
|
1480
|
+
deleteConditional(key, options) {
|
|
1481
|
+
const condition = options
|
|
1482
|
+
?.condition;
|
|
1483
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
1484
|
+
return Promise.reject(invalidConditionalArgument('conditional delete requires a condition object.', key, 'delete'));
|
|
1485
|
+
}
|
|
1486
|
+
const etag = 'etag' in condition ? condition.etag : undefined;
|
|
1487
|
+
if (!isCanonicalStorageEtag(etag)) {
|
|
1488
|
+
return Promise.reject(invalidConditionalEtag('condition.etag', key, 'delete'));
|
|
1489
|
+
}
|
|
1490
|
+
this.#assertLogicalKey(key);
|
|
1491
|
+
if (this.#readOnly) {
|
|
1492
|
+
return Promise.reject(new StorageError(`Cannot call deleteConditional() on a read-only storage adapter.`, {
|
|
1493
|
+
code: StorageErrorCode.READ_ONLY,
|
|
1494
|
+
key,
|
|
1495
|
+
operation: 'delete',
|
|
1496
|
+
permanent: true,
|
|
1497
|
+
}));
|
|
1498
|
+
}
|
|
1499
|
+
if (this.#files.capabilities.conditional.delete) {
|
|
1500
|
+
return this.#call(() => this.#files.delete(key, {
|
|
1501
|
+
...operationOptions(options),
|
|
1502
|
+
condition: options.condition,
|
|
1503
|
+
}));
|
|
1504
|
+
}
|
|
1505
|
+
const adapter = this.#conditionalDelete;
|
|
1506
|
+
const directFallbackSupported = adapter !== undefined && adapter.conditionalDelete.etag;
|
|
1507
|
+
if (directFallbackSupported && this.#directConditionalFallbackBlocked) {
|
|
1508
|
+
return Promise.reject(this.#conditionalFallbackPolicyError(key, 'delete'));
|
|
1509
|
+
}
|
|
1510
|
+
if (!directFallbackSupported) {
|
|
1511
|
+
return Promise.reject(new StorageError(`Storage adapter "${this.#name}" does not support conditional delete.`, {
|
|
1512
|
+
code: StorageErrorCode.NOT_SUPPORTED,
|
|
1513
|
+
key,
|
|
1514
|
+
operation: 'delete',
|
|
1515
|
+
permanent: true,
|
|
1516
|
+
}));
|
|
1517
|
+
}
|
|
1518
|
+
const mergedOptions = this.#conditionalOptions(options);
|
|
1519
|
+
return this.#call(() => adapter.deleteConditional(this.#path(key), mergedOptions));
|
|
1520
|
+
}
|
|
303
1521
|
copy(sourceKey, destinationKey, options) {
|
|
1522
|
+
this.#assertLogicalKey(sourceKey);
|
|
1523
|
+
this.#assertLogicalKey(destinationKey);
|
|
304
1524
|
return this.#call(() => this.#files.copy(sourceKey, destinationKey, operationOptions(options)));
|
|
305
1525
|
}
|
|
306
1526
|
move(sourceKey, destinationKey, options) {
|
|
1527
|
+
this.#assertLogicalKey(sourceKey);
|
|
1528
|
+
this.#assertLogicalKey(destinationKey);
|
|
307
1529
|
return this.#call(() => this.#files.move(sourceKey, destinationKey, operationOptions(options)));
|
|
308
1530
|
}
|
|
1531
|
+
promote(sourceKey, destinationKey, options) {
|
|
1532
|
+
if (typeof options !== 'object' || options === null) {
|
|
1533
|
+
return Promise.reject(invalidConditionalArgument('promote requires an options object with a precondition.', sourceKey, 'promote'));
|
|
1534
|
+
}
|
|
1535
|
+
const destination = options.destination;
|
|
1536
|
+
if (destination !== undefined &&
|
|
1537
|
+
(typeof destination !== 'object' ||
|
|
1538
|
+
destination === null ||
|
|
1539
|
+
(destination.type !== 'create' && destination.type !== 'replace'))) {
|
|
1540
|
+
return Promise.reject(new StorageError('destination.type must be "create" or "replace".', {
|
|
1541
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1542
|
+
key: destinationKey,
|
|
1543
|
+
operation: 'promote',
|
|
1544
|
+
permanent: true,
|
|
1545
|
+
}));
|
|
1546
|
+
}
|
|
1547
|
+
if (options.sourceVersion !== undefined &&
|
|
1548
|
+
(typeof options.sourceVersion !== 'string' ||
|
|
1549
|
+
options.sourceVersion.length === 0)) {
|
|
1550
|
+
return Promise.reject(invalidConditionalArgument('sourceVersion must be a non-empty string.', sourceKey, 'promote'));
|
|
1551
|
+
}
|
|
1552
|
+
if (options.sourceEtag === undefined &&
|
|
1553
|
+
options.sourceVersion === undefined &&
|
|
1554
|
+
destination === undefined) {
|
|
1555
|
+
return Promise.reject(invalidConditionalArgument('promote requires a source or destination precondition.', sourceKey, 'promote'));
|
|
1556
|
+
}
|
|
1557
|
+
if (options.sourceEtag !== undefined &&
|
|
1558
|
+
!isCanonicalStorageEtag(options.sourceEtag)) {
|
|
1559
|
+
return Promise.reject(invalidConditionalEtag('sourceEtag', sourceKey, 'promote'));
|
|
1560
|
+
}
|
|
1561
|
+
if (destination?.type === 'replace' &&
|
|
1562
|
+
!isCanonicalStorageEtag(destination.etag)) {
|
|
1563
|
+
return Promise.reject(invalidConditionalEtag('destination.etag', destinationKey, 'promote'));
|
|
1564
|
+
}
|
|
1565
|
+
this.#assertLogicalKey(sourceKey);
|
|
1566
|
+
this.#assertLogicalKey(destinationKey);
|
|
1567
|
+
if (this.#readOnly) {
|
|
1568
|
+
return Promise.reject(new StorageError(`Cannot call promote() on a read-only storage adapter.`, {
|
|
1569
|
+
code: StorageErrorCode.READ_ONLY,
|
|
1570
|
+
key: sourceKey,
|
|
1571
|
+
operation: 'promote',
|
|
1572
|
+
permanent: true,
|
|
1573
|
+
}));
|
|
1574
|
+
}
|
|
1575
|
+
const pipelineCopy = this.#files.capabilities.conditional.copy;
|
|
1576
|
+
const pipelineDestinationSupported = destination?.type === 'create'
|
|
1577
|
+
? pipelineCopy.destinationCreate
|
|
1578
|
+
: destination?.type === 'replace'
|
|
1579
|
+
? pipelineCopy.destinationReplace
|
|
1580
|
+
: false;
|
|
1581
|
+
const pipelineSupported = options.sourceEtag !== undefined &&
|
|
1582
|
+
options.sourceVersion === undefined &&
|
|
1583
|
+
destination !== undefined &&
|
|
1584
|
+
pipelineCopy.sourceEtag &&
|
|
1585
|
+
pipelineCopy.atomicSourceDestination &&
|
|
1586
|
+
pipelineDestinationSupported;
|
|
1587
|
+
if (pipelineSupported) {
|
|
1588
|
+
return this.#call(() => this.#files.copy(sourceKey, destinationKey, {
|
|
1589
|
+
...operationOptions(options),
|
|
1590
|
+
condition: {
|
|
1591
|
+
destination,
|
|
1592
|
+
source: { etag: options.sourceEtag },
|
|
1593
|
+
},
|
|
1594
|
+
}));
|
|
1595
|
+
}
|
|
1596
|
+
const adapter = this.#conditionalCopy;
|
|
1597
|
+
const directFallbackSupported = adapter !== undefined &&
|
|
1598
|
+
(options.sourceEtag === undefined ||
|
|
1599
|
+
adapter.conditionalCopySource?.etag === true) &&
|
|
1600
|
+
(options.sourceVersion === undefined ||
|
|
1601
|
+
adapter.conditionalCopySource?.version === true) &&
|
|
1602
|
+
(options.sourceEtag === undefined && options.sourceVersion === undefined
|
|
1603
|
+
? true
|
|
1604
|
+
: options.destination !== undefined ||
|
|
1605
|
+
adapter.conditionalCopySource?.requiresDestinationPredicate !==
|
|
1606
|
+
true) &&
|
|
1607
|
+
(options.destination?.type !== 'create' ||
|
|
1608
|
+
adapter.conditionalCopyDestination?.create === true) &&
|
|
1609
|
+
(options.destination?.type !== 'replace' ||
|
|
1610
|
+
adapter.conditionalCopyDestination?.replace === true) &&
|
|
1611
|
+
(options.destination === undefined ||
|
|
1612
|
+
options.sourceEtag !== undefined ||
|
|
1613
|
+
options.sourceVersion !== undefined ||
|
|
1614
|
+
adapter.conditionalCopyDestination?.requiresSourcePredicate !== true) &&
|
|
1615
|
+
(options.destination === undefined ||
|
|
1616
|
+
(options.sourceEtag === undefined &&
|
|
1617
|
+
options.sourceVersion === undefined) ||
|
|
1618
|
+
adapter.conditionalCopyDestination?.atomicWithSource === true);
|
|
1619
|
+
if (directFallbackSupported && this.#directConditionalFallbackBlocked) {
|
|
1620
|
+
return Promise.reject(this.#conditionalFallbackPolicyError(sourceKey, 'promote'));
|
|
1621
|
+
}
|
|
1622
|
+
if (!directFallbackSupported) {
|
|
1623
|
+
return Promise.reject(new StorageError(`Storage adapter "${this.#name}" does not support conditional promotion.`, {
|
|
1624
|
+
code: StorageErrorCode.NOT_SUPPORTED,
|
|
1625
|
+
key: sourceKey,
|
|
1626
|
+
operation: 'promote',
|
|
1627
|
+
permanent: true,
|
|
1628
|
+
}));
|
|
1629
|
+
}
|
|
1630
|
+
return this.#call(() => adapter.promote(this.#path(sourceKey), this.#path(destinationKey), this.#conditionalOptions(options)));
|
|
1631
|
+
}
|
|
309
1632
|
async list(options) {
|
|
1633
|
+
this.#assertListPhysicalPrefix(options?.prefix);
|
|
310
1634
|
return this.#call(async () => {
|
|
311
1635
|
const result = await this.#files.list(listOptions(options));
|
|
312
1636
|
return {
|
|
313
|
-
items: result.items.map(metadataOf),
|
|
1637
|
+
items: result.items.map((file) => metadataOf(file, 'list')),
|
|
314
1638
|
...(result.cursor !== undefined && { cursor: result.cursor }),
|
|
315
1639
|
...(result.prefixes !== undefined && {
|
|
316
1640
|
prefixes: [...result.prefixes],
|
|
@@ -324,17 +1648,19 @@ export class FilesSdkStorageDriver {
|
|
|
324
1648
|
async *#search(pattern, options) {
|
|
325
1649
|
try {
|
|
326
1650
|
for await (const file of this.#files.search(pattern, searchOptions(options))) {
|
|
327
|
-
yield metadataOf(file);
|
|
1651
|
+
yield metadataOf(file, 'search');
|
|
328
1652
|
}
|
|
329
1653
|
}
|
|
330
1654
|
catch (error) {
|
|
331
|
-
throw
|
|
1655
|
+
throw mapFilesSdkError(error);
|
|
332
1656
|
}
|
|
333
1657
|
}
|
|
334
1658
|
signDownload(key, options) {
|
|
1659
|
+
this.#assertLogicalKey(key);
|
|
335
1660
|
return this.#call(() => this.#files.url(key, signedDownloadOptions(options)));
|
|
336
1661
|
}
|
|
337
1662
|
signUpload(key, options) {
|
|
1663
|
+
this.#assertLogicalKey(key);
|
|
338
1664
|
return this.#call(() => this.#files.signedUploadUrl(key, signedUploadOptions(options)));
|
|
339
1665
|
}
|
|
340
1666
|
async #call(operation) {
|
|
@@ -342,9 +1668,83 @@ export class FilesSdkStorageDriver {
|
|
|
342
1668
|
return await operation();
|
|
343
1669
|
}
|
|
344
1670
|
catch (error) {
|
|
345
|
-
throw
|
|
1671
|
+
throw mapFilesSdkError(error);
|
|
346
1672
|
}
|
|
347
1673
|
}
|
|
1674
|
+
#path(key) {
|
|
1675
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
1676
|
+
throw new StorageError('key must be a non-empty string.', {
|
|
1677
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1678
|
+
permanent: true,
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
if (key.includes('\0')) {
|
|
1682
|
+
throw new StorageError('key must not contain null bytes.', {
|
|
1683
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1684
|
+
key,
|
|
1685
|
+
permanent: true,
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
const normalized = key.replace(/^\/+/u, '');
|
|
1689
|
+
if (normalized
|
|
1690
|
+
.split('/')
|
|
1691
|
+
.some((segment) => segment === '.' || segment === '..')) {
|
|
1692
|
+
throw new StorageError('key must not contain . or .. path segments.', {
|
|
1693
|
+
code: StorageErrorCode.INVALID_ARGUMENT,
|
|
1694
|
+
key,
|
|
1695
|
+
permanent: true,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
const physicalKey = this.#prefix.length === 0 ? key : `${this.#prefix}/${normalized}`;
|
|
1699
|
+
this.#assertPhysicalKeyBudget(physicalKey, key);
|
|
1700
|
+
return physicalKey;
|
|
1701
|
+
}
|
|
1702
|
+
#assertLogicalKey(key) {
|
|
1703
|
+
this.#path(key);
|
|
1704
|
+
}
|
|
1705
|
+
#assertListPhysicalPrefix(prefix) {
|
|
1706
|
+
if (prefix !== undefined && prefix.length > 0) {
|
|
1707
|
+
this.#assertLogicalKey(prefix);
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (this.#prefix.length > 0) {
|
|
1711
|
+
this.#assertPhysicalKeyBudget(`${this.#prefix}/`);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
#physicalKeyExceedsBudget(physicalKey) {
|
|
1715
|
+
const maxBytes = this.#physicalKey?.physicalKey.maxBytes;
|
|
1716
|
+
return (maxBytes !== undefined &&
|
|
1717
|
+
new TextEncoder().encode(physicalKey).byteLength > maxBytes);
|
|
1718
|
+
}
|
|
1719
|
+
#assertPhysicalKeyBudget(physicalKey, logicalKey) {
|
|
1720
|
+
const maxBytes = this.#physicalKey?.physicalKey.maxBytes;
|
|
1721
|
+
if (maxBytes === undefined || !this.#physicalKeyExceedsBudget(physicalKey))
|
|
1722
|
+
return;
|
|
1723
|
+
throw new StorageError(`The combined storage prefix and key exceed the provider's ${maxBytes}-byte physical-key limit.`, {
|
|
1724
|
+
code: StorageErrorCode.LIMIT_EXCEEDED,
|
|
1725
|
+
...(logicalKey !== undefined && { key: logicalKey }),
|
|
1726
|
+
permanent: true,
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
#conditionalFallbackPolicyError(key, operation) {
|
|
1730
|
+
return new StorageError('This conditional form requires a NestM adapter fallback and is unavailable while Files SDK plugins, hooks, or receipts are configured.', {
|
|
1731
|
+
code: StorageErrorCode.NOT_SUPPORTED,
|
|
1732
|
+
key,
|
|
1733
|
+
operation,
|
|
1734
|
+
permanent: true,
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
#conditionalOptions(options) {
|
|
1738
|
+
return {
|
|
1739
|
+
...options,
|
|
1740
|
+
...(options.retries === undefined &&
|
|
1741
|
+
this.#retries !== undefined && { retries: this.#retries }),
|
|
1742
|
+
...(options.signal === undefined &&
|
|
1743
|
+
this.#signal !== undefined && { signal: this.#signal }),
|
|
1744
|
+
...(options.timeout === undefined &&
|
|
1745
|
+
this.#timeout !== undefined && { timeout: this.#timeout }),
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
348
1748
|
}
|
|
349
1749
|
export function createFilesSdkDriver(options) {
|
|
350
1750
|
return new FilesSdkStorageDriver(options);
|