@camstack/addon-export-hap 1.1.24 → 1.1.26

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.
@@ -35,7 +35,7 @@ let node_fs_promises = require("node:fs/promises");
35
35
  node_fs_promises = __toESM(node_fs_promises);
36
36
  let node_dgram = require("node:dgram");
37
37
  let node_os = require("node:os");
38
- //#region ../types/dist/event-category-H4AVePnn.mjs
38
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
39
39
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
40
40
  EventCategory["SystemBoot"] = "system.boot";
41
41
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -188,6 +188,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
188
188
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
189
189
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
190
190
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
191
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
192
+ * progress bar the client reconciles via `recordingExport.getExport`. */
193
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
194
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
195
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
191
196
  EventCategory["DetectionEvent"] = "detection.event";
192
197
  EventCategory["SessionTrackNew"] = "session.track.new";
193
198
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -477,6 +482,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
477
482
  */
478
483
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
479
484
  /**
485
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
486
+ * a package zone — a newly-appeared stationary object of a package class
487
+ * that cleared the class / zone / dwell / size gates. Payload:
488
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
489
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
490
+ * Telemetry (D8): the durable record is the `package-events` store row;
491
+ * this bus topic drives notifier rules + live UI. See
492
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
493
+ */
494
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
495
+ /**
496
+ * Fired by `addon-post-analysis` when a previously-delivered package
497
+ * leaves its zone (the stationary entry departed — moved or swept).
498
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
499
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
500
+ * Telemetry (D8). See package-zones-design §5.2.
501
+ */
502
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
503
+ /**
480
504
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
481
505
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
482
506
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5215,6 +5239,25 @@ function _instanceof(cls, params = {}) {
5215
5239
  };
5216
5240
  return inst;
5217
5241
  }
5242
+ //#endregion
5243
+ //#region ../../node_modules/zod/v4/classic/compat.js
5244
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5245
+ var ZodIssueCode = {
5246
+ invalid_type: "invalid_type",
5247
+ too_big: "too_big",
5248
+ too_small: "too_small",
5249
+ invalid_format: "invalid_format",
5250
+ not_multiple_of: "not_multiple_of",
5251
+ unrecognized_keys: "unrecognized_keys",
5252
+ invalid_union: "invalid_union",
5253
+ invalid_key: "invalid_key",
5254
+ invalid_element: "invalid_element",
5255
+ invalid_value: "invalid_value",
5256
+ custom: "custom"
5257
+ };
5258
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5259
+ var ZodFirstPartyTypeKind;
5260
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5218
5261
  Object.fromEntries([
5219
5262
  {
5220
5263
  id: "overview",
@@ -7257,6 +7300,24 @@ var RecordingRetentionSchema = object({
7257
7300
  maxSizeGb: number().min(0).optional()
7258
7301
  });
7259
7302
  /**
7303
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7304
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7305
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7306
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7307
+ *
7308
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7309
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7310
+ * preset changed over time renders every historical window at the dims it was
7311
+ * written with.
7312
+ */
7313
+ var ScrubThumbnailPresetSchema = _enum([
7314
+ "minimal",
7315
+ "low",
7316
+ "standard",
7317
+ "high",
7318
+ "max"
7319
+ ]);
7320
+ /**
7260
7321
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7261
7322
  *
7262
7323
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7293,7 +7354,14 @@ var RecordingConfigSchema = object({
7293
7354
  * derived into bands once via `migrateConfigToBands`.
7294
7355
  */
7295
7356
  bands: array(RecordingBandSchema).optional(),
7296
- retention: RecordingRetentionSchema.optional()
7357
+ retention: RecordingRetentionSchema.optional(),
7358
+ /**
7359
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7360
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7361
+ * windows only — existing sheets are immutable, and each window's index
7362
+ * carries its own tile dims so mixed-preset history renders correctly.
7363
+ */
7364
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7297
7365
  });
7298
7366
  /**
7299
7367
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -12599,7 +12667,11 @@ array(ZoneRuleSchema).readonly();
12599
12667
  * Extend the enum here when a new gating consumer comes online (audio
12600
12668
  * gating, alert filtering, …) — no other surface needs to change.
12601
12669
  */
12602
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
12670
+ var ZoneRuleStageEnum = _enum([
12671
+ "motion",
12672
+ "detection",
12673
+ "package"
12674
+ ]);
12603
12675
  DeviceType.Camera, method(object({
12604
12676
  deviceId: number(),
12605
12677
  stage: ZoneRuleStageEnum
@@ -12612,7 +12684,8 @@ DeviceType.Camera, method(object({
12612
12684
  auth: "admin"
12613
12685
  }), object({
12614
12686
  motion: array(ZoneRuleSchema).readonly(),
12615
- detection: array(ZoneRuleSchema).readonly()
12687
+ detection: array(ZoneRuleSchema).readonly(),
12688
+ package: array(ZoneRuleSchema).readonly()
12616
12689
  });
12617
12690
  var ProviderStatusSchema = object({
12618
12691
  connected: boolean(),
@@ -15820,6 +15893,7 @@ var EventKindIconSchema = _enum([
15820
15893
  "smoke",
15821
15894
  "water",
15822
15895
  "button",
15896
+ "package",
15823
15897
  "generic"
15824
15898
  ]);
15825
15899
  var EventKindCategorySchema = _enum([
@@ -15827,7 +15901,8 @@ var EventKindCategorySchema = _enum([
15827
15901
  "audio",
15828
15902
  "detection",
15829
15903
  "sensor",
15830
- "custom"
15904
+ "custom",
15905
+ "package"
15831
15906
  ]);
15832
15907
  var EventKindDescriptorSchema = object({
15833
15908
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19473,6 +19548,108 @@ method(object({
19473
19548
  auth: "admin"
19474
19549
  });
19475
19550
  /**
19551
+ * `recordingExport` cap — render a footage time range into a single downloadable
19552
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
19553
+ * bounded lifetime with a durable history, auto-expiry, and optional
19554
+ * delete-after-download.
19555
+ *
19556
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
19557
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
19558
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
19559
+ * input and dispatch to that single provider; the render runs on the node that
19560
+ * owns the footage (no cross-node segment transfer). Download rides the
19561
+ * framework addon data-plane. State persists in a DurableState blob (NOT
19562
+ * SQLite); history rows survive file deletion for audit.
19563
+ */
19564
+ /** Playback-speed multiplier for the render (1 = realtime). */
19565
+ var ExportSpeedSchema = number().min(.25).max(32);
19566
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
19567
+ var ExportTimelapseSchema = object({
19568
+ everyMs: number().int().positive(),
19569
+ outputFps: number().int().min(1).max(60).optional()
19570
+ });
19571
+ /**
19572
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
19573
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
19574
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
19575
+ * `deleteAfterDownload` removes it shortly after the first complete download.
19576
+ */
19577
+ var ExportOptionsSchema = object({
19578
+ speed: ExportSpeedSchema.optional(),
19579
+ timelapse: ExportTimelapseSchema.optional(),
19580
+ includeAudio: boolean(),
19581
+ maxLifeMs: number().int().positive(),
19582
+ deleteAfterDownload: boolean(),
19583
+ title: string().max(200).optional()
19584
+ }).superRefine((v, ctx) => {
19585
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
19586
+ code: ZodIssueCode.custom,
19587
+ message: "speed and timelapse are mutually exclusive",
19588
+ path: ["timelapse"]
19589
+ });
19590
+ });
19591
+ var ExportStateSchema = _enum([
19592
+ "queued",
19593
+ "rendering",
19594
+ "ready",
19595
+ "failed",
19596
+ "expired",
19597
+ "deleted"
19598
+ ]);
19599
+ /** One export job / history row. */
19600
+ var ExportRecordSchema = object({
19601
+ id: string(),
19602
+ deviceId: number(),
19603
+ profile: string(),
19604
+ fromMs: number(),
19605
+ toMs: number(),
19606
+ options: ExportOptionsSchema,
19607
+ state: ExportStateSchema,
19608
+ /** 0–100 while rendering; null otherwise. */
19609
+ progressPct: number().nullable(),
19610
+ /** File size once ready; null before. */
19611
+ fileBytes: number().nullable(),
19612
+ expiresAt: number(),
19613
+ deleteAfterDownload: boolean(),
19614
+ /** Epoch of the first complete download; null until then. */
19615
+ downloadedAt: number().nullable(),
19616
+ createdAt: number(),
19617
+ /** User id/name that requested the export. */
19618
+ createdBy: string(),
19619
+ /** Failure reason when state is 'failed'; null otherwise. */
19620
+ error: string().nullable()
19621
+ });
19622
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
19623
+ var ExportDownloadSchema = object({
19624
+ url: string(),
19625
+ endpoints: array(string())
19626
+ });
19627
+ method(object({
19628
+ deviceId: number(),
19629
+ profile: string(),
19630
+ fromMs: number(),
19631
+ toMs: number(),
19632
+ options: ExportOptionsSchema
19633
+ }), ExportRecordSchema, {
19634
+ kind: "mutation",
19635
+ auth: "protected"
19636
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
19637
+ kind: "query",
19638
+ auth: "protected"
19639
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19640
+ kind: "query",
19641
+ auth: "protected"
19642
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19643
+ kind: "mutation",
19644
+ auth: "protected"
19645
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19646
+ kind: "mutation",
19647
+ auth: "protected"
19648
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
19649
+ kind: "query",
19650
+ auth: "protected"
19651
+ });
19652
+ /**
19476
19653
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
19477
19654
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
19478
19655
  * `deviceId`.
@@ -23396,6 +23573,42 @@ Object.freeze({
23396
23573
  addonId: null,
23397
23574
  access: "create"
23398
23575
  },
23576
+ "recordingExport.cancelExport": {
23577
+ capName: "recordingExport",
23578
+ capScope: "system",
23579
+ addonId: null,
23580
+ access: "create"
23581
+ },
23582
+ "recordingExport.createExport": {
23583
+ capName: "recordingExport",
23584
+ capScope: "system",
23585
+ addonId: null,
23586
+ access: "create"
23587
+ },
23588
+ "recordingExport.deleteExport": {
23589
+ capName: "recordingExport",
23590
+ capScope: "system",
23591
+ addonId: null,
23592
+ access: "delete"
23593
+ },
23594
+ "recordingExport.getDownloadUrl": {
23595
+ capName: "recordingExport",
23596
+ capScope: "system",
23597
+ addonId: null,
23598
+ access: "view"
23599
+ },
23600
+ "recordingExport.getExport": {
23601
+ capName: "recordingExport",
23602
+ capScope: "system",
23603
+ addonId: null,
23604
+ access: "view"
23605
+ },
23606
+ "recordingExport.listExports": {
23607
+ capName: "recordingExport",
23608
+ capScope: "system",
23609
+ addonId: null,
23610
+ access: "view"
23611
+ },
23399
23612
  "sceneMonitor.captureReference": {
23400
23613
  capName: "scene-monitor",
23401
23614
  capScope: "device",
@@ -10,7 +10,7 @@ import { networkInterfaces } from "node:os";
10
10
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
11
11
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
12
12
  //#endregion
13
- //#region ../types/dist/event-category-H4AVePnn.mjs
13
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
14
14
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  EventCategory["SystemBoot"] = "system.boot";
16
16
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -163,6 +163,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
163
163
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
164
164
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
165
165
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
166
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
167
+ * progress bar the client reconciles via `recordingExport.getExport`. */
168
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
169
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
170
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
166
171
  EventCategory["DetectionEvent"] = "detection.event";
167
172
  EventCategory["SessionTrackNew"] = "session.track.new";
168
173
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -452,6 +457,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
452
457
  */
453
458
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
454
459
  /**
460
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
461
+ * a package zone — a newly-appeared stationary object of a package class
462
+ * that cleared the class / zone / dwell / size gates. Payload:
463
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
464
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
465
+ * Telemetry (D8): the durable record is the `package-events` store row;
466
+ * this bus topic drives notifier rules + live UI. See
467
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
468
+ */
469
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
470
+ /**
471
+ * Fired by `addon-post-analysis` when a previously-delivered package
472
+ * leaves its zone (the stationary entry departed — moved or swept).
473
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
474
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
475
+ * Telemetry (D8). See package-zones-design §5.2.
476
+ */
477
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
478
+ /**
455
479
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
456
480
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
457
481
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5190,6 +5214,25 @@ function _instanceof(cls, params = {}) {
5190
5214
  };
5191
5215
  return inst;
5192
5216
  }
5217
+ //#endregion
5218
+ //#region ../../node_modules/zod/v4/classic/compat.js
5219
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5220
+ var ZodIssueCode = {
5221
+ invalid_type: "invalid_type",
5222
+ too_big: "too_big",
5223
+ too_small: "too_small",
5224
+ invalid_format: "invalid_format",
5225
+ not_multiple_of: "not_multiple_of",
5226
+ unrecognized_keys: "unrecognized_keys",
5227
+ invalid_union: "invalid_union",
5228
+ invalid_key: "invalid_key",
5229
+ invalid_element: "invalid_element",
5230
+ invalid_value: "invalid_value",
5231
+ custom: "custom"
5232
+ };
5233
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5234
+ var ZodFirstPartyTypeKind;
5235
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5193
5236
  Object.fromEntries([
5194
5237
  {
5195
5238
  id: "overview",
@@ -7232,6 +7275,24 @@ var RecordingRetentionSchema = object({
7232
7275
  maxSizeGb: number().min(0).optional()
7233
7276
  });
7234
7277
  /**
7278
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7279
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7280
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7281
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7282
+ *
7283
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7284
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7285
+ * preset changed over time renders every historical window at the dims it was
7286
+ * written with.
7287
+ */
7288
+ var ScrubThumbnailPresetSchema = _enum([
7289
+ "minimal",
7290
+ "low",
7291
+ "standard",
7292
+ "high",
7293
+ "max"
7294
+ ]);
7295
+ /**
7235
7296
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7236
7297
  *
7237
7298
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7268,7 +7329,14 @@ var RecordingConfigSchema = object({
7268
7329
  * derived into bands once via `migrateConfigToBands`.
7269
7330
  */
7270
7331
  bands: array(RecordingBandSchema).optional(),
7271
- retention: RecordingRetentionSchema.optional()
7332
+ retention: RecordingRetentionSchema.optional(),
7333
+ /**
7334
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7335
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7336
+ * windows only — existing sheets are immutable, and each window's index
7337
+ * carries its own tile dims so mixed-preset history renders correctly.
7338
+ */
7339
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7272
7340
  });
7273
7341
  /**
7274
7342
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -12574,7 +12642,11 @@ array(ZoneRuleSchema).readonly();
12574
12642
  * Extend the enum here when a new gating consumer comes online (audio
12575
12643
  * gating, alert filtering, …) — no other surface needs to change.
12576
12644
  */
12577
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
12645
+ var ZoneRuleStageEnum = _enum([
12646
+ "motion",
12647
+ "detection",
12648
+ "package"
12649
+ ]);
12578
12650
  DeviceType.Camera, method(object({
12579
12651
  deviceId: number(),
12580
12652
  stage: ZoneRuleStageEnum
@@ -12587,7 +12659,8 @@ DeviceType.Camera, method(object({
12587
12659
  auth: "admin"
12588
12660
  }), object({
12589
12661
  motion: array(ZoneRuleSchema).readonly(),
12590
- detection: array(ZoneRuleSchema).readonly()
12662
+ detection: array(ZoneRuleSchema).readonly(),
12663
+ package: array(ZoneRuleSchema).readonly()
12591
12664
  });
12592
12665
  var ProviderStatusSchema = object({
12593
12666
  connected: boolean(),
@@ -15795,6 +15868,7 @@ var EventKindIconSchema = _enum([
15795
15868
  "smoke",
15796
15869
  "water",
15797
15870
  "button",
15871
+ "package",
15798
15872
  "generic"
15799
15873
  ]);
15800
15874
  var EventKindCategorySchema = _enum([
@@ -15802,7 +15876,8 @@ var EventKindCategorySchema = _enum([
15802
15876
  "audio",
15803
15877
  "detection",
15804
15878
  "sensor",
15805
- "custom"
15879
+ "custom",
15880
+ "package"
15806
15881
  ]);
15807
15882
  var EventKindDescriptorSchema = object({
15808
15883
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -19448,6 +19523,108 @@ method(object({
19448
19523
  auth: "admin"
19449
19524
  });
19450
19525
  /**
19526
+ * `recordingExport` cap — render a footage time range into a single downloadable
19527
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
19528
+ * bounded lifetime with a durable history, auto-expiry, and optional
19529
+ * delete-after-download.
19530
+ *
19531
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
19532
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
19533
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
19534
+ * input and dispatch to that single provider; the render runs on the node that
19535
+ * owns the footage (no cross-node segment transfer). Download rides the
19536
+ * framework addon data-plane. State persists in a DurableState blob (NOT
19537
+ * SQLite); history rows survive file deletion for audit.
19538
+ */
19539
+ /** Playback-speed multiplier for the render (1 = realtime). */
19540
+ var ExportSpeedSchema = number().min(.25).max(32);
19541
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
19542
+ var ExportTimelapseSchema = object({
19543
+ everyMs: number().int().positive(),
19544
+ outputFps: number().int().min(1).max(60).optional()
19545
+ });
19546
+ /**
19547
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
19548
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
19549
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
19550
+ * `deleteAfterDownload` removes it shortly after the first complete download.
19551
+ */
19552
+ var ExportOptionsSchema = object({
19553
+ speed: ExportSpeedSchema.optional(),
19554
+ timelapse: ExportTimelapseSchema.optional(),
19555
+ includeAudio: boolean(),
19556
+ maxLifeMs: number().int().positive(),
19557
+ deleteAfterDownload: boolean(),
19558
+ title: string().max(200).optional()
19559
+ }).superRefine((v, ctx) => {
19560
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
19561
+ code: ZodIssueCode.custom,
19562
+ message: "speed and timelapse are mutually exclusive",
19563
+ path: ["timelapse"]
19564
+ });
19565
+ });
19566
+ var ExportStateSchema = _enum([
19567
+ "queued",
19568
+ "rendering",
19569
+ "ready",
19570
+ "failed",
19571
+ "expired",
19572
+ "deleted"
19573
+ ]);
19574
+ /** One export job / history row. */
19575
+ var ExportRecordSchema = object({
19576
+ id: string(),
19577
+ deviceId: number(),
19578
+ profile: string(),
19579
+ fromMs: number(),
19580
+ toMs: number(),
19581
+ options: ExportOptionsSchema,
19582
+ state: ExportStateSchema,
19583
+ /** 0–100 while rendering; null otherwise. */
19584
+ progressPct: number().nullable(),
19585
+ /** File size once ready; null before. */
19586
+ fileBytes: number().nullable(),
19587
+ expiresAt: number(),
19588
+ deleteAfterDownload: boolean(),
19589
+ /** Epoch of the first complete download; null until then. */
19590
+ downloadedAt: number().nullable(),
19591
+ createdAt: number(),
19592
+ /** User id/name that requested the export. */
19593
+ createdBy: string(),
19594
+ /** Failure reason when state is 'failed'; null otherwise. */
19595
+ error: string().nullable()
19596
+ });
19597
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
19598
+ var ExportDownloadSchema = object({
19599
+ url: string(),
19600
+ endpoints: array(string())
19601
+ });
19602
+ method(object({
19603
+ deviceId: number(),
19604
+ profile: string(),
19605
+ fromMs: number(),
19606
+ toMs: number(),
19607
+ options: ExportOptionsSchema
19608
+ }), ExportRecordSchema, {
19609
+ kind: "mutation",
19610
+ auth: "protected"
19611
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
19612
+ kind: "query",
19613
+ auth: "protected"
19614
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19615
+ kind: "query",
19616
+ auth: "protected"
19617
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19618
+ kind: "mutation",
19619
+ auth: "protected"
19620
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
19621
+ kind: "mutation",
19622
+ auth: "protected"
19623
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
19624
+ kind: "query",
19625
+ auth: "protected"
19626
+ });
19627
+ /**
19451
19628
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
19452
19629
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
19453
19630
  * `deviceId`.
@@ -23371,6 +23548,42 @@ Object.freeze({
23371
23548
  addonId: null,
23372
23549
  access: "create"
23373
23550
  },
23551
+ "recordingExport.cancelExport": {
23552
+ capName: "recordingExport",
23553
+ capScope: "system",
23554
+ addonId: null,
23555
+ access: "create"
23556
+ },
23557
+ "recordingExport.createExport": {
23558
+ capName: "recordingExport",
23559
+ capScope: "system",
23560
+ addonId: null,
23561
+ access: "create"
23562
+ },
23563
+ "recordingExport.deleteExport": {
23564
+ capName: "recordingExport",
23565
+ capScope: "system",
23566
+ addonId: null,
23567
+ access: "delete"
23568
+ },
23569
+ "recordingExport.getDownloadUrl": {
23570
+ capName: "recordingExport",
23571
+ capScope: "system",
23572
+ addonId: null,
23573
+ access: "view"
23574
+ },
23575
+ "recordingExport.getExport": {
23576
+ capName: "recordingExport",
23577
+ capScope: "system",
23578
+ addonId: null,
23579
+ access: "view"
23580
+ },
23581
+ "recordingExport.listExports": {
23582
+ capName: "recordingExport",
23583
+ capScope: "system",
23584
+ addonId: null,
23585
+ access: "view"
23586
+ },
23374
23587
  "sceneMonitor.captureReference": {
23375
23588
  capName: "scene-monitor",
23376
23589
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "HomeKit (HAP) bridge exporter for CamStack devices. Publishes a bridged accessory per exposed device — MotionSensor in this MVP; Camera/Doorbell/Switch/Light follow.",
5
5
  "keywords": [
6
6
  "camstack",