secure-upload-fastify-sdk 1.0.5 → 1.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"fastify-plugin.d.ts","sourceRoot":"","sources":["../../src/server/fastify-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAC5F,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAQhG,OAAO,KAAK,EACV,mBAAmB,EAIpB,MAAM,SAAS,CAAC;AAmDjB,iBAAe,sBAAsB,CACnC,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqHf;;AAED,wBAGG;AAKH,OAAO,EACL,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,GACvB,CAAC"}
1
+ {"version":3,"file":"fastify-plugin.d.ts","sourceRoot":"","sources":["../../src/server/fastify-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAC5F,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAQhG,OAAO,KAAK,EACV,mBAAmB,EAIpB,MAAM,SAAS,CAAC;AAmDjB,iBAAe,sBAAsB,CACnC,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAsHf;;AAED,wBAGG;AAKH,OAAO,EACL,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,GACvB,CAAC"}
@@ -12,6 +12,6 @@
12
12
  * import { chooseStrategy, suggestConcurrency, DEFAULT_STRATEGY_CONFIG } from 'secure-upload-fastify-sdk/strategy';
13
13
  */
14
14
  export { default, default as secureUploadPlugin, LocalStorage, InMemoryStorage, PrismaMultipartRepository, PrismaFileRepository, InMemoryMultipartRepository, InMemoryFileRepository, } from './fastify-plugin';
15
- export type { SecureUploadOptions, SecureUploadRuntime, SecureMode, RepositoryConfig, StorageConfig, PathPrefixConfig, SessionValidator, InitResponse, PartResponse, CompleteResponse, AbortResponse, StatusResponse, MultipartRecord, FileRecord, } from './types';
15
+ export type { SecureUploadOptions, SecureUploadRuntime, SecureMode, RepositoryConfig, StorageConfig, PathPrefixConfig, SessionValidator, InitResponse, PartResponse, CompleteResponse, AbortResponse, StatusResponse, MultipartRecord, FileRecord, OnCompleteHook, OnCompleteContext, OnCompletePatch, } from './types';
16
16
  export { ErrorCode, SecureError, getHttpStatus, } from './errors';
17
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,OAAO,EACP,OAAO,IAAI,kBAAkB,EAC7B,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,EACf,UAAU,GACX,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,GACd,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,OAAO,EACP,OAAO,IAAI,kBAAkB,EAC7B,YAAY,EACZ,eAAe,EACf,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,eAAe,EACf,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,GACd,MAAM,UAAU,CAAC"}
@@ -346,7 +346,12 @@ function toFileRecord(row) {
346
346
  meta: row.meta && typeof row.meta === "object" ? row.meta : null,
347
347
  createdAt: row.createdAt,
348
348
  updatedAt: row.updatedAt,
349
- deletedAt: row.deletedAt
349
+ deletedAt: row.deletedAt,
350
+ // v1.5.1+:水印溯源字段(可选;旧 schema/老库里 row 可能没有,需要兜底)
351
+ watermarked: row.watermarked ?? false,
352
+ isOriginal: row.isOriginal ?? true,
353
+ parentId: row.parentId ?? null,
354
+ retentionExpiresAt: row.retentionExpiresAt ?? null
350
355
  };
351
356
  }
352
357
  var PrismaMultipartRepository = class {
@@ -512,6 +517,27 @@ var PrismaFileRepository = class {
512
517
  });
513
518
  return toFileRecord(row);
514
519
  }
520
+ /**
521
+ * v1.5.1: 钩子 onComplete 返回 patch 后 SDK 调这个方法
522
+ * - 只更新传入的字段;其他字段保留原值
523
+ * - 文件不存在 → 抛错(由调用方决定是否捕获)
524
+ * - 业务字段 watermarked/isOriginal/parentId/retentionExpiresAt 需要 schema v1.4.0+
525
+ */
526
+ async update(id, patch) {
527
+ const data = {};
528
+ if (patch.storageKey !== void 0) data.storageKey = patch.storageKey;
529
+ if (patch.size !== void 0) data.size = BigInt(patch.size);
530
+ if (patch.contentType !== void 0) data.contentType = patch.contentType;
531
+ if (patch.meta !== void 0) data.meta = patch.meta;
532
+ if (patch.watermarked !== void 0) data.watermarked = patch.watermarked;
533
+ if (patch.isOriginal !== void 0) data.isOriginal = patch.isOriginal;
534
+ if (patch.parentId !== void 0) data.parentId = patch.parentId;
535
+ if (patch.retentionExpiresAt !== void 0) {
536
+ data.retentionExpiresAt = patch.retentionExpiresAt;
537
+ }
538
+ const row = await this.prisma.file.update({ where: { id }, data });
539
+ return toFileRecord(row);
540
+ }
515
541
  async softDelete(id, metaPatch) {
516
542
  if (metaPatch) {
517
543
  const cur = await this.prisma.file.findUnique({ where: { id } });
@@ -738,6 +764,30 @@ var InMemoryFileRepository = class {
738
764
  };
739
765
  this.store.set(id, next);
740
766
  }
767
+ /**
768
+ * v1.5.1: 钩子 onComplete 返回 patch 后 SDK 调这个方法
769
+ * - 只更新传入的字段;其他字段保留原值
770
+ * - 文件不存在 → 抛错(由调用方决定是否捕获)
771
+ */
772
+ async update(id, patch) {
773
+ const rec = this.store.get(id);
774
+ if (!rec) throw new Error("NOT_FOUND");
775
+ const now = /* @__PURE__ */ new Date();
776
+ const next = {
777
+ ...rec,
778
+ storageKey: patch.storageKey ?? rec.storageKey,
779
+ size: patch.size !== void 0 ? BigInt(patch.size) : rec.size,
780
+ contentType: patch.contentType ?? rec.contentType,
781
+ meta: patch.meta === void 0 ? rec.meta : patch.meta,
782
+ watermarked: patch.watermarked ?? rec.watermarked,
783
+ isOriginal: patch.isOriginal ?? rec.isOriginal,
784
+ parentId: patch.parentId === void 0 ? rec.parentId : patch.parentId,
785
+ retentionExpiresAt: patch.retentionExpiresAt === void 0 ? rec.retentionExpiresAt : patch.retentionExpiresAt,
786
+ updatedAt: now
787
+ };
788
+ this.store.set(id, next);
789
+ return next;
790
+ }
741
791
  async listOrphans(opts) {
742
792
  const sinceMs = opts.sinceDays * 24 * 3600 * 1e3;
743
793
  const list = [];
@@ -1185,6 +1235,47 @@ async function lock(rt, key, ttlMs, fn, retries = 0) {
1185
1235
  }
1186
1236
  return await memoryWithLock(key, fn, retries);
1187
1237
  }
1238
+ async function streamToBuffer(stream) {
1239
+ const chunks = [];
1240
+ for await (const chunk of stream) {
1241
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1242
+ }
1243
+ return Buffer.concat(chunks);
1244
+ }
1245
+ async function runOnComplete(rt, file, log) {
1246
+ if (!rt.onComplete) return file;
1247
+ try {
1248
+ const patch = await rt.onComplete({
1249
+ file,
1250
+ storage: rt.storage,
1251
+ log,
1252
+ getOriginalBuffer: async () => {
1253
+ const stream = await Promise.resolve(rt.storage.get(file.storageKey));
1254
+ return streamToBuffer(stream);
1255
+ }
1256
+ });
1257
+ if (!patch || Object.keys(patch).length === 0) {
1258
+ return file;
1259
+ }
1260
+ const updated = await rt.fileRepo.update(file.id, patch);
1261
+ log.info(
1262
+ {
1263
+ fileId: file.id,
1264
+ patchKeys: Object.keys(patch),
1265
+ newStorageKey: patch.storageKey,
1266
+ watermarked: patch.watermarked
1267
+ },
1268
+ "\u2705 onComplete \u94A9\u5B50\u5DF2\u5E94\u7528 patch"
1269
+ );
1270
+ return updated;
1271
+ } catch (err) {
1272
+ log.warn(
1273
+ { err, fileId: file.id, uploadId: file.uploadId },
1274
+ "\u26A0\uFE0F onComplete \u94A9\u5B50\u6267\u884C\u5931\u8D25,\u5DF2\u8DF3\u8FC7\u4E1A\u52A1\u540E\u5904\u7406(file record \u4FDD\u6301\u539F\u72B6)"
1275
+ );
1276
+ return file;
1277
+ }
1278
+ }
1188
1279
  async function registerMultipartRoutes(app, rt) {
1189
1280
  if (!rt.config.enabled) {
1190
1281
  app.log.warn("\u26A0\uFE0F multipart \u4E0A\u4F20\u5DF2\u7981\u7528 (enabled=false)");
@@ -1490,8 +1581,14 @@ async function registerMultipartRoutes(app, rt) {
1490
1581
  contentHash: finalHash,
1491
1582
  meta: rec.meta ?? void 0
1492
1583
  });
1584
+ const finalFile = await runOnComplete(rt, file2, req.log);
1493
1585
  await rt.multipartRepo.markCompleted(rec.id, fileId);
1494
- return { idempotent: false, fileId: file2.id, size: merged.size, finalHash };
1586
+ return {
1587
+ idempotent: false,
1588
+ fileId: finalFile.id,
1589
+ size: Number(finalFile.size),
1590
+ finalHash
1591
+ };
1495
1592
  }
1496
1593
  );
1497
1594
  if (!result.idempotent) {
@@ -1504,7 +1601,8 @@ async function registerMultipartRoutes(app, rt) {
1504
1601
  uploadId: body.uploadId,
1505
1602
  fileId: result.fileId,
1506
1603
  size: result.size,
1507
- userId: rec.userId
1604
+ userId: rec.userId,
1605
+ watermarkHook: rt.onComplete ? "on" : "off"
1508
1606
  },
1509
1607
  "\u2705 multipart: complete"
1510
1608
  );
@@ -1980,7 +2078,8 @@ async function secureUploadPluginImpl(app, options) {
1980
2078
  ...options.strategy ?? {}
1981
2079
  }
1982
2080
  },
1983
- validateSession: options.sessionValidator ?? defaultSessionValidator
2081
+ validateSession: options.sessionValidator ?? defaultSessionValidator,
2082
+ onComplete: options.onComplete ?? null
1984
2083
  };
1985
2084
  app.decorate("secureUpload", runtime);
1986
2085
  await registerMultipartRoutes(app, runtime);