@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 +19 -0
- package/dist/index.d.mts +135 -14
- package/dist/index.d.ts +135 -14
- package/dist/index.js +323 -25
- package/dist/index.mjs +316 -24
- package/package.json +1 -1
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:
|
|
134
|
+
uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
|
|
130
135
|
download(key: string): Promise<{
|
|
131
|
-
stream:
|
|
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:
|
|
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:
|
|
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:
|
|
302
|
+
uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
|
|
298
303
|
download(key: string): Promise<{
|
|
299
|
-
stream:
|
|
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
|
|
316
|
-
password
|
|
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:
|
|
336
|
-
|
|
337
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
134
|
+
uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
|
|
130
135
|
download(key: string): Promise<{
|
|
131
|
-
stream:
|
|
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:
|
|
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:
|
|
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:
|
|
302
|
+
uploadStream(stream: Readable, key: string, options?: UploadOptions): Promise<UploadResult>;
|
|
298
303
|
download(key: string): Promise<{
|
|
299
|
-
stream:
|
|
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
|
|
316
|
-
password
|
|
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:
|
|
336
|
-
|
|
337
|
-
|
|
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
|
|
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
|
-
|
|
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 };
|