secure-upload-fastify-sdk 1.0.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 (74) hide show
  1. package/README.md +467 -0
  2. package/dist/index.d.ts +14 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2069 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +2023 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/server/adapters/secure-crypto.d.ts +28 -0
  9. package/dist/server/adapters/secure-crypto.d.ts.map +1 -0
  10. package/dist/server/errors.d.ts +48 -0
  11. package/dist/server/errors.d.ts.map +1 -0
  12. package/dist/server/errors.js +112 -0
  13. package/dist/server/errors.js.map +1 -0
  14. package/dist/server/errors.mjs +85 -0
  15. package/dist/server/errors.mjs.map +1 -0
  16. package/dist/server/fastify-plugin.d.ts +38 -0
  17. package/dist/server/fastify-plugin.d.ts.map +1 -0
  18. package/dist/server/index.d.ts +17 -0
  19. package/dist/server/index.d.ts.map +1 -0
  20. package/dist/server/index.js +2070 -0
  21. package/dist/server/index.js.map +1 -0
  22. package/dist/server/index.mjs +2024 -0
  23. package/dist/server/index.mjs.map +1 -0
  24. package/dist/server/routes/_internal.d.ts +42 -0
  25. package/dist/server/routes/_internal.d.ts.map +1 -0
  26. package/dist/server/routes/multipart.d.ts +22 -0
  27. package/dist/server/routes/multipart.d.ts.map +1 -0
  28. package/dist/server/routes/smart.d.ts +16 -0
  29. package/dist/server/routes/smart.d.ts.map +1 -0
  30. package/dist/server/session.d.ts +19 -0
  31. package/dist/server/session.d.ts.map +1 -0
  32. package/dist/server/types.d.ts +233 -0
  33. package/dist/server/types.d.ts.map +1 -0
  34. package/dist/server/utils/cleaner.d.ts +20 -0
  35. package/dist/server/utils/cleaner.d.ts.map +1 -0
  36. package/dist/server/utils/memory-rate-limit.d.ts +29 -0
  37. package/dist/server/utils/memory-rate-limit.d.ts.map +1 -0
  38. package/dist/server/utils/orphan-scanner.d.ts +34 -0
  39. package/dist/server/utils/orphan-scanner.d.ts.map +1 -0
  40. package/dist/server/utils/redis-helpers.d.ts +25 -0
  41. package/dist/server/utils/redis-helpers.d.ts.map +1 -0
  42. package/dist/server/utils/repository/index.d.ts +27 -0
  43. package/dist/server/utils/repository/index.d.ts.map +1 -0
  44. package/dist/server/utils/repository/index.js +501 -0
  45. package/dist/server/utils/repository/index.js.map +1 -0
  46. package/dist/server/utils/repository/index.mjs +469 -0
  47. package/dist/server/utils/repository/index.mjs.map +1 -0
  48. package/dist/server/utils/repository/memory.d.ts +33 -0
  49. package/dist/server/utils/repository/memory.d.ts.map +1 -0
  50. package/dist/server/utils/repository/prisma.d.ts +49 -0
  51. package/dist/server/utils/repository/prisma.d.ts.map +1 -0
  52. package/dist/server/utils/repository/types-extra.d.ts +9 -0
  53. package/dist/server/utils/repository/types-extra.d.ts.map +1 -0
  54. package/dist/server/utils/repository/types.d.ts +128 -0
  55. package/dist/server/utils/repository/types.d.ts.map +1 -0
  56. package/dist/server/utils/storage/index.d.ts +115 -0
  57. package/dist/server/utils/storage/index.d.ts.map +1 -0
  58. package/dist/server/utils/storage/index.js +285 -0
  59. package/dist/server/utils/storage/index.js.map +1 -0
  60. package/dist/server/utils/storage/index.mjs +247 -0
  61. package/dist/server/utils/storage/index.mjs.map +1 -0
  62. package/dist/server/utils/storage/local.d.ts +46 -0
  63. package/dist/server/utils/storage/local.d.ts.map +1 -0
  64. package/dist/server/utils/storage/memory.d.ts +34 -0
  65. package/dist/server/utils/storage/memory.d.ts.map +1 -0
  66. package/dist/server/utils/storage/types.d.ts +78 -0
  67. package/dist/server/utils/storage/types.d.ts.map +1 -0
  68. package/dist/server/utils/strategy.d.ts +64 -0
  69. package/dist/server/utils/strategy.d.ts.map +1 -0
  70. package/dist/server/utils/strategy.js +130 -0
  71. package/dist/server/utils/strategy.js.map +1 -0
  72. package/dist/server/utils/strategy.mjs +103 -0
  73. package/dist/server/utils/strategy.mjs.map +1 -0
  74. package/package.json +97 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * SDK 公共类型
3
+ *
4
+ * 包含:
5
+ * - SecureUploadOptions (插件入参)
6
+ * - SecureUploadRuntime (运行时上下文,挂到 fastify)
7
+ * - 业务响应类型
8
+ */
9
+ import type { Redis } from 'ioredis';
10
+ import type { StorageAdapter } from './utils/storage/types';
11
+ import type { MultipartRepository, FileRepository, MultipartRecord, FileRecord } from './utils/repository/types';
12
+ import type { StrategyConfig } from './utils/strategy';
13
+ /**
14
+ * 安全模式
15
+ * - 'raw': SDK 不读加密字段,直接读 req.body / req.headers(网关已解密的明文)
16
+ * - 'secure-crypto': SDK 读 req.secureRequest.biz + x-secure-raw-base64(从加密 SDK 拿数据)
17
+ */
18
+ export type SecureMode = 'raw' | 'secure-crypto';
19
+ /**
20
+ * 数据层配置
21
+ * - kind: 'prisma' → SDK 内部用 PrismaMultipartRepository + PrismaFileRepository
22
+ * - kind: 'custom' → 用户注入自己的实现(Drizzle / TypeORM / 内存 / Mongo)
23
+ */
24
+ type AnyPrisma = any;
25
+ export type RepositoryConfig = {
26
+ kind: 'prisma';
27
+ prisma: AnyPrisma;
28
+ } | {
29
+ kind: 'custom';
30
+ multipart: MultipartRepository;
31
+ file: FileRepository;
32
+ };
33
+ /**
34
+ * 存储配置
35
+ * - 字符串 preset → SDK 内部 new LocalStorage
36
+ * - 直接传 StorageAdapter 实例 → S3 / OSS / COS / 自定义
37
+ */
38
+ export type StorageConfig = {
39
+ adapter: 'local';
40
+ options: {
41
+ baseDir: string;
42
+ };
43
+ } | StorageAdapter;
44
+ /**
45
+ * Session 验证函数
46
+ * - 入参: session token (JWT 字符串,可能为 undefined)
47
+ * - 返回: userId(非公开文件必须返回有效 userId) / null(公开文件)
48
+ * - 抛错: 鉴权失败(BINARY_AUTH_REQUIRED / BINARY_AUTH_FAILED)
49
+ *
50
+ * SDK 默认实现: base64 解码 JWT payload,读 sub / exp 字段
51
+ * 高级场景: 用户注入自己的 jwt.verify() 验签
52
+ */
53
+ export type SessionValidator = (token: string | undefined, isPublic: boolean) => Promise<number | null> | number | null;
54
+ /**
55
+ * 路径前缀
56
+ * - 默认 '/multipart' 和 '/smart'
57
+ * - 业务挂到 '/api/v1/upload' 时,传 pathPrefix: '/api/v1/upload'
58
+ * 实际端点 = pathPrefix + '/multipart/init' = '/api/v1/upload/multipart/init'
59
+ */
60
+ export interface PathPrefixConfig {
61
+ multipart?: string;
62
+ smart?: string;
63
+ }
64
+ export interface SecureUploadOptions {
65
+ /** 存储适配器 */
66
+ storage: StorageConfig;
67
+ /** 数据层(Prisma 注入 / 自实现) */
68
+ repository: RepositoryConfig;
69
+ /**
70
+ * 安全模式(默认 'raw')
71
+ * - 'raw': 业务直传,SDK 读 req.body / req.headers
72
+ * - 'secure-crypto': 与 secure-crypto-top-zod-sdk 桥接,读 req.secureRequest.biz
73
+ */
74
+ secure?: {
75
+ mode: SecureMode;
76
+ };
77
+ /** Redis 客户端(ioredis)。不传则 SDK 用内存版限流 + 锁(单进程) */
78
+ redis?: Redis;
79
+ /** Smart 策略(默认 DEFAULT_STRATEGY_CONFIG) */
80
+ strategy?: Partial<StrategyConfig>;
81
+ /** 限流配置 */
82
+ rateLimit?: {
83
+ /** 每用户每分钟 init 次数(默认 30) */
84
+ initPerMin?: number;
85
+ /** 每用户每分钟 part 次数(默认 600) */
86
+ partPerMin?: number;
87
+ /** /smart/init 单次最多文件数(默认 50) */
88
+ smartMaxFilesPerInit?: number;
89
+ };
90
+ /** 分片上传总开关(默认 true) */
91
+ enabled?: boolean;
92
+ /** 单文件总大小上限,字节(默认 5GB) */
93
+ fileMaxSize?: number;
94
+ /** 分片大小配置(字节) */
95
+ partSize?: {
96
+ min?: number;
97
+ max?: number;
98
+ default?: number;
99
+ };
100
+ /** TTL(秒),init 后多久必须完成(默认 24h) */
101
+ ttlSeconds?: number;
102
+ /** 客户端并发建议值(默认 3) */
103
+ suggestedClientConcurrency?: number;
104
+ /** 路径前缀(默认 '/multipart' '/smart') */
105
+ pathPrefix?: PathPrefixConfig;
106
+ /**
107
+ * 文件下载 URL 前缀(用于 complete 响应中的 url 字段)
108
+ * - 默认 '/files'
109
+ * - 业务挂到 '/api/v1/upload' 时,应传 '/api/v1/upload/files'
110
+ * → complete 返回 url = '/api/v1/upload/files/<fileId>'
111
+ */
112
+ fileUrlPrefix?: string;
113
+ /** 自定义 session 验证(默认 base64 解 JWT payload) */
114
+ sessionValidator?: SessionValidator;
115
+ /** 清理过期 uploadId 任务(默认 true) */
116
+ cleanupTask?: {
117
+ enabled?: boolean;
118
+ intervalSeconds?: number;
119
+ batchLimit?: number;
120
+ };
121
+ /** 孤儿文件扫描任务(默认 true) */
122
+ orphanScan?: {
123
+ enabled?: boolean;
124
+ intervalSeconds?: number;
125
+ lookbackDays?: number;
126
+ batchSize?: number;
127
+ };
128
+ }
129
+ /**
130
+ * 运行时上下文(挂到 fastify.secureUpload)
131
+ * - 路由处理函数从这里拿 storage / repo / validator
132
+ */
133
+ export interface SecureUploadRuntime {
134
+ storage: StorageAdapter;
135
+ multipartRepo: MultipartRepository;
136
+ fileRepo: FileRepository;
137
+ redis: Redis | null;
138
+ config: {
139
+ enabled: boolean;
140
+ secure: SecureMode;
141
+ fileMaxSize: number;
142
+ partMin: number;
143
+ partMax: number;
144
+ partDefault: number;
145
+ ttlSeconds: number;
146
+ suggestedConcurrency: number;
147
+ initRateLimit: number;
148
+ partRateLimit: number;
149
+ smartMaxFiles: number;
150
+ smartInitRateLimit: number;
151
+ multipartPath: string;
152
+ smartPath: string;
153
+ fileUrlPrefix: string;
154
+ strategy: StrategyConfig;
155
+ };
156
+ /** session → userId,默认 base64 JWT,用户可注入 */
157
+ validateSession: SessionValidator;
158
+ }
159
+ declare module 'fastify' {
160
+ interface FastifyInstance {
161
+ secureUpload: SecureUploadRuntime;
162
+ }
163
+ }
164
+ export interface InitResponse {
165
+ success: true;
166
+ data: {
167
+ uploadId: string;
168
+ partSize: number;
169
+ totalParts: number;
170
+ totalSize: number;
171
+ expiresAt: string;
172
+ ttl: number;
173
+ suggestedConcurrency: number;
174
+ dedup?: {
175
+ fileId: string;
176
+ filename: string;
177
+ size: number;
178
+ contentType: string;
179
+ url: string;
180
+ };
181
+ };
182
+ }
183
+ export interface PartResponse {
184
+ success: true;
185
+ data: {
186
+ uploadId: string;
187
+ partNumber: number;
188
+ size: number;
189
+ receivedCount: number;
190
+ totalParts: number;
191
+ };
192
+ }
193
+ export interface CompleteResponse {
194
+ success: true;
195
+ data: {
196
+ fileId: string;
197
+ filename: string;
198
+ size: number;
199
+ contentType: string;
200
+ url: string;
201
+ finalHash: string;
202
+ idempotent: boolean;
203
+ };
204
+ }
205
+ export interface AbortResponse {
206
+ success: true;
207
+ data: {
208
+ uploadId: string;
209
+ status: 'ABORTED';
210
+ idempotent?: boolean;
211
+ };
212
+ }
213
+ export interface StatusResponse {
214
+ success: true;
215
+ data: {
216
+ uploadId: string;
217
+ status: string;
218
+ filename: string;
219
+ contentType: string;
220
+ totalSize: number;
221
+ partSize: number;
222
+ totalParts: number;
223
+ receivedParts: number[];
224
+ receivedCount: number;
225
+ isPublic: boolean;
226
+ expiresAt: string;
227
+ createdAt: string;
228
+ completedAt: string | null;
229
+ fileId: string | null;
230
+ };
231
+ }
232
+ export type { MultipartRecord, FileRecord };
233
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 后台清理过期 uploadId 任务
3
+ *
4
+ * - 每 intervalSeconds 扫描一次
5
+ * - 把超时的 INIT/UPLOADING 标记为 EXPIRED + 清理磁盘
6
+ */
7
+ import type { FastifyBaseLogger } from 'fastify';
8
+ import type { StorageAdapter } from './storage/types';
9
+ import type { MultipartRepository } from './repository/types';
10
+ export interface MultipartCleanerOptions {
11
+ enabled: boolean;
12
+ intervalSeconds: number;
13
+ batchLimit: number;
14
+ storage: StorageAdapter;
15
+ multipartRepo: MultipartRepository;
16
+ log: FastifyBaseLogger;
17
+ onClose: (cb: () => void) => void;
18
+ }
19
+ export declare function startMultipartCleanupTask(opts: MultipartCleanerOptions): void;
20
+ //# sourceMappingURL=cleaner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleaner.d.ts","sourceRoot":"","sources":["../../../src/server/utils/cleaner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,mBAAmB,CAAC;IACnC,GAAG,EAAE,iBAAiB,CAAC;IACvB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,uBAAuB,GAAG,IAAI,CA+B7E"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 内存版限流 + 锁(单进程降级)
3
+ *
4
+ * 当 SDK 未注入 redis 时,使用本实现:
5
+ * - 限流: 进程内 Map<key, timestamps[]>(滑动窗口)
6
+ * - 锁: 进程内 Map<key, boolean>(互斥)
7
+ *
8
+ * 注意: 内存版只在单进程内有效,多实例部署请传 redis。
9
+ */
10
+ export declare function memorySlidingWindowRateLimit(key: string, windowMs: number, limit: number): {
11
+ ok: boolean;
12
+ retryAfterMs: number;
13
+ remaining: number;
14
+ };
15
+ export declare function memoryAcquireLock(key: string): boolean;
16
+ export declare function memoryReleaseLock(key: string): void;
17
+ export declare function memoryWithLock<T>(key: string, fn: () => Promise<T>, retries?: number): Promise<T>;
18
+ /** 内部:统一抽象(让路由层不感知 redis / 内存) */
19
+ export interface LockLike {
20
+ withLock<T>(key: string, ttlMs: number, fn: () => Promise<T>, retries?: number): Promise<T>;
21
+ }
22
+ export interface RateLimitLike {
23
+ rateLimit(key: string, windowMs: number, limit: number): Promise<{
24
+ ok: boolean;
25
+ retryAfterMs: number;
26
+ remaining: number;
27
+ }>;
28
+ }
29
+ //# sourceMappingURL=memory-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-rate-limit.d.ts","sourceRoot":"","sources":["../../../src/server/utils/memory-rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAiB1D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAItD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,SAAI,GACV,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,kCAAkC;AAClC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7F;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5H"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 孤儿文件扫描任务
3
+ *
4
+ * 背景:
5
+ * 秒传路径只查 DB,可能存在"DB 有 / 磁盘无"的不一致:
6
+ * - 用户/运维手动 rm 磁盘文件
7
+ * - 容器重启后丢失 volume mount
8
+ * - 分布式存储同步失败
9
+ *
10
+ * 已有被动防御:秒传命中 DB 后 storage.exists() 二次校验
11
+ * 本任务作为"主动巡检"兜底:定期扫一遍,自动 soft-delete,
12
+ * 防止其他业务路径(如 /files/:id GET)又返回 404 "文件已丢失"。
13
+ *
14
+ * 触发:服务冷启动(setImmediate) + setInterval
15
+ * 容错:
16
+ * - 单条 storage.exists() 抛错 → 跳过该条,记 warn
17
+ * - 单条 fileRepo.softDelete 失败 → 跳过该条,记 warn
18
+ * - 整个 tick 失败 → 记 error,不影响下次
19
+ */
20
+ import type { FastifyBaseLogger } from 'fastify';
21
+ import type { StorageAdapter } from './storage/types';
22
+ import type { FileRepository } from './repository/types';
23
+ export interface OrphanScannerOptions {
24
+ enabled: boolean;
25
+ intervalSeconds: number;
26
+ lookbackDays: number;
27
+ batchSize: number;
28
+ storage: StorageAdapter;
29
+ fileRepo: FileRepository;
30
+ log: FastifyBaseLogger;
31
+ onClose: (cb: () => void) => void;
32
+ }
33
+ export declare function startOrphanFileScanTask(opts: OrphanScannerOptions): void;
34
+ //# sourceMappingURL=orphan-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphan-scanner.d.ts","sourceRoot":"","sources":["../../../src/server/utils/orphan-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,iBAAiB,CAAC;IACvB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAgExE"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Redis 辅助工具(可选注入,分布式场景使用)
3
+ *
4
+ * 提供:
5
+ * - slidingWindowRateLimit: 滑动窗口限流(Lua + ZSET,精确无突刺)
6
+ * - acquireLock / releaseLock / withLock: 分布式锁
7
+ *
8
+ * ioredis 客户端作为 SDK peerDependency(可选),不传时 SDK 用内存降级
9
+ */
10
+ import type Redis from 'ioredis';
11
+ export declare function slidingWindowRateLimit(redis: Redis, key: string, windowMs: number, limit: number): Promise<{
12
+ ok: boolean;
13
+ retryAfterMs: number;
14
+ remaining: number;
15
+ }>;
16
+ export declare function acquireLock(redis: Redis, key: string, ttlMs?: number): Promise<{
17
+ ok: true;
18
+ token: string;
19
+ } | {
20
+ ok: false;
21
+ token: null;
22
+ }>;
23
+ export declare function releaseLock(redis: Redis, key: string, token: string): Promise<boolean>;
24
+ export declare function withLock<T>(redis: Redis, key: string, ttlMs: number, fn: () => Promise<T>, retries?: number): Promise<T>;
25
+ //# sourceMappingURL=redis-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-helpers.d.ts","sourceRoot":"","sources":["../../../src/server/utils/redis-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AA6CjC,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBnE;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,KAAK,SAAQ,GACZ,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC,CAKnE;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG5F;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,SAAI,GACV,OAAO,CAAC,CAAC,CAAC,CAsBZ"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Repository 统一导出
3
+ *
4
+ * 用法 1:用 Prisma(SDK 内置实现)
5
+ * import { PrismaMultipartRepository, PrismaFileRepository } from '.../repository';
6
+ * const multipart = new PrismaMultipartRepository(prisma);
7
+ * const file = new PrismaFileRepository(prisma);
8
+ * await app.register(secureUploadPlugin, {
9
+ * repository: { kind: 'custom', multipart, file },
10
+ * ...
11
+ * });
12
+ *
13
+ * 用法 2:用内存实现(测试)
14
+ * import { InMemoryMultipartRepository, InMemoryFileRepository } from '.../repository';
15
+ *
16
+ * 用法 3:完全自实现(Drizzle / TypeORM / Mongo / ...)
17
+ * class MyMultipartRepo implements MultipartRepository { ... }
18
+ * await app.register(secureUploadPlugin, {
19
+ * repository: { kind: 'custom', multipart: new MyMultipartRepo(), file: new MyFileRepo() },
20
+ * ...
21
+ * });
22
+ */
23
+ export { PrismaMultipartRepository, PrismaFileRepository, } from './prisma';
24
+ export { InMemoryMultipartRepository, InMemoryFileRepository, } from './memory';
25
+ export { MultipartStatus, FileStatus, type MultipartRecord, type FileRecord, type CreateInitInput, type CreateFileInput, type AddPartOutcome, type MultipartRepository, type FileRepository, } from './types';
26
+ export type { MultipartStatusT, FileStatusT } from './types-extra';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/utils/repository/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,eAAe,EACf,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}