@mieweb/pulsevault 0.1.0 → 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 (56) hide show
  1. package/CHANGELOG.md +82 -8
  2. package/OPERATIONS.md +4 -4
  3. package/PROTOCOL.md +60 -27
  4. package/README.md +42 -26
  5. package/dist/app.d.ts +5 -4
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +1 -1
  8. package/dist/app.js.map +1 -1
  9. package/dist/core.d.ts +2 -2
  10. package/dist/core.d.ts.map +1 -1
  11. package/dist/core.js +141 -78
  12. package/dist/core.js.map +1 -1
  13. package/dist/lib/authorize.d.ts +1 -1
  14. package/dist/lib/authorize.d.ts.map +1 -1
  15. package/dist/lib/capability-token.d.ts +3 -3
  16. package/dist/lib/capability-token.d.ts.map +1 -1
  17. package/dist/lib/capability-token.js +14 -6
  18. package/dist/lib/capability-token.js.map +1 -1
  19. package/dist/lib/checksum.d.ts.map +1 -1
  20. package/dist/lib/checksum.js +4 -1
  21. package/dist/lib/checksum.js.map +1 -1
  22. package/dist/lib/deeplinks.d.ts +2 -2
  23. package/dist/lib/deeplinks.d.ts.map +1 -1
  24. package/dist/lib/deeplinks.js +2 -2
  25. package/dist/lib/deeplinks.js.map +1 -1
  26. package/dist/lib/errors.d.ts +7 -0
  27. package/dist/lib/errors.d.ts.map +1 -1
  28. package/dist/lib/errors.js +18 -0
  29. package/dist/lib/errors.js.map +1 -1
  30. package/dist/lib/options.d.ts +3 -1
  31. package/dist/lib/options.d.ts.map +1 -1
  32. package/dist/lib/options.js +12 -4
  33. package/dist/lib/options.js.map +1 -1
  34. package/dist/lib/pulsevaultTus.d.ts +1 -0
  35. package/dist/lib/pulsevaultTus.d.ts.map +1 -1
  36. package/dist/lib/pulsevaultTus.js +72 -21
  37. package/dist/lib/pulsevaultTus.js.map +1 -1
  38. package/dist/lib/request.d.ts +2 -0
  39. package/dist/lib/request.d.ts.map +1 -1
  40. package/dist/lib/request.js +6 -0
  41. package/dist/lib/request.js.map +1 -1
  42. package/dist/routes/pulsevault.d.ts +1 -1
  43. package/dist/routes/pulsevault.d.ts.map +1 -1
  44. package/dist/routes/pulsevault.js +4 -3
  45. package/dist/routes/pulsevault.js.map +1 -1
  46. package/dist/storage/local.d.ts.map +1 -1
  47. package/dist/storage/local.js +46 -9
  48. package/dist/storage/local.js.map +1 -1
  49. package/dist/storage/s3.d.ts.map +1 -1
  50. package/dist/storage/s3.js +10 -4
  51. package/dist/storage/s3.js.map +1 -1
  52. package/dist/storage/types.d.ts +14 -3
  53. package/dist/storage/types.d.ts.map +1 -1
  54. package/dist/storage/types.js +13 -1
  55. package/dist/storage/types.js.map +1 -1
  56. package/package.json +1 -7
package/CHANGELOG.md CHANGED
@@ -47,6 +47,23 @@ if you've evaluated against an intermediate build.
47
47
  requirement on server implementations generally, not just this package).
48
48
  - Both are covered by new regression tests in `test/plugin.test.mjs` that
49
49
  fail against the pre-fix code and pass against the fix.
50
+ - **Removed `s3rver`** (unmaintained) from the dev/test toolchain, clearing
51
+ its open `npm audit` findings: its dependency chain carried high-severity
52
+ advisories (`busboy`/`dicer`) and forced pinning `fast-xml-parser` via an
53
+ `overrides` entry plus a hand-written API-compat shim. The S3 suite now runs
54
+ against `test/mock-s3.mjs`, a zero-dependency in-memory S3 double
55
+ implementing exactly the eleven operations the code under test uses. Unlike
56
+ s3rver it enforces `If-None-Match: "*"` (412), so the adapter's atomic
57
+ reserve collision guard is now genuinely exercised — a concurrent-reserve
58
+ test that was impossible against s3rver has been added. The `ListParts` shim
59
+ (s3rver never implemented it) is gone too.
60
+ - **5xx responses no longer echo internal error messages.** A `markReady`,
61
+ `onUploadComplete`, or 5xx-class `validatePayload` failure now logs the real
62
+ error server-side and returns a generic body, so a consumer's DB/storage error
63
+ text (schema names, paths, infra detail) can't reach the uploading client. 4xx
64
+ validation rejections (e.g. checksum mismatch) keep their descriptive messages.
65
+ - **Storage subdirectories are created with mode `0o750`** so the upload tree
66
+ isn't world-readable under a permissive umask.
50
67
 
51
68
  ### Breaking
52
69
 
@@ -77,12 +94,15 @@ if you've evaluated against an intermediate build.
77
94
 
78
95
  ### Added
79
96
 
80
- - **`kind: "captions"`** artifact type (default extension `.srt`), running
97
+ - **`kind: "captions"`** artifact type (default extension `.vtt`), running
81
98
  through the same generic `validatePayload`/`onUploadComplete` hooks as
82
- every other kind.
99
+ every other kind. WebVTT carries word-level inline cue timestamps
100
+ (`<00:00:01.500>word`) for karaoke rendering, and both storage adapters
101
+ serve `.vtt` artifacts with the `text/vtt` content type.
83
102
  - **`relatedTo`** — an optional `Upload-Metadata` key (and matching
84
103
  `ReserveUploadParams`/storage field) linking one artifact to another (e.g.
85
- a video's captions, or a beat belonging to a pulse manifest's session).
104
+ a merged video's captions/beat-manifest/thumbnail, or a clip belonging to a
105
+ segment ordering manifest's session).
86
106
  Storage adapters expose it via the new optional `getRelatedTo` method.
87
107
  - **`checksum`** — an optional `Upload-Metadata` key (`<algorithm>:<hex
88
108
  digest>`) verified post-upload via the new `createChecksumValidator`
@@ -102,14 +122,21 @@ if you've evaluated against an intermediate build.
102
122
  `uploadUnit`, `kinds`, `allowedExtensions`, `maxUploadSize`, and supported
103
123
  checksum algorithms. Lets independently-versioned deployments and clients
104
124
  detect compatibility before pairing.
105
- - **`uploadUnit` plugin option** (`"beat" | "merged"`, default `"beat"`) —
125
+ - **`uploadUnit` plugin option** (`"segment" | "merged"`, default `"segment"`) —
106
126
  purely advertised via `/capabilities`; the operator declares which upload
107
127
  strategy this deployment expects, the client branches on it. Not enforced
108
- by the plugin.
128
+ by the plugin. `"segment"` uploads each recorded clip plus an ordering
129
+ manifest; `"merged"` uploads one pre-merged video plus its captions, a
130
+ beat-timecode manifest, and a thumbnail. (A "beat" is a timecode range on
131
+ the merged timeline, not an upload unit.)
132
+ - **`kind: "thumbnail"`** artifact type (default extensions `.jpg`/`.jpeg`/
133
+ `.png`) — the pulse poster frame uploaded alongside a merged video, running
134
+ through the same generic `validatePayload`/`onUploadComplete` hooks and a
135
+ `thumbnail/` storage subdir like every other kind.
109
136
  - **`buildUploadLink({ uploadUnit })`** (PROTOCOL.md §3, §8) — optional
110
137
  per-session override of the deployment-wide `uploadUnit`, carried on the
111
138
  pairing link itself instead of only `/capabilities`. Lets an operator run
112
- `"beat"` and `"merged"` sessions concurrently (staged rollout, A/B test,
139
+ `"segment"` and `"merged"` sessions concurrently (staged rollout, A/B test,
113
140
  per-tenant policy) without racing a client's separate `/capabilities` fetch
114
141
  against a single, deployment-wide value. Fully backward compatible: omit it
115
142
  and a client falls back to `/capabilities` exactly as before.
@@ -144,8 +171,55 @@ if you've evaluated against an intermediate build.
144
171
  counterpart to `readAll`, now used internally by
145
172
  `createS3ChecksumValidator` (see "Fixed" below).
146
173
 
174
+ ### Changed
175
+
176
+ - Replaced `examples/rn-demo` with two focused Fastify examples:
177
+ `examples/fastify-demo` (the smallest runnable server — no auth, local
178
+ storage, QR pairing; start here) and `examples/fastify-auth-demo`
179
+ (production-shaped reference — capability tokens always on, failing fast at
180
+ boot without `PULSEVAULT_SECRET`, pulse-session grouping via `relatedTo`,
181
+ WebVTT captions, Swagger UI, artifact-event feed). Both pairing pages
182
+ are React (pinned ESM CDN builds, no bundler). Both examples are
183
+ local-storage only; S3 wiring is documented in the README's S3 section
184
+ rather than demonstrated in an example. `npm run e2e` now drives
185
+ `fastify-auth-demo` and additionally asserts the fail-fast boot contract.
186
+ - `examples/fastify-auth-demo` gained a [Better Auth](https://better-auth.com)
187
+ dashboard layer: the human-facing routes (`/deeplinks`, `/pulses`,
188
+ `/events`, `/reserve`) require an email+password session, while the vault
189
+ routes the Pulse app talks to stay capability-token authorized — two auth
190
+ systems for two audiences, per PROTOCOL.md §5. The demo is Postgres-only,
191
+ run through Docker Compose (`compose.yaml` + `Dockerfile`; `npm run db:up`
192
+ + `npm start` for bare-metal dev against the same db), with one Prisma
193
+ schema (`prisma/schema.prisma`, migrations committed and applied by
194
+ `npm start`) holding both the auth tables and an **artifact index**:
195
+ `onUploadComplete` writes each finished upload once, authorized deletes
196
+ prune it, a boot-time reconcile keeps it honest against the filesystem
197
+ sidecars (still the source of truth), and `/pulses` becomes one indexed
198
+ query instead of a per-request sidecar crawl. `npm run e2e` provisions the
199
+ compose db, applies migrations, and covers the 401 gate plus the
200
+ sign-up/sign-in flow.
201
+
147
202
  ### Fixed
148
203
 
204
+ - **Artifact `GET`/`DELETE` handlers now fail closed.** They run on a hijacked
205
+ socket, so a thrown storage error (S3 unreachable, disk fault) previously left
206
+ the socket hung until timeout; each now emits a `500` (or destroys the socket
207
+ if headers are already sent), mirroring the TUS handler. `GET` streams also
208
+ catch a mid-stream read error instead of letting an unhandled `'error'` crash
209
+ the process, and destroy the source stream on client disconnect so file
210
+ descriptors aren't leaked. Covered by `test/fail-closed.test.mjs`.
211
+ - A consumer error carrying an out-of-range `statusCode` (e.g. `42`) now degrades
212
+ to the handler's fallback status instead of crashing `res.writeHead`; a
213
+ malformed request-target that `URL` can't parse returns `400` instead of an
214
+ uncaught throw.
215
+ - Checksum verification requested against an adapter with no local path now
216
+ reports `500` (server misconfiguration) instead of `422` (which wrongly
217
+ implied the client's file was rejected).
218
+ - `verifyCapabilityToken` no longer throws on a token whose payload is valid
219
+ JSON but not an object (e.g. the literal `null`) — it returns `null` like
220
+ every other failure, per its single-failure-shape contract.
221
+ - Bearer-token extraction accepts the auth scheme case-insensitively per
222
+ RFC 7235 (`bearer`/`BEARER`), not only the canonical `Bearer `.
149
223
  - `maxUploadSize` enforcement and the in-progress-upload 404 behavior now
150
224
  have explicit test coverage proving bytes are rejected/hidden at the right
151
225
  point, not just implied by the implementation.
@@ -175,8 +249,8 @@ if you've evaluated against an intermediate build.
175
249
  - **Silent TOCTOU on S3-compatible backends without `IfNoneMatch` support.**
176
250
  `reserveUpload`'s conditional-write collision guard falls back to a
177
251
  weaker check-then-write on backends that reject `IfNoneMatch` (some
178
- S3-compatible stores, and the `s3rver` mock this repo's own test suite
179
- runs against) — this reopens the race between concurrent/retried creates
252
+ S3-compatible stores) — this reopens the race between concurrent/retried
253
+ creates
180
254
  for the same artifactId. There's no way to close it without an external
181
255
  lock, so this is now at least surfaced: a one-time `console.warn` fires
182
256
  per process the first time a deployment falls into this degraded mode.
package/OPERATIONS.md CHANGED
@@ -66,8 +66,8 @@ you have a specific reason to use local disk.
66
66
  `reserveUpload`'s collision guard normally uses `PutObjectCommand`'s
67
67
  `IfNoneMatch: "*"` to atomically reject a second create for an artifactId
68
68
  that already has an upload. Some S3-compatible backends (older/less-complete
69
- implementations; the `s3rver` mock this package's own test suite runs
70
- against) don't support conditional writes and reject that header outright.
69
+ implementations) don't support conditional writes and reject that header
70
+ outright.
71
71
  On those backends, `reserveUpload` falls back to a weaker check-then-write —
72
72
  functionally the same guard, but with the original race reopened: two
73
73
  truly concurrent (or retried) creates for the same artifactId can both pass
@@ -153,8 +153,8 @@ upload routes.
153
153
  ## Backup and restore (local storage)
154
154
 
155
155
  The entire state of the local adapter lives under `workspaceDir`:
156
- `.pulsevault/` (sidecars), `video/`, `project/`, `captions/` (bytes). Back it
157
- up as a normal filesystem tree — there's no separate database to keep in
156
+ `.pulsevault/` (sidecars), `video/`, `project/`, `captions/`, `thumbnail/`
157
+ (bytes). Back it up as a normal filesystem tree — there's no separate database to keep in
158
158
  sync. To restore, copy the tree back and restart; in-progress uploads at
159
159
  backup time will resume correctly via the normal TUS `HEAD`-then-resume path
160
160
  once the client retries, or will simply sit as abandoned partial uploads
package/PROTOCOL.md CHANGED
@@ -37,9 +37,9 @@ The response body MUST be a JSON object with at least the following fields:
37
37
  "protocolVersion": 1,
38
38
  "minSupportedVersion": 1,
39
39
  "maxSupportedVersion": 1,
40
- "uploadUnit": "beat",
41
- "kinds": ["video", "project", "captions"],
42
- "allowedExtensions": { "video": [".mp4"], "project": [".pulse", ".zip"], "captions": [".srt"] },
40
+ "uploadUnit": "segment",
41
+ "kinds": ["video", "project", "captions", "thumbnail"],
42
+ "allowedExtensions": { "video": [".mp4"], "project": [".pulse", ".zip"], "captions": [".vtt"], "thumbnail": [".jpg", ".jpeg", ".png"] },
43
43
  "maxUploadSize": 5368709120,
44
44
  "checksum": { "algorithms": ["sha256", "sha1", "md5"] }
45
45
  }
@@ -52,9 +52,10 @@ The response body MUST be a JSON object with at least the following fields:
52
52
  SHOULD refuse to pair if its own version falls outside this range, and
53
53
  SHOULD show the user an actionable message ("update the app" /
54
54
  "this server needs an update") rather than a generic error.
55
- - `uploadUnit` (`"beat"` or `"merged"`, REQUIRED): which upload strategy this
55
+ - `uploadUnit` (`"segment"` or `"merged"`, REQUIRED): which upload strategy this
56
56
  deployment expects (§8). The client MUST read this before doing any
57
- merge/upload work and branch accordingly.
57
+ merge/upload work and branch accordingly. (Note: "beat" no longer refers to
58
+ an upload unit — it now names a timecode range on the merged timeline; see §8.)
58
59
  - `kinds` (array of string, REQUIRED): artifact kinds this server accepts.
59
60
  - `allowedExtensions` (object, REQUIRED): allowed file extensions per kind.
60
61
  - `maxUploadSize` (integer, REQUIRED): maximum artifact size in bytes.
@@ -74,7 +75,7 @@ the integer from `protocolVersion`.
74
75
  A server presents a pairing link or QR code of the form:
75
76
 
76
77
  ```
77
- pulsecam://?v=1&artifactId=<uuid>&server=<origin>&token=<opaque>&uploadUnit=<beat|merged>
78
+ pulsecam://?v=1&artifactId=<uuid>&server=<origin>&token=<opaque>&uploadUnit=<segment|merged>
78
79
  ```
79
80
 
80
81
  - `v` (REQUIRED): the deep-link schema version. A client MUST refuse and
@@ -96,14 +97,14 @@ pulsecam://?v=1&artifactId=<uuid>&server=<origin>&token=<opaque>&uploadUnit=<bea
96
97
  `artifactId` (or a session it anchors, §5.4), not a standing general
97
98
  credential — see §5.4 for the recommended (but not required) capability-
98
99
  token shape.
99
- - `uploadUnit` (OPTIONAL, `"beat"` or `"merged"`): per-session override of the
100
+ - `uploadUnit` (OPTIONAL, `"segment"` or `"merged"`): per-session override of the
100
101
  deployment-wide value reported by `GET /capabilities` (§2, §8). When
101
102
  present, a client MUST use this value for the session anchored to this
102
103
  link instead of whatever `/capabilities` currently reports, and MUST NOT
103
104
  perform a separate `/capabilities` fetch just to decide merge/upload
104
105
  strategy for it. When absent, a client MUST fall back to `/capabilities`
105
106
  exactly as before this field existed — an operator that never sets it sees
106
- no change in client behavior. This lets one deployment run "beat" and
107
+ no change in client behavior. This lets one deployment run "segment" and
107
108
  "merged" sessions concurrently (e.g. a staged rollout) without racing a
108
109
  single, deployment-wide `/capabilities` value against whichever moment a
109
110
  client happened to fetch it.
@@ -138,7 +139,7 @@ pairs) MUST be parsed for at least:
138
139
  |---|---|---|
139
140
  | `artifactId` | Yes (or a legacy `videoid`/`projectid` alias) | UUID for this artifact. |
140
141
  | `filename` | Yes | Original filename; extension validated against `kind`'s allowed list. |
141
- | `kind` | No, defaults to `video` | `video`, `project`, or `captions`. |
142
+ | `kind` | No, defaults to `video` | `video`, `project`, `captions`, or `thumbnail`. |
142
143
  | `relatedTo` | No | UUID of another artifact this one belongs to (§8). |
143
144
  | `checksum` | No | `<algorithm>:<hex digest>` of the finished file (§6.3). |
144
145
 
@@ -239,8 +240,9 @@ expired token. `issuer` prevents a token minted by one deployment from being
239
240
  replayed against a different one that happens to share a secret. A token MAY
240
241
  authorize an artifact other than the one it names if that artifact declares
241
242
  the token's `artifactId` as its `relatedTo` (§8) — this lets one token cover
242
- an entire upload session (a video, its captions, and every beat + manifest
243
- under `uploadUnit: "beat"`) rather than requiring one token per artifact.
243
+ an entire upload session (a merged video plus its captions, beat manifest and
244
+ thumbnail, or every clip plus the ordering manifest under
245
+ `uploadUnit: "segment"`) rather than requiring one token per artifact.
244
246
 
245
247
  This shape is exactly what `@mieweb/pulsevault`'s `issueCapabilityToken`/
246
248
  `verifyCapabilityToken`/`createCapabilityAuthorize` implement, but any server
@@ -282,22 +284,53 @@ clear, specific message rather than a generic failure.
282
284
 
283
285
  ## 8. Artifact relationships (`relatedTo`) and `uploadUnit`
284
286
 
285
- A pulse (a short composed of one or more beats) MAY be uploaded as a single
286
- pre-merged video (`uploadUnit: "merged"`) or as individual per-beat artifacts
287
- plus a manifest (`uploadUnit: "beat"`) — the operator declares which via
288
- `/capabilities` (§2), optionally overridden per session via the pairing
289
- link's `uploadUnit` param (§3); this document does not prefer one over the
290
- other.
291
-
292
- Under `uploadUnit: "beat"`, a client uploads each beat under its own
293
- `artifactId`, plus one manifest artifact (`kind: "project"`, a JSON document
294
- listing the ordered beat `artifactId`s) and, optionally, per-beat captions
295
- (`kind: "captions"`). Every non-primary artifact in the session SHOULD
296
- declare `relatedTo` pointing at the session's anchor `artifactId` (the one
297
- named in the pairing link) so a single capability token can authorize the
298
- whole session (§5.4).
299
-
300
- The relational graph of replies between beats/pulses (who replied to what,
287
+ A pulse (a short composed of one or more recorded **segments**) MAY be uploaded
288
+ as a single pre-merged video (`uploadUnit: "merged"`) or as individual
289
+ per-segment artifacts plus an ordering manifest (`uploadUnit: "segment"`) — the
290
+ operator declares which via `/capabilities` (§2), optionally overridden per
291
+ session via the pairing link's `uploadUnit` param (§3); this document does not
292
+ prefer one over the other.
293
+
294
+ > **Terminology:** a **segment** is a recorded clip (the source unit). A
295
+ > **beat** is a *timecode range on the merged timeline* — one recorded
296
+ > segment's start/end within the merged video — carried only in the merged
297
+ > mode's beat manifest below. (Earlier revisions used "beat" for what is now
298
+ > called the `segment` upload unit; that meaning is retired.)
299
+
300
+ Under `uploadUnit: "segment"`, a client uploads each segment under its own
301
+ `artifactId`, plus exactly one **ordering manifest** artifact (`kind:
302
+ "project"`, e.g. `<draftId>-segments.pulse`, a JSON document listing the
303
+ ordered segment `artifactId`s). Segmented mode carries **no** captions and
304
+ **no** thumbnail.
305
+
306
+ ```json
307
+ { "version": 1, "segments": [ { "artifactId": "<uuid>", "order": 0 } ] }
308
+ ```
309
+
310
+ Under `uploadUnit: "merged"`, a client uploads one pre-merged video as the
311
+ session anchor, plus (all as related artifacts):
312
+
313
+ - **captions** (`kind: "captions"`, `<draftId>.vtt`) — WebVTT for the whole
314
+ merged video, OPTIONAL (absent when the video has no speech or no on-device
315
+ model was available); MAY carry word-level inline cue timestamps like
316
+ `<00:00:01.500>word` for karaoke rendering;
317
+ - a **beat manifest** (`kind: "project"`, e.g. `<draftId>-beats.pulse`) giving
318
+ each recorded segment's precise `startMs`/`endMs` on the merged timeline,
319
+ contiguous and summing to the true merged `durationMs` (groundwork for
320
+ keyframe-aligned deep links / HLS);
321
+ - a **thumbnail** (`kind: "thumbnail"`, `<draftId>.jpg`) — the pulse's poster
322
+ frame.
323
+
324
+ ```json
325
+ { "version": 1, "type": "beat-manifest", "durationMs": 47320,
326
+ "beats": [ { "segmentId": "<local id>", "order": 0, "startMs": 0, "endMs": 4210 } ] }
327
+ ```
328
+
329
+ Every non-anchor artifact in either session SHOULD declare `relatedTo` pointing
330
+ at the session's anchor `artifactId` (the one named in the pairing link) so a
331
+ single capability token can authorize the whole session (§5.4).
332
+
333
+ The relational graph of replies between segments/pulses (who replied to what,
301
334
  rendering a thread) is explicitly **out of scope** for this document. That
302
335
  graph is a query/relational concern for the operator's own systems, built
303
336
  from `relatedTo` and whatever additional metadata the operator chooses to
package/README.md CHANGED
@@ -173,14 +173,28 @@ diagnostics — the Fastify plugin always uses `request.log` for these
173
173
  automatically, but a non-Fastify host has no equivalent to infer one from,
174
174
  so it falls back to `console` when omitted.
175
175
 
176
- See [`examples/express-demo`](examples/express-demo) and
177
- [`examples/meteor-demo`](examples/meteor-demo) for full runnable servers
178
- (pairing page, QR codes, auth demo, local/S3 storage) — the Express and
179
- Meteor counterparts to [`examples/rn-demo`](examples/rn-demo)'s Fastify
180
- demo, both built on `@mieweb/pulsevault/core` instead of the plugin. Both
181
- are verified against the real frameworks, not just the test suite —
182
- `meteor-demo` in particular against a real `meteor create` app, since
183
- Meteor's bundler needed the compatibility fixes described above.
176
+ Four runnable example servers live under [`examples/`](examples):
177
+
178
+ - [`examples/fastify-demo`](examples/fastify-demo) — the smallest runnable
179
+ server: Fastify plugin mount, QR pairing, flat upload listing, no auth,
180
+ local storage. Start here.
181
+ - [`examples/fastify-auth-demo`](examples/fastify-auth-demo) —
182
+ production-shaped Fastify reference, run through Docker Compose (server +
183
+ Postgres, one `docker compose up --build`): capability tokens always on
184
+ (fails fast at boot without `PULSEVAULT_SECRET`), a [Better
185
+ Auth](https://better-auth.com)-protected React dashboard (email+password),
186
+ a Prisma schema holding both the auth tables and an artifact index
187
+ (`onUploadComplete` writes each finished upload once; `/pulses` is one
188
+ query instead of a sidecar crawl), uploads grouped by recording session
189
+ via `relatedTo`, WebVTT captions, Swagger UI, and an `onArtifactEvent`
190
+ live feed.
191
+ - [`examples/express-demo`](examples/express-demo) and
192
+ [`examples/meteor-demo`](examples/meteor-demo) — the same demo on
193
+ `@mieweb/pulsevault/core` instead of the plugin, proving the core needs
194
+ about the same amount of glue code under a different framework. Both are
195
+ verified against the real frameworks, not just the test suite —
196
+ `meteor-demo` in particular against a real `meteor create` app, since
197
+ Meteor's bundler needed the compatibility fixes described above.
184
198
 
185
199
  ## How a pairing + upload session flows
186
200
 
@@ -229,12 +243,12 @@ type PulseVaultPluginOptions = {
229
243
  storage: PulseVaultStorage;
230
244
  prefix: string;
231
245
  maxUploadSize: number;
232
- uploadUnit?: "beat" | "merged"; // default: "beat" — see "Upload unit"
246
+ uploadUnit?: "segment" | "merged"; // default: "segment" — see "Upload unit"
233
247
  decoratorName?: string; // default: "pulseVault"
234
248
  allowedExtensions?:
235
- | string[] // legacy — treated as video-only
236
- | { video?: string[]; project?: string[]; captions?: string[] }; // per-kind (recommended)
237
- // defaults: { video: [".mp4"], project: [".pulse", ".zip"], captions: [".srt"] }
249
+ | string[] // legacy — treated as video-only
250
+ | { video?: string[]; project?: string[]; captions?: string[]; thumbnail?: string[] }; // per-kind (recommended)
251
+ // defaults: { video: [".mp4"], project: [".pulse", ".zip"], captions: [".vtt"], thumbnail: [".jpg", ".jpeg", ".png"] }
238
252
  cache?: PulseVaultCacheOptions;
239
253
  authorize?: PulseVaultAuthorize;
240
254
  validatePayload?: PulseVaultValidatePayload; // runs for every kind; branch on ctx.kind
@@ -263,12 +277,14 @@ Maximum upload size in bytes. Use `Infinity` for no cap.
263
277
 
264
278
  ### Upload unit
265
279
 
266
- `uploadUnit: "beat" | "merged"` — purely advertised via `GET /pulsevault/capabilities`; **the plugin doesn't enforce either.** It tells the client which upload strategy this deployment expects:
280
+ `uploadUnit: "segment" | "merged"` — purely advertised via `GET /pulsevault/capabilities`; **the plugin doesn't enforce either.** It tells the client which upload strategy this deployment expects:
267
281
 
268
- - `"beat"` (default): the client uploads each clip individually (no merge/re-encode pass), plus a `kind=project` manifest artifact for ordering. Lower client-side cost, finer-grained resumability.
269
- - `"merged"`: the client pre-merges a pulse's clips into one video before uploading. Simpler server-side mental model (one artifact per pulse), more client-side work.
282
+ - `"segment"` (default): the client uploads each recorded clip individually (no merge/re-encode pass), plus one `kind=project` ordering manifest (`<draftId>-segments.pulse`). No captions or thumbnail. Lower client-side cost, finer-grained resumability.
283
+ - `"merged"`: the client pre-merges a pulse's clips into one video before uploading, alongside its captions (`kind=captions`), a beat-timecode manifest (`kind=project`, `<draftId>-beats.pulse` — per-segment `startMs`/`endMs` on the merged timeline), and a poster `kind=thumbnail`. Simpler server-side mental model (one primary artifact per pulse), more client-side work.
270
284
 
271
- A client reads this at pairing time, before doing any merge or upload work, and branches accordingly. See `PROTOCOL.md` §8 for the full contract.
285
+ (A "beat" is a timecode range on the merged timeline — one recorded segment's placement within the merged video — not an upload unit. The old `"beat"` upload unit was renamed to `"segment"`.)
286
+
287
+ A client reads `uploadUnit` at pairing time, before doing any merge or upload work, and branches accordingly. See `PROTOCOL.md` §8 for the full contract.
272
288
 
273
289
  Need both strategies live at once instead of one fixed value for the whole deployment? Pass `uploadUnit` to [`buildUploadLink`](#deep-link-helper) per session — it overrides `/capabilities` for that pairing only, with no server-side option to change.
274
290
 
@@ -288,13 +304,13 @@ File extensions accepted per artifact kind. Three accepted forms:
288
304
 
289
305
  ```ts
290
306
  // 1. Omit entirely — uses all three defaults:
291
- // video: [".mp4"] project: [".pulse", ".zip"] captions: [".srt"]
307
+ // video: [".mp4"] project: [".pulse", ".zip"] captions: [".vtt"]
292
308
 
293
309
  // 2. Flat array (legacy) — video-only; project/captions keep their defaults:
294
310
  allowedExtensions: [".mp4"]
295
311
 
296
312
  // 3. Per-kind object — unset keys fall back to their defaults:
297
- allowedExtensions: { video: [".mp4"], project: [".pulse"], captions: [".srt"] }
313
+ allowedExtensions: { video: [".mp4"], project: [".pulse"], captions: [".vtt"] }
298
314
  ```
299
315
 
300
316
  All extensions must include the leading dot and are matched case-insensitively. The `kind` field in `Upload-Metadata` determines which list is checked.
@@ -457,9 +473,9 @@ When the final PATCH lands the plugin runs the following steps in order, for eve
457
473
  "protocolVersion": 1,
458
474
  "minSupportedVersion": 1,
459
475
  "maxSupportedVersion": 1,
460
- "uploadUnit": "beat",
461
- "kinds": ["video", "project", "captions"],
462
- "allowedExtensions": { "video": [".mp4"], "project": [".pulse", ".zip"], "captions": [".srt"] },
476
+ "uploadUnit": "segment",
477
+ "kinds": ["video", "project", "captions", "thumbnail"],
478
+ "allowedExtensions": { "video": [".mp4"], "project": [".pulse", ".zip"], "captions": [".vtt"], "thumbnail": [".jpg", ".jpeg", ".png"] },
463
479
  "maxUploadSize": 5368709120,
464
480
  "checksum": { "algorithms": ["sha256", "sha1", "md5"] }
465
481
  }
@@ -506,7 +522,7 @@ app.post("/pair", async (_req, reply) => {
506
522
  });
507
523
  ```
508
524
 
509
- A token authorizes either the artifact it names, or any artifact that declares that one as its `relatedTo` — so one token issued for a video also covers its captions and (under `uploadUnit: "beat"`) every beat and the manifest in the same session, without minting a token per artifact. See `PROTOCOL.md` §5.4 for the full claim shape (`kid`/`iat`/`exp`/`issuer`/`artifactId`) and rationale.
525
+ A token authorizes either the artifact it names, or any artifact that declares that one as its `relatedTo` — so one token issued for a merged video also covers its captions, beat manifest and thumbnail (or, under `uploadUnit: "segment"`, every clip and the ordering manifest) in the same session, without minting a token per artifact. See `PROTOCOL.md` §5.4 for the full claim shape (`kid`/`iat`/`exp`/`issuer`/`artifactId`) and rationale.
510
526
 
511
527
  ## Upload-Metadata protocol
512
528
 
@@ -524,7 +540,7 @@ The TUS `Upload-Metadata` header is a comma-separated list of `<key> <base64>` p
524
540
  Example (`kind=captions`, linked to a video):
525
541
 
526
542
  ```
527
- Upload-Metadata: artifactId <base64(uuid)>, filename <base64("clip.srt")>, kind <base64("captions")>, relatedTo <base64(videoArtifactId)>
543
+ Upload-Metadata: artifactId <base64(uuid)>, filename <base64("clip.vtt")>, kind <base64("captions")>, relatedTo <base64(videoArtifactId)>
528
544
  ```
529
545
 
530
546
  ## Local storage
@@ -664,7 +680,7 @@ Credentials are optional — omit `accessKeyId`/`secretAccessKey` to use the AWS
664
680
  | `metaCacheLimit` | `10000` | Caps the in-memory metadata cache before evicting the oldest entry. |
665
681
  | `clientConfig` | — | Advanced: extra `S3ClientConfig` merged into the client. |
666
682
 
667
- > The runnable demo wires these to environment variables — see [`examples/rn-demo/.env.example`](examples/rn-demo/.env.example) for the full list (`STORAGE`, `S3_BUCKET`, `S3_ENDPOINT`, `AWS_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, …).
683
+ > A typical deployment wires these to environment variables (e.g. `S3_BUCKET`, `S3_ENDPOINT`, `AWS_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`) and picks the storage adapter with a `STORAGE=local|s3` switch — the options table above maps one-to-one onto `createS3Storage(...)`.
668
684
 
669
685
  ### Payload validation on remote storage
670
686
 
@@ -745,7 +761,7 @@ const uploadLink = buildUploadLink({
745
761
  // pulsecam://?v=1&artifactId=...&server=https%3A%2F%2Fexample.com%2Fpulsevault&token=secret&uploadUnit=merged
746
762
  ```
747
763
 
748
- `uploadUnit` on the link is a **per-session override** of whatever `GET /capabilities` currently reports (PROTOCOL.md §3, §8). Omit it and nothing changes — the client falls back to `/capabilities` exactly as before. Set it when you want "beat" and "merged" sessions live at the same time (staged rollout, A/B test, per-tenant policy) instead of one fixed value for the whole deployment — `/capabilities` can only ever report one current value, and a client reading it separately from opening the link is racing whatever the server happened to be serving at that moment, not the value this specific session was paired under.
764
+ `uploadUnit` on the link is a **per-session override** of whatever `GET /capabilities` currently reports (PROTOCOL.md §3, §8). Omit it and nothing changes — the client falls back to `/capabilities` exactly as before. Set it when you want "segment" and "merged" sessions live at the same time (staged rollout, A/B test, per-tenant policy) instead of one fixed value for the whole deployment — `/capabilities` can only ever report one current value, and a client reading it separately from opening the link is racing whatever the server happened to be serving at that moment, not the value this specific session was paired under.
749
765
 
750
766
  ## Tests
751
767
 
@@ -770,7 +786,7 @@ Runs a Node `--test` suite against the built plugin. Coverage includes:
770
786
  - `createChecksumValidator`/`createS3ChecksumValidator`: matching digest accepted, mismatch rejected with cleanup
771
787
  - `issueCapabilityToken`/`verifyCapabilityToken`/`createCapabilityAuthorize`: round-trip, tampered signature/payload, expiry + clock tolerance, unknown `kid`, issuer mismatch, key-rotation overlap, `relatedTo`-based session authorization — both as fast unit tests (no server) and wired into real HTTP requests
772
788
  - `GET /capabilities`
773
- - S3/R2 backend (`createS3Storage`): full resumable upload → presigned-redirect playback, `createS3Mp4Sniffer`, `createS3ChecksumValidator`, `DELETE`, `kind=project`/`captions`, run against an in-process [s3rver](https://github.com/jamhall/s3rver) mock (no cloud credentials needed)
789
+ - S3/R2 backend (`createS3Storage`): full resumable upload → presigned-redirect playback, `createS3Mp4Sniffer`, `createS3ChecksumValidator`, `DELETE`, `kind=project`/`captions`, run against an in-process zero-dependency S3 mock (`test/mock-s3.mjs`, no cloud credentials needed)
774
790
  - `@mieweb/pulsevault/core` (the framework-agnostic entry point): the same protocol suite re-run against a bare `http.createServer` wrapping `core.handler`, plus an Express-specific smoke test proving `app.use(prefix, handler)` composition
775
791
 
776
792
  ## Accessing storage outside the plugin routes
package/dist/app.d.ts CHANGED
@@ -40,13 +40,14 @@ export type PulseVaultPluginOptions = {
40
40
  */
41
41
  maxUploadSize: number;
42
42
  /**
43
- * Which upload strategy this deployment expects: `"beat"` uploads each
43
+ * Which upload strategy this deployment expects: `"segment"` uploads each
44
44
  * clip individually (no client-side merge/re-encode pass) plus a manifest
45
45
  * artifact for ordering, while `"merged"` expects one pre-merged video per
46
- * pulse. Purely advertised via `GET /capabilities` for the client to branch
47
- * on — `pulsevault` doesn't enforce either. Defaults to `"beat"`.
46
+ * pulse (plus its captions, beat manifest and thumbnail). Purely advertised
47
+ * via `GET /capabilities` for the client to branch on — `pulsevault` doesn't
48
+ * enforce either. Defaults to `"segment"`.
48
49
  */
49
- uploadUnit?: 'beat' | 'merged';
50
+ uploadUnit?: 'segment' | 'merged';
50
51
  /**
51
52
  * Fastify instance decorator name under which the storage adapter is
52
53
  * exposed. Defaults to `"pulseVault"`. Override when registering this
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/app.js CHANGED
@@ -22,7 +22,7 @@ const app = async (fastify, opts) => {
22
22
  prefix: opts.prefix,
23
23
  storage: opts.storage,
24
24
  maxUploadSize: opts.maxUploadSize,
25
- uploadUnit: opts.uploadUnit ?? 'beat',
25
+ uploadUnit: opts.uploadUnit ?? 'segment',
26
26
  allowedExtensions,
27
27
  cache: opts.cache,
28
28
  authorize: opts.authorize,
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAChC,OAAO,gBAAgB,EAAE,EAA4B,MAAM,wBAAwB,CAAC;AAIpF,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,yBAAyB,EACzB,iCAAiC,EACjC,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAoJ1B,MAAM,sBAAsB,GAAG,YAAY,CAAC;AAE5C,MAAM,GAAG,GAAgD,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC/E,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,qBAAqB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1C,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,yBAAyB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAClD,iCAAiC,CAAC,IAAI,CAAC,CAAC;IAExC,2EAA2E;IAC3E,wEAAwE;IACxE,+BAA+B;IAC/B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;IAElC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACnE,MAAM,iBAAiB,GAAG,0BAA0B,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE7E,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAE9C,MAAM,OAAO,CAAC,QAAQ,CAAC,gBAAgB,EAAE;QACvC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,MAAM;QACrC,iBAAiB;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,eAAe,EAAE,sBAAsB,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,sBAAsB,CAAC;QAC1F,gBAAgB,EAAE,uBAAuB,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,uBAAuB,CAAC;QAC9F,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,eAAe,EAAE,CAAC,GAAG,EAAE;IACrB,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,KAAK;CACf,CAAC,CAAC;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAkBlD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAChC,OAAO,gBAAgB,EAAE,EAA4B,MAAM,wBAAwB,CAAC;AAIpF,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,yBAAyB,EACzB,iCAAiC,EACjC,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAqJ1B,MAAM,sBAAsB,GAAG,YAAY,CAAC;AAE5C,MAAM,GAAG,GAAgD,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC/E,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,qBAAqB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1C,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,yBAAyB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAClD,iCAAiC,CAAC,IAAI,CAAC,CAAC;IAExC,2EAA2E;IAC3E,wEAAwE;IACxE,+BAA+B;IAC/B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;IAElC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACnE,MAAM,iBAAiB,GAAG,0BAA0B,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE7E,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAE9C,MAAM,OAAO,CAAC,QAAQ,CAAC,gBAAgB,EAAE;QACvC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;QACxC,iBAAiB;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,eAAe,EAAE,sBAAsB,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,sBAAsB,CAAC;QAC1F,gBAAgB,EAAE,uBAAuB,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,uBAAuB,CAAC;QAC9F,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,eAAe,EAAE,CAAC,GAAG,EAAE;IACrB,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,KAAK;CACf,CAAC,CAAC;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAkBlD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC"}
package/dist/core.d.ts CHANGED
@@ -38,8 +38,8 @@ export type PulseVaultCoreOptions = {
38
38
  stripBasePath?: boolean;
39
39
  /** Max TUS upload size in bytes. Required — consumers must choose an explicit cap. Use `Infinity` for no cap. */
40
40
  maxUploadSize: number;
41
- /** Which upload strategy this deployment expects. Purely advertised via `GET /capabilities`. Defaults to `"beat"`. */
42
- uploadUnit?: 'beat' | 'merged';
41
+ /** Which upload strategy this deployment expects. Purely advertised via `GET /capabilities`. Defaults to `"segment"`. */
42
+ uploadUnit?: 'segment' | 'merged';
43
43
  /** File extensions allowed per artifact kind. See the Fastify plugin's `allowedExtensions` for the full shape. */
44
44
  allowedExtensions?: PulseVaultAllowedExtensionsInput;
45
45
  /** Cache-control options forwarded to `@fastify/send` for the GET route. */
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}