@nestm/storage 0.1.0-alpha.12 → 0.1.0-alpha.13
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 +10 -0
- package/README.md +48 -0
- package/dist/files-sdk/azure/azure-conditional.d.ts +5 -0
- package/dist/files-sdk/azure/azure-conditional.d.ts.map +1 -0
- package/dist/files-sdk/azure/azure-conditional.js +184 -0
- package/dist/files-sdk/azure/azure-conditional.js.map +1 -0
- package/dist/files-sdk/azure/index.d.ts +10 -0
- package/dist/files-sdk/azure/index.d.ts.map +1 -0
- package/dist/files-sdk/azure/index.js +34 -0
- package/dist/files-sdk/azure/index.js.map +1 -0
- package/dist/files-sdk/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/package.json +20 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @nestm/storage
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.13
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 211e8c4: Add native Azure Blob create-only uploads, ETag-conditional replacement and deletion,
|
|
8
|
+
and exact ETag reads with ranges through the Files SDK conditional pipeline. Expose
|
|
9
|
+
`createAzureStorageDriver` and decorate the named Azure provider with the same
|
|
10
|
+
primitives, allowing staged encrypted content to run on Azure without application
|
|
11
|
+
provider code.
|
|
12
|
+
|
|
3
13
|
## 0.1.0-alpha.12
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -528,6 +528,54 @@ ordering, bounds and the actual local SHA-256 before returning the next offset.
|
|
|
528
528
|
Filename/size/mtime are insufficient proof of resumed content identity. Filename
|
|
529
529
|
classification, browser session storage and HTTP transport remain host policy.
|
|
530
530
|
|
|
531
|
+
### Azure Blob staged content
|
|
532
|
+
|
|
533
|
+
Use the Azure entrypoint for native create-only uploads, ETag-conditional
|
|
534
|
+
replacement/deletion, and exact ETag reads, including byte ranges. These operations
|
|
535
|
+
run through the Files SDK conditional pipeline, so hooks, retries, prefix policy,
|
|
536
|
+
readonly checks, and sanitized errors apply to them. The named `azure` provider
|
|
537
|
+
also receives these primitives.
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
import { DefaultAzureCredential } from '@azure/identity';
|
|
541
|
+
import { createAzureStorageDriver } from '@nestm/storage/files-sdk/azure';
|
|
542
|
+
|
|
543
|
+
const driver = createAzureStorageDriver({
|
|
544
|
+
adapter: {
|
|
545
|
+
accountName: 'workspacestorage',
|
|
546
|
+
container: 'private-content',
|
|
547
|
+
credential: new DefaultAzureCredential(),
|
|
548
|
+
useUserDelegationSas: false,
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Install `@azure/storage-blob`, `@azure/core-auth`, and the credential provider used
|
|
554
|
+
by the host. The Blob container must already exist. Grant the workload identity
|
|
555
|
+
the required Blob data permissions; signed URLs are unnecessary for protected
|
|
556
|
+
server-side content operations. Explicit token credentials cannot be combined
|
|
557
|
+
with shared keys, SAS tokens, or connection strings, and cannot be overridden by
|
|
558
|
+
ambient shared-key environment variables.
|
|
559
|
+
|
|
560
|
+
Conditional uploads stream through bounded Azure SDK blocks and atomically apply
|
|
561
|
+
their predicate when committing. They return the committed ETag and actual byte
|
|
562
|
+
count without a separate metadata read. Failed uploads may leave uncommitted
|
|
563
|
+
blocks for Azure's own expiration; they never fall back to unconditional writes.
|
|
564
|
+
Exact reads verify the response ETag before exposing its body. The driver uses a
|
|
565
|
+
conservative 1024-byte UTF-8 physical-key budget within Azure's name limit.
|
|
566
|
+
Conditional server-side copy, version-ID predicates, and explicit conditional
|
|
567
|
+
multipart completion are not advertised. Workspace safe copy/move can use the
|
|
568
|
+
existing exact-read/create/delete workflow. The generic staged and encrypted
|
|
569
|
+
content stores require no Azure-specific changes.
|
|
570
|
+
|
|
571
|
+
Run `test/azure-content.e2e-spec.ts` with `STORAGE_AZURE_CONFORMANCE=true` and
|
|
572
|
+
`STORAGE_AZURE_TEST_CONNECTION_STRING` against a dedicated account or Azurite.
|
|
573
|
+
For live identity tests, supply `STORAGE_AZURE_TEST_ACCOUNT_NAME` instead; the
|
|
574
|
+
suite uses `DefaultAzureCredential`. It creates and deletes its own uniquely
|
|
575
|
+
named test container. The CI Azure job covers native predicates, competing
|
|
576
|
+
writes, bounded streams, ranges, encrypted content, and the provider conformance
|
|
577
|
+
suite on Azurite. Live Azure identity remains a separate deployment check.
|
|
578
|
+
|
|
531
579
|
## Configure named stores
|
|
532
580
|
|
|
533
581
|
Use the package-owned S3 factory when applicable. For other providers, create a
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AzureAdapter } from 'files-sdk/azure';
|
|
2
|
+
import type { FilesSdkPhysicalKeyAdapter } from '../files-sdk.driver.js';
|
|
3
|
+
/** Native Azure predicates execute inside Files SDK's conditional policy pipeline. */
|
|
4
|
+
export declare function withAzureConditionalOperations(adapter: AzureAdapter): AzureAdapter & FilesSdkPhysicalKeyAdapter;
|
|
5
|
+
//# sourceMappingURL=azure-conditional.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-conditional.d.ts","sourceRoot":"","sources":["../../../src/files-sdk/azure/azure-conditional.ts"],"names":[],"mappings":"AASA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAMzE,sFAAsF;AACtF,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,YAAY,GACpB,YAAY,GAAG,0BAA0B,CAyK3C"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { createStoredFile, FilesError, } from 'files-sdk';
|
|
3
|
+
import { mapAzureError } from 'files-sdk/azure';
|
|
4
|
+
import { normalizeProviderStorageEtag, storageEtagHeader, } from '../../storage-etag.js';
|
|
5
|
+
/** Native Azure predicates execute inside Files SDK's conditional policy pipeline. */
|
|
6
|
+
export function withAzureConditionalOperations(adapter) {
|
|
7
|
+
const container = adapter.raw.getContainerClient(adapter.bucket);
|
|
8
|
+
const upload = async (key, body, expectedEtag, options) => {
|
|
9
|
+
const conditions = expectedEtag === null
|
|
10
|
+
? { ifNoneMatch: '*' }
|
|
11
|
+
: { ifMatch: etagHeader(expectedEtag) };
|
|
12
|
+
const contentType = options?.contentType ??
|
|
13
|
+
(body instanceof Blob && body.type
|
|
14
|
+
? body.type
|
|
15
|
+
: 'application/octet-stream');
|
|
16
|
+
let size = 0;
|
|
17
|
+
const reader = bodyStream(body).getReader();
|
|
18
|
+
const node = Readable.from((async function* () {
|
|
19
|
+
for (;;) {
|
|
20
|
+
const next = await reader.read();
|
|
21
|
+
options?.signal?.throwIfAborted();
|
|
22
|
+
if (next.done)
|
|
23
|
+
break;
|
|
24
|
+
const chunk = next.value;
|
|
25
|
+
size += chunk.byteLength;
|
|
26
|
+
if (!Number.isSafeInteger(size))
|
|
27
|
+
throw new FilesError('Provider', 'Azure upload is too large.');
|
|
28
|
+
// Azure's upload scheduler requires Buffer.copy(), including when
|
|
29
|
+
// the source is a web stream of ordinary Uint8Array chunks.
|
|
30
|
+
yield Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
31
|
+
}
|
|
32
|
+
})(), { objectMode: false });
|
|
33
|
+
// Destroying the Node stream also cancels the web source, including a source
|
|
34
|
+
// blocked waiting for its next chunk when cancellation arrives.
|
|
35
|
+
const abort = () => {
|
|
36
|
+
void reader.cancel(options?.signal?.reason).catch(() => { });
|
|
37
|
+
};
|
|
38
|
+
options?.signal?.addEventListener('abort', abort, { once: true });
|
|
39
|
+
try {
|
|
40
|
+
options?.signal?.throwIfAborted();
|
|
41
|
+
const result = await container
|
|
42
|
+
.getBlockBlobClient(key)
|
|
43
|
+
.uploadStream(node, 4 * 1024 * 1024, 2, {
|
|
44
|
+
conditions,
|
|
45
|
+
blobHTTPHeaders: {
|
|
46
|
+
blobContentType: contentType,
|
|
47
|
+
...(options?.cacheControl === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: { blobCacheControl: options.cacheControl }),
|
|
50
|
+
},
|
|
51
|
+
...(options?.metadata === undefined
|
|
52
|
+
? {}
|
|
53
|
+
: { metadata: options.metadata }),
|
|
54
|
+
...(options?.signal === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { abortSignal: options.signal }),
|
|
57
|
+
...(options?.onProgress === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: {
|
|
60
|
+
onProgress: ({ loadedBytes }) => options.onProgress?.({ loaded: loadedBytes }),
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
key,
|
|
65
|
+
size,
|
|
66
|
+
contentType,
|
|
67
|
+
etag: requiredEtag(result.etag),
|
|
68
|
+
...(result.lastModified === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { lastModified: result.lastModified.getTime() }),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw mapAzureError(error);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
options?.signal?.removeEventListener('abort', abort);
|
|
78
|
+
node.destroy();
|
|
79
|
+
await reader.cancel().catch(() => { });
|
|
80
|
+
reader.releaseLock();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const conditional = {
|
|
84
|
+
create: (key, body, options) => upload(key, body, null, options),
|
|
85
|
+
replace: (key, body, etag, options) => upload(key, body, etag, options),
|
|
86
|
+
exactRead: async (key, etag, options) => {
|
|
87
|
+
const ifMatch = etagHeader(etag);
|
|
88
|
+
options?.signal?.throwIfAborted();
|
|
89
|
+
try {
|
|
90
|
+
const range = options?.range;
|
|
91
|
+
const result = await container
|
|
92
|
+
.getBlobClient(key)
|
|
93
|
+
.download(range?.start ?? 0, range?.end === undefined ? undefined : range.end - range.start + 1, {
|
|
94
|
+
conditions: { ifMatch },
|
|
95
|
+
...(options?.signal === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { abortSignal: options.signal }),
|
|
98
|
+
});
|
|
99
|
+
const stream = result.readableStreamBody;
|
|
100
|
+
if (!stream)
|
|
101
|
+
throw new FilesError('Provider', 'Azure returned no download stream.');
|
|
102
|
+
let returnedEtag;
|
|
103
|
+
try {
|
|
104
|
+
returnedEtag = requiredEtag(result.etag);
|
|
105
|
+
if (returnedEtag !== etag)
|
|
106
|
+
throw new FilesError('Conflict', 'Azure object condition did not match.');
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
stream.destroy();
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
return createStoredFile({
|
|
113
|
+
key,
|
|
114
|
+
etag: returnedEtag,
|
|
115
|
+
size: result.contentLength ?? 0,
|
|
116
|
+
type: result.contentType ?? 'application/octet-stream',
|
|
117
|
+
...(result.lastModified === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { lastModified: result.lastModified.getTime() }),
|
|
120
|
+
...(result.metadata === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { metadata: result.metadata }),
|
|
123
|
+
}, {
|
|
124
|
+
kind: 'stream',
|
|
125
|
+
factory: () => Readable.toWeb(Readable.from(stream)),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw mapAzureError(error);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
delete: async (key, etag, options) => {
|
|
133
|
+
const ifMatch = etagHeader(etag);
|
|
134
|
+
options?.signal?.throwIfAborted();
|
|
135
|
+
try {
|
|
136
|
+
await container.getBlobClient(key).delete({
|
|
137
|
+
conditions: { ifMatch },
|
|
138
|
+
...(options?.signal === undefined
|
|
139
|
+
? {}
|
|
140
|
+
: { abortSignal: options.signal }),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw mapAzureError(error);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
...adapter,
|
|
150
|
+
conditional: Object.freeze(conditional),
|
|
151
|
+
// A conservative UTF-8 budget within Azure's 1024-character blob-name limit.
|
|
152
|
+
physicalKey: Object.freeze({ maxBytes: 1024 }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function etagHeader(etag) {
|
|
156
|
+
const header = storageEtagHeader(etag);
|
|
157
|
+
if (header === undefined)
|
|
158
|
+
throw new FilesError('Provider', 'Azure requires one canonical strong ETag.');
|
|
159
|
+
return header;
|
|
160
|
+
}
|
|
161
|
+
function requiredEtag(value) {
|
|
162
|
+
const etag = normalizeProviderStorageEtag(value);
|
|
163
|
+
if (etag === undefined)
|
|
164
|
+
throw new FilesError('Provider', 'Azure returned no canonical strong ETag.');
|
|
165
|
+
return etag;
|
|
166
|
+
}
|
|
167
|
+
function bodyStream(body) {
|
|
168
|
+
if (body instanceof ReadableStream)
|
|
169
|
+
return body;
|
|
170
|
+
if (body instanceof Blob)
|
|
171
|
+
return body.stream();
|
|
172
|
+
const bytes = typeof body === 'string'
|
|
173
|
+
? new TextEncoder().encode(body)
|
|
174
|
+
: ArrayBuffer.isView(body)
|
|
175
|
+
? new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
176
|
+
: new Uint8Array(body);
|
|
177
|
+
return new ReadableStream({
|
|
178
|
+
start(controller) {
|
|
179
|
+
controller.enqueue(bytes);
|
|
180
|
+
controller.close();
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=azure-conditional.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-conditional.js","sourceRoot":"","sources":["../../../src/files-sdk/azure/azure-conditional.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,gBAAgB,EAChB,UAAU,GAKX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAEnE,OAAO,EACL,4BAA4B,EAC5B,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,sFAAsF;AACtF,MAAM,UAAU,8BAA8B,CAC5C,OAAqB;IAErB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,KAAK,EAClB,GAAW,EACX,IAAU,EACV,YAA2B,EAC3B,OAA8B,EACI,EAAE;QACpC,MAAM,UAAU,GACd,YAAY,KAAK,IAAI;YACnB,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACtB,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5C,MAAM,WAAW,GACf,OAAO,EAAE,WAAW;YACpB,CAAC,IAAI,YAAY,IAAI,IAAI,IAAI,CAAC,IAAI;gBAChC,CAAC,CAAC,IAAI,CAAC,IAAI;gBACX,CAAC,CAAC,0BAA0B,CAAC,CAAC;QAClC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CACxB,CAAC,KAAK,SAAS,CAAC;YACd,SAAS,CAAC;gBACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,IAAI;oBAAE,MAAM;gBACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBACzB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;gBACzB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;oBAC7B,MAAM,IAAI,UAAU,CAAC,UAAU,EAAE,4BAA4B,CAAC,CAAC;gBACjE,kEAAkE;gBAClE,4DAA4D;gBAC5D,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;YACtE,CAAC;QACH,CAAC,CAAC,EAAE,EACJ,EAAE,UAAU,EAAE,KAAK,EAAE,CACtB,CAAC;QACF,6EAA6E;QAC7E,gEAAgE;QAChE,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,SAAS;iBAC3B,kBAAkB,CAAC,GAAG,CAAC;iBACvB,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE;gBACtC,UAAU;gBACV,eAAe,EAAE;oBACf,eAAe,EAAE,WAAW;oBAC5B,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,SAAS;wBACrC,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;iBAChD;gBACD,GAAG,CAAC,OAAO,EAAE,QAAQ,KAAK,SAAS;oBACjC,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACnC,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS;oBAC/B,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBACpC,GAAG,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS;oBACnC,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC;wBACE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;qBAChD,CAAC;aACP,CAAC,CAAC;YACL,OAAO;gBACL,GAAG;gBACH,IAAI;gBACJ,WAAW;gBACX,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC/B,GAAG,CAAC,MAAM,CAAC,YAAY,KAAK,SAAS;oBACnC,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;aACrD,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACrD,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACtC,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IACF,MAAM,WAAW,GAAiC;QAChD,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;QAChE,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC;QACvE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YACtC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YACjC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC;gBAC7B,MAAM,MAAM,GAAG,MAAM,SAAS;qBAC3B,aAAa,CAAC,GAAG,CAAC;qBAClB,QAAQ,CACP,KAAK,EAAE,KAAK,IAAI,CAAC,EACjB,KAAK,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,EAClE;oBACE,UAAU,EAAE,EAAE,OAAO,EAAE;oBACvB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS;wBAC/B,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;iBACrC,CACF,CAAC;gBACJ,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC;gBACzC,IAAI,CAAC,MAAM;oBACT,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,oCAAoC,CACrC,CAAC;gBACJ,IAAI,YAAoB,CAAC;gBACzB,IAAI,CAAC;oBACH,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBACzC,IAAI,YAAY,KAAK,IAAI;wBACvB,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,uCAAuC,CACxC,CAAC;gBACN,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,MAAM,KAAK,CAAC;gBACd,CAAC;gBACD,OAAO,gBAAgB,CACrB;oBACE,GAAG;oBACH,IAAI,EAAE,YAAY;oBAClB,IAAI,EAAE,MAAM,CAAC,aAAa,IAAI,CAAC;oBAC/B,IAAI,EAAE,MAAM,CAAC,WAAW,IAAI,0BAA0B;oBACtD,GAAG,CAAC,MAAM,CAAC,YAAY,KAAK,SAAS;wBACnC,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;oBACpD,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS;wBAC/B,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;iBACnC,EACD;oBACE,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,GAAG,EAAE,CACZ,QAAQ,CAAC,KAAK,CACZ,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CACQ;iBAClC,CACF,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YACnC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YACjC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;oBACxC,UAAU,EAAE,EAAE,OAAO,EAAE;oBACvB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS;wBAC/B,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;iBACrC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;KACF,CAAC;IACF,OAAO;QACL,GAAG,OAAO;QACV,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;QACvC,6EAA6E;QAC7E,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,MAAM,KAAK,SAAS;QACtB,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,2CAA2C,CAC5C,CAAC;IACJ,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,IAAI,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACjD,IAAI,IAAI,KAAK,SAAS;QACpB,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,0CAA0C,CAC3C,CAAC;IACJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,IAAU;IAC5B,IAAI,IAAI,YAAY,cAAc;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,IAAI,YAAY,IAAI;QAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IAC/C,MAAM,KAAK,GACT,OAAO,IAAI,KAAK,QAAQ;QACtB,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;QAChC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC;YACxB,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC;YAC/D,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,cAAc,CAAC;QACxB,KAAK,CAAC,UAAU;YACd,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { Readable } from 'node:stream';\nimport {\n createStoredFile,\n FilesError,\n type AdapterConditionalOperations,\n type AdapterUploadOptions,\n type Body,\n type ConditionalUploadResult,\n} from 'files-sdk';\nimport { mapAzureError, type AzureAdapter } from 'files-sdk/azure';\nimport type { FilesSdkPhysicalKeyAdapter } from '../files-sdk.driver.js';\nimport {\n normalizeProviderStorageEtag,\n storageEtagHeader,\n} from '../../storage-etag.js';\n\n/** Native Azure predicates execute inside Files SDK's conditional policy pipeline. */\nexport function withAzureConditionalOperations(\n adapter: AzureAdapter,\n): AzureAdapter & FilesSdkPhysicalKeyAdapter {\n const container = adapter.raw.getContainerClient(adapter.bucket);\n const upload = async (\n key: string,\n body: Body,\n expectedEtag: string | null,\n options?: AdapterUploadOptions,\n ): Promise<ConditionalUploadResult> => {\n const conditions =\n expectedEtag === null\n ? { ifNoneMatch: '*' }\n : { ifMatch: etagHeader(expectedEtag) };\n const contentType =\n options?.contentType ??\n (body instanceof Blob && body.type\n ? body.type\n : 'application/octet-stream');\n let size = 0;\n const reader = bodyStream(body).getReader();\n const node = Readable.from(\n (async function* () {\n for (;;) {\n const next = await reader.read();\n options?.signal?.throwIfAborted();\n if (next.done) break;\n const chunk = next.value;\n size += chunk.byteLength;\n if (!Number.isSafeInteger(size))\n throw new FilesError('Provider', 'Azure upload is too large.');\n // Azure's upload scheduler requires Buffer.copy(), including when\n // the source is a web stream of ordinary Uint8Array chunks.\n yield Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n }\n })(),\n { objectMode: false },\n );\n // Destroying the Node stream also cancels the web source, including a source\n // blocked waiting for its next chunk when cancellation arrives.\n const abort = () => {\n void reader.cancel(options?.signal?.reason).catch(() => {});\n };\n options?.signal?.addEventListener('abort', abort, { once: true });\n try {\n options?.signal?.throwIfAborted();\n const result = await container\n .getBlockBlobClient(key)\n .uploadStream(node, 4 * 1024 * 1024, 2, {\n conditions,\n blobHTTPHeaders: {\n blobContentType: contentType,\n ...(options?.cacheControl === undefined\n ? {}\n : { blobCacheControl: options.cacheControl }),\n },\n ...(options?.metadata === undefined\n ? {}\n : { metadata: options.metadata }),\n ...(options?.signal === undefined\n ? {}\n : { abortSignal: options.signal }),\n ...(options?.onProgress === undefined\n ? {}\n : {\n onProgress: ({ loadedBytes }) =>\n options.onProgress?.({ loaded: loadedBytes }),\n }),\n });\n return {\n key,\n size,\n contentType,\n etag: requiredEtag(result.etag),\n ...(result.lastModified === undefined\n ? {}\n : { lastModified: result.lastModified.getTime() }),\n };\n } catch (error) {\n throw mapAzureError(error);\n } finally {\n options?.signal?.removeEventListener('abort', abort);\n node.destroy();\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\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 const ifMatch = etagHeader(etag);\n options?.signal?.throwIfAborted();\n try {\n const range = options?.range;\n const result = await container\n .getBlobClient(key)\n .download(\n range?.start ?? 0,\n range?.end === undefined ? undefined : range.end - range.start + 1,\n {\n conditions: { ifMatch },\n ...(options?.signal === undefined\n ? {}\n : { abortSignal: options.signal }),\n },\n );\n const stream = result.readableStreamBody;\n if (!stream)\n throw new FilesError(\n 'Provider',\n 'Azure returned no download stream.',\n );\n let returnedEtag: string;\n try {\n returnedEtag = requiredEtag(result.etag);\n if (returnedEtag !== etag)\n throw new FilesError(\n 'Conflict',\n 'Azure object condition did not match.',\n );\n } catch (error) {\n stream.destroy();\n throw error;\n }\n return createStoredFile(\n {\n key,\n etag: returnedEtag,\n size: result.contentLength ?? 0,\n type: result.contentType ?? 'application/octet-stream',\n ...(result.lastModified === undefined\n ? {}\n : { lastModified: result.lastModified.getTime() }),\n ...(result.metadata === undefined\n ? {}\n : { metadata: result.metadata }),\n },\n {\n kind: 'stream',\n factory: () =>\n Readable.toWeb(\n Readable.from(stream),\n ) as ReadableStream<Uint8Array>,\n },\n );\n } catch (error) {\n throw mapAzureError(error);\n }\n },\n delete: async (key, etag, options) => {\n const ifMatch = etagHeader(etag);\n options?.signal?.throwIfAborted();\n try {\n await container.getBlobClient(key).delete({\n conditions: { ifMatch },\n ...(options?.signal === undefined\n ? {}\n : { abortSignal: options.signal }),\n });\n } catch (error) {\n throw mapAzureError(error);\n }\n },\n };\n return {\n ...adapter,\n conditional: Object.freeze(conditional),\n // A conservative UTF-8 budget within Azure's 1024-character blob-name limit.\n physicalKey: Object.freeze({ maxBytes: 1024 }),\n };\n}\n\nfunction etagHeader(etag: string): string {\n const header = storageEtagHeader(etag);\n if (header === undefined)\n throw new FilesError(\n 'Provider',\n 'Azure requires one canonical strong ETag.',\n );\n return header;\n}\n\nfunction requiredEtag(value: unknown): string {\n const etag = normalizeProviderStorageEtag(value);\n if (etag === undefined)\n throw new FilesError(\n 'Provider',\n 'Azure returned no canonical strong ETag.',\n );\n return etag;\n}\n\nfunction bodyStream(body: Body): ReadableStream<Uint8Array> {\n if (body instanceof ReadableStream) return body;\n if (body instanceof Blob) return body.stream();\n const bytes =\n typeof body === 'string'\n ? new TextEncoder().encode(body)\n : ArrayBuffer.isView(body)\n ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength)\n : new Uint8Array(body);\n return new ReadableStream({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AzureAdapter, type AzureAdapterOptions } from 'files-sdk/azure';
|
|
2
|
+
import { type FilesSdkDriverOptions, type FilesSdkStorageDriver } from '../files-sdk.driver.js';
|
|
3
|
+
export interface AzureStorageDriverOptions extends Omit<FilesSdkDriverOptions<AzureAdapter>, 'adapter'> {
|
|
4
|
+
adapter: AzureAdapterOptions;
|
|
5
|
+
}
|
|
6
|
+
/** Files SDK Azure adapter with native create/replace/read/delete predicates. */
|
|
7
|
+
export declare function azure(options: AzureAdapterOptions): AzureAdapter;
|
|
8
|
+
export declare function createAzureStorageDriver(options: AzureStorageDriverOptions): FilesSdkStorageDriver<AzureAdapter>;
|
|
9
|
+
export type { AzureAdapter, AzureAdapterOptions } from 'files-sdk/azure';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/files-sdk/azure/index.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAGhC,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CACrD,qBAAqB,CAAC,YAAY,CAAC,EACnC,SAAS,CACV;IACC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAyBhE;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,yBAAyB,GACjC,qBAAqB,CAAC,YAAY,CAAC,CAOrC;AAED,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FilesError } from 'files-sdk';
|
|
2
|
+
import { azure as createFilesSdkAzureAdapter, } from 'files-sdk/azure';
|
|
3
|
+
import { createFilesSdkDriver, mapFilesSdkError, } from '../files-sdk.driver.js';
|
|
4
|
+
import { withAzureConditionalOperations } from './azure-conditional.js';
|
|
5
|
+
/** Files SDK Azure adapter with native create/replace/read/delete predicates. */
|
|
6
|
+
export function azure(options) {
|
|
7
|
+
if (options.credential !== undefined) {
|
|
8
|
+
if (!options.accountName ||
|
|
9
|
+
options.connectionString ||
|
|
10
|
+
options.accountKey ||
|
|
11
|
+
options.sasToken) {
|
|
12
|
+
throw new FilesError('Provider', 'Azure token authentication requires an account name and no competing credentials.');
|
|
13
|
+
}
|
|
14
|
+
// Explicit workload identity must not silently fall back to ambient shared
|
|
15
|
+
// keys or connection strings inherited from an unrelated Azure application.
|
|
16
|
+
return withAzureConditionalOperations(createFilesSdkAzureAdapter({
|
|
17
|
+
...options,
|
|
18
|
+
connectionString: '',
|
|
19
|
+
accountKey: '',
|
|
20
|
+
sasToken: '',
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
return withAzureConditionalOperations(createFilesSdkAzureAdapter(options));
|
|
24
|
+
}
|
|
25
|
+
export function createAzureStorageDriver(options) {
|
|
26
|
+
const { adapter, ...driver } = options;
|
|
27
|
+
try {
|
|
28
|
+
return createFilesSdkDriver({ ...driver, adapter: azure(adapter) });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw mapFilesSdkError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/files-sdk/azure/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EACL,KAAK,IAAI,0BAA0B,GAGpC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,GAGjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AASxE,iFAAiF;AACjF,MAAM,UAAU,KAAK,CAAC,OAA4B;IAChD,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,IACE,CAAC,OAAO,CAAC,WAAW;YACpB,OAAO,CAAC,gBAAgB;YACxB,OAAO,CAAC,UAAU;YAClB,OAAO,CAAC,QAAQ,EAChB,CAAC;YACD,MAAM,IAAI,UAAU,CAClB,UAAU,EACV,mFAAmF,CACpF,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,4EAA4E;QAC5E,OAAO,8BAA8B,CACnC,0BAA0B,CAAC;YACzB,GAAG,OAAO;YACV,gBAAgB,EAAE,EAAE;YACpB,UAAU,EAAE,EAAE;YACd,QAAQ,EAAE,EAAE;SACb,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,8BAA8B,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,OAAkC;IAElC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACvC,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;AACH,CAAC","sourcesContent":["import { FilesError } from 'files-sdk';\nimport {\n azure as createFilesSdkAzureAdapter,\n type AzureAdapter,\n type AzureAdapterOptions,\n} from 'files-sdk/azure';\nimport {\n createFilesSdkDriver,\n mapFilesSdkError,\n type FilesSdkDriverOptions,\n type FilesSdkStorageDriver,\n} from '../files-sdk.driver.js';\nimport { withAzureConditionalOperations } from './azure-conditional.js';\n\nexport interface AzureStorageDriverOptions extends Omit<\n FilesSdkDriverOptions<AzureAdapter>,\n 'adapter'\n> {\n adapter: AzureAdapterOptions;\n}\n\n/** Files SDK Azure adapter with native create/replace/read/delete predicates. */\nexport function azure(options: AzureAdapterOptions): AzureAdapter {\n if (options.credential !== undefined) {\n if (\n !options.accountName ||\n options.connectionString ||\n options.accountKey ||\n options.sasToken\n ) {\n throw new FilesError(\n 'Provider',\n 'Azure token authentication requires an account name and no competing credentials.',\n );\n }\n // Explicit workload identity must not silently fall back to ambient shared\n // keys or connection strings inherited from an unrelated Azure application.\n return withAzureConditionalOperations(\n createFilesSdkAzureAdapter({\n ...options,\n connectionString: '',\n accountKey: '',\n sasToken: '',\n }),\n );\n }\n return withAzureConditionalOperations(createFilesSdkAzureAdapter(options));\n}\n\nexport function createAzureStorageDriver(\n options: AzureStorageDriverOptions,\n): FilesSdkStorageDriver<AzureAdapter> {\n const { adapter, ...driver } = options;\n try {\n return createFilesSdkDriver({ ...driver, adapter: azure(adapter) });\n } catch (error) {\n throw mapFilesSdkError(error);\n }\n}\n\nexport type { AzureAdapter, AzureAdapterOptions } from 'files-sdk/azure';\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;AAkID,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 === 'azure') {
|
|
66
|
+
const { withAzureConditionalOperations } = await import('../azure/azure-conditional.js');
|
|
67
|
+
return withAzureConditionalOperations(adapter);
|
|
68
|
+
}
|
|
65
69
|
if (provider === 'memory') {
|
|
66
70
|
const { withMemoryConditionalOperations } = await import('../memory.js');
|
|
67
71
|
return withMemoryConditionalOperations(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 === '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
|
+
{"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 === 'azure') {\n const { withAzureConditionalOperations } =\n await import('../azure/azure-conditional.js');\n return withAzureConditionalOperations(\n adapter as Parameters<typeof withAzureConditionalOperations>[0],\n );\n }\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestm/storage",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"description": "Framework-neutral storage clients, capability-scoped agent workspaces, NestJS 12 integration, and guarded HTTP access.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kauan Guesser",
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
"./crypto": {
|
|
64
64
|
"types": "./dist/crypto/index.d.ts",
|
|
65
65
|
"import": "./dist/crypto/index.js"
|
|
66
|
+
},
|
|
67
|
+
"./files-sdk/azure": {
|
|
68
|
+
"types": "./dist/files-sdk/azure/index.d.ts",
|
|
69
|
+
"import": "./dist/files-sdk/azure/index.js"
|
|
66
70
|
}
|
|
67
71
|
},
|
|
68
72
|
"main": "./dist/index.js",
|
|
@@ -102,13 +106,15 @@
|
|
|
102
106
|
"@aws-sdk/lib-storage": "^3.700.0",
|
|
103
107
|
"@aws-sdk/s3-presigned-post": "^3.700.0",
|
|
104
108
|
"@aws-sdk/s3-request-presigner": "^3.700.0",
|
|
109
|
+
"@azure/core-auth": "^1.11.0",
|
|
110
|
+
"@azure/storage-blob": "^12.33.0",
|
|
105
111
|
"@nestjs/common": "^12.0.0",
|
|
106
112
|
"@nestjs/core": "^12.0.0",
|
|
113
|
+
"@nestm/crypto": "0.1.0-alpha.9",
|
|
107
114
|
"ai": ">=7.0.0 <8.0.0",
|
|
108
115
|
"reflect-metadata": "^0.2.2",
|
|
109
116
|
"rxjs": "^7.8.1",
|
|
110
|
-
"zod": "^4.1.8"
|
|
111
|
-
"@nestm/crypto": "0.1.0-alpha.9"
|
|
117
|
+
"zod": "^4.1.8"
|
|
112
118
|
},
|
|
113
119
|
"peerDependenciesMeta": {
|
|
114
120
|
"@aws-sdk/client-s3": {
|
|
@@ -143,6 +149,12 @@
|
|
|
143
149
|
},
|
|
144
150
|
"@nestm/crypto": {
|
|
145
151
|
"optional": true
|
|
152
|
+
},
|
|
153
|
+
"@azure/storage-blob": {
|
|
154
|
+
"optional": true
|
|
155
|
+
},
|
|
156
|
+
"@azure/core-auth": {
|
|
157
|
+
"optional": true
|
|
146
158
|
}
|
|
147
159
|
},
|
|
148
160
|
"devDependencies": {
|
|
@@ -150,12 +162,16 @@
|
|
|
150
162
|
"@aws-sdk/lib-storage": "3.1120.0",
|
|
151
163
|
"@aws-sdk/s3-presigned-post": "3.1120.0",
|
|
152
164
|
"@aws-sdk/s3-request-presigner": "3.1120.0",
|
|
165
|
+
"@azure/core-auth": "1.11.0",
|
|
166
|
+
"@azure/identity": "4.13.2",
|
|
167
|
+
"@azure/storage-blob": "12.33.0",
|
|
153
168
|
"@changesets/cli": "3.0.1",
|
|
154
169
|
"@nestjs/common": "12.0.1",
|
|
155
170
|
"@nestjs/core": "12.0.1",
|
|
156
171
|
"@nestjs/platform-express": "12.0.1",
|
|
157
172
|
"@nestjs/platform-fastify": "12.0.1",
|
|
158
173
|
"@nestjs/testing": "12.0.1",
|
|
174
|
+
"@nestm/crypto": "0.1.0-alpha.9",
|
|
159
175
|
"@types/node": "26.4.0",
|
|
160
176
|
"@types/supertest": "7.2.1",
|
|
161
177
|
"@vitest/coverage-v8": "4.1.11",
|
|
@@ -169,8 +185,7 @@
|
|
|
169
185
|
"supertest": "7.2.2",
|
|
170
186
|
"typescript": "7.0.2",
|
|
171
187
|
"vitest": "4.1.11",
|
|
172
|
-
"zod": "4.4.3"
|
|
173
|
-
"@nestm/crypto": "0.1.0-alpha.9"
|
|
188
|
+
"zod": "4.4.3"
|
|
174
189
|
},
|
|
175
190
|
"scripts": {
|
|
176
191
|
"build": "node scripts/clean.mjs && tsc -p tsconfig.build.json",
|