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