secure-upload-fastify-sdk 1.0.4 → 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.
@@ -300,7 +300,12 @@ function toFileRecord(row) {
300
300
  meta: row.meta && typeof row.meta === "object" ? row.meta : null,
301
301
  createdAt: row.createdAt,
302
302
  updatedAt: row.updatedAt,
303
- deletedAt: row.deletedAt
303
+ deletedAt: row.deletedAt,
304
+ // v1.5.1+:水印溯源字段(可选;旧 schema/老库里 row 可能没有,需要兜底)
305
+ watermarked: row.watermarked ?? false,
306
+ isOriginal: row.isOriginal ?? true,
307
+ parentId: row.parentId ?? null,
308
+ retentionExpiresAt: row.retentionExpiresAt ?? null
304
309
  };
305
310
  }
306
311
  var PrismaMultipartRepository = class {
@@ -466,6 +471,27 @@ var PrismaFileRepository = class {
466
471
  });
467
472
  return toFileRecord(row);
468
473
  }
474
+ /**
475
+ * v1.5.1: 钩子 onComplete 返回 patch 后 SDK 调这个方法
476
+ * - 只更新传入的字段;其他字段保留原值
477
+ * - 文件不存在 → 抛错(由调用方决定是否捕获)
478
+ * - 业务字段 watermarked/isOriginal/parentId/retentionExpiresAt 需要 schema v1.4.0+
479
+ */
480
+ async update(id, patch) {
481
+ const data = {};
482
+ if (patch.storageKey !== void 0) data.storageKey = patch.storageKey;
483
+ if (patch.size !== void 0) data.size = BigInt(patch.size);
484
+ if (patch.contentType !== void 0) data.contentType = patch.contentType;
485
+ if (patch.meta !== void 0) data.meta = patch.meta;
486
+ if (patch.watermarked !== void 0) data.watermarked = patch.watermarked;
487
+ if (patch.isOriginal !== void 0) data.isOriginal = patch.isOriginal;
488
+ if (patch.parentId !== void 0) data.parentId = patch.parentId;
489
+ if (patch.retentionExpiresAt !== void 0) {
490
+ data.retentionExpiresAt = patch.retentionExpiresAt;
491
+ }
492
+ const row = await this.prisma.file.update({ where: { id }, data });
493
+ return toFileRecord(row);
494
+ }
469
495
  async softDelete(id, metaPatch) {
470
496
  if (metaPatch) {
471
497
  const cur = await this.prisma.file.findUnique({ where: { id } });
@@ -692,6 +718,30 @@ var InMemoryFileRepository = class {
692
718
  };
693
719
  this.store.set(id, next);
694
720
  }
721
+ /**
722
+ * v1.5.1: 钩子 onComplete 返回 patch 后 SDK 调这个方法
723
+ * - 只更新传入的字段;其他字段保留原值
724
+ * - 文件不存在 → 抛错(由调用方决定是否捕获)
725
+ */
726
+ async update(id, patch) {
727
+ const rec = this.store.get(id);
728
+ if (!rec) throw new Error("NOT_FOUND");
729
+ const now = /* @__PURE__ */ new Date();
730
+ const next = {
731
+ ...rec,
732
+ storageKey: patch.storageKey ?? rec.storageKey,
733
+ size: patch.size !== void 0 ? BigInt(patch.size) : rec.size,
734
+ contentType: patch.contentType ?? rec.contentType,
735
+ meta: patch.meta === void 0 ? rec.meta : patch.meta,
736
+ watermarked: patch.watermarked ?? rec.watermarked,
737
+ isOriginal: patch.isOriginal ?? rec.isOriginal,
738
+ parentId: patch.parentId === void 0 ? rec.parentId : patch.parentId,
739
+ retentionExpiresAt: patch.retentionExpiresAt === void 0 ? rec.retentionExpiresAt : patch.retentionExpiresAt,
740
+ updatedAt: now
741
+ };
742
+ this.store.set(id, next);
743
+ return next;
744
+ }
695
745
  async listOrphans(opts) {
696
746
  const sinceMs = opts.sinceDays * 24 * 3600 * 1e3;
697
747
  const list = [];
@@ -1139,6 +1189,47 @@ async function lock(rt, key, ttlMs, fn, retries = 0) {
1139
1189
  }
1140
1190
  return await memoryWithLock(key, fn, retries);
1141
1191
  }
1192
+ async function streamToBuffer(stream) {
1193
+ const chunks = [];
1194
+ for await (const chunk of stream) {
1195
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1196
+ }
1197
+ return Buffer.concat(chunks);
1198
+ }
1199
+ async function runOnComplete(rt, file, log) {
1200
+ if (!rt.onComplete) return file;
1201
+ try {
1202
+ const patch = await rt.onComplete({
1203
+ file,
1204
+ storage: rt.storage,
1205
+ log,
1206
+ getOriginalBuffer: async () => {
1207
+ const stream = await Promise.resolve(rt.storage.get(file.storageKey));
1208
+ return streamToBuffer(stream);
1209
+ }
1210
+ });
1211
+ if (!patch || Object.keys(patch).length === 0) {
1212
+ return file;
1213
+ }
1214
+ const updated = await rt.fileRepo.update(file.id, patch);
1215
+ log.info(
1216
+ {
1217
+ fileId: file.id,
1218
+ patchKeys: Object.keys(patch),
1219
+ newStorageKey: patch.storageKey,
1220
+ watermarked: patch.watermarked
1221
+ },
1222
+ "\u2705 onComplete \u94A9\u5B50\u5DF2\u5E94\u7528 patch"
1223
+ );
1224
+ return updated;
1225
+ } catch (err) {
1226
+ log.warn(
1227
+ { err, fileId: file.id, uploadId: file.uploadId },
1228
+ "\u26A0\uFE0F onComplete \u94A9\u5B50\u6267\u884C\u5931\u8D25,\u5DF2\u8DF3\u8FC7\u4E1A\u52A1\u540E\u5904\u7406(file record \u4FDD\u6301\u539F\u72B6)"
1229
+ );
1230
+ return file;
1231
+ }
1232
+ }
1142
1233
  async function registerMultipartRoutes(app, rt) {
1143
1234
  if (!rt.config.enabled) {
1144
1235
  app.log.warn("\u26A0\uFE0F multipart \u4E0A\u4F20\u5DF2\u7981\u7528 (enabled=false)");
@@ -1444,8 +1535,14 @@ async function registerMultipartRoutes(app, rt) {
1444
1535
  contentHash: finalHash,
1445
1536
  meta: rec.meta ?? void 0
1446
1537
  });
1538
+ const finalFile = await runOnComplete(rt, file2, req.log);
1447
1539
  await rt.multipartRepo.markCompleted(rec.id, fileId);
1448
- return { idempotent: false, fileId: file2.id, size: merged.size, finalHash };
1540
+ return {
1541
+ idempotent: false,
1542
+ fileId: finalFile.id,
1543
+ size: Number(finalFile.size),
1544
+ finalHash
1545
+ };
1449
1546
  }
1450
1547
  );
1451
1548
  if (!result.idempotent) {
@@ -1458,7 +1555,8 @@ async function registerMultipartRoutes(app, rt) {
1458
1555
  uploadId: body.uploadId,
1459
1556
  fileId: result.fileId,
1460
1557
  size: result.size,
1461
- userId: rec.userId
1558
+ userId: rec.userId,
1559
+ watermarkHook: rt.onComplete ? "on" : "off"
1462
1560
  },
1463
1561
  "\u2705 multipart: complete"
1464
1562
  );
@@ -1934,7 +2032,8 @@ async function secureUploadPluginImpl(app, options) {
1934
2032
  ...options.strategy ?? {}
1935
2033
  }
1936
2034
  },
1937
- validateSession: options.sessionValidator ?? defaultSessionValidator
2035
+ validateSession: options.sessionValidator ?? defaultSessionValidator,
2036
+ onComplete: options.onComplete ?? null
1938
2037
  };
1939
2038
  app.decorate("secureUpload", runtime);
1940
2039
  await registerMultipartRoutes(app, runtime);