@nestm/storage 0.1.0-alpha.12 → 0.1.0-alpha.14

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @nestm/storage
2
2
 
3
+ ## 0.1.0-alpha.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 79609f1: Use conditional multipart completion for staged content streams when the provider supports create-only completion. This enables encrypted file writes on AWS S3 without buffering the entire content or weakening immutable creation and exact ETag reads.
8
+
9
+ ## 0.1.0-alpha.13
10
+
11
+ ### Minor Changes
12
+
13
+ - 211e8c4: Add native Azure Blob create-only uploads, ETag-conditional replacement and deletion,
14
+ and exact ETag reads with ranges through the Files SDK conditional pipeline. Expose
15
+ `createAzureStorageDriver` and decorate the named Azure provider with the same
16
+ primitives, allowing staged encrypted content to run on Azure without application
17
+ provider code.
18
+
3
19
  ## 0.1.0-alpha.12
4
20
 
5
21
  ### 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
@@ -1 +1 @@
1
- {"version":3,"file":"storage-staged-content.d.ts","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AACD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AACD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC/C;AACD,MAAM,WAAW,oBAAoB,CAAC,KAAK;IACzC,KAAK,CACH,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,IAAI,CACF,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;CACxC;AACD,MAAM,WAAW,gCAAgC,CAAC,KAAK;IACrD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D;AAED,4FAA4F;AAC5F,qBAAa,yBAAyB,CACpC,KAAK,CACL,YAAW,oBAAoB,CAAC,KAAK,CAAC;;IAGtC,YAAY,OAAO,EAAE,gCAAgC,CAAC,KAAK,CAAC,EAG3D;IACK,KAAK,CACT,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAE5B;IACD,mFAAmF;IAC7E,aAAa,CACjB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAgE5B;IACK,IAAI,CACR,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAgBrC;IACD,oFAAoF;IAC9E,MAAM,CACV,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,IAAI,CAAC,CAMf;CACF"}
1
+ {"version":3,"file":"storage-staged-content.d.ts","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AACD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AACD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC/C;AACD,MAAM,WAAW,oBAAoB,CAAC,KAAK;IACzC,KAAK,CACH,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9B,IAAI,CACF,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;CACxC;AACD,MAAM,WAAW,gCAAgC,CAAC,KAAK;IACrD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CAC3D;AAED,4FAA4F;AAC5F,qBAAa,yBAAyB,CACpC,KAAK,CACL,YAAW,oBAAoB,CAAC,KAAK,CAAC;;IAGtC,YAAY,OAAO,EAAE,gCAAgC,CAAC,KAAK,CAAC,EAG3D;IACK,KAAK,CACT,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAE5B;IACD,mFAAmF;IAC7E,aAAa,CACjB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAqE5B;IACK,IAAI,CACR,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAgBrC;IACD,oFAAoF;IAC9E,MAAM,CACV,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,IAAI,CAAC,CAMf;CACF"}
@@ -47,6 +47,11 @@ export class StorageStagedContentStore {
47
47
  const result = await this.#client.uploadConditional(this.#key(scope, payloadId), measured, {
48
48
  condition: { type: 'create' },
49
49
  contentType: 'application/octet-stream',
50
+ // Unknown-length streams need bounded parts on providers such as S3.
51
+ // Only opt in when the provider can enforce create at completion.
52
+ ...(capabilities.conditionalMultipartCompletion?.create === true && {
53
+ multipart: true,
54
+ }),
50
55
  retries: 0,
51
56
  ...(signal === undefined ? {} : { signal }),
52
57
  });
@@ -1 +1 @@
1
- {"version":3,"file":"storage-staged-content.js","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { StorageError } from '../storage.error.js';\nimport type { StorageClient } from '../storage.client.js';\nimport type { StorageByteRange } from '../storage.types.js';\nimport { storageInteger } from './storage-streams.js';\n\nexport interface StorageStagedBody {\n readonly payloadId: string;\n readonly size: number;\n readonly sha256: string;\n readonly etag: string;\n}\nexport interface StorageStagedWriteOptions {\n readonly signal?: AbortSignal | undefined;\n readonly maxBytes?: number | undefined;\n}\nexport interface StorageStagedReadOptions {\n readonly signal?: AbortSignal | undefined;\n readonly range?: StorageByteRange | undefined;\n}\nexport interface StorageStagedContent<Scope> {\n write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options?: StorageStagedWriteOptions,\n ): Promise<StorageStagedBody>;\n read(\n scope: Scope,\n body: StorageStagedBody,\n options?: StorageStagedReadOptions,\n ): Promise<ReadableStream<Uint8Array>>;\n}\nexport interface StorageStagedContentStoreOptions<Scope> {\n readonly client: StorageClient;\n /** Trusted, injective scope mapping; never accept this function from a model. */\n readonly key: (scope: Scope, payloadId: string) => string;\n}\n\n/** Immutable create-only bodies. Retention eligibility and references remain host-owned. */\nexport class StorageStagedContentStore<\n Scope,\n> implements StorageStagedContent<Scope> {\n readonly #client: StorageClient;\n readonly #key: (scope: Scope, payloadId: string) => string;\n constructor(options: StorageStagedContentStoreOptions<Scope>) {\n this.#client = options.client;\n this.#key = options.key;\n }\n async write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n return this.writeReserved(scope, randomUUID(), body, options);\n }\n /** Trusted callers reserve a fresh identity before preparing detached metadata. */\n async writeReserved(\n scope: Scope,\n payloadId: string,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n validatePayloadId(payloadId);\n const { signal } = options;\n const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;\n storageInteger(maxBytes, 'maxBytes');\n const capabilities = this.#client.capabilities;\n if (\n !capabilities.conditionalCreate?.resultEtag ||\n !capabilities.conditionalRead?.etag\n ) {\n await body.cancel().catch(() => {});\n throw new StorageError(\n 'Staged content requires native create-only writes and exact ETag reads.',\n { code: 'NOT_SUPPORTED' },\n );\n }\n const hash = createHash('sha256');\n let size = 0;\n let completed = false;\n const measured = body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n signal?.throwIfAborted();\n size += chunk.byteLength;\n if (!Number.isSafeInteger(size) || size > maxBytes)\n throw new StorageError('Staged body exceeds its byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n hash.update(chunk);\n controller.enqueue(chunk);\n },\n flush() {\n completed = true;\n },\n }),\n signal === undefined ? {} : { signal },\n );\n try {\n signal?.throwIfAborted();\n const result = await this.#client.uploadConditional(\n this.#key(scope, payloadId),\n measured,\n {\n condition: { type: 'create' },\n contentType: 'application/octet-stream',\n retries: 0,\n ...(signal === undefined ? {} : { signal }),\n },\n );\n if (!completed || result.size !== size || result.etag === undefined)\n throw new StorageError(\n 'Provider did not acknowledge the complete staged body.',\n { code: 'PROVIDER', applied: true },\n );\n return Object.freeze({\n payloadId,\n size,\n sha256: hash.digest('hex'),\n etag: result.etag,\n });\n } catch (error) {\n await measured.cancel(error).catch(() => {});\n throw error;\n }\n }\n async read(\n scope: Scope,\n body: StorageStagedBody,\n options: StorageStagedReadOptions = {},\n ): Promise<ReadableStream<Uint8Array>> {\n validateBody(body);\n options.signal?.throwIfAborted();\n if (options.range !== undefined && !this.#client.capabilities.rangeRead)\n throw new StorageError('Provider does not support byte ranges.', {\n code: 'NOT_SUPPORTED',\n });\n const result = await this.#client.downloadConditional(\n this.#key(scope, body.payloadId),\n {\n condition: { etag: body.etag },\n ...(options.range === undefined ? {} : { range: options.range }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n },\n );\n return result.body;\n }\n /** Host must prove no durable/in-flight reference can be created before removal. */\n async remove(\n scope: Scope,\n body: StorageStagedBody,\n options: { readonly signal?: AbortSignal } = {},\n ): Promise<void> {\n validateBody(body);\n await this.#client.deleteConditional(this.#key(scope, body.payloadId), {\n condition: { etag: body.etag },\n ...options,\n });\n }\n}\nfunction validateBody(body: StorageStagedBody): void {\n validatePayloadId(body.payloadId);\n if (!/^[0-9a-f]{64}$/u.test(body.sha256))\n throw new StorageError('Invalid staged body receipt.', {\n code: 'INVALID_ARGUMENT',\n });\n storageInteger(body.size, 'size');\n}\n\nfunction validatePayloadId(payloadId: string): void {\n if (\n !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(\n payloadId,\n )\n )\n throw new StorageError('Invalid staged payload identity.', {\n code: 'INVALID_ARGUMENT',\n });\n}\n"]}
1
+ {"version":3,"file":"storage-staged-content.js","sourceRoot":"","sources":["../../src/core/storage-staged-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { StorageError } from '../storage.error.js';\nimport type { StorageClient } from '../storage.client.js';\nimport type { StorageByteRange } from '../storage.types.js';\nimport { storageInteger } from './storage-streams.js';\n\nexport interface StorageStagedBody {\n readonly payloadId: string;\n readonly size: number;\n readonly sha256: string;\n readonly etag: string;\n}\nexport interface StorageStagedWriteOptions {\n readonly signal?: AbortSignal | undefined;\n readonly maxBytes?: number | undefined;\n}\nexport interface StorageStagedReadOptions {\n readonly signal?: AbortSignal | undefined;\n readonly range?: StorageByteRange | undefined;\n}\nexport interface StorageStagedContent<Scope> {\n write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options?: StorageStagedWriteOptions,\n ): Promise<StorageStagedBody>;\n read(\n scope: Scope,\n body: StorageStagedBody,\n options?: StorageStagedReadOptions,\n ): Promise<ReadableStream<Uint8Array>>;\n}\nexport interface StorageStagedContentStoreOptions<Scope> {\n readonly client: StorageClient;\n /** Trusted, injective scope mapping; never accept this function from a model. */\n readonly key: (scope: Scope, payloadId: string) => string;\n}\n\n/** Immutable create-only bodies. Retention eligibility and references remain host-owned. */\nexport class StorageStagedContentStore<\n Scope,\n> implements StorageStagedContent<Scope> {\n readonly #client: StorageClient;\n readonly #key: (scope: Scope, payloadId: string) => string;\n constructor(options: StorageStagedContentStoreOptions<Scope>) {\n this.#client = options.client;\n this.#key = options.key;\n }\n async write(\n scope: Scope,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n return this.writeReserved(scope, randomUUID(), body, options);\n }\n /** Trusted callers reserve a fresh identity before preparing detached metadata. */\n async writeReserved(\n scope: Scope,\n payloadId: string,\n body: ReadableStream<Uint8Array>,\n options: StorageStagedWriteOptions = {},\n ): Promise<StorageStagedBody> {\n validatePayloadId(payloadId);\n const { signal } = options;\n const maxBytes = options.maxBytes ?? Number.MAX_SAFE_INTEGER;\n storageInteger(maxBytes, 'maxBytes');\n const capabilities = this.#client.capabilities;\n if (\n !capabilities.conditionalCreate?.resultEtag ||\n !capabilities.conditionalRead?.etag\n ) {\n await body.cancel().catch(() => {});\n throw new StorageError(\n 'Staged content requires native create-only writes and exact ETag reads.',\n { code: 'NOT_SUPPORTED' },\n );\n }\n const hash = createHash('sha256');\n let size = 0;\n let completed = false;\n const measured = body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n signal?.throwIfAborted();\n size += chunk.byteLength;\n if (!Number.isSafeInteger(size) || size > maxBytes)\n throw new StorageError('Staged body exceeds its byte budget.', {\n code: 'LIMIT_EXCEEDED',\n });\n hash.update(chunk);\n controller.enqueue(chunk);\n },\n flush() {\n completed = true;\n },\n }),\n signal === undefined ? {} : { signal },\n );\n try {\n signal?.throwIfAborted();\n const result = await this.#client.uploadConditional(\n this.#key(scope, payloadId),\n measured,\n {\n condition: { type: 'create' },\n contentType: 'application/octet-stream',\n // Unknown-length streams need bounded parts on providers such as S3.\n // Only opt in when the provider can enforce create at completion.\n ...(capabilities.conditionalMultipartCompletion?.create === true && {\n multipart: true,\n }),\n retries: 0,\n ...(signal === undefined ? {} : { signal }),\n },\n );\n if (!completed || result.size !== size || result.etag === undefined)\n throw new StorageError(\n 'Provider did not acknowledge the complete staged body.',\n { code: 'PROVIDER', applied: true },\n );\n return Object.freeze({\n payloadId,\n size,\n sha256: hash.digest('hex'),\n etag: result.etag,\n });\n } catch (error) {\n await measured.cancel(error).catch(() => {});\n throw error;\n }\n }\n async read(\n scope: Scope,\n body: StorageStagedBody,\n options: StorageStagedReadOptions = {},\n ): Promise<ReadableStream<Uint8Array>> {\n validateBody(body);\n options.signal?.throwIfAborted();\n if (options.range !== undefined && !this.#client.capabilities.rangeRead)\n throw new StorageError('Provider does not support byte ranges.', {\n code: 'NOT_SUPPORTED',\n });\n const result = await this.#client.downloadConditional(\n this.#key(scope, body.payloadId),\n {\n condition: { etag: body.etag },\n ...(options.range === undefined ? {} : { range: options.range }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n },\n );\n return result.body;\n }\n /** Host must prove no durable/in-flight reference can be created before removal. */\n async remove(\n scope: Scope,\n body: StorageStagedBody,\n options: { readonly signal?: AbortSignal } = {},\n ): Promise<void> {\n validateBody(body);\n await this.#client.deleteConditional(this.#key(scope, body.payloadId), {\n condition: { etag: body.etag },\n ...options,\n });\n }\n}\nfunction validateBody(body: StorageStagedBody): void {\n validatePayloadId(body.payloadId);\n if (!/^[0-9a-f]{64}$/u.test(body.sha256))\n throw new StorageError('Invalid staged body receipt.', {\n code: 'INVALID_ARGUMENT',\n });\n storageInteger(body.size, 'size');\n}\n\nfunction validatePayloadId(payloadId: string): void {\n if (\n !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(\n payloadId,\n )\n )\n throw new StorageError('Invalid staged payload identity.', {\n code: 'INVALID_ARGUMENT',\n });\n}\n"]}
@@ -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":"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","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;AA2HD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,mBAAmB,CAE7E;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,IAAI,SAAS,QAAQ,EAAE,CAE1D;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAErE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjE;AAED,6EAA6E;AAC7E,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvE;AAED,YAAY,EACV,MAAM,IAAI,qBAAqB,EAC/B,QAAQ,IAAI,mBAAmB,GAChC,MAAM,qBAAqB,CAAC"}
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.12",
3
+ "version": "0.1.0-alpha.14",
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",