@camstack/types 1.2.236 → 1.2.238

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.
@@ -6,13 +6,14 @@ import { type InferProvider } from './capability-definition.js';
6
6
  *
7
7
  * A device-scoped wrapper COLLECTION (D549 decision 2, D554): every provider a
8
8
  * camera has is a SOURCE, and they are listed beside each other — never one
9
- * substituted for another. The DEFAULT provider (`analytics`, registered by
10
- * the RECORDER — the addon that owns the footage, D560 — `defaultActive: true`)
11
- * composes the analytics event log (markers + thumbnails, read over `ctx.api`)
12
- * with the recorder's own `getPlaybackManifest` — a clip is a time-WINDOW over
13
- * existing footage, never a separate file. A camera that exposes NATIVE onboard
14
- * clips (Reolink SD card, a hub's storage, HKSV) adds its own catalog
15
- * ALONGSIDE that one.
9
+ * substituted for another, and none of them privileged by name. One source,
10
+ * `analytics`, is registered by the RECORDER — the addon that owns the
11
+ * footage, D560 — and composes the analytics event log (markers + thumbnails,
12
+ * read over `ctx.api`) with the recorder's own `getPlaybackManifest`: a clip
13
+ * is a time-WINDOW over existing footage, never a separate file. A camera that
14
+ * exposes NATIVE onboard clips (Reolink SD card, a hub's storage, HKSV) adds
15
+ * its own catalog ALONGSIDE that one. (`defaultActive` above is the CAP's
16
+ * auto-bind to cameras, not a ranking among these sources — there is none.)
16
17
  *
17
18
  * The mount stays per-device (`resolveCapMount` → `device-scoped`), and a
18
19
  * device's SOURCES are its BINDINGS: `deviceManager.getBindings(deviceId)` is
@@ -22,10 +23,17 @@ import { type InferProvider } from './capability-definition.js';
22
23
  * native provider, as the hub-local registration and as an
23
24
  * `<addonId>@<nodeId>` one (D554 amended 2026-09-20).
24
25
  *
25
- * `listClips` is asked of ONE provider at a time
26
- * ({@link videoclipsCapability.methods.listClips} `provider`), defaulting to
27
- * the bound one, which is CamStack on every camera today. It is NOT a union
28
- * over everything a camera has: *"l'utilizzatore è uno solo"*.
26
+ * `listClips` is asked of ONE provider at a time, and NAMING it is REQUIRED
27
+ * ({@link videoclipsCapability.methods.listClips} `provider`, D554 amended). It is not
28
+ * a union over everything a camera has: *"l'utilizzatore è uno solo"*. There
29
+ * is no default, because a collection cap's binding set is DERIVED and PLURAL
30
+ * (D554 amended, `getBindings` step 0) — "the bound one" does not exist to
31
+ * resolve to, and the one thing that could take its place is a second
32
+ * authority naming a provider per camera, which D554 decision 3 rejected.
33
+ * The pick is a SURFACE's to make, from the {@link ClipSourceSchema} rows it
34
+ * already holds, and it says which one it made (D569 § 1–3).
35
+ * {@link videoclipsCapability.methods.listSources} is the method that fans out
36
+ * — one row per source, by construction.
29
37
  * {@link videoclipsCapability.methods.getClipPlayback} dispatches by the
30
38
  * source namespace inside the clip id, which is why ids are self-contained.
31
39
  *
@@ -169,6 +177,68 @@ export type ClipPlayback = z.infer<typeof ClipPlaybackSchema>;
169
177
  * which days it asked for and got nothing — "no clips" would be a lie about a
170
178
  * card that is full of them.
171
179
  */
180
+ /**
181
+ * The operator's authorisation to WAKE a sleeping camera for one clip read.
182
+ *
183
+ * ONE value, absent by default, and it is a `force`-shaped operator signal in
184
+ * exactly the sense `snapshot-wake-gate.ts` uses the word: *"`snapshot.getSnapshot`'s
185
+ * `force` flag, and nothing else… a background caller must never set it…
186
+ * Stale but honest beats woken"*. A scheduler, a retry, a reconcile and a
187
+ * prefetch never set it; a surface sets it only behind the same confirm the
188
+ * "Wake and refresh" gesture uses, and it is refused below 15 % battery
189
+ * exactly as that gesture is.
190
+ *
191
+ * The gate is decided BEFORE `getApi()`, because on UDP the login IS the wake
192
+ * (D549) — a read that opened a session and then checked would have woken the
193
+ * camera to find out it was not allowed to.
194
+ *
195
+ * **One authorised yes is one wake.** `next-natural` — take the clip the next
196
+ * time the camera is awake for its own reasons — is deliberately not a member:
197
+ * it was proposed, it is free on the battery, and the operator declined it on
198
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
199
+ * D558 "Considered and not taken").
200
+ */
201
+ export declare const ClipWakeSchema: z.ZodEnum<{
202
+ authorised: "authorised";
203
+ }>;
204
+ export type ClipWake = z.infer<typeof ClipWakeSchema>;
205
+ /**
206
+ * Hard ceiling on ONE {@link videoclipsCapability.methods.readClipBytes} — the
207
+ * same 50 MiB `RECORDING_EXPORT_MAX_READ_BYTES` uses, and for the same second
208
+ * reason: the envelope is unary, so a base64 payload is held whole (~1.33× its
209
+ * size) in the provider AND in the caller, on a hub this repo has already
210
+ * OOM'd once (D9/D18).
211
+ *
212
+ * Measured clips sit far below it — 92 KB–2.29 MB for a sub twin, 455 KB for a
213
+ * 16 s main clip — so the bound bites rarely. "Rarely" is not "never": a long
214
+ * 4K main twin can exceed it, and above the bound the provider REFUSES with
215
+ * the size in the message, never truncates. Half a video is worse than an
216
+ * honest refusal.
217
+ *
218
+ * The clean follow-on is a CHUNKED read so a `high` twin of a long clip stops
219
+ * being refusable at all. That is a later slice, named here so the bound is
220
+ * not mistaken for a design ceiling.
221
+ */
222
+ export declare const VIDEOCLIPS_MAX_READ_BYTES: number;
223
+ /**
224
+ * A clip's finished bytes, inline — the twin of `recordingExport.readExportBytes`.
225
+ *
226
+ * `bytes` is the DECODED length, so nobody infers it from the base64 length,
227
+ * and `served` says which twin the caller actually got.
228
+ */
229
+ export declare const ClipBytesSchema: z.ZodObject<{
230
+ base64: z.ZodString;
231
+ contentType: z.ZodString;
232
+ name: z.ZodString;
233
+ bytes: z.ZodNumber;
234
+ served: z.ZodEnum<{
235
+ high: "high";
236
+ mid: "mid";
237
+ low: "low";
238
+ }>;
239
+ durationMs: z.ZodOptional<z.ZodNumber>;
240
+ }, z.core.$strip>;
241
+ export type ClipBytes = z.infer<typeof ClipBytesSchema>;
172
242
  export declare const ClipSourceAvailabilitySchema: z.ZodObject<{
173
243
  state: z.ZodEnum<{
174
244
  ok: "ok";
@@ -259,7 +329,7 @@ export declare const videoclipsCapability: {
259
329
  since: z.ZodNumber;
260
330
  until: z.ZodNumber;
261
331
  limit: z.ZodOptional<z.ZodNumber>;
262
- provider: z.ZodOptional<z.ZodString>;
332
+ provider: z.ZodString;
263
333
  }, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
264
334
  id: z.ZodString;
265
335
  source: z.ZodString;
@@ -358,6 +428,66 @@ export declare const videoclipsCapability: {
358
428
  playbackEndpoints: z.ZodOptional<z.ZodArray<z.ZodString>>;
359
429
  token: z.ZodOptional<z.ZodString>;
360
430
  }, z.core.$strip>, "query">;
431
+ /**
432
+ * This clip's BYTES, base64, bounded — the by-handle read a clip EXPORT
433
+ * pulls once (D558).
434
+ *
435
+ * `getClipPlayback` is the right answer for a player: it hands back a URL
436
+ * on a plane the hub serves `access:'authenticated'`, which a browser and a
437
+ * viewer session satisfy. It is the wrong answer for another ADDON. There
438
+ * is no addon→addon byte transport in this framework — `AddonDataPlane`
439
+ * only lets an addon SERVE, on `127.0.0.1` behind a per-listener secret
440
+ * only the hub may present — so a recorder that wants a camera's clip
441
+ * cannot fetch that URL. This method is the one seam that exists for it,
442
+ * and it is deliberately the same shape (and the same bound) as
443
+ * `recordingExport.readExportBytes`, which exists for the mirror-image
444
+ * reason.
445
+ *
446
+ * Routing needs no `provider` pin: the id is source-prefixed and
447
+ * self-contained, so `device-collection-dispatch.ts` rule 3 hands the call
448
+ * to the source that claims it — and an id nobody claims is REFUSED rather
449
+ * than answered by another source.
450
+ *
451
+ * The producer reuses the fetch path it already has, completion rules
452
+ * included: a clip is taken by cmd 5 and finished on a short idle window
453
+ * whose result is PROVED against the catalog row's own span, retried once
454
+ * when it comes up materially short, and served-and-named when it is still
455
+ * short (D568). A second fetch with different completion rules is exactly
456
+ * the second authority D558 refuses to create.
457
+ *
458
+ * Every refusal THROWS with its reason and none of them is silent — the
459
+ * sleep gate (decided before `getApi()`, liftable only by
460
+ * {@link ClipWakeSchema}), a catalog row nobody claims, a clip with no
461
+ * bytes behind it, a mux that failed, and the size bound. The caller turns
462
+ * that reason into an operator-facing one; a truncated file is never an
463
+ * answer.
464
+ */
465
+ readonly readClipBytes: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
466
+ deviceId: z.ZodNumber;
467
+ clipId: z.ZodString;
468
+ profile: z.ZodOptional<z.ZodEnum<{
469
+ high: "high";
470
+ mid: "mid";
471
+ low: "low";
472
+ }>>;
473
+ maxBytes: z.ZodOptional<z.ZodNumber>;
474
+ wake: z.ZodOptional<z.ZodEnum<{
475
+ authorised: "authorised";
476
+ }>>;
477
+ }, z.core.$strip>, z.ZodObject<{
478
+ base64: z.ZodString;
479
+ contentType: z.ZodString;
480
+ name: z.ZodString;
481
+ bytes: z.ZodNumber;
482
+ served: z.ZodEnum<{
483
+ high: "high";
484
+ mid: "mid";
485
+ low: "low";
486
+ }>;
487
+ durationMs: z.ZodOptional<z.ZodNumber>;
488
+ }, z.core.$strip>, "query"> & {
489
+ readonly providerOptional: true;
490
+ };
361
491
  };
362
492
  };
363
493
  export type IVideoclipsProvider = InferProvider<typeof videoclipsCapability>;
@@ -9080,6 +9080,13 @@ export type AppRouter = TrpcCoreRouter<{
9080
9080
  output: z.infer<typeof videoclipsCapability.methods.getClipPlayback.output>;
9081
9081
  meta: object;
9082
9082
  }>;
9083
+ readClipBytes: TRPCQueryProcedure<{
9084
+ input: {
9085
+ [x: string]: unknown;
9086
+ } & z.input<typeof videoclipsCapability.methods.readClipBytes.input>;
9087
+ output: z.infer<typeof videoclipsCapability.methods.readClipBytes.output>;
9088
+ meta: object;
9089
+ }>;
9083
9090
  }>>;
9084
9091
  viewerUi: TRPCBuiltRouter<{
9085
9092
  ctx: TrpcContext;
@@ -6,7 +6,7 @@
6
6
  * scope+access check inside `protectedProcedure` (see
7
7
  * `server/backend/src/api/trpc/trpc.middleware.ts`).
8
8
  *
9
- * Coverage: 1058 method paths across 131 capabilities.
9
+ * Coverage: 1059 method paths across 131 capabilities.
10
10
  */
11
11
  import type { CapabilityMethodAccess } from '../capabilities/capability-definition.js';
12
12
  export interface MethodAccessRecord {
@@ -6,7 +6,7 @@
6
6
  * system-scope cap that takes a deviceId was previously never device-filtered
7
7
  * — see the generator header).
8
8
  *
9
- * Coverage: 390 methods carry a device reference, of which
9
+ * Coverage: 391 methods carry a device reference, of which
10
10
  * 141 are on SYSTEM-scope caps.
11
11
  *
12
12
  * Top-level fields, number arrays, and one-level arrays of objects carrying