@zucker-framework/storage 1.0.0 → 1.0.5

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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Storage
2
+
3
+ ## WebDAV streaming and directory APIs (unreleased)
4
+
5
+ `WebDavStorage.uploadStream(readable, key, { contentType?, signal? })` streams through a bounded Node Transform to the WebDAV SDK without collecting the file. It counts bytes, awaits both the source pipeline and remote response, and aborts/destroys the streams on source failure, remote failure, setup failure or cancellation. Failed remote writes may leave a partial remote object: the provider does not promise atomic overwrite or delete a previous object on failure. Consumers retain recovery/commit policy.
6
+
7
+ `download(key, { signal? })` checks stat is a file and returns `{ stream: Readable, size }` using the SDK's real read stream (no buffer conversion). Abort destroys the stream. Callers must consume or destroy returned streams and handle stream errors; a resolved download call does not mean all bytes have arrived.
8
+
9
+ **Type compatibility:** `FileManager.download().stream` is now honestly typed as Node `Readable`, not an `fs.ReadStream` (WebDAV/S3 streams have no file descriptor/path). `FileManager.uploadStream` accepts `Readable`. Consumers using filesystem-specific stream properties must migrate. This is a public type-contract breaking correction; release/version it accordingly. Local and S3 streaming implementations are not changed by this WebDAV work.
10
+
11
+ `list(prefix, { deep: false, includeDirectories: true, missing: 'throw' })` requests shallow entries including directories and propagates missing-directory errors for host fallback. Defaults preserve recursive files-only enumeration and empty-on-404 behavior. `WebDavEntry` includes the original `filename`, `basename`, `lastmod`, `type`, as well as generic `key`, `size`, `lastModified`, `contentType`.
12
+
13
+ `ensureDirectory(key)` is public, recursively creates missing parents and coalesces concurrent uncancelled creation. All public keys retain base-path traversal protection. Cancellation-owned upload directory operations use their own SDK signal rather than sharing another caller's cancellation.
14
+
15
+ Credentials remain required by default. Explicit `allowAnonymous: true` permits missing credentials for hosts that deliberately allow unauthenticated WebDAV endpoints; URL remains mandatory. This opt-in does not read environment variables or change host authentication policy. Storage logs never include credentials or endpoint URLs.
16
+
17
+ The host owns URL-to-key normalization, public URL settings, tenant ownership, disk fallback/recovery paths and error disclosure. No in-memory fallback or product environment configuration is introduced.
18
+
19
+ Verification includes SDK mocks and the actual installed WebDAV SDK against a local synthetic HTTP server. Neither demonstrates real infrastructure permissions, durability or remote cancellation cleanup.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Readable } from 'node:stream';
1
2
  import { ReadStream } from 'fs';
2
3
  import { OnModuleDestroy, Type, DynamicModule, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
3
4
 
@@ -18,6 +19,10 @@ interface UploadResult {
18
19
  }
19
20
  interface FileInfo {
20
21
  key: string;
22
+ /** Available when the provider exposes filesystem entries (e.g. WebDAV). */
23
+ type?: 'file' | 'directory';
24
+ filename?: string;
25
+ basename?: string;
21
26
  size: number;
22
27
  lastModified: Date;
23
28
  contentType?: string;
@@ -126,9 +131,9 @@ interface FileManager {
126
131
  readonly providerType: StorageProviderType;
127
132
  isConfigured(): boolean;
128
133
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
129
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
134
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
130
135
  download(key: string): Promise<{
131
- stream: ReadStream;
136
+ stream: Readable;
132
137
  size: number;
133
138
  }>;
134
139
  delete(key: string): Promise<void>;
@@ -149,7 +154,7 @@ interface FileManager {
149
154
  * @param position 起始偏移量(字节)
150
155
  */
151
156
  downloadRange?(key: string, position: number, length?: number): Promise<{
152
- stream: ReadStream;
157
+ stream: Readable;
153
158
  size: number;
154
159
  }>;
155
160
  /**
@@ -228,7 +233,7 @@ declare class LocalFileStorage implements FileManager {
228
233
  });
229
234
  isConfigured(): boolean;
230
235
  upload(buffer: Buffer, key: string, _options?: UploadOptions): Promise<UploadResult>;
231
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
236
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
232
237
  download(key: string): Promise<{
233
238
  stream: ReadStream;
234
239
  size: number;
@@ -294,9 +299,9 @@ declare class S3Storage implements FileManager, OnModuleDestroy {
294
299
  onModuleDestroy(): void;
295
300
  isConfigured(): boolean;
296
301
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
297
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
302
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
298
303
  download(key: string): Promise<{
299
- stream: ReadStream;
304
+ stream: Readable;
300
305
  size: number;
301
306
  }>;
302
307
  delete(key: string): Promise<void>;
@@ -309,11 +314,19 @@ declare class S3Storage implements FileManager, OnModuleDestroy {
309
314
  move(sourceKey: string, destKey: string): Promise<void>;
310
315
  }
311
316
 
317
+ interface WebDavEntry extends FileInfo {
318
+ type: 'file' | 'directory';
319
+ filename: string;
320
+ basename: string;
321
+ lastmod: string;
322
+ }
312
323
  interface WebDavStorageOptions {
313
324
  /** WebDAV 服务地址,如 https://nextcloud.example.com/remote.php/dav/files/user */
314
325
  url: string;
315
- username: string;
316
- password: string;
326
+ username?: string;
327
+ password?: string;
328
+ /** Explicit opt-in for endpoints/consumer policies allowing missing credentials. Default false. */
329
+ allowAnonymous?: boolean;
317
330
  /** 公开访问 URL 前缀(可选) */
318
331
  publicUrl?: string;
319
332
  /** 基础路径前缀(可选,默认 '/') */
@@ -332,23 +345,33 @@ declare class WebDavStorage implements FileManager {
332
345
  private getClient;
333
346
  isConfigured(): boolean;
334
347
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
335
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
336
- download(key: string): Promise<{
337
- stream: ReadStream;
348
+ uploadStream(stream: Readable, key: string, options?: UploadOptions & {
349
+ signal?: AbortSignal;
350
+ }): Promise<UploadResult>;
351
+ download(key: string, options?: {
352
+ signal?: AbortSignal;
353
+ }): Promise<{
354
+ stream: Readable;
338
355
  size: number;
339
356
  }>;
340
357
  delete(key: string): Promise<void>;
341
358
  exists(key: string): Promise<boolean>;
342
359
  getPublicUrl(key: string): Promise<string>;
343
360
  getDownloadUrl(key: string): Promise<string>;
344
- list(prefix: string): Promise<FileInfo[]>;
361
+ list(prefix: string, options?: {
362
+ deep?: boolean;
363
+ includeDirectories?: boolean;
364
+ missing?: 'empty' | 'throw';
365
+ }): Promise<WebDavEntry[]>;
345
366
  getFileInfo(key: string): Promise<FileInfo | undefined>;
346
367
  copy(sourceKey: string, destKey: string): Promise<void>;
347
368
  move(sourceKey: string, destKey: string): Promise<void>;
348
369
  /**
349
370
  * 递归创建目录
350
371
  */
351
- private ensureDirectory;
372
+ /** Accepts a storage key, with the same traversal protection as upload. */
373
+ ensureDirectory(key: string): Promise<void>;
374
+ private ensureRemoteDirectory;
352
375
  private createDirectoryIfMissing;
353
376
  private resolvePath;
354
377
  private stripBasePath;
@@ -424,10 +447,108 @@ declare class ZuckerStorageModule {
424
447
  private static createAsyncProviders;
425
448
  }
426
449
 
450
+ interface StorageUrlHydrationOptions {
451
+ /** The application must establish ownership before allowing a key to be signed. */
452
+ allowedStorageKeys: ReadonlySet<string>;
453
+ getDownloadUrl: (key: string) => Promise<string>;
454
+ storageKeyField: string;
455
+ urlFields: readonly string[];
456
+ }
457
+ /**
458
+ * Clone a JSON-like response graph and resolve only authorized object keys.
459
+ * Dates/class instances pass through. Signatures are deduplicated within this
460
+ * call, never cached between requests. A signing failure rejects the whole call;
461
+ * generated URLs never mutate the source graph that might later be persisted.
462
+ */
463
+ declare function hydrateStorageUrls<T>(value: T, options: StorageUrlHydrationOptions): Promise<T>;
464
+
465
+ /** Disabled storage preserves startup availability while all data operations remain explicit. */
466
+ declare class UnavailableFileManager implements FileManager {
467
+ readonly providerType: StorageProviderType;
468
+ private readonly message;
469
+ constructor(providerType: StorageProviderType, message?: string);
470
+ isConfigured(): boolean;
471
+ private unavailable;
472
+ upload(_buffer: Buffer, _key: string, _options?: UploadOptions): Promise<UploadResult>;
473
+ uploadStream(_stream: Readable, _key: string, _options?: UploadOptions): Promise<UploadResult>;
474
+ download(_key: string): Promise<{
475
+ stream: Readable;
476
+ size: number;
477
+ }>;
478
+ delete(_key: string): Promise<void>;
479
+ exists(_key: string): Promise<boolean>;
480
+ getPublicUrl(_key: string): Promise<string>;
481
+ getDownloadUrl(_key: string): Promise<string>;
482
+ list(_prefix: string): Promise<FileInfo[]>;
483
+ }
484
+
485
+ interface DownloadedStorageObject {
486
+ body: Buffer;
487
+ reportedSize: number;
488
+ }
489
+ interface BufferedObjectStore<TMimeType extends string = string> {
490
+ read(key: string): Promise<DownloadedStorageObject>;
491
+ exists(key: string): Promise<boolean>;
492
+ write(key: string, body: Buffer, options: {
493
+ contentType: TMimeType;
494
+ metadata: Record<string, string>;
495
+ private: true;
496
+ }): Promise<{
497
+ size: number;
498
+ }>;
499
+ }
500
+ type StorageTransferErrorCode = 'STORAGE_NOT_CONFIGURED' | 'OBJECT_SIZE_OUT_OF_BOUNDS' | 'SOURCE_SIZE_MISMATCH' | 'TARGET_SIZE_CONFLICT' | 'TARGET_MIME_CONFLICT' | 'TARGET_HASH_CONFLICT' | 'UPLOAD_SIZE_MISMATCH';
501
+ declare class StorageTransferError extends Error {
502
+ readonly code: StorageTransferErrorCode;
503
+ constructor(code: StorageTransferErrorCode);
504
+ }
505
+ type StorageTransferErrorFactory = (code: StorageTransferErrorCode) => Error;
506
+ interface VerifiedStorageMetadata<TMimeType extends string = string> {
507
+ sha256: string;
508
+ size: number;
509
+ mimeType: TMimeType;
510
+ }
511
+ /** Compare every byte via SHA-256, with explicit size and MIME conflict reasons. */
512
+ declare function verifyStoredObject<TMimeType extends string>(object: DownloadedStorageObject, expected: VerifiedStorageMetadata<TMimeType>, detectMimeType: (body: Buffer) => TMimeType, errorFactory?: StorageTransferErrorFactory): void;
513
+ interface VerifiedObjectTransferOptions<TMimeType extends string> {
514
+ source: DownloadedStorageObject;
515
+ target: BufferedObjectStore<TMimeType>;
516
+ destinationKey: string;
517
+ mode: 'dry-run' | 'apply';
518
+ allowUpload: boolean;
519
+ detectMimeType: (body: Buffer) => TMimeType;
520
+ errorFactory?: StorageTransferErrorFactory;
521
+ }
522
+ /**
523
+ * Copy a bounded source buffer into a private destination, verify existing data
524
+ * before reuse and round-trip every upload before reporting success. Dry-run and
525
+ * disallowed uploads never write. This does not delete sources or commit metadata.
526
+ */
527
+ declare function transferVerifiedObject<TMimeType extends string>(options: VerifiedObjectTransferOptions<TMimeType>): Promise<VerifiedStorageMetadata<TMimeType> & {
528
+ targetDisposition: 'planned' | 'uploaded' | 'reused';
529
+ }>;
530
+
531
+ /** Bounded in-memory transfer adapter over the existing FileManager contract. */
532
+ declare class BufferedFileManagerStore implements BufferedObjectStore {
533
+ private readonly manager;
534
+ private readonly maxObjectBytes;
535
+ private readonly errorFactory;
536
+ constructor(manager: FileManager, maxObjectBytes: number, errorFactory?: StorageTransferErrorFactory);
537
+ read(key: string): Promise<DownloadedStorageObject>;
538
+ exists(key: string): Promise<boolean>;
539
+ write(key: string, body: Buffer, options: {
540
+ contentType: string;
541
+ metadata: Record<string, string>;
542
+ private: true;
543
+ }): Promise<{
544
+ size: number;
545
+ }>;
546
+ }
547
+
427
548
  declare enum StorageProvider {
428
549
  LOCAL = "LOCAL",
429
550
  S3 = "S3",
430
551
  WEBDAV = "WEBDAV"
431
552
  }
432
553
 
433
- export { type ChunkUploadState, DANGEROUS_EXTENSIONS, FILE_MANAGER, type FileInfo, FileLifecycleManager, type FileLifecyclePolicy, type FileManager, type FileMetadata, type FileUploadValidation, type LifecycleResult, LocalFileStorage, type PosixFilePermissions, type S3ObjectAclMode, S3Storage, type S3StorageOptions, type S3UploadUrlMode, type StorageModuleAsyncOptions, type StorageModuleOptions, type StorageModuleOptionsFactory, StorageProvider, type StorageProviderType, type UploadOptions, type UploadResult, WebDavStorage, type WebDavStorageOptions, ZuckerStorageModule, generateDateBasedKey, isFileDenied, isSafeOriginalFileName, sanitizeFileName };
554
+ export { BufferedFileManagerStore, type BufferedObjectStore, type ChunkUploadState, DANGEROUS_EXTENSIONS, type DownloadedStorageObject, FILE_MANAGER, type FileInfo, FileLifecycleManager, type FileLifecyclePolicy, type FileManager, type FileMetadata, type FileUploadValidation, type LifecycleResult, LocalFileStorage, type PosixFilePermissions, type S3ObjectAclMode, S3Storage, type S3StorageOptions, type S3UploadUrlMode, type StorageModuleAsyncOptions, type StorageModuleOptions, type StorageModuleOptionsFactory, StorageProvider, type StorageProviderType, StorageTransferError, type StorageTransferErrorCode, type StorageTransferErrorFactory, type StorageUrlHydrationOptions, UnavailableFileManager, type UploadOptions, type UploadResult, type VerifiedObjectTransferOptions, type VerifiedStorageMetadata, type WebDavEntry, WebDavStorage, type WebDavStorageOptions, ZuckerStorageModule, generateDateBasedKey, hydrateStorageUrls, isFileDenied, isSafeOriginalFileName, sanitizeFileName, transferVerifiedObject, verifyStoredObject };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Readable } from 'node:stream';
1
2
  import { ReadStream } from 'fs';
2
3
  import { OnModuleDestroy, Type, DynamicModule, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
3
4
 
@@ -18,6 +19,10 @@ interface UploadResult {
18
19
  }
19
20
  interface FileInfo {
20
21
  key: string;
22
+ /** Available when the provider exposes filesystem entries (e.g. WebDAV). */
23
+ type?: 'file' | 'directory';
24
+ filename?: string;
25
+ basename?: string;
21
26
  size: number;
22
27
  lastModified: Date;
23
28
  contentType?: string;
@@ -126,9 +131,9 @@ interface FileManager {
126
131
  readonly providerType: StorageProviderType;
127
132
  isConfigured(): boolean;
128
133
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
129
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
134
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
130
135
  download(key: string): Promise<{
131
- stream: ReadStream;
136
+ stream: Readable;
132
137
  size: number;
133
138
  }>;
134
139
  delete(key: string): Promise<void>;
@@ -149,7 +154,7 @@ interface FileManager {
149
154
  * @param position 起始偏移量(字节)
150
155
  */
151
156
  downloadRange?(key: string, position: number, length?: number): Promise<{
152
- stream: ReadStream;
157
+ stream: Readable;
153
158
  size: number;
154
159
  }>;
155
160
  /**
@@ -228,7 +233,7 @@ declare class LocalFileStorage implements FileManager {
228
233
  });
229
234
  isConfigured(): boolean;
230
235
  upload(buffer: Buffer, key: string, _options?: UploadOptions): Promise<UploadResult>;
231
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
236
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
232
237
  download(key: string): Promise<{
233
238
  stream: ReadStream;
234
239
  size: number;
@@ -294,9 +299,9 @@ declare class S3Storage implements FileManager, OnModuleDestroy {
294
299
  onModuleDestroy(): void;
295
300
  isConfigured(): boolean;
296
301
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
297
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
302
+ uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
298
303
  download(key: string): Promise<{
299
- stream: ReadStream;
304
+ stream: Readable;
300
305
  size: number;
301
306
  }>;
302
307
  delete(key: string): Promise<void>;
@@ -309,11 +314,19 @@ declare class S3Storage implements FileManager, OnModuleDestroy {
309
314
  move(sourceKey: string, destKey: string): Promise<void>;
310
315
  }
311
316
 
317
+ interface WebDavEntry extends FileInfo {
318
+ type: 'file' | 'directory';
319
+ filename: string;
320
+ basename: string;
321
+ lastmod: string;
322
+ }
312
323
  interface WebDavStorageOptions {
313
324
  /** WebDAV 服务地址,如 https://nextcloud.example.com/remote.php/dav/files/user */
314
325
  url: string;
315
- username: string;
316
- password: string;
326
+ username?: string;
327
+ password?: string;
328
+ /** Explicit opt-in for endpoints/consumer policies allowing missing credentials. Default false. */
329
+ allowAnonymous?: boolean;
317
330
  /** 公开访问 URL 前缀(可选) */
318
331
  publicUrl?: string;
319
332
  /** 基础路径前缀(可选,默认 '/') */
@@ -332,23 +345,33 @@ declare class WebDavStorage implements FileManager {
332
345
  private getClient;
333
346
  isConfigured(): boolean;
334
347
  upload(buffer: Buffer, key: string, options?: UploadOptions): Promise<UploadResult>;
335
- uploadStream(stream: ReadStream, key: string, options?: UploadOptions): Promise<UploadResult>;
336
- download(key: string): Promise<{
337
- stream: ReadStream;
348
+ uploadStream(stream: Readable, key: string, options?: UploadOptions & {
349
+ signal?: AbortSignal;
350
+ }): Promise<UploadResult>;
351
+ download(key: string, options?: {
352
+ signal?: AbortSignal;
353
+ }): Promise<{
354
+ stream: Readable;
338
355
  size: number;
339
356
  }>;
340
357
  delete(key: string): Promise<void>;
341
358
  exists(key: string): Promise<boolean>;
342
359
  getPublicUrl(key: string): Promise<string>;
343
360
  getDownloadUrl(key: string): Promise<string>;
344
- list(prefix: string): Promise<FileInfo[]>;
361
+ list(prefix: string, options?: {
362
+ deep?: boolean;
363
+ includeDirectories?: boolean;
364
+ missing?: 'empty' | 'throw';
365
+ }): Promise<WebDavEntry[]>;
345
366
  getFileInfo(key: string): Promise<FileInfo | undefined>;
346
367
  copy(sourceKey: string, destKey: string): Promise<void>;
347
368
  move(sourceKey: string, destKey: string): Promise<void>;
348
369
  /**
349
370
  * 递归创建目录
350
371
  */
351
- private ensureDirectory;
372
+ /** Accepts a storage key, with the same traversal protection as upload. */
373
+ ensureDirectory(key: string): Promise<void>;
374
+ private ensureRemoteDirectory;
352
375
  private createDirectoryIfMissing;
353
376
  private resolvePath;
354
377
  private stripBasePath;
@@ -424,10 +447,108 @@ declare class ZuckerStorageModule {
424
447
  private static createAsyncProviders;
425
448
  }
426
449
 
450
+ interface StorageUrlHydrationOptions {
451
+ /** The application must establish ownership before allowing a key to be signed. */
452
+ allowedStorageKeys: ReadonlySet<string>;
453
+ getDownloadUrl: (key: string) => Promise<string>;
454
+ storageKeyField: string;
455
+ urlFields: readonly string[];
456
+ }
457
+ /**
458
+ * Clone a JSON-like response graph and resolve only authorized object keys.
459
+ * Dates/class instances pass through. Signatures are deduplicated within this
460
+ * call, never cached between requests. A signing failure rejects the whole call;
461
+ * generated URLs never mutate the source graph that might later be persisted.
462
+ */
463
+ declare function hydrateStorageUrls<T>(value: T, options: StorageUrlHydrationOptions): Promise<T>;
464
+
465
+ /** Disabled storage preserves startup availability while all data operations remain explicit. */
466
+ declare class UnavailableFileManager implements FileManager {
467
+ readonly providerType: StorageProviderType;
468
+ private readonly message;
469
+ constructor(providerType: StorageProviderType, message?: string);
470
+ isConfigured(): boolean;
471
+ private unavailable;
472
+ upload(_buffer: Buffer, _key: string, _options?: UploadOptions): Promise<UploadResult>;
473
+ uploadStream(_stream: Readable, _key: string, _options?: UploadOptions): Promise<UploadResult>;
474
+ download(_key: string): Promise<{
475
+ stream: Readable;
476
+ size: number;
477
+ }>;
478
+ delete(_key: string): Promise<void>;
479
+ exists(_key: string): Promise<boolean>;
480
+ getPublicUrl(_key: string): Promise<string>;
481
+ getDownloadUrl(_key: string): Promise<string>;
482
+ list(_prefix: string): Promise<FileInfo[]>;
483
+ }
484
+
485
+ interface DownloadedStorageObject {
486
+ body: Buffer;
487
+ reportedSize: number;
488
+ }
489
+ interface BufferedObjectStore<TMimeType extends string = string> {
490
+ read(key: string): Promise<DownloadedStorageObject>;
491
+ exists(key: string): Promise<boolean>;
492
+ write(key: string, body: Buffer, options: {
493
+ contentType: TMimeType;
494
+ metadata: Record<string, string>;
495
+ private: true;
496
+ }): Promise<{
497
+ size: number;
498
+ }>;
499
+ }
500
+ type StorageTransferErrorCode = 'STORAGE_NOT_CONFIGURED' | 'OBJECT_SIZE_OUT_OF_BOUNDS' | 'SOURCE_SIZE_MISMATCH' | 'TARGET_SIZE_CONFLICT' | 'TARGET_MIME_CONFLICT' | 'TARGET_HASH_CONFLICT' | 'UPLOAD_SIZE_MISMATCH';
501
+ declare class StorageTransferError extends Error {
502
+ readonly code: StorageTransferErrorCode;
503
+ constructor(code: StorageTransferErrorCode);
504
+ }
505
+ type StorageTransferErrorFactory = (code: StorageTransferErrorCode) => Error;
506
+ interface VerifiedStorageMetadata<TMimeType extends string = string> {
507
+ sha256: string;
508
+ size: number;
509
+ mimeType: TMimeType;
510
+ }
511
+ /** Compare every byte via SHA-256, with explicit size and MIME conflict reasons. */
512
+ declare function verifyStoredObject<TMimeType extends string>(object: DownloadedStorageObject, expected: VerifiedStorageMetadata<TMimeType>, detectMimeType: (body: Buffer) => TMimeType, errorFactory?: StorageTransferErrorFactory): void;
513
+ interface VerifiedObjectTransferOptions<TMimeType extends string> {
514
+ source: DownloadedStorageObject;
515
+ target: BufferedObjectStore<TMimeType>;
516
+ destinationKey: string;
517
+ mode: 'dry-run' | 'apply';
518
+ allowUpload: boolean;
519
+ detectMimeType: (body: Buffer) => TMimeType;
520
+ errorFactory?: StorageTransferErrorFactory;
521
+ }
522
+ /**
523
+ * Copy a bounded source buffer into a private destination, verify existing data
524
+ * before reuse and round-trip every upload before reporting success. Dry-run and
525
+ * disallowed uploads never write. This does not delete sources or commit metadata.
526
+ */
527
+ declare function transferVerifiedObject<TMimeType extends string>(options: VerifiedObjectTransferOptions<TMimeType>): Promise<VerifiedStorageMetadata<TMimeType> & {
528
+ targetDisposition: 'planned' | 'uploaded' | 'reused';
529
+ }>;
530
+
531
+ /** Bounded in-memory transfer adapter over the existing FileManager contract. */
532
+ declare class BufferedFileManagerStore implements BufferedObjectStore {
533
+ private readonly manager;
534
+ private readonly maxObjectBytes;
535
+ private readonly errorFactory;
536
+ constructor(manager: FileManager, maxObjectBytes: number, errorFactory?: StorageTransferErrorFactory);
537
+ read(key: string): Promise<DownloadedStorageObject>;
538
+ exists(key: string): Promise<boolean>;
539
+ write(key: string, body: Buffer, options: {
540
+ contentType: string;
541
+ metadata: Record<string, string>;
542
+ private: true;
543
+ }): Promise<{
544
+ size: number;
545
+ }>;
546
+ }
547
+
427
548
  declare enum StorageProvider {
428
549
  LOCAL = "LOCAL",
429
550
  S3 = "S3",
430
551
  WEBDAV = "WEBDAV"
431
552
  }
432
553
 
433
- export { type ChunkUploadState, DANGEROUS_EXTENSIONS, FILE_MANAGER, type FileInfo, FileLifecycleManager, type FileLifecyclePolicy, type FileManager, type FileMetadata, type FileUploadValidation, type LifecycleResult, LocalFileStorage, type PosixFilePermissions, type S3ObjectAclMode, S3Storage, type S3StorageOptions, type S3UploadUrlMode, type StorageModuleAsyncOptions, type StorageModuleOptions, type StorageModuleOptionsFactory, StorageProvider, type StorageProviderType, type UploadOptions, type UploadResult, WebDavStorage, type WebDavStorageOptions, ZuckerStorageModule, generateDateBasedKey, isFileDenied, isSafeOriginalFileName, sanitizeFileName };
554
+ export { BufferedFileManagerStore, type BufferedObjectStore, type ChunkUploadState, DANGEROUS_EXTENSIONS, type DownloadedStorageObject, FILE_MANAGER, type FileInfo, FileLifecycleManager, type FileLifecyclePolicy, type FileManager, type FileMetadata, type FileUploadValidation, type LifecycleResult, LocalFileStorage, type PosixFilePermissions, type S3ObjectAclMode, S3Storage, type S3StorageOptions, type S3UploadUrlMode, type StorageModuleAsyncOptions, type StorageModuleOptions, type StorageModuleOptionsFactory, StorageProvider, type StorageProviderType, StorageTransferError, type StorageTransferErrorCode, type StorageTransferErrorFactory, type StorageUrlHydrationOptions, UnavailableFileManager, type UploadOptions, type UploadResult, type VerifiedObjectTransferOptions, type VerifiedStorageMetadata, type WebDavEntry, WebDavStorage, type WebDavStorageOptions, ZuckerStorageModule, generateDateBasedKey, hydrateStorageUrls, isFileDenied, isSafeOriginalFileName, sanitizeFileName, transferVerifiedObject, verifyStoredObject };