@mieweb/pulsevault 0.0.2 → 0.1.1

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 (76) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/LICENSE +15 -39
  3. package/OPERATIONS.md +223 -0
  4. package/PROTOCOL.md +345 -0
  5. package/README.md +548 -122
  6. package/augment.d.ts +1 -0
  7. package/augment.js +3 -0
  8. package/core.d.ts +1 -0
  9. package/core.js +7 -0
  10. package/dist/app.d.ts +91 -34
  11. package/dist/app.d.ts.map +1 -1
  12. package/dist/app.js +23 -33
  13. package/dist/app.js.map +1 -1
  14. package/dist/augment.d.ts +14 -7
  15. package/dist/augment.d.ts.map +1 -1
  16. package/dist/augment.js.map +1 -1
  17. package/dist/core.d.ts +108 -0
  18. package/dist/core.d.ts.map +1 -0
  19. package/dist/core.js +494 -0
  20. package/dist/core.js.map +1 -0
  21. package/dist/lib/authorize.d.ts +19 -0
  22. package/dist/lib/authorize.d.ts.map +1 -0
  23. package/dist/lib/authorize.js +2 -0
  24. package/dist/lib/authorize.js.map +1 -0
  25. package/dist/lib/capability-token.d.ts +80 -0
  26. package/dist/lib/capability-token.d.ts.map +1 -0
  27. package/dist/lib/capability-token.js +134 -0
  28. package/dist/lib/capability-token.js.map +1 -0
  29. package/dist/lib/checksum.d.ts +45 -0
  30. package/dist/lib/checksum.d.ts.map +1 -0
  31. package/dist/lib/checksum.js +105 -0
  32. package/dist/lib/checksum.js.map +1 -0
  33. package/dist/lib/deeplinks.d.ts +37 -21
  34. package/dist/lib/deeplinks.d.ts.map +1 -1
  35. package/dist/lib/deeplinks.js +54 -13
  36. package/dist/lib/deeplinks.js.map +1 -1
  37. package/dist/lib/errors.d.ts +7 -0
  38. package/dist/lib/errors.d.ts.map +1 -1
  39. package/dist/lib/errors.js +18 -0
  40. package/dist/lib/errors.js.map +1 -1
  41. package/dist/lib/magic.d.ts +30 -5
  42. package/dist/lib/magic.d.ts.map +1 -1
  43. package/dist/lib/magic.js +51 -14
  44. package/dist/lib/magic.js.map +1 -1
  45. package/dist/lib/options.d.ts +41 -0
  46. package/dist/lib/options.d.ts.map +1 -0
  47. package/dist/lib/options.js +111 -0
  48. package/dist/lib/options.js.map +1 -0
  49. package/dist/lib/pulsevaultTus.d.ts +53 -26
  50. package/dist/lib/pulsevaultTus.d.ts.map +1 -1
  51. package/dist/lib/pulsevaultTus.js +174 -55
  52. package/dist/lib/pulsevaultTus.js.map +1 -1
  53. package/dist/lib/request.d.ts +26 -0
  54. package/dist/lib/request.d.ts.map +1 -0
  55. package/dist/lib/request.js +22 -0
  56. package/dist/lib/request.js.map +1 -0
  57. package/dist/lib/uuid.d.ts.map +1 -1
  58. package/dist/lib/uuid.js +1 -1
  59. package/dist/lib/uuid.js.map +1 -1
  60. package/dist/routes/pulsevault.d.ts +18 -16
  61. package/dist/routes/pulsevault.d.ts.map +1 -1
  62. package/dist/routes/pulsevault.js +121 -251
  63. package/dist/routes/pulsevault.js.map +1 -1
  64. package/dist/storage/local.d.ts +31 -10
  65. package/dist/storage/local.d.ts.map +1 -1
  66. package/dist/storage/local.js +205 -84
  67. package/dist/storage/local.js.map +1 -1
  68. package/dist/storage/s3.d.ts +109 -0
  69. package/dist/storage/s3.d.ts.map +1 -0
  70. package/dist/storage/s3.js +438 -0
  71. package/dist/storage/s3.js.map +1 -0
  72. package/dist/storage/types.d.ts +80 -17
  73. package/dist/storage/types.d.ts.map +1 -1
  74. package/dist/storage/types.js +13 -1
  75. package/dist/storage/types.js.map +1 -1
  76. package/package.json +29 -7
package/dist/lib/magic.js CHANGED
@@ -1,4 +1,4 @@
1
- import fs from "node:fs/promises";
1
+ import fs from 'node:fs/promises';
2
2
  /**
3
3
  * Check whether a file's first bytes match the ISO base media file format
4
4
  * (`ftyp` box at offset 4), which covers MP4, MOV, M4V, 3GP, and related
@@ -12,7 +12,7 @@ import fs from "node:fs/promises";
12
12
  export async function sniffMp4(filePath) {
13
13
  let fd;
14
14
  try {
15
- fd = await fs.open(filePath, "r");
15
+ fd = await fs.open(filePath, 'r');
16
16
  }
17
17
  catch {
18
18
  return false;
@@ -22,19 +22,23 @@ export async function sniffMp4(filePath) {
22
22
  const { bytesRead } = await fd.read(buf, 0, 12, 0);
23
23
  if (bytesRead < 12)
24
24
  return false;
25
- // Bytes 4..7 must spell "ftyp" (ASCII 0x66 0x74 0x79 0x70). The first
26
- // four bytes are the box size and the remaining four after "ftyp" are
27
- // the brand (e.g. "isom", "mp42", "qt ") — brand validation is left
28
- // to downstream tools.
29
- return (buf[4] === 0x66 &&
30
- buf[5] === 0x74 &&
31
- buf[6] === 0x79 &&
32
- buf[7] === 0x70);
25
+ return hasFtypBox(buf);
33
26
  }
34
27
  finally {
35
28
  await fd.close();
36
29
  }
37
30
  }
31
+ /**
32
+ * Whether a buffer's first 12 bytes carry an ISO base media `ftyp` box.
33
+ * Bytes 4..7 must spell "ftyp" (ASCII 0x66 0x74 0x79 0x70). The first four
34
+ * bytes are the box size and the four after "ftyp" are the brand (e.g.
35
+ * "isom", "mp42", "qt ") — brand validation is left to downstream tools.
36
+ */
37
+ function hasFtypBox(buf) {
38
+ if (buf.length < 12)
39
+ return false;
40
+ return buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70;
41
+ }
38
42
  /**
39
43
  * Build a `validatePayload` hook that enforces every uploaded file is a
40
44
  * valid MP4-family container. Only works with `LocalStorage` (or any
@@ -52,14 +56,47 @@ export async function sniffMp4(filePath) {
52
56
  * ```
53
57
  */
54
58
  export function createMp4Sniffer(storage) {
55
- return async (_request, { videoid }) => {
56
- const localPath = await storage.getLocalPath(videoid);
59
+ return async (_request, { artifactId }) => {
60
+ const localPath = await storage.getLocalPath(artifactId);
57
61
  if (!localPath) {
58
- throw Object.assign(new Error(`Cannot validate upload ${videoid}: no local path available`), { statusCode: 500 });
62
+ throw Object.assign(new Error(`Cannot validate upload ${artifactId}: no local path available`), { statusCode: 500 });
59
63
  }
60
64
  const ok = await sniffMp4(localPath);
61
65
  if (!ok) {
62
- throw Object.assign(new Error("Uploaded bytes are not a valid MP4 (missing ftyp header)"), { statusCode: 422 });
66
+ throw Object.assign(new Error('Uploaded bytes are not a valid MP4 (missing ftyp header)'), {
67
+ statusCode: 422,
68
+ });
69
+ }
70
+ };
71
+ }
72
+ /**
73
+ * `createMp4Sniffer` for the S3 / R2 backend. Instead of opening a local file,
74
+ * it asks the adapter for the first 12 bytes of the finalized object via a
75
+ * small ranged GET (`S3Storage.readHeader`) and applies the same `ftyp` check.
76
+ * Runs in the same lifecycle slot — after `@tus/s3-store` completes the
77
+ * multipart upload (so the object is readable) but before `markReady` — so a
78
+ * non-MP4 upload is removed and never served.
79
+ *
80
+ * Usage:
81
+ * ```ts
82
+ * const storage = await createS3Storage({ ... });
83
+ * await app.register(pulseVault, {
84
+ * storage,
85
+ * validatePayload: createS3Mp4Sniffer(storage),
86
+ * // ...
87
+ * });
88
+ * ```
89
+ */
90
+ export function createS3Mp4Sniffer(storage) {
91
+ return async (_request, { artifactId }) => {
92
+ const header = await storage.readHeader(artifactId, 12);
93
+ if (!header) {
94
+ throw Object.assign(new Error(`Cannot validate upload ${artifactId}: no object bytes available`), { statusCode: 500 });
95
+ }
96
+ if (!hasFtypBox(header)) {
97
+ throw Object.assign(new Error('Uploaded bytes are not a valid MP4 (missing ftyp header)'), {
98
+ statusCode: 422,
99
+ });
63
100
  }
64
101
  };
65
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"magic.js","sourceRoot":"","sources":["../../src/lib/magic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AA2BlC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,EAAiB,CAAC;IACtB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,SAAS,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC;QACjC,sEAAsE;QACtE,sEAAsE;QACtE,qEAAqE;QACrE,uBAAuB;QACvB,OAAO,CACL,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAChB,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAqB;IAErB,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACrC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CACP,0BAA0B,OAAO,2BAA2B,CAC7D,EACD,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,0DAA0D,CAAC,EACrE,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"magic.js","sourceRoot":"","sources":["../../src/lib/magic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAiClC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,EAAiB,CAAC;IACtB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,SAAS,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC;QACjC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACxC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,0BAA0B,UAAU,2BAA2B,CAAC,EAC1E,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,EAAE;gBACzF,UAAU,EAAE,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAkB;IACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,0BAA0B,UAAU,6BAA6B,CAAC,EAC5E,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0DAA0D,CAAC,EAAE;gBACzF,UAAU,EAAE,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { PulseVaultValidatePayload } from './magic.js';
2
+ import type { PulseVaultOnUploadComplete } from './pulsevaultTus.js';
3
+ /** Per-kind form of `PulseVaultAllowedExtensionsInput`, named once so it isn't re-declared at every use site. */
4
+ type AllowedExtensionsByKind = {
5
+ video?: readonly string[];
6
+ project?: readonly string[];
7
+ captions?: readonly string[];
8
+ thumbnail?: readonly string[];
9
+ };
10
+ export type PulseVaultAllowedExtensionsInput = readonly string[] | AllowedExtensionsByKind;
11
+ export type PulseVaultAllowedExtensions = {
12
+ video: readonly string[];
13
+ project: readonly string[];
14
+ captions: readonly string[];
15
+ thumbnail: readonly string[];
16
+ };
17
+ /**
18
+ * Normalize the consumer's `allowedExtensions` option (legacy array or per-kind
19
+ * object) into the canonical `{ video, project, captions }` shape both
20
+ * adapters pass down to the tus layer.
21
+ */
22
+ export declare function normalizeAllowedExtensions(raw: PulseVaultAllowedExtensionsInput | undefined): PulseVaultAllowedExtensions;
23
+ /** Shared by the Fastify plugin's `prefix` and the core's `basePath` — same shape, different option name. */
24
+ export declare function validateBasePath(basePath: string, optionName: string): void;
25
+ export declare function validateMaxUploadSize(maxUploadSize: number): void;
26
+ export declare function validateUploadUnit(uploadUnit: 'segment' | 'merged' | undefined): void;
27
+ export declare function validateAllowedExtensions(allowedExtensions: PulseVaultAllowedExtensionsInput | undefined): void;
28
+ /**
29
+ * One-time (not per-request) warning for the now-deprecated per-kind project
30
+ * hooks, so consumers notice the migration need instead of it being silently
31
+ * aliased away until a future major version removes it outright.
32
+ */
33
+ export declare function warnIfUsingDeprecatedProjectHooks(opts: {
34
+ validateProjectPayload?: unknown;
35
+ onProjectUploadComplete?: unknown;
36
+ }): void;
37
+ /** Compose the legacy per-kind hook (if any) with the generic hook, mapped onto the `"project"` kind only. */
38
+ export declare function composeValidatePayload(generic: PulseVaultValidatePayload | undefined, legacyProject: PulseVaultValidatePayload | undefined): PulseVaultValidatePayload | undefined;
39
+ export declare function composeOnUploadComplete(generic: PulseVaultOnUploadComplete | undefined, legacyProject: PulseVaultOnUploadComplete | undefined): PulseVaultOnUploadComplete | undefined;
40
+ export {};
41
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/lib/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE,iHAAiH;AACjH,KAAK,uBAAuB,GAAG;IAC7B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG,SAAS,MAAM,EAAE,GAAG,uBAAuB,CAAC;AAE3F,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAWF;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,gCAAgC,GAAG,SAAS,GAChD,2BAA2B,CAyB7B;AAED,6GAA6G;AAC7G,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAM3E;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAIjE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAIrF;AAED,wBAAgB,yBAAyB,CACvC,iBAAiB,EAAE,gCAAgC,GAAG,SAAS,GAC9D,IAAI,CAiBN;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE;IACtD,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GAAG,IAAI,CAaP;AAED,8GAA8G;AAC9G,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,aAAa,EAAE,yBAAyB,GAAG,SAAS,GACnD,yBAAyB,GAAG,SAAS,CASvC;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,0BAA0B,GAAG,SAAS,EAC/C,aAAa,EAAE,0BAA0B,GAAG,SAAS,GACpD,0BAA0B,GAAG,SAAS,CASxC"}
@@ -0,0 +1,111 @@
1
+ const DEFAULT_VIDEO_EXTENSIONS = ['.mp4'];
2
+ const DEFAULT_PROJECT_EXTENSIONS = ['.pulse', '.zip'];
3
+ // WebVTT only — it's what the Pulse app uploads, and it carries word-level
4
+ // inline cue timestamps (`<00:00:01.500>word`) for karaoke rendering.
5
+ const DEFAULT_CAPTIONS_EXTENSIONS = ['.vtt'];
6
+ // The merged-mode poster frame (the draft's first-clip thumbnail).
7
+ const DEFAULT_THUMBNAIL_EXTENSIONS = ['.jpg', '.jpeg', '.png'];
8
+ const EXTENSION_REGEX = /^\.[^.\s/\\]+$/;
9
+ /**
10
+ * Normalize the consumer's `allowedExtensions` option (legacy array or per-kind
11
+ * object) into the canonical `{ video, project, captions }` shape both
12
+ * adapters pass down to the tus layer.
13
+ */
14
+ export function normalizeAllowedExtensions(raw) {
15
+ if (!raw) {
16
+ return {
17
+ video: DEFAULT_VIDEO_EXTENSIONS,
18
+ project: DEFAULT_PROJECT_EXTENSIONS,
19
+ captions: DEFAULT_CAPTIONS_EXTENSIONS,
20
+ thumbnail: DEFAULT_THUMBNAIL_EXTENSIONS,
21
+ };
22
+ }
23
+ if (Array.isArray(raw)) {
24
+ // Legacy flat array: treat as video-only, project/captions/thumbnail keep their defaults.
25
+ return {
26
+ video: raw.map((ext) => ext.toLowerCase()),
27
+ project: DEFAULT_PROJECT_EXTENSIONS,
28
+ captions: DEFAULT_CAPTIONS_EXTENSIONS,
29
+ thumbnail: DEFAULT_THUMBNAIL_EXTENSIONS,
30
+ };
31
+ }
32
+ const byKind = raw;
33
+ return {
34
+ video: (byKind.video ?? DEFAULT_VIDEO_EXTENSIONS).map((ext) => ext.toLowerCase()),
35
+ project: (byKind.project ?? DEFAULT_PROJECT_EXTENSIONS).map((ext) => ext.toLowerCase()),
36
+ captions: (byKind.captions ?? DEFAULT_CAPTIONS_EXTENSIONS).map((ext) => ext.toLowerCase()),
37
+ thumbnail: (byKind.thumbnail ?? DEFAULT_THUMBNAIL_EXTENSIONS).map((ext) => ext.toLowerCase()),
38
+ };
39
+ }
40
+ /** Shared by the Fastify plugin's `prefix` and the core's `basePath` — same shape, different option name. */
41
+ export function validateBasePath(basePath, optionName) {
42
+ if (basePath !== '' && (!basePath.startsWith('/') || basePath.endsWith('/'))) {
43
+ throw new TypeError(`\`${optionName}\` must be '' or start with '/' with no trailing slash (e.g. '/pulsevault')`);
44
+ }
45
+ }
46
+ export function validateMaxUploadSize(maxUploadSize) {
47
+ if (!(maxUploadSize > 0)) {
48
+ throw new TypeError('`maxUploadSize` must be a positive number (use Infinity for no cap)');
49
+ }
50
+ }
51
+ export function validateUploadUnit(uploadUnit) {
52
+ if (uploadUnit && uploadUnit !== 'segment' && uploadUnit !== 'merged') {
53
+ throw new TypeError('`uploadUnit` must be "segment" or "merged"');
54
+ }
55
+ }
56
+ export function validateAllowedExtensions(allowedExtensions) {
57
+ if (!allowedExtensions)
58
+ return;
59
+ const exts = Array.isArray(allowedExtensions)
60
+ ? allowedExtensions
61
+ : [
62
+ ...(allowedExtensions.video ?? []),
63
+ ...(allowedExtensions.project ?? []),
64
+ ...(allowedExtensions.captions ?? []),
65
+ ...(allowedExtensions.thumbnail ?? []),
66
+ ];
67
+ for (const ext of exts) {
68
+ if (!EXTENSION_REGEX.test(ext)) {
69
+ throw new TypeError(`\`allowedExtensions\` entry ${JSON.stringify(ext)} must start with '.' and contain no nested dots, slashes, or whitespace (e.g. '.mp4')`);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * One-time (not per-request) warning for the now-deprecated per-kind project
75
+ * hooks, so consumers notice the migration need instead of it being silently
76
+ * aliased away until a future major version removes it outright.
77
+ */
78
+ export function warnIfUsingDeprecatedProjectHooks(opts) {
79
+ if (opts.validateProjectPayload) {
80
+ process.emitWarning('pulsevault: `validateProjectPayload` is deprecated — use `validatePayload` and branch on `ctx.kind === "project"` instead. Still honored this release.', 'DeprecationWarning');
81
+ }
82
+ if (opts.onProjectUploadComplete) {
83
+ process.emitWarning('pulsevault: `onProjectUploadComplete` is deprecated — use `onUploadComplete` and branch on `ctx.kind === "project"` instead. Still honored this release.', 'DeprecationWarning');
84
+ }
85
+ }
86
+ /** Compose the legacy per-kind hook (if any) with the generic hook, mapped onto the `"project"` kind only. */
87
+ export function composeValidatePayload(generic, legacyProject) {
88
+ if (!legacyProject)
89
+ return generic;
90
+ return async (request, ctx) => {
91
+ if (ctx.kind === 'project') {
92
+ await legacyProject(request, ctx);
93
+ return;
94
+ }
95
+ if (generic)
96
+ await generic(request, ctx);
97
+ };
98
+ }
99
+ export function composeOnUploadComplete(generic, legacyProject) {
100
+ if (!legacyProject)
101
+ return generic;
102
+ return async (request, ctx) => {
103
+ if (ctx.kind === 'project') {
104
+ await legacyProject(request, ctx);
105
+ return;
106
+ }
107
+ if (generic)
108
+ await generic(request, ctx);
109
+ };
110
+ }
111
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/lib/options.ts"],"names":[],"mappings":"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"}
@@ -1,23 +1,45 @@
1
- import { Server } from "@tus/server";
2
- import type { FastifyRequest } from "fastify";
3
- import { AsyncLocalStorage } from "node:async_hooks";
4
- import type { PulseVaultValidatePayload } from "./magic.js";
5
- import type { PulseVaultStorage } from "../storage/types.js";
1
+ import { Server } from '@tus/server';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import type { PulseVaultValidatePayload } from './magic.js';
4
+ import { type PulseVaultRequest, type PulseVaultLogger } from './request.js';
5
+ import type { PulseVaultStorage } from '../storage/types.js';
6
+ import type { UploadKind } from '../storage/types.js';
6
7
  /**
7
- * Context the plugin stashes on each incoming Fastify request for the lifetime
8
- * of a TUS call. Shared with the tus hooks via `AsyncLocalStorage` because
9
- * `@tus/server` v2 hooks receive a web `Request`, not the FastifyRequest.
8
+ * Context the plugin stashes on each incoming request for the lifetime of a
9
+ * TUS call. Shared with the tus hooks via `AsyncLocalStorage` because
10
+ * `@tus/server` v2 hooks receive a raw `req`/`res`, not the host's request
11
+ * wrapper (Fastify's `FastifyRequest`, Express's `req`, etc.).
10
12
  */
11
13
  export type PulseVaultTusContext = {
12
- request: FastifyRequest;
13
- videoid?: string;
14
+ request: PulseVaultRequest;
15
+ artifactId?: string;
16
+ /** Kind resolved during TUS create; available on the same request only. */
17
+ kind?: UploadKind;
18
+ /** `relatedTo` resolved during TUS create; available on the same request only. */
19
+ relatedTo?: string;
20
+ /** Raw `checksum` metadata value (`<algorithm>:<hex>`), if the client sent one. */
21
+ checksum?: string;
14
22
  };
15
23
  export declare const pulseVaultTusContext: AsyncLocalStorage<PulseVaultTusContext>;
16
- export type PulseVaultOnUploadComplete = (request: FastifyRequest, ctx: {
17
- videoid: string;
24
+ export type PulseVaultOnUploadComplete = (request: PulseVaultRequest, ctx: {
25
+ artifactId: string;
26
+ kind: UploadKind;
18
27
  size: number;
19
28
  uploadId: string;
20
29
  }) => void | Promise<void>;
30
+ /**
31
+ * Fired at low-frequency, audit-worthy moments — never per chunk — so an
32
+ * operator can wire one hook to get both ops metrics and a compliance audit
33
+ * trail without hand-rolling both from the lower-level hooks.
34
+ */
35
+ export type PulseVaultArtifactEvent = {
36
+ phase: 'authorize' | 'complete' | 'reject';
37
+ artifactId: string;
38
+ kind: UploadKind;
39
+ size?: number;
40
+ reason?: string;
41
+ };
42
+ export type PulseVaultOnArtifactEvent = (event: PulseVaultArtifactEvent) => void | Promise<void>;
21
43
  export type PulsevaultTusOptions = {
22
44
  storage: PulseVaultStorage;
23
45
  /** Absolute URL path where TUS is mounted, e.g. `/pulsevault/upload`. */
@@ -25,25 +47,28 @@ export type PulsevaultTusOptions = {
25
47
  /** Max total upload size in bytes. Use `Infinity` for no cap. */
26
48
  maxSize: number;
27
49
  /**
28
- * File extensions allowed in the upload `filename` metadata. Must be
29
- * pre-normalized to lowercase and include the leading dot.
50
+ * Allowed extensions per kind. Must be pre-normalized to lowercase and
51
+ * include the leading dot.
30
52
  */
31
- allowedExtensions: readonly string[];
53
+ allowedExtensions: {
54
+ video: readonly string[];
55
+ project: readonly string[];
56
+ captions: readonly string[];
57
+ thumbnail: readonly string[];
58
+ };
32
59
  /**
33
- * Optional payload-validation hook. Runs after TUS writes the final byte
34
- * but before `markReady` and `onUploadComplete`. Throwing from this hook
35
- * causes the plugin to `storage.remove?.(videoid)` and return a 4xx
36
- * (default 422) to the client. Use for magic-byte sniffing, virus
37
- * scanning, size re-checks — anything that needs the final bytes.
60
+ * Optional payload-validation hook, called for every kind with `ctx.kind`
61
+ * set accordingly. Runs after TUS writes the final byte but before
62
+ * `markReady` and `onUploadComplete`. Throwing causes
63
+ * `storage.remove?.(artifactId)` and a 4xx (default 422).
38
64
  */
39
65
  validatePayload?: PulseVaultValidatePayload;
40
- /**
41
- * Fired once the final byte of an upload has been written *and* any
42
- * `validatePayload` has passed. Use this to flip consumer state (DB row,
43
- * queue job, audit log). Throwing here returns a 500 — bytes are on disk
44
- * and marked ready, but consumer-side work failed.
45
- */
66
+ /** Fired once the final byte has been written and any `validatePayload` has passed, for every kind. */
46
67
  onUploadComplete?: PulseVaultOnUploadComplete;
68
+ /** See `PulseVaultOnArtifactEvent`. */
69
+ onArtifactEvent?: PulseVaultOnArtifactEvent;
70
+ /** Logger for internal diagnostics (cleanup failures, etc). Defaults to `console`. */
71
+ logger?: PulseVaultLogger;
47
72
  };
48
73
  /**
49
74
  * Shape `@tus/server` recognizes for sending an error response. We tag both
@@ -52,5 +77,7 @@ export type PulsevaultTusOptions = {
52
77
  * right HTTP status.
53
78
  */
54
79
  export declare function tusError(status: number, body: string): Error;
80
+ /** Parse the artifactId UUID from a tus upload id of the form `<kind>/<artifactId><ext>`. */
81
+ export declare function artifactIdFromUploadId(id: string): string | undefined;
55
82
  export declare function createPulsevaultTusServer(options: PulsevaultTusOptions): Server;
56
83
  //# sourceMappingURL=pulsevaultTus.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pulsevaultTus.d.ts","sourceRoot":"","sources":["../../src/lib/pulsevaultTus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AAElF,MAAM,MAAM,0BAA0B,GAAG,CACvC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KACrD,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;CAC/C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAM5D;AAmBD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,UAyHtE"}
1
+ {"version":3,"file":"pulsevaultTus.d.ts","sourceRoot":"","sources":["../../src/lib/pulsevaultTus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAiB,MAAM,cAAc,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AAElF,MAAM,MAAM,0BAA0B,GAAG,CACvC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC1E,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjG,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,EAAE;QACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KAC9B,CAAC;IACF;;;;;OAKG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,uGAAuG;IACvG,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,uCAAuC;IACvC,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,sFAAsF;IACtF,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAM5D;AAED,6FAA6F;AAC7F,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAoCD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,UA8JtE"}