@nestm/storage 0.1.0-alpha.0

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +83 -0
  3. package/LICENSE +21 -0
  4. package/README.md +386 -0
  5. package/SECURITY.md +25 -0
  6. package/dist/files-sdk/files-sdk.driver.d.ts +36 -0
  7. package/dist/files-sdk/files-sdk.driver.d.ts.map +1 -0
  8. package/dist/files-sdk/files-sdk.driver.js +352 -0
  9. package/dist/files-sdk/files-sdk.driver.js.map +1 -0
  10. package/dist/files-sdk/index.d.ts +3 -0
  11. package/dist/files-sdk/index.d.ts.map +1 -0
  12. package/dist/files-sdk/index.js +2 -0
  13. package/dist/files-sdk/index.js.map +1 -0
  14. package/dist/gateway/index.d.ts +3 -0
  15. package/dist/gateway/index.d.ts.map +1 -0
  16. package/dist/gateway/index.js +3 -0
  17. package/dist/gateway/index.js.map +1 -0
  18. package/dist/gateway/storage-gateway-fastify-parser.d.ts +9 -0
  19. package/dist/gateway/storage-gateway-fastify-parser.d.ts.map +1 -0
  20. package/dist/gateway/storage-gateway-fastify-parser.js +50 -0
  21. package/dist/gateway/storage-gateway-fastify-parser.js.map +1 -0
  22. package/dist/gateway/storage-gateway.controller.d.ts +31 -0
  23. package/dist/gateway/storage-gateway.controller.d.ts.map +1 -0
  24. package/dist/gateway/storage-gateway.controller.js +601 -0
  25. package/dist/gateway/storage-gateway.controller.js.map +1 -0
  26. package/dist/gateway/storage-gateway.guard.d.ts +7 -0
  27. package/dist/gateway/storage-gateway.guard.d.ts.map +1 -0
  28. package/dist/gateway/storage-gateway.guard.js +40 -0
  29. package/dist/gateway/storage-gateway.guard.js.map +1 -0
  30. package/dist/gateway/storage-gateway.module.d.ts +6 -0
  31. package/dist/gateway/storage-gateway.module.d.ts.map +1 -0
  32. package/dist/gateway/storage-gateway.module.js +98 -0
  33. package/dist/gateway/storage-gateway.module.js.map +1 -0
  34. package/dist/gateway/storage-gateway.tokens.d.ts +3 -0
  35. package/dist/gateway/storage-gateway.tokens.d.ts.map +1 -0
  36. package/dist/gateway/storage-gateway.tokens.js +3 -0
  37. package/dist/gateway/storage-gateway.tokens.js.map +1 -0
  38. package/dist/gateway/storage-gateway.types.d.ts +65 -0
  39. package/dist/gateway/storage-gateway.types.d.ts.map +1 -0
  40. package/dist/gateway/storage-gateway.types.js +13 -0
  41. package/dist/gateway/storage-gateway.types.js.map +1 -0
  42. package/dist/index.d.ts +11 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +8 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/inject-storage.decorator.d.ts +2 -0
  47. package/dist/inject-storage.decorator.d.ts.map +1 -0
  48. package/dist/inject-storage.decorator.js +6 -0
  49. package/dist/inject-storage.decorator.js.map +1 -0
  50. package/dist/internal/settle-many.d.ts +7 -0
  51. package/dist/internal/settle-many.d.ts.map +1 -0
  52. package/dist/internal/settle-many.js +62 -0
  53. package/dist/internal/settle-many.js.map +1 -0
  54. package/dist/storage-module.options.d.ts +62 -0
  55. package/dist/storage-module.options.d.ts.map +1 -0
  56. package/dist/storage-module.options.js +2 -0
  57. package/dist/storage-module.options.js.map +1 -0
  58. package/dist/storage-upload-control.d.ts +22 -0
  59. package/dist/storage-upload-control.d.ts.map +1 -0
  60. package/dist/storage-upload-control.js +139 -0
  61. package/dist/storage-upload-control.js.map +1 -0
  62. package/dist/storage.client.d.ts +51 -0
  63. package/dist/storage.client.d.ts.map +1 -0
  64. package/dist/storage.client.js +395 -0
  65. package/dist/storage.client.js.map +1 -0
  66. package/dist/storage.driver.d.ts +18 -0
  67. package/dist/storage.driver.d.ts.map +1 -0
  68. package/dist/storage.driver.js +2 -0
  69. package/dist/storage.driver.js.map +1 -0
  70. package/dist/storage.error.d.ts +39 -0
  71. package/dist/storage.error.d.ts.map +1 -0
  72. package/dist/storage.error.js +49 -0
  73. package/dist/storage.error.js.map +1 -0
  74. package/dist/storage.module.d.ts +11 -0
  75. package/dist/storage.module.d.ts.map +1 -0
  76. package/dist/storage.module.js +170 -0
  77. package/dist/storage.module.js.map +1 -0
  78. package/dist/storage.service.d.ts +12 -0
  79. package/dist/storage.service.d.ts.map +1 -0
  80. package/dist/storage.service.js +246 -0
  81. package/dist/storage.service.js.map +1 -0
  82. package/dist/storage.tokens.d.ts +6 -0
  83. package/dist/storage.tokens.d.ts.map +1 -0
  84. package/dist/storage.tokens.js +13 -0
  85. package/dist/storage.tokens.js.map +1 -0
  86. package/dist/storage.types.d.ts +244 -0
  87. package/dist/storage.types.d.ts.map +1 -0
  88. package/dist/storage.types.js +2 -0
  89. package/dist/storage.types.js.map +1 -0
  90. package/dist/testing/index.d.ts +9 -0
  91. package/dist/testing/index.d.ts.map +1 -0
  92. package/dist/testing/index.js +10 -0
  93. package/dist/testing/index.js.map +1 -0
  94. package/package.json +112 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # @nestm/storage
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ The project intends to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
7
+ after the initial experimental releases.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.1.0-alpha.0] - 2026-07-30
12
+
13
+ ### Added
14
+
15
+ - NestJS 12 dynamic module with local-by-default root and feature registration.
16
+ - Default and named stores through `StorageService.use()` and `@InjectStorage()`.
17
+ - NestM-owned streaming, buffered, bulk, search, signing, resumable-upload, and
18
+ error contracts backed by `files-sdk`.
19
+ - Cross-store streaming transfer and mirror/sync workflows.
20
+ - Optional guard-required Express/Fastify HTTP gateway.
21
+ - Node 22/24 CI and Changesets-based alpha publishing through npm OIDC.
@@ -0,0 +1,83 @@
1
+ # Contributing
2
+
3
+ Thank you for helping improve `@nestm/storage`.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js 22.12 or newer
8
+ - Corepack
9
+ - Git
10
+
11
+ Enable the package manager declared by the repository:
12
+
13
+ ```sh
14
+ corepack enable
15
+ ```
16
+
17
+ ## Local setup
18
+
19
+ 1. Fork and clone the repository.
20
+ 2. Create a focused branch from `main`.
21
+ 3. Install the locked dependencies:
22
+
23
+ ```sh
24
+ pnpm install --frozen-lockfile
25
+ ```
26
+
27
+ 4. Make the change and add or update tests.
28
+ 5. Run:
29
+
30
+ ```sh
31
+ pnpm run check
32
+ pnpm run test
33
+ pnpm run verify:pack
34
+ ```
35
+
36
+ 6. Add a Changeset for user-visible changes:
37
+
38
+ ```sh
39
+ pnpm changeset
40
+ ```
41
+
42
+ ## Design guidelines
43
+
44
+ - Keep root public contracts owned by `@nestm/storage`; upstream
45
+ `files-sdk` types belong only in the explicit `files-sdk` bridge.
46
+ - Preserve streaming and backpressure. Do not buffer an object unless the
47
+ method name and a tested byte limit make that behavior explicit.
48
+ - Keep provider-specific dependencies optional and outside the build output.
49
+ - Fail closed when a requested capability cannot be honored.
50
+ - Keep the HTTP gateway optional, deny-by-default, and protected by Nest guards.
51
+ - Keep `StorageModule` local by default.
52
+ - Include `.js` suffixes for local imports compiled as Node ESM.
53
+
54
+ Tests should cover the memory driver contract, named Nest registration,
55
+ duplicate isolation, transfer/sync behavior, and both Express and Fastify for
56
+ gateway changes. Keep tests deterministic and close every Nest application.
57
+
58
+ ## One-time npm bootstrap
59
+
60
+ The first prerelease must be published interactively from a clean checkout of
61
+ `main`:
62
+
63
+ ```sh
64
+ npm publish --access public --tag alpha
65
+ ```
66
+
67
+ Then configure npm Trusted Publishing:
68
+
69
+ ```sh
70
+ npm trust github @nestm/storage \
71
+ --file release.yml \
72
+ --repository nestm-dev/storage \
73
+ --environment release \
74
+ --allow-publish
75
+ ```
76
+
77
+ Subsequent releases use GitHub OIDC with provenance and keep prereleases on the
78
+ `alpha` dist-tag.
79
+
80
+ ## Security issues
81
+
82
+ Do not open a public issue for a suspected vulnerability. Follow
83
+ [SECURITY.md](./SECURITY.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kauan Guesser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # @nestm/storage
2
+
3
+ NestJS 12 storage integration with named stores, explicit streaming I/O,
4
+ cross-store workflows, and an optional guarded HTTP gateway.
5
+
6
+ The package uses [`files-sdk`](https://github.com/haydenbleasel/files-sdk) as
7
+ its provider engine, but owns the API injected into Nest applications. Provider
8
+ SDK types, errors, and `files.raw` do not leak through the root package.
9
+
10
+ > This package targets the NestJS 12 prerelease line and is itself published on
11
+ > the `alpha` dist-tag.
12
+
13
+ ## Requirements
14
+
15
+ - Node.js 22.12 or newer
16
+ - NestJS `12.0.0-alpha.5` or newer in the Nest 12 prerelease line
17
+ - ESM
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ pnpm add @nestm/storage@alpha files-sdk@2.2.2
23
+ ```
24
+
25
+ Install only the native SDKs required by the chosen provider. For example:
26
+
27
+ ```sh
28
+ # S3 and S3-compatible providers
29
+ pnpm add @aws-sdk/client-s3 @aws-sdk/s3-presigned-post \
30
+ @aws-sdk/s3-request-presigner @aws-sdk/lib-storage
31
+
32
+ # Google Cloud Storage
33
+ pnpm add @google-cloud/storage google-auth-library
34
+
35
+ # Azure Blob Storage
36
+ pnpm add @azure/storage-blob @azure/core-auth @azure/identity
37
+ ```
38
+
39
+ `files-sdk` currently declares its optional Nest peer for Nest 10 and 11. This
40
+ library does not import `files-sdk/nestjs`; the Nest 12 integration is entirely
41
+ owned here. A package manager may nevertheless report that temporary optional
42
+ peer mismatch while Nest 12 remains prerelease.
43
+
44
+ NestJS 12 alpha also has prerelease peer declarations that npm may reject under
45
+ its strict resolver. If npm reports an `ERESOLVE` error for Nest's own peers,
46
+ install with `npm install --legacy-peer-deps`; pnpm works with the repository's
47
+ checked-in peer-version policy.
48
+
49
+ ## Configure named stores
50
+
51
+ Create provider adapters with `files-sdk`, wrap them through the explicit
52
+ bridge, and register the resulting drivers:
53
+
54
+ ```ts
55
+ import { Module } from '@nestjs/common';
56
+ import { s3 } from 'files-sdk/s3';
57
+ import { gcs } from 'files-sdk/gcs';
58
+ import { createFilesSdkDriver } from '@nestm/storage/files-sdk';
59
+ import { StorageModule } from '@nestm/storage';
60
+
61
+ export const StorageKey = {
62
+ MEDIA: 'media',
63
+ ARCHIVE: 'archive',
64
+ } as const;
65
+
66
+ @Module({
67
+ imports: [
68
+ StorageModule.forRoot({
69
+ default: StorageKey.MEDIA,
70
+ stores: [
71
+ {
72
+ name: StorageKey.MEDIA,
73
+ driver: createFilesSdkDriver({
74
+ adapter: s3({
75
+ bucket: 'media',
76
+ region: 'us-east-1',
77
+ }),
78
+ }),
79
+ },
80
+ {
81
+ name: StorageKey.ARCHIVE,
82
+ driver: createFilesSdkDriver({
83
+ adapter: gcs({
84
+ bucket: 'archive',
85
+ }),
86
+ }),
87
+ },
88
+ ],
89
+ }),
90
+ ],
91
+ exports: [StorageModule],
92
+ })
93
+ export class AppStorageModule {}
94
+ ```
95
+
96
+ `StorageModule` is not global by default. Import the configured module wherever
97
+ its exports are needed, re-export it from an infrastructure module as above, or
98
+ set `isGlobal: true` deliberately.
99
+
100
+ Inject one store directly:
101
+
102
+ ```ts
103
+ import { Injectable } from '@nestjs/common';
104
+ import { InjectStorage, type StorageClient } from '@nestm/storage';
105
+
106
+ @Injectable()
107
+ export class AvatarService {
108
+ constructor(
109
+ @InjectStorage(StorageKey.MEDIA)
110
+ private readonly media: StorageClient,
111
+ ) {}
112
+
113
+ upload(userId: string, body: ReadableStream<Uint8Array>) {
114
+ return this.media.upload(`avatars/${userId}.png`, body, {
115
+ contentType: 'image/png',
116
+ multipart: true,
117
+ });
118
+ }
119
+ }
120
+ ```
121
+
122
+ Or select stores through the manager:
123
+
124
+ ```ts
125
+ import { Injectable } from '@nestjs/common';
126
+ import { StorageService } from '@nestm/storage';
127
+
128
+ @Injectable()
129
+ export class ArchiveService {
130
+ constructor(private readonly storage: StorageService) {}
131
+
132
+ async archive(key: string) {
133
+ const object = await this.storage.use(StorageKey.MEDIA).downloadStream(key);
134
+ return this.storage
135
+ .use(StorageKey.ARCHIVE)
136
+ .upload(`retained/${key}`, object.body, {
137
+ contentType: object.contentType,
138
+ ...(object.metadata !== undefined && {
139
+ metadata: object.metadata,
140
+ }),
141
+ });
142
+ }
143
+ }
144
+ ```
145
+
146
+ `storage.use()` selects the configured default. When several stores are
147
+ registered without `default`, callers must pass a name.
148
+
149
+ ### Async registration
150
+
151
+ Store names remain static because Nest must create their injection tokens
152
+ before an async factory runs:
153
+
154
+ ```ts
155
+ StorageModule.forRootAsync({
156
+ default: 'media',
157
+ imports: [ConfigModule],
158
+ stores: [
159
+ {
160
+ name: 'media',
161
+ inject: [ConfigService],
162
+ useFactory: (config: ConfigService) =>
163
+ createFilesSdkDriver({
164
+ adapter: s3({
165
+ bucket: config.getOrThrow('MEDIA_BUCKET'),
166
+ region: config.getOrThrow('AWS_REGION'),
167
+ }),
168
+ }),
169
+ },
170
+ ],
171
+ });
172
+ ```
173
+
174
+ `useClass` and `useExisting` are also supported through
175
+ `StorageDriverFactory#createStorageDriver(name)`. Feature-owned stores use the
176
+ same shapes through `forFeature()` and `forFeatureAsync()` and export only
177
+ their named `@InjectStorage(name)` clients; the root `StorageService` remains
178
+ isolated to the stores declared by `forRoot`.
179
+
180
+ Duplicate names within one registration fail immediately. Separate feature
181
+ modules retain their own DI scope rather than mutating an application-wide
182
+ registry. Names are case-sensitive and cannot contain leading or trailing
183
+ whitespace.
184
+
185
+ ## Storage API
186
+
187
+ `StorageClient` exposes:
188
+
189
+ - `upload`, `downloadStream`, `head`, `exists`, `delete`, `copy`, and `move`;
190
+ - `list`, cursor-aware `listAll`, and lazy `search`;
191
+ - `signDownload` and discriminated PUT/POST `signUpload`;
192
+ - `uploadMany`, `downloadMany`, `headMany`, `existsMany`, and `deleteMany`;
193
+ - `file(key)` handles;
194
+ - provider capability inspection; and
195
+ - pause/resume/abort through `StorageUploadControl`.
196
+
197
+ Downloads are streaming by default:
198
+
199
+ ```ts
200
+ const object = await storage.use('media').downloadStream('video.mp4');
201
+ // object.body is a Web ReadableStream<Uint8Array>
202
+ ```
203
+
204
+ The explicit `downloadBytes`, `downloadText`, and `downloadJson` helpers default
205
+ to a 10 MiB in-memory limit:
206
+
207
+ ```ts
208
+ const manifest = await storage
209
+ .use('media')
210
+ .downloadJson<{ version: number }>('manifest.json', {
211
+ maxBytes: 256 * 1024,
212
+ });
213
+ ```
214
+
215
+ Node `Readable` uploads are accepted and converted to Web streams without
216
+ buffering. Provider capability gaps fail closed with `StorageError` rather than
217
+ silently discarding a range, metadata, or cache-control request.
218
+
219
+ ### Resumable uploads
220
+
221
+ ```ts
222
+ import { StorageUploadControl } from '@nestm/storage';
223
+
224
+ const control = new StorageUploadControl();
225
+ const upload = media.upload('large.bin', file, {
226
+ control,
227
+ multipart: { partSize: 8 * 1024 * 1024, concurrency: 4 },
228
+ });
229
+
230
+ control.pause();
231
+ const token = control.toJSON(); // opaque and JSON-serializable
232
+ control.resume();
233
+ await upload;
234
+ ```
235
+
236
+ Persisted tokens are intentionally opaque and versioned. Restore one with
237
+ `StorageUploadControl.from(token)`. Resumable uploads require a repeatable,
238
+ known-length body; a one-shot stream cannot be resumed.
239
+
240
+ ### Cross-store transfer and sync
241
+
242
+ ```ts
243
+ await storage.transfer({
244
+ from: 'media',
245
+ to: 'archive',
246
+ prefix: 'uploads/',
247
+ concurrency: 4,
248
+ });
249
+
250
+ const preview = await storage.sync({
251
+ from: 'media',
252
+ to: 'archive',
253
+ compare: 'size',
254
+ prune: true,
255
+ dryRun: true,
256
+ });
257
+ ```
258
+
259
+ Object bodies stream directly between stores. Key lists and metadata are
260
+ collected to provide deterministic progress and sync plans. Partial failures
261
+ are returned in `errors`. `prune: true` is destructive; use `dryRun` first and
262
+ scope the destination with `destinationPrefix`.
263
+
264
+ ## Optional HTTP gateway
265
+
266
+ The gateway lives at `@nestm/storage/gateway` and is never mounted by
267
+ `StorageModule`.
268
+
269
+ ```ts
270
+ import { Module } from '@nestjs/common';
271
+ import {
272
+ StorageGatewayModule,
273
+ StorageGatewayOperation,
274
+ } from '@nestm/storage/gateway';
275
+
276
+ @Module({
277
+ imports: [
278
+ AppStorageModule,
279
+ AuthModule,
280
+ StorageGatewayModule.register({
281
+ imports: [AppStorageModule, AuthModule],
282
+ store: 'media',
283
+ guards: [JwtAuthGuard],
284
+ mode: 'hybrid',
285
+ operations: [
286
+ StorageGatewayOperation.DOWNLOAD,
287
+ StorageGatewayOperation.UPLOAD,
288
+ StorageGatewayOperation.HEAD,
289
+ StorageGatewayOperation.LIST,
290
+ StorageGatewayOperation.SIGN_DOWNLOAD,
291
+ StorageGatewayOperation.SIGN_UPLOAD,
292
+ ],
293
+ maxUploadBytes: 100 * 1024 * 1024,
294
+ maxListResults: 1000,
295
+ maxSearchResults: 1000,
296
+ proxyInlineContentTypes: ['image/jpeg', 'image/png'],
297
+ }),
298
+ ],
299
+ })
300
+ export class AppModule {}
301
+ ```
302
+
303
+ Registration fails without at least one existing Nest guard. The only bypass is
304
+ the explicit `allowUnauthenticated: true` development escape hatch. Operations
305
+ are deny-by-default and must be allowlisted individually.
306
+
307
+ Proxy downloads default to `Content-Disposition: attachment` and always send
308
+ `X-Content-Type-Options: nosniff`. Add only trusted, non-active MIME types to
309
+ `proxyInlineContentTypes` when browser rendering is required. Search responses
310
+ are capped by `maxSearchResults`, and list pages by `maxListResults` (both
311
+ 1,000 by default).
312
+
313
+ The fixed gateway prefix is `/storage`:
314
+
315
+ | Method | Path | Operation |
316
+ | -------- | --------------------------- | ------------------------------------ |
317
+ | `GET` | `/storage/object?key=...` | signed redirect or streamed download |
318
+ | `PUT` | `/storage/object?key=...` | streamed proxy upload |
319
+ | `HEAD` | `/storage/metadata?key=...` | object metadata |
320
+ | `DELETE` | `/storage/object?key=...` | delete |
321
+ | `GET` | `/storage/list` | list one page |
322
+ | `GET` | `/storage/search` | search keys |
323
+ | `POST` | `/storage/sign-download` | sign download |
324
+ | `POST` | `/storage/sign-upload` | sign direct upload |
325
+ | `POST` | `/storage/copy` | copy |
326
+ | `POST` | `/storage/move` | move |
327
+
328
+ Proxy uploads use `Content-Type: application/octet-stream`; put the stored MIME
329
+ type in `X-Storage-Content-Type`. This keeps Express and Fastify uploads as raw
330
+ streams and avoids global body-parser changes. Generic multipart/form-data is
331
+ not mounted by the gateway—prefer direct signed uploads or handle form parsing
332
+ in an application controller.
333
+
334
+ If an application has already registered an
335
+ `application/octet-stream` parser, the gateway leaves it in place; that parser
336
+ must enforce its own body limit and expose either a byte buffer or readable
337
+ stream. Without an existing parser, the built-in Fastify parser is restricted
338
+ to the marked gateway upload route.
339
+
340
+ `hybrid` mode prefers a signed download when both `download` and
341
+ `signDownload` are allowed and the provider advertises support, then uses the
342
+ streaming proxy when signing is unavailable. `proxy` disables signing routes;
343
+ `signed` disables proxy uploads and downloads.
344
+
345
+ ## Errors and capabilities
346
+
347
+ Every thrown engine/provider failure is normalized to `StorageError`. Branch on
348
+ `error.code`, not provider classes:
349
+
350
+ ```ts
351
+ import { isStorageError, StorageErrorCode } from '@nestm/storage';
352
+
353
+ try {
354
+ await media.head(key);
355
+ } catch (error) {
356
+ if (isStorageError(error) && error.code === StorageErrorCode.NOT_FOUND) {
357
+ return null;
358
+ }
359
+ throw error;
360
+ }
361
+ ```
362
+
363
+ Capability flags cover range reads, native byte-level upload progress,
364
+ delimiter listing, metadata, cache control, resumable uploads, server-side
365
+ copy, and signed transfers.
366
+ Provider-specific native clients are intentionally not exposed from the root
367
+ package.
368
+
369
+ ## Local and in-memory stores
370
+
371
+ The test helper wraps the `files-sdk` in-memory adapter:
372
+
373
+ ```ts
374
+ import { createMemoryStorageDriver } from '@nestm/storage/testing';
375
+
376
+ StorageModule.forRoot({
377
+ stores: [{ name: 'test', driver: createMemoryStorageDriver() }],
378
+ });
379
+ ```
380
+
381
+ For local filesystem storage, import `fs` from `files-sdk/fs` and pass it to
382
+ `createFilesSdkDriver` exactly like a cloud adapter.
383
+
384
+ ## License
385
+
386
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,25 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Until the first stable release, security fixes target the newest published
6
+ prerelease. Pin and test the exact alpha version used with the matching NestJS
7
+ 12 prerelease.
8
+
9
+ ## Reporting a vulnerability
10
+
11
+ Do not disclose a suspected vulnerability in a public issue, discussion, or
12
+ pull request. Use GitHub's private vulnerability reporting flow:
13
+
14
+ <https://github.com/nestm-dev/storage/security/advisories/new>
15
+
16
+ Include the package and Nest versions, a minimal reproduction, expected and
17
+ observed behavior, likely impact, and any tested mitigation.
18
+
19
+ Reports about authorization bypasses in the optional gateway, unsafe signed URL
20
+ behavior, unbounded buffering, path/key confusion, credential leakage,
21
+ cross-store pruning, or dependency compromise are especially useful.
22
+
23
+ For a vulnerability originating in NestJS, `files-sdk`, or a provider SDK,
24
+ follow that project's security policy as well. You may still report it here
25
+ privately when this integration needs a mitigation.
@@ -0,0 +1,36 @@
1
+ import { type Adapter, type FilesOptions } from 'files-sdk';
2
+ import type { StorageDriver } from '../storage.driver.js';
3
+ import type { StorageBody, StorageDownloadOptions, StorageListOptions, StorageListResult, StorageObject, StorageObjectMetadata, StorageOperationOptions, StorageSearchOptions, StorageSignedDownloadOptions, StorageSignedUpload, StorageSignedUploadOptions, StorageUploadOptions, StorageUploadResult } from '../storage.types.js';
4
+ export type FilesSdkDriverOptions<AdapterType extends Adapter> = FilesOptions<AdapterType>;
5
+ export declare class FilesSdkStorageDriver<AdapterType extends Adapter = Adapter> implements StorageDriver {
6
+ #private;
7
+ constructor(options: FilesSdkDriverOptions<AdapterType>);
8
+ get name(): string;
9
+ get capabilities(): {
10
+ cacheControl: boolean;
11
+ delimiter: boolean;
12
+ metadata: boolean;
13
+ rangeRead: boolean;
14
+ resumableUpload: boolean;
15
+ serverSideCopy: boolean;
16
+ signedDownload: {
17
+ supported: boolean;
18
+ maxExpiresIn?: number;
19
+ };
20
+ signedUpload: "runtime";
21
+ nativeUploadProgress: boolean;
22
+ };
23
+ upload(key: string, body: StorageBody, options?: StorageUploadOptions): Promise<StorageUploadResult>;
24
+ download(key: string, options?: StorageDownloadOptions): Promise<StorageObject>;
25
+ head(key: string, options?: StorageOperationOptions): Promise<StorageObjectMetadata>;
26
+ exists(key: string, options?: StorageOperationOptions): Promise<boolean>;
27
+ delete(key: string, options?: StorageOperationOptions): Promise<void>;
28
+ copy(sourceKey: string, destinationKey: string, options?: StorageOperationOptions): Promise<void>;
29
+ move(sourceKey: string, destinationKey: string, options?: StorageOperationOptions): Promise<void>;
30
+ list(options?: StorageListOptions): Promise<StorageListResult>;
31
+ search(pattern: string | RegExp, options?: StorageSearchOptions): AsyncIterable<StorageObjectMetadata>;
32
+ signDownload(key: string, options?: StorageSignedDownloadOptions): Promise<string>;
33
+ signUpload(key: string, options: StorageSignedUploadOptions): Promise<StorageSignedUpload>;
34
+ }
35
+ export declare function createFilesSdkDriver<AdapterType extends Adapter>(options: FilesSdkDriverOptions<AdapterType>): FilesSdkStorageDriver<AdapterType>;
36
+ //# sourceMappingURL=files-sdk.driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files-sdk.driver.d.ts","sourceRoot":"","sources":["../../src/files-sdk/files-sdk.driver.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,OAAO,EAGZ,KAAK,YAAY,EAUlB,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EACV,WAAW,EACX,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EAEvB,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,qBAAqB,CAAC,WAAW,SAAS,OAAO,IAC3D,YAAY,CAAC,WAAW,CAAC,CAAC;AAyS5B,qBAAa,qBAAqB,CAChC,WAAW,SAAS,OAAO,GAAG,OAAO,CACrC,YAAW,aAAa;;gBAIZ,OAAO,EAAE,qBAAqB,CAAC,WAAW,CAAC;IAKvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,YAAY;;;;;;;;;;;;;MAaf;IAEK,MAAM,CACV,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC;IAQzB,QAAQ,CACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,aAAa,CAAC;IAUnB,IAAI,CACR,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,qBAAqB,CAAC;IAMjC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrE,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,IAAI,CAAC;IAMhB,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,IAAI,CAAC;IAMV,IAAI,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAapE,MAAM,CACJ,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa,CAAC,qBAAqB,CAAC;IAoBvC,YAAY,CACV,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,4BAA4B,GACrC,OAAO,CAAC,MAAM,CAAC;IAMlB,UAAU,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,mBAAmB,CAAC;CAahC;AAED,wBAAgB,oBAAoB,CAAC,WAAW,SAAS,OAAO,EAC9D,OAAO,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAC1C,qBAAqB,CAAC,WAAW,CAAC,CAEpC"}