@mieweb/pulsevault 0.0.1 → 0.1.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 (70) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/LICENSE +15 -39
  3. package/OPERATIONS.md +223 -0
  4. package/PROTOCOL.md +312 -0
  5. package/README.md +532 -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 +90 -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 +431 -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 +126 -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 +102 -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/magic.d.ts +30 -5
  38. package/dist/lib/magic.d.ts.map +1 -1
  39. package/dist/lib/magic.js +51 -14
  40. package/dist/lib/magic.js.map +1 -1
  41. package/dist/lib/options.d.ts +39 -0
  42. package/dist/lib/options.d.ts.map +1 -0
  43. package/dist/lib/options.js +103 -0
  44. package/dist/lib/options.js.map +1 -0
  45. package/dist/lib/pulsevaultTus.d.ts +52 -26
  46. package/dist/lib/pulsevaultTus.d.ts.map +1 -1
  47. package/dist/lib/pulsevaultTus.js +111 -43
  48. package/dist/lib/pulsevaultTus.js.map +1 -1
  49. package/dist/lib/request.d.ts +24 -0
  50. package/dist/lib/request.d.ts.map +1 -0
  51. package/dist/lib/request.js +16 -0
  52. package/dist/lib/request.js.map +1 -0
  53. package/dist/lib/uuid.d.ts.map +1 -1
  54. package/dist/lib/uuid.js +1 -1
  55. package/dist/lib/uuid.js.map +1 -1
  56. package/dist/routes/pulsevault.d.ts +18 -14
  57. package/dist/routes/pulsevault.d.ts.map +1 -1
  58. package/dist/routes/pulsevault.js +127 -247
  59. package/dist/routes/pulsevault.js.map +1 -1
  60. package/dist/storage/local.d.ts +31 -10
  61. package/dist/storage/local.d.ts.map +1 -1
  62. package/dist/storage/local.js +166 -82
  63. package/dist/storage/local.js.map +1 -1
  64. package/dist/storage/s3.d.ts +109 -0
  65. package/dist/storage/s3.d.ts.map +1 -0
  66. package/dist/storage/s3.js +432 -0
  67. package/dist/storage/s3.js.map +1 -0
  68. package/dist/storage/types.d.ts +69 -17
  69. package/dist/storage/types.d.ts.map +1 -1
  70. package/package.json +35 -7
@@ -0,0 +1,432 @@
1
+ import { createHash } from 'node:crypto';
2
+ const SIDECAR_VERSION = 1;
3
+ /** Key prefix inside the bucket that holds the per-upload sidecar objects. */
4
+ const PULSEVAULT_META_PREFIX = '.pulsevault';
5
+ /** Default presigned playback URL lifetime (15 minutes). */
6
+ const DEFAULT_PRESIGN_TTL_SECONDS = 900;
7
+ /** Default cap on the in-memory metadata cache before evicting the oldest entry. */
8
+ const DEFAULT_META_CACHE_LIMIT = 10_000;
9
+ /** Map a file extension to the `Content-Type` the playback URL should return. */
10
+ function extToContentType(ext) {
11
+ switch (ext) {
12
+ case '.mp4':
13
+ return 'video/mp4';
14
+ case '.zip':
15
+ return 'application/zip';
16
+ case '.srt':
17
+ return 'application/x-subrip';
18
+ default:
19
+ return 'application/octet-stream';
20
+ }
21
+ }
22
+ /** Project a `Sidecar` into the shape kept in the in-memory cache. */
23
+ function sidecarToCachedMeta(sidecar, ready) {
24
+ return {
25
+ ext: sidecar.ext,
26
+ ready,
27
+ kind: sidecar.kind ?? 'video',
28
+ relatedTo: sidecar.relatedTo,
29
+ checksum: sidecar.checksum,
30
+ };
31
+ }
32
+ /**
33
+ * Build an S3/R2-backed storage adapter.
34
+ *
35
+ * Async because it lazily imports the optional `@aws-sdk/*` and
36
+ * `@tus/s3-store` packages — `await` it before registering the plugin:
37
+ *
38
+ * ```ts
39
+ * const storage = await createS3Storage({
40
+ * bucket: "pulse-videos",
41
+ * endpoint: `https://${ACCOUNT}.r2.cloudflarestorage.com`,
42
+ * accessKeyId: process.env.R2_ACCESS_KEY,
43
+ * secretAccessKey: process.env.R2_SECRET_KEY,
44
+ * });
45
+ * await app.register(pulseVault, { storage, prefix: "/pulsevault", maxUploadSize: Infinity });
46
+ * ```
47
+ */
48
+ export async function createS3Storage(opts) {
49
+ let s3;
50
+ let presigner;
51
+ let s3store;
52
+ try {
53
+ [s3, presigner, s3store] = await Promise.all([
54
+ import('@aws-sdk/client-s3'),
55
+ import('@aws-sdk/s3-request-presigner'),
56
+ import('@tus/s3-store'),
57
+ ]);
58
+ }
59
+ catch (err) {
60
+ throw new Error('createS3Storage requires the optional packages `@aws-sdk/client-s3`, ' +
61
+ '`@aws-sdk/s3-request-presigner`, and `@tus/s3-store`. Install them with:\n' +
62
+ ' npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner @tus/s3-store\n' +
63
+ `(original error: ${err instanceof Error ? err.message : String(err)})`);
64
+ }
65
+ const { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } = s3;
66
+ const { getSignedUrl } = presigner;
67
+ const { S3Store } = s3store;
68
+ const bucket = opts.bucket;
69
+ const presignTtl = opts.presignTtlSeconds ?? DEFAULT_PRESIGN_TTL_SECONDS;
70
+ const metaCacheLimit = opts.metaCacheLimit ?? DEFAULT_META_CACHE_LIMIT;
71
+ const credentials = opts.accessKeyId && opts.secretAccessKey
72
+ ? {
73
+ accessKeyId: opts.accessKeyId,
74
+ secretAccessKey: opts.secretAccessKey,
75
+ ...(opts.sessionToken ? { sessionToken: opts.sessionToken } : {}),
76
+ }
77
+ : undefined;
78
+ const clientConfig = {
79
+ region: opts.region ?? (opts.endpoint ? 'auto' : undefined),
80
+ ...(opts.endpoint ? { endpoint: opts.endpoint } : {}),
81
+ forcePathStyle: opts.forcePathStyle ?? Boolean(opts.endpoint),
82
+ ...(credentials ? { credentials } : {}),
83
+ ...opts.clientConfig,
84
+ };
85
+ // Our own client, used for sidecar I/O, ranged header reads, deletes, and
86
+ // presigning. The TUS datastore creates its own client from the same config.
87
+ const client = new S3Client(clientConfig);
88
+ const datastore = new S3Store({
89
+ ...(opts.partSize ? { partSize: opts.partSize } : {}),
90
+ s3ClientConfig: { ...clientConfig, bucket },
91
+ });
92
+ // Metadata cache keyed by artifactId, mirroring the local adapter: populated
93
+ // eagerly on reserve and lazily from the sidecar on a cache-miss, so the GET
94
+ // hot path avoids a per-request round-trip to the bucket. Bounded with
95
+ // simple insertion-order eviction, same rationale as the local adapter.
96
+ const metaCache = new Map();
97
+ const cacheSet = (artifactId, meta) => {
98
+ metaCache.delete(artifactId);
99
+ metaCache.set(artifactId, meta);
100
+ if (metaCache.size > metaCacheLimit) {
101
+ const oldest = metaCache.keys().next().value;
102
+ if (oldest !== undefined)
103
+ metaCache.delete(oldest);
104
+ }
105
+ };
106
+ const sidecarKey = (artifactId) => `${PULSEVAULT_META_PREFIX}/${artifactId}.json`;
107
+ /** Object key for the artifact bytes — also the TUS file id / multipart key. */
108
+ const artifactKey = (artifactId, kind, ext) => `${kind}/${artifactId}${ext}`;
109
+ const writeSidecar = async (artifactId, sidecar) => {
110
+ await client.send(new PutObjectCommand({
111
+ Bucket: bucket,
112
+ Key: sidecarKey(artifactId),
113
+ Body: JSON.stringify(sidecar),
114
+ ContentType: 'application/json',
115
+ }));
116
+ };
117
+ const readSidecar = async (artifactId) => {
118
+ let raw;
119
+ try {
120
+ const res = await client.send(new GetObjectCommand({ Bucket: bucket, Key: sidecarKey(artifactId) }));
121
+ raw = (await bodyToBuffer(res.Body)).toString('utf8');
122
+ }
123
+ catch (err) {
124
+ if (isNotFound(err))
125
+ return null;
126
+ throw err;
127
+ }
128
+ try {
129
+ const parsed = JSON.parse(raw);
130
+ if (typeof parsed.ext !== 'string')
131
+ return null;
132
+ if (typeof parsed.filename !== 'string')
133
+ return null;
134
+ const status = parsed.status === 'uploading' ? 'uploading' : 'ready';
135
+ const kind = parsed.kind === 'project' ? 'project' : parsed.kind === 'captions' ? 'captions' : 'video';
136
+ return {
137
+ version: SIDECAR_VERSION,
138
+ ext: parsed.ext,
139
+ filename: parsed.filename,
140
+ status,
141
+ kind,
142
+ relatedTo: typeof parsed.relatedTo === 'string' ? parsed.relatedTo : undefined,
143
+ checksum: typeof parsed.checksum === 'string' ? parsed.checksum : undefined,
144
+ };
145
+ }
146
+ catch {
147
+ // Malformed sidecar — treat as absent; `reserveUpload` rewrites it.
148
+ return null;
149
+ }
150
+ };
151
+ const loadMeta = async (artifactId) => {
152
+ const cached = metaCache.get(artifactId);
153
+ if (cached)
154
+ return cached;
155
+ const sidecar = await readSidecar(artifactId);
156
+ if (!sidecar)
157
+ return null;
158
+ const meta = sidecarToCachedMeta(sidecar, sidecar.status === 'ready');
159
+ cacheSet(artifactId, meta);
160
+ return meta;
161
+ };
162
+ const reserveUpload = async ({ artifactId, filename, ext, kind, relatedTo, checksum, }) => {
163
+ const sidecar = {
164
+ version: SIDECAR_VERSION,
165
+ ext,
166
+ filename,
167
+ status: 'uploading',
168
+ kind,
169
+ relatedTo,
170
+ checksum,
171
+ };
172
+ // Fast-path rejection for the common case. Not atomic by itself (two concurrent
173
+ // requests can both observe no existing meta before either writes) — the conditional
174
+ // write below closes that race on backends that support it — but it's also the only
175
+ // enforcement on S3-compatible backends that silently ignore `IfNoneMatch` instead of
176
+ // erroring (e.g. the `s3rver` mock this repo's own test suite runs against).
177
+ const existing = await loadMeta(artifactId);
178
+ if (existing) {
179
+ throw Object.assign(new Error(`artifactId ${artifactId} already has an upload`), {
180
+ statusCode: 409,
181
+ status_code: 409,
182
+ });
183
+ }
184
+ // Collision guard: `IfNoneMatch: "*"` makes the write itself atomically fail
185
+ // (PreconditionFailed) if a sidecar object already exists for this artifactId, closing
186
+ // the race the check above can't close by itself. Surfaces as HTTP 409 via @tus/server's
187
+ // error path, same as before.
188
+ try {
189
+ await client.send(new PutObjectCommand({
190
+ Bucket: bucket,
191
+ Key: sidecarKey(artifactId),
192
+ Body: JSON.stringify(sidecar),
193
+ ContentType: 'application/json',
194
+ IfNoneMatch: '*',
195
+ }));
196
+ }
197
+ catch (err) {
198
+ if (isPreconditionFailed(err)) {
199
+ throw Object.assign(new Error(`artifactId ${artifactId} already has an upload`), {
200
+ statusCode: 409,
201
+ status_code: 409,
202
+ });
203
+ }
204
+ if (!isConditionalWriteUnsupported(err))
205
+ throw err;
206
+ // Some S3-compatible backends don't support conditional writes — fall back to the
207
+ // previous check-then-write. Weaker (the original TOCTOU window reopens) but keeps
208
+ // reserve working on those backends instead of hard-failing every upload. Surface
209
+ // this degraded mode once per process so operators know their backend can't fully
210
+ // guarantee collision safety under concurrent/retried creates for the same artifactId.
211
+ warnAboutConditionalWriteFallbackOnce();
212
+ const meta = await loadMeta(artifactId);
213
+ if (meta) {
214
+ throw Object.assign(new Error(`artifactId ${artifactId} already has an upload`), {
215
+ statusCode: 409,
216
+ status_code: 409,
217
+ });
218
+ }
219
+ await writeSidecar(artifactId, sidecar);
220
+ }
221
+ cacheSet(artifactId, { ext, ready: false, kind, relatedTo, checksum });
222
+ // @tus/s3-store uses this as the object key for the multipart upload, so
223
+ // the finished object lands at `<kind>/<artifactId><ext>`.
224
+ return artifactKey(artifactId, kind, ext);
225
+ };
226
+ const resolve = async (artifactId) => {
227
+ const meta = await loadMeta(artifactId);
228
+ // Only serve ready uploads — an object that exists but is mid-upload or
229
+ // failed validation stays hidden.
230
+ if (!meta || !meta.ready)
231
+ return null;
232
+ const key = artifactKey(artifactId, meta.kind, meta.ext);
233
+ const url = await getSignedUrl(client, new GetObjectCommand({
234
+ Bucket: bucket,
235
+ Key: key,
236
+ // Force the right Content-Type on the redirected download regardless
237
+ // of what was stored on the object.
238
+ ResponseContentType: extToContentType(meta.ext),
239
+ }), { expiresIn: presignTtl });
240
+ return { kind: 'redirect', url, statusCode: 302 };
241
+ };
242
+ const markReady = async (artifactId) => {
243
+ const sidecar = await readSidecar(artifactId);
244
+ if (!sidecar) {
245
+ throw new Error(`markReady: no sidecar for artifactId ${artifactId} (was reserveUpload called?)`);
246
+ }
247
+ const next = sidecarToCachedMeta(sidecar, true);
248
+ if (sidecar.status === 'ready') {
249
+ // Idempotent: already ready, just keep the cache consistent.
250
+ cacheSet(artifactId, next);
251
+ return;
252
+ }
253
+ await writeSidecar(artifactId, { ...sidecar, status: 'ready' });
254
+ cacheSet(artifactId, next);
255
+ };
256
+ const remove = async (artifactId) => {
257
+ const meta = await loadMeta(artifactId);
258
+ // Evict before deleting so a racing `resolve` can't hand back a stale key.
259
+ metaCache.delete(artifactId);
260
+ if (!meta)
261
+ return false;
262
+ const key = artifactKey(artifactId, meta.kind, meta.ext);
263
+ // Abort any still-in-progress multipart upload (no-op / best-effort once
264
+ // the upload has completed) so we never orphan an open multipart session.
265
+ await Promise.allSettled([
266
+ datastore.remove?.(key),
267
+ ]);
268
+ // Delete the finalized object, the @tus/s3-store `.info` sidecar, and our
269
+ // metadata sidecar. DeleteObject is idempotent, so this is safe whether or
270
+ // not the multipart abort above already removed some of them.
271
+ await Promise.all([
272
+ client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key })),
273
+ client.send(new DeleteObjectCommand({ Bucket: bucket, Key: `${key}.info` })),
274
+ client.send(new DeleteObjectCommand({ Bucket: bucket, Key: sidecarKey(artifactId) })),
275
+ ]);
276
+ return true;
277
+ };
278
+ const readHeader = async (artifactId, n) => {
279
+ const meta = await loadMeta(artifactId);
280
+ if (!meta)
281
+ return null;
282
+ const key = artifactKey(artifactId, meta.kind, meta.ext);
283
+ try {
284
+ const res = await client.send(new GetObjectCommand({
285
+ Bucket: bucket,
286
+ Key: key,
287
+ Range: `bytes=0-${Math.max(0, n - 1)}`,
288
+ }));
289
+ return await bodyToBuffer(res.Body);
290
+ }
291
+ catch (err) {
292
+ if (isNotFound(err))
293
+ return null;
294
+ throw err;
295
+ }
296
+ };
297
+ const readAll = async (artifactId) => {
298
+ const meta = await loadMeta(artifactId);
299
+ if (!meta)
300
+ return null;
301
+ const key = artifactKey(artifactId, meta.kind, meta.ext);
302
+ try {
303
+ const res = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
304
+ return await bodyToBuffer(res.Body);
305
+ }
306
+ catch (err) {
307
+ if (isNotFound(err))
308
+ return null;
309
+ throw err;
310
+ }
311
+ };
312
+ const digestAll = async (artifactId, algorithm) => {
313
+ const meta = await loadMeta(artifactId);
314
+ if (!meta)
315
+ return null;
316
+ const key = artifactKey(artifactId, meta.kind, meta.ext);
317
+ try {
318
+ const res = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
319
+ return await digestBody(res.Body, algorithm);
320
+ }
321
+ catch (err) {
322
+ if (isNotFound(err))
323
+ return null;
324
+ throw err;
325
+ }
326
+ };
327
+ const getKind = async (artifactId) => {
328
+ const meta = await loadMeta(artifactId);
329
+ return meta ? meta.kind : null;
330
+ };
331
+ const getRelatedTo = async (artifactId) => {
332
+ const meta = await loadMeta(artifactId);
333
+ return meta?.relatedTo ?? null;
334
+ };
335
+ const getChecksum = async (artifactId) => {
336
+ const meta = await loadMeta(artifactId);
337
+ return meta?.checksum ?? null;
338
+ };
339
+ const shutdown = async () => {
340
+ client.destroy();
341
+ };
342
+ return {
343
+ datastore,
344
+ bucket,
345
+ reserveUpload,
346
+ resolve,
347
+ markReady,
348
+ remove,
349
+ readHeader,
350
+ readAll,
351
+ digestAll,
352
+ getKind,
353
+ getRelatedTo,
354
+ getChecksum,
355
+ shutdown,
356
+ };
357
+ }
358
+ function hasTransformToByteArray(body) {
359
+ return (!!body &&
360
+ typeof body.transformToByteArray === 'function');
361
+ }
362
+ /** Collect an AWS SDK response body into a Buffer. */
363
+ async function bodyToBuffer(body) {
364
+ if (hasTransformToByteArray(body)) {
365
+ return Buffer.from(await body.transformToByteArray());
366
+ }
367
+ const chunks = [];
368
+ for await (const chunk of body) {
369
+ chunks.push(Buffer.from(chunk));
370
+ }
371
+ return Buffer.concat(chunks);
372
+ }
373
+ /**
374
+ * Stream an AWS SDK response body through a hash digest without buffering the
375
+ * whole thing into memory first — the streaming counterpart to
376
+ * `bodyToBuffer`, used by `digestAll`.
377
+ */
378
+ async function digestBody(body, algorithm) {
379
+ const hash = createHash(algorithm);
380
+ if (hasTransformToByteArray(body)) {
381
+ hash.update(await body.transformToByteArray());
382
+ return hash.digest('hex');
383
+ }
384
+ for await (const chunk of body) {
385
+ hash.update(chunk);
386
+ }
387
+ return hash.digest('hex');
388
+ }
389
+ /** Whether an AWS SDK error represents a missing key/object (404-ish). */
390
+ function isNotFound(err) {
391
+ const e = err;
392
+ return (e?.name === 'NoSuchKey' ||
393
+ e?.name === 'NotFound' ||
394
+ e?.Code === 'NoSuchKey' ||
395
+ e?.Code === 'NotFound' ||
396
+ e?.$metadata?.httpStatusCode === 404);
397
+ }
398
+ /** Whether a conditional `PutObjectCommand` (`IfNoneMatch`) failed because the object already exists. */
399
+ function isPreconditionFailed(err) {
400
+ const e = err;
401
+ return (e?.name === 'PreconditionFailed' ||
402
+ e?.Code === 'PreconditionFailed' ||
403
+ e?.$metadata?.httpStatusCode === 412);
404
+ }
405
+ /** Whether the backend rejected `IfNoneMatch` itself as unsupported, rather than the condition failing. */
406
+ function isConditionalWriteUnsupported(err) {
407
+ const e = err;
408
+ return (e?.name === 'NotImplemented' ||
409
+ e?.Code === 'NotImplemented' ||
410
+ e?.$metadata?.httpStatusCode === 501);
411
+ }
412
+ let warnedAboutConditionalWriteFallback = false;
413
+ /**
414
+ * One-time-per-process warning when `reserveUpload` falls back to
415
+ * check-then-write because the configured S3-compatible backend doesn't
416
+ * support `IfNoneMatch`. That fallback reopens a genuine TOCTOU window
417
+ * (two concurrent/retried `reserveUpload` calls for the same artifactId can
418
+ * both pass the check before either writes, silently clobbering one
419
+ * another's sidecar) — there's no way to close it without a real distributed
420
+ * lock, so the best this library can do is make the degraded mode visible.
421
+ */
422
+ function warnAboutConditionalWriteFallbackOnce() {
423
+ if (warnedAboutConditionalWriteFallback)
424
+ return;
425
+ warnedAboutConditionalWriteFallback = true;
426
+ console.warn('[pulsevault] S3-compatible backend does not support conditional writes (IfNoneMatch); ' +
427
+ 'falling back to check-then-write for reserveUpload. This reopens a collision race ' +
428
+ 'between concurrent/retried creates for the same artifactId. If this matters for your ' +
429
+ 'deployment, use a backend that supports IfNoneMatch, or serialize artifactId creation ' +
430
+ 'in front of pulsevault (e.g. in your own /reserve endpoint).');
431
+ }
432
+ //# sourceMappingURL=s3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../../src/storage/s3.ts"],"names":[],"mappings":"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"}
@@ -1,28 +1,61 @@
1
- import type { DataStore } from "@tus/server";
1
+ import type { DataStore } from '@tus/server';
2
2
  /**
3
- * How the GET route should serve a resolved video. Stream means `@fastify/send`
4
- * reads directly from a local path; Redirect issues an HTTP 3xx to a URL the
5
- * adapter produced (e.g. a pre-signed object storage URL).
3
+ * How the GET route should serve a resolved artifact. Stream means
4
+ * `@fastify/send` reads directly from a local path; Redirect issues an HTTP
5
+ * 3xx to a URL the adapter produced (e.g. a pre-signed object storage URL).
6
6
  */
7
7
  export type PulseVaultResolution = {
8
- kind: "stream";
8
+ kind: 'stream';
9
9
  /** Root directory `@fastify/send` should jail to. */
10
10
  root: string;
11
11
  /** Filename relative to `root`. */
12
12
  filename: string;
13
+ /**
14
+ * Explicit `Content-Type` to set on the response. When present the GET
15
+ * route overrides whatever `@fastify/send` infers from the extension.
16
+ * Populated by adapters for non-standard extensions (e.g. `.pulse`).
17
+ */
18
+ contentType?: string;
13
19
  } | {
14
- kind: "redirect";
20
+ kind: 'redirect';
15
21
  url: string;
16
22
  /** Defaults to 302. */
17
23
  statusCode?: number;
18
24
  };
25
+ export type UploadKind = 'video' | 'project' | 'captions';
19
26
  export type ReserveUploadParams = {
20
- /** UUID from `Upload-Metadata.videoid`. */
21
- videoid: string;
27
+ /** UUID from `Upload-Metadata.artifactId` (or the `videoid`/`projectid` legacy aliases). */
28
+ artifactId: string;
22
29
  /** Raw filename from `Upload-Metadata.filename`. */
23
30
  filename: string;
24
31
  /** Lowercase extension including the leading dot, validated upstream. */
25
32
  ext: string;
33
+ /** Artifact kind derived from `Upload-Metadata.kind`. Defaults to `"video"`. */
34
+ kind: UploadKind;
35
+ /**
36
+ * Optional UUID of another artifact this one belongs to, from
37
+ * `Upload-Metadata.relatedTo`. Lets a single capability token scoped to one
38
+ * "session" artifact (e.g. a video) authorize related artifacts uploaded in
39
+ * the same session (its captions, or — under `uploadUnit: "beat"` — each
40
+ * beat plus the pulse manifest) without minting a token per artifact.
41
+ * The storage layer itself only stores and returns this value — it does not
42
+ * validate that the referenced artifact exists or was created by the same
43
+ * caller. The actual authorization check lives in `createCapabilityAuthorize`
44
+ * (`lib/capability-token.ts`), which treats a request's `relatedTo` as
45
+ * authorized whenever it matches the presented token's own signed
46
+ * `artifactId` claim. That's safe only because a token can't be forged or
47
+ * guessed (HMAC-signed) — do not read this field as "unauthenticated
48
+ * metadata with no security relevance."
49
+ */
50
+ relatedTo?: string;
51
+ /**
52
+ * Optional client-supplied integrity digest from `Upload-Metadata.checksum`
53
+ * (`<algorithm>:<hex>`), persisted so `createChecksumValidator` can read it
54
+ * at completion time even when completion happens on a different request
55
+ * than creation (chunked uploads, or even a single-PATCH upload sent as
56
+ * two separate HTTP requests).
57
+ */
58
+ checksum?: string;
26
59
  };
27
60
  /**
28
61
  * Storage backend contract. Keep the surface small: one write hook, one read
@@ -46,12 +79,12 @@ export interface PulseVaultStorage {
46
79
  */
47
80
  reserveUpload(params: ReserveUploadParams): Promise<string>;
48
81
  /**
49
- * Called by the GET route. Returns how to serve the video, or `null` if
50
- * the videoid is unknown *or* the upload is still in progress — adapters
82
+ * Called by the GET route. Returns how to serve the artifact, or `null` if
83
+ * the artifactId is unknown *or* the upload is still in progress — adapters
51
84
  * that implement `markReady` should only return non-null once the bytes
52
85
  * have been promoted to "ready."
53
86
  */
54
- resolve(videoid: string): Promise<PulseVaultResolution | null>;
87
+ resolve(artifactId: string): Promise<PulseVaultResolution | null>;
55
88
  /**
56
89
  * Mark an upload as fully written and safe to serve. Called by the plugin
57
90
  * after the final byte lands and any `validatePayload` + `onUploadComplete`
@@ -60,13 +93,32 @@ export interface PulseVaultStorage {
60
93
  * may omit this method — `resolve` will be trusted to only return finished
61
94
  * uploads in that case.
62
95
  */
63
- markReady?(videoid: string): Promise<void>;
96
+ markReady?(artifactId: string): Promise<void>;
97
+ /**
98
+ * Delete all storage associated with an artifactId. Returns `true` if
99
+ * something was removed, `false` if the artifactId was already absent.
100
+ * Called both from the `DELETE /artifacts/:artifactId` route and from the
101
+ * plugin's cleanup path when `validatePayload` rejects a completed upload.
102
+ */
103
+ remove?(artifactId: string): Promise<boolean>;
104
+ /**
105
+ * Return the artifact kind for a known artifactId, or `null` if the
106
+ * artifactId is not found. Used by the generic artifact route to resolve
107
+ * which kind to serve without it being encoded in the URL.
108
+ */
109
+ getKind?(artifactId: string): Promise<UploadKind | null>;
110
+ /**
111
+ * Return the `relatedTo` artifact id stored at `reserveUpload` time, or
112
+ * `null` if the artifactId is unknown or has no relation recorded. Used by
113
+ * `createCapabilityAuthorize` to authorize an artifact against a token
114
+ * scoped to the session it belongs to, not just its own id.
115
+ */
116
+ getRelatedTo?(artifactId: string): Promise<string | null>;
64
117
  /**
65
- * Delete all storage associated with a videoid. Returns `true` if
66
- * something was removed, `false` if the videoid was already absent.
67
- * Called both from the `DELETE /:videoid` route and from the plugin's
68
- * cleanup path when `validatePayload` rejects a completed upload.
118
+ * Return the `checksum` metadata stored at `reserveUpload` time, or `null`
119
+ * if the artifactId is unknown or no checksum was supplied. Used by
120
+ * `createChecksumValidator`/`createS3ChecksumValidator` at completion time.
69
121
  */
70
- remove?(videoid: string): Promise<boolean>;
122
+ getChecksum?(artifactId: string): Promise<string | null>;
71
123
  }
72
124
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAAG;IAChC,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,sDAAsD;IACtD,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3B;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5D;;;;;OAKG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAE/D;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C;;;;;OAKG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAE1D,MAAM,MAAM,mBAAmB,GAAG;IAChC,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,sDAAsD;IACtD,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3B;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5D;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAElE;;;;;;;OAOG;IACH,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;;;;OAKG;IACH,MAAM,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9C;;;;OAIG;IACH,OAAO,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEzD;;;;;OAKG;IACH,YAAY,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE1D;;;;OAIG;IACH,WAAW,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D"}