@camstack/types 1.2.63 → 1.2.64

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.
@@ -1141,6 +1141,27 @@ export interface AddonExecution {
1141
1141
  * former hard-coded addon-name list in `process-service.ts`.
1142
1142
  */
1143
1143
  readonly heapProfile?: AddonHeapProfile;
1144
+ /**
1145
+ * `@camstack/system` builtin ONLY: run this builtin in its OWN forked runner
1146
+ * instead of in-process on the hub root.
1147
+ *
1148
+ * The hub drops `@camstack/system` builtins from the runner plan because the
1149
+ * infrastructure ones (storage, settings, logging, device-manager) must be
1150
+ * resident in the process every runner depends on. That is a reason to keep
1151
+ * THOSE in-process — not a property of the package. A builtin that owns
1152
+ * capture, transcode and byte caches (`snapshot`) charges all of it to the
1153
+ * event loop that also answers the tRPC API.
1154
+ *
1155
+ * Declaring `isolate: true` opts a single builtin out of that blanket rule:
1156
+ * it is planned, spawned, restarted and route-mounted exactly like any other
1157
+ * forked addon, and `resolveRunnerId` still owns its runner id (D2 — one
1158
+ * addon, one process). Nothing else changes: the manifest entry keeps its
1159
+ * `entry` path inside the `@camstack/system` closure, and the runner loads it
1160
+ * from there.
1161
+ *
1162
+ * Ignored for non-system packages — they already fork.
1163
+ */
1164
+ readonly isolate?: boolean;
1144
1165
  }
1145
1166
  export type AddonHeapProfile = 'light' | 'heavy';
1146
1167
  export declare const DEFAULT_ADDON_PLACEMENT: AddonPlacement;
@@ -1184,6 +1205,17 @@ export declare function resolveAddonGroup(decl: Pick<AddonDeclaration, 'executio
1184
1205
  export declare function resolveRunnerId(decl: Pick<AddonDeclaration, 'execution'>, addonId: string): string;
1185
1206
  /** Convenience accessor — the resolved placement (defaults to `hub-only`). */
1186
1207
  export declare function resolveAddonPlacement(decl: Pick<AddonDeclaration, 'execution'>): AddonPlacement;
1208
+ /**
1209
+ * True when a `@camstack/system` builtin opted out of the in-process rule and
1210
+ * must be planned as a forked runner (`execution.isolate`).
1211
+ *
1212
+ * ONE predicate, because the hub asks this question in three places that must
1213
+ * agree: the runner plan (`buildAddonGroupPlan`), the "does this boot
1214
+ * in-process" filter, and `isForkedAddonEntry` (which decides route mounts,
1215
+ * data-plane mounts, restart and uninstall). They diverged once before for
1216
+ * `auth-oidc` and the addon's routes were mounted against an async UDS proxy.
1217
+ */
1218
+ export declare function isIsolatedBuiltin(decl: Pick<AddonDeclaration, 'execution'>): boolean;
1187
1219
  /**
1188
1220
  * One entry of a `RunnerPlan` — an addon assigned to a runner, paired
1189
1221
  * with the on-disk directory the runner subprocess loads it from.
@@ -526,6 +526,29 @@ export interface TopologyNode {
526
526
  readonly version: string;
527
527
  } | null;
528
528
  }
529
+ /**
530
+ * Why a broker's stream health flipped — the discriminator that separates an
531
+ * OUTAGE from an intentional state change.
532
+ *
533
+ * A `stream.offline` on its own says nothing about whether anything is wrong:
534
+ * the same event is fired when a camera stops sending video AND when the last
535
+ * consumer left and the broker suspended its dial on purpose. A consumer that
536
+ * reads only the category cannot tell them apart, and the Notification Center
537
+ * shipped exactly that bug — an operator notified that a stream went down
538
+ * every time nobody was watching it.
539
+ *
540
+ * - `stale-timeout` — no video packet for the broker's stale window
541
+ * (120 s). The ONLY outage. This is what a notification may act on.
542
+ * - `recovered` — packets resumed after a `stale-timeout`. The
543
+ * matching end of an outage.
544
+ * - `first-packet` — a broker's first ever packet. A stream coming up, not
545
+ * a recovery: there was no outage before it.
546
+ * - `suspended` — the lazy-dial idle suspend (no demand). Deliberate.
547
+ * - `resumed` — demand returned and the dial came back. The matching
548
+ * end of a `suspended`, and equally deliberate.
549
+ * - `broker-destroyed` — the stream was unpublished/released. Deliberate.
550
+ */
551
+ export type StreamHealthReason = 'stale-timeout' | 'recovered' | 'first-packet' | 'suspended' | 'resumed' | 'broker-destroyed';
529
552
  /** All known event categories with their typed payloads */
530
553
  export interface EventCatalog {
531
554
  'system.boot': {
@@ -1011,7 +1034,7 @@ export interface EventCatalog {
1011
1034
  readonly engine: PipelineEngineChoice;
1012
1035
  readonly modelsLoaded: readonly string[];
1013
1036
  readonly inUseByCameras: readonly number[];
1014
- readonly kind: 'runtime' | 'warm-override';
1037
+ readonly kind: 'runtime' | 'warm-override' | 'device-pool';
1015
1038
  readonly poolPid: number | null;
1016
1039
  readonly idleMs: number | null;
1017
1040
  readonly idleTtlMs: number | null;
@@ -1159,6 +1182,9 @@ export interface EventCatalog {
1159
1182
  * (every broker is keyed by its cam stream now); `profile` is the
1160
1183
  * profile slot currently bound to it (or `null` if the broker is
1161
1184
  * only kept alive by a manual activation).
1185
+ *
1186
+ * `reason` is the discriminator a consumer MUST read before treating a flip
1187
+ * as an outage — see {@link StreamHealthReason}.
1162
1188
  */
1163
1189
  'stream.online': {
1164
1190
  readonly deviceId: number;
@@ -1167,7 +1193,7 @@ export interface EventCatalog {
1167
1193
  readonly brokerId: string;
1168
1194
  readonly sourceType: string;
1169
1195
  readonly lastPacketAt: number;
1170
- readonly reason?: string;
1196
+ readonly reason?: StreamHealthReason;
1171
1197
  };
1172
1198
  'stream.offline': {
1173
1199
  readonly deviceId: number;
@@ -1176,7 +1202,7 @@ export interface EventCatalog {
1176
1202
  readonly brokerId: string;
1177
1203
  readonly sourceType: string;
1178
1204
  readonly lastPacketAt: number;
1179
- readonly reason?: string;
1205
+ readonly reason?: StreamHealthReason;
1180
1206
  };
1181
1207
  'retention.cleanup': {
1182
1208
  readonly deletedEvents?: number;
@@ -8,7 +8,15 @@ import { z } from 'zod';
8
8
  * Each completed/failed run also lands one durable ops-log row on its owning
9
9
  * addon surface.
10
10
  */
11
+ /**
12
+ * `queued` exists because the recorder mover is SINGLE-FLIGHT and an operator
13
+ * rebalance enqueues one job per (camera, profile). Refusing the second job —
14
+ * what the engine did before — turned a fifteen-camera rebalance into fifteen
15
+ * manual retries. Queued jobs run FIFO; a queued job that is cancelled never
16
+ * runs at all.
17
+ */
11
18
  export declare const RelocateJobStateSchema: z.ZodEnum<{
19
+ queued: "queued";
12
20
  done: "done";
13
21
  failed: "failed";
14
22
  running: "running";
@@ -18,6 +26,7 @@ export type RelocateJobState = z.infer<typeof RelocateJobStateSchema>;
18
26
  export declare const RelocateJobSchema: z.ZodObject<{
19
27
  jobId: z.ZodString;
20
28
  state: z.ZodEnum<{
29
+ queued: "queued";
21
30
  done: "done";
22
31
  failed: "failed";
23
32
  running: "running";
@@ -52,6 +61,8 @@ export declare const RelocateFootageInputSchema: z.ZodObject<{
52
61
  recordings: "recordings";
53
62
  recordingsLow: "recordingsLow";
54
63
  }>>;
64
+ deviceId: z.ZodOptional<z.ZodNumber>;
65
+ profiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
55
66
  throttleMbps: z.ZodOptional<z.ZodNumber>;
56
67
  }, z.core.$strip>;
57
68
  export type RelocateFootageInput = z.infer<typeof RelocateFootageInputSchema>;
@@ -72,6 +83,8 @@ export declare const StorageMigrationFootageMoveInputSchema: z.ZodObject<{
72
83
  recordings: "recordings";
73
84
  recordingsLow: "recordingsLow";
74
85
  }>>;
86
+ deviceId: z.ZodOptional<z.ZodNumber>;
87
+ profiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
75
88
  throttleMbps: z.ZodOptional<z.ZodNumber>;
76
89
  leaseId: z.ZodString;
77
90
  }, z.core.$strip>;
@@ -147,6 +160,7 @@ export declare const StorageMigrationMoveSchema: z.ZodObject<{
147
160
  toLocationId: z.ZodString;
148
161
  moverJobId: z.ZodNullable<z.ZodString>;
149
162
  state: z.ZodNullable<z.ZodEnum<{
163
+ queued: "queued";
150
164
  done: "done";
151
165
  failed: "failed";
152
166
  running: "running";
@@ -185,6 +199,7 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
185
199
  toLocationId: z.ZodString;
186
200
  moverJobId: z.ZodNullable<z.ZodString>;
187
201
  state: z.ZodNullable<z.ZodEnum<{
202
+ queued: "queued";
188
203
  done: "done";
189
204
  failed: "failed";
190
205
  running: "running";
@@ -265,6 +265,20 @@ export interface IStreamBroker {
265
265
  * `H265Repacketizer` once the camera's first RTP+SDP arrive.
266
266
  */
267
267
  onSdpParameterSets(callback: (ps: ReadonlyArray<Buffer>) => void): Unsubscribe;
268
+ /**
269
+ * Subscribe to SOURCE RESTARTS — every dial after the first, carrying a
270
+ * short reason (`dial#2`, …). The WebRTC server subscribes for EVERY
271
+ * session shape (RTP passthrough, AnnexB, transcode): each holds parameter
272
+ * sets and a bootstrap latch belonging to the connection that ended, so a
273
+ * re-dial must re-sync them rather than leave the session decoding against
274
+ * a dead mapping.
275
+ *
276
+ * Fires on the DIAL, not on the first packet: a restart that never delivers
277
+ * must still be observable rather than silently indistinguishable from a
278
+ * healthy stream. The returned function detaches ONE subscriber (a broker
279
+ * fans out to many live sessions).
280
+ */
281
+ onSourceRestart(observer: (reason: string) => void): Unsubscribe;
268
282
  /**
269
283
  * Source type as configured at `start()` — see `StreamSourceType`
270
284
  * for the supported values. `null` before `start()` resolves.
@@ -28,6 +28,35 @@ export declare const TimelapseTemplateSchema: z.ZodObject<{
28
28
  body: z.ZodOptional<z.ZodString>;
29
29
  }, z.core.$strip>;
30
30
  export type TimelapseTemplate = z.infer<typeof TimelapseTemplateSchema>;
31
+ /**
32
+ * Floor on any dense cadence, seconds — the recording's own frame interval.
33
+ *
34
+ * Declared here because it bounds BOTH the rule field and the renderer's
35
+ * derivation, and two copies of a floor are two floors that can drift.
36
+ */
37
+ export declare const TIMELAPSE_DENSE_FLOOR_SEC = 0.1;
38
+ /**
39
+ * Whether the notification's preview is a STILL or a short animation.
40
+ *
41
+ * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
42
+ * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
43
+ * night reads better as three seconds of motion than as one frame of it. Both
44
+ * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
45
+ * simply applies it to a dozen frames sampled across the render and assembles
46
+ * them.
47
+ *
48
+ * `'image'` is the default and stays the default: a GIF costs a dozen ffmpeg
49
+ * seeks and a palette pass, and no rule that never asked for one should start
50
+ * paying that on the deploy that shipped it.
51
+ *
52
+ * A GIF that cannot be assembled DEGRADES to the still — never to nothing.
53
+ */
54
+ declare const PreviewModeField: z.ZodEnum<{
55
+ image: "image";
56
+ gif: "gif";
57
+ }>;
58
+ /** Still or animation — the rule field, as a type the renderer can take. */
59
+ export type TimelapsePreviewMode = z.infer<typeof PreviewModeField>;
31
60
  /**
32
61
  * Client-supplied timelapse-rule fields. The server stamps id / createdBy /
33
62
  * createdAt / updatedAt / ownerUserId / lastGeneratedAt — none of them appear
@@ -48,6 +77,8 @@ export declare const TimelapseRuleInputSchema: z.ZodObject<{
48
77
  }, z.core.$strip>;
49
78
  cadenceSec: z.ZodDefault<z.ZodNumber>;
50
79
  framerate: z.ZodDefault<z.ZodNumber>;
80
+ denseCadenceSec: z.ZodOptional<z.ZodNumber>;
81
+ minDwellSec: z.ZodOptional<z.ZodNumber>;
51
82
  targets: z.ZodArray<z.ZodObject<{
52
83
  targetId: z.ZodString;
53
84
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -56,9 +87,23 @@ export declare const TimelapseRuleInputSchema: z.ZodObject<{
56
87
  title: z.ZodOptional<z.ZodString>;
57
88
  body: z.ZodOptional<z.ZodString>;
58
89
  }, z.core.$strip>>;
90
+ previewText: z.ZodOptional<z.ZodString>;
91
+ previewMode: z.ZodDefault<z.ZodEnum<{
92
+ image: "image";
93
+ gif: "gif";
94
+ }>>;
95
+ reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
96
  priority: z.ZodDefault<z.ZodNumber>;
60
97
  }, z.core.$strip>;
61
98
  export type TimelapseRuleInput = z.infer<typeof TimelapseRuleInputSchema>;
99
+ /**
100
+ * The caption a rule that never set one gets.
101
+ *
102
+ * Rendered through the ordinary `{{var}}` pass at delivery, so a rule created
103
+ * before this field existed still reads "Timelapse Videocamera ingresso" and
104
+ * not the literal braces.
105
+ */
106
+ export declare const DEFAULT_TIMELAPSE_PREVIEW_TEXT = "Timelapse {{camera}}";
62
107
  /**
63
108
  * Partial patch for an update — any subset of the INPUT fields, with NO
64
109
  * defaults (an absent key means "leave unchanged", never "reset to default").
@@ -90,6 +135,8 @@ export declare const TimelapseRulePatchSchema: z.ZodObject<{
90
135
  }, z.core.$strip>>;
91
136
  cadenceSec: z.ZodOptional<z.ZodNumber>;
92
137
  framerate: z.ZodOptional<z.ZodNumber>;
138
+ denseCadenceSec: z.ZodOptional<z.ZodNumber>;
139
+ minDwellSec: z.ZodOptional<z.ZodNumber>;
93
140
  targets: z.ZodOptional<z.ZodArray<z.ZodObject<{
94
141
  targetId: z.ZodString;
95
142
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -98,6 +145,12 @@ export declare const TimelapseRulePatchSchema: z.ZodObject<{
98
145
  title: z.ZodOptional<z.ZodString>;
99
146
  body: z.ZodOptional<z.ZodString>;
100
147
  }, z.core.$strip>>>;
148
+ previewText: z.ZodOptional<z.ZodString>;
149
+ previewMode: z.ZodOptional<z.ZodEnum<{
150
+ image: "image";
151
+ gif: "gif";
152
+ }>>;
153
+ reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
101
154
  priority: z.ZodOptional<z.ZodNumber>;
102
155
  }, z.core.$strip>;
103
156
  export type TimelapseRulePatch = z.infer<typeof TimelapseRulePatchSchema>;
@@ -117,6 +170,8 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
117
170
  }, z.core.$strip>;
118
171
  cadenceSec: z.ZodDefault<z.ZodNumber>;
119
172
  framerate: z.ZodDefault<z.ZodNumber>;
173
+ denseCadenceSec: z.ZodOptional<z.ZodNumber>;
174
+ minDwellSec: z.ZodOptional<z.ZodNumber>;
120
175
  targets: z.ZodArray<z.ZodObject<{
121
176
  targetId: z.ZodString;
122
177
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -125,6 +180,12 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
125
180
  title: z.ZodOptional<z.ZodString>;
126
181
  body: z.ZodOptional<z.ZodString>;
127
182
  }, z.core.$strip>>;
183
+ previewText: z.ZodOptional<z.ZodString>;
184
+ previewMode: z.ZodDefault<z.ZodEnum<{
185
+ image: "image";
186
+ gif: "gif";
187
+ }>>;
188
+ reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
128
189
  priority: z.ZodDefault<z.ZodNumber>;
129
190
  id: z.ZodString;
130
191
  ownerUserId: z.ZodOptional<z.ZodString>;
@@ -135,6 +196,27 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
135
196
  updatedAt: z.ZodNumber;
136
197
  }, z.core.$strip>;
137
198
  export type TimelapseRule = z.infer<typeof TimelapseRuleSchema>;
199
+ /** The cadence pair a rule must satisfy — the guard's whole input. */
200
+ export interface TimelapseCadencePair {
201
+ readonly cadenceSec: number;
202
+ readonly denseCadenceSec?: number;
203
+ }
204
+ /**
205
+ * Refuse a dense cadence that is not denser than the base.
206
+ *
207
+ * Called at UPSERT, on the MERGED rule — a patch that lowers `cadenceSec`
208
+ * alone can invalidate a `denseCadenceSec` set months earlier, so checking the
209
+ * patch in isolation would let the bad pair through.
210
+ *
211
+ * Refusing here and not at render time is the whole point: `ExportTimelapseSchema`
212
+ * also rejects the pair, but it does so when the window has already closed and
213
+ * the footage is being cut — the operator learns at 06:05 that last night was
214
+ * never going to render, and a closed window does not come back. This turns
215
+ * that into a failed edit he can see and correct.
216
+ *
217
+ * @throws Error naming both numbers, so the message is actionable in a toast.
218
+ */
219
+ export declare function assertTimelapseCadences(pair: TimelapseCadencePair): void;
138
220
  /**
139
221
  * The last successful generation for ONE camera of a rule, epoch-ms.
140
222
  *
@@ -148,3 +230,4 @@ export type TimelapseRule = z.infer<typeof TimelapseRuleSchema>;
148
230
  * entire rule set once.
149
231
  */
150
232
  export declare function readTimelapseGeneratedAt(rule: Pick<TimelapseRule, 'lastGeneratedAt' | 'generatedByDevice'>, deviceId: number): number;
233
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.63",
3
+ "version": "1.2.64",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",