@camstack/addon-notifiers 1.2.8 → 1.2.10

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.
package/dist/addon.js CHANGED
@@ -29,7 +29,7 @@ let node_fs = require("node:fs");
29
29
  let node_path = require("node:path");
30
30
  node_path = __toESM(node_path);
31
31
  let node_url = require("node:url");
32
- //#region ../types/dist/event-category-BLcNejAE.mjs
32
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
33
33
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
34
34
  EventCategory["SystemBoot"] = "system.boot";
35
35
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -300,6 +300,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
300
300
  */
301
301
  EventCategory["DeviceStateChanged"] = "device.state-changed";
302
302
  /**
303
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
304
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
305
+ *
306
+ * Emitted only on a change, so a steady scene is silent. It exists so a
307
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
308
+ * one live badge with no push signal at all, and it cost a request every
309
+ * four seconds per visible camera.
310
+ *
311
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
312
+ * keeps a slow reconcile rather than trusting it alone.
313
+ */
314
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
315
+ /**
303
316
  * Cap event fired by every device that registers the `battery`
304
317
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
305
318
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9406,6 +9419,29 @@ var NcOccupancyConditionSchema = object({
9406
9419
  count: number().int().min(0).default(1),
9407
9420
  sustainSeconds: number().int().min(0).max(3600).default(15)
9408
9421
  });
9422
+ /**
9423
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9424
+ *
9425
+ * The values are not symmetric, and deliberately so — the absent value has to
9426
+ * mean exactly what every rule authored before this condition existed already
9427
+ * does:
9428
+ * - `enter` — entries and every NON-crossing record (movement state,
9429
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9430
+ * an operator who never asked for exits must not start receiving them.
9431
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9432
+ * fails closed, because "the car left the drive" is a question about a
9433
+ * boundary, not about a detection.
9434
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9435
+ *
9436
+ * A rule asking for a direction should normally also scope `zones`, which the
9437
+ * engine evaluates against the crossed zone as well as the current membership
9438
+ * (an exit's membership no longer contains the zone it just left).
9439
+ */
9440
+ var NcCrossingSchema = _enum([
9441
+ "enter",
9442
+ "exit",
9443
+ "any"
9444
+ ]);
9409
9445
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9410
9446
  var NcZoneConditionSchema = object({
9411
9447
  ids: array(string().min(1)).min(1),
@@ -9430,6 +9466,13 @@ var NcConditionsSchema = object({
9430
9466
  /** Veto zones — any hit fails the rule. */
9431
9467
  zonesExclude: array(string().min(1)).optional(),
9432
9468
  /**
9469
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9470
+ * and a closed track carries none, so a `track-end` rule asking for one
9471
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9472
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9473
+ */
9474
+ crossing: NcCrossingSchema.optional(),
9475
+ /**
9433
9476
  * Exact (case-insensitive) match on the record's collapsed `label`
9434
9477
  * (identity name / plate text / subclass).
9435
9478
  */
@@ -9618,11 +9661,31 @@ var NcMediaPolicySchema = object({
9618
9661
  */
9619
9662
  profile: CamProfileSchema.optional()
9620
9663
  });
9664
+ /**
9665
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9666
+ * notification suppresses.
9667
+ * - `shared` (default, and the absent value) — one window for the whole
9668
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9669
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9670
+ * at once and cat→cat still waits.
9671
+ *
9672
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9673
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9674
+ * see `cooldownKey` in the rule engine).
9675
+ */
9676
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9621
9677
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9622
9678
  var NcThrottleSchema = object({
9623
9679
  cooldownSec: number().int().min(0).max(86400).default(60),
9624
9680
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9625
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9681
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9682
+ /**
9683
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9684
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9685
+ * rule authored before this field simply carries none — and the engine
9686
+ * reads absent as `shared`, the pre-existing behaviour.
9687
+ */
9688
+ granularity: NcThrottleGranularitySchema.optional()
9626
9689
  });
9627
9690
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9628
9691
  var NcRuleInputSchema = object({
@@ -9730,6 +9793,7 @@ var NcConditionDescriptorSchema = object({
9730
9793
  "schedule",
9731
9794
  "plateMatcher",
9732
9795
  "packagePhase",
9796
+ "crossingSelect",
9733
9797
  "polygonDraw",
9734
9798
  "occupancy"
9735
9799
  ]),
@@ -16253,7 +16317,10 @@ method(object({
16253
16317
  }), method(object({
16254
16318
  deviceId: number(),
16255
16319
  caps: array(string()).readonly().optional()
16256
- }), record(string(), unknown().nullable()));
16320
+ }), record(string(), unknown().nullable())), method(object({
16321
+ deviceIds: array(number()).readonly(),
16322
+ caps: array(string()).readonly().optional()
16323
+ }), record(string(), record(string(), unknown().nullable())));
16257
16324
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
16258
16325
  deviceId: number(),
16259
16326
  capName: string()
@@ -17675,6 +17742,29 @@ var MotionEventSchema = object({
17675
17742
  * Absent on legacy rows ⇒ treat as `pipeline`.
17676
17743
  */
17677
17744
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
17745
+ /**
17746
+ * The confirmed zone crossing that produced an object event. Present ONLY on
17747
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
17748
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
17749
+ * appearance event carry none, so a rule asking for a direction fails closed
17750
+ * on them.
17751
+ *
17752
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
17753
+ * into its own event, so a frame in which a track enters A while leaving B
17754
+ * produces two events with two directions — never one ambiguous row.
17755
+ *
17756
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
17757
+ * membership the box has NOW, and by definition it no longer contains the zone
17758
+ * that was just left. Without the id here, a zone-scoped rule could never match
17759
+ * the exit it asked for.
17760
+ */
17761
+ var ZoneCrossingSchema = object({
17762
+ direction: _enum(["enter", "exit"]),
17763
+ /** Admin zone id crossed. */
17764
+ zoneId: string(),
17765
+ /** Zone display name at crossing time (falls back to the id). */
17766
+ zoneName: string().optional()
17767
+ });
17678
17768
  var ObjectEventSchema = object({
17679
17769
  ...BaseEventFields,
17680
17770
  kind: literal("object"),
@@ -17701,6 +17791,12 @@ var ObjectEventSchema = object({
17701
17791
  zones: array(string()).readonly().optional(),
17702
17792
  /** Omitted in slim projection. */
17703
17793
  state: TrackStateSchema.optional(),
17794
+ /**
17795
+ * The zone crossing this event IS, when it is one. Absent on every other
17796
+ * event kind (movement state, appearance, package) — see
17797
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
17798
+ */
17799
+ zoneCrossing: ZoneCrossingSchema.optional(),
17704
17800
  /** Detection-frame dimensions in pixels — let consumers normalize the
17705
17801
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
17706
17802
  frameWidth: number().optional(),
@@ -23025,6 +23121,12 @@ Object.freeze({
23025
23121
  addonId: null,
23026
23122
  access: "view"
23027
23123
  },
23124
+ "deviceManager.getDeviceStatusAggregateBatch": {
23125
+ capName: "device-manager",
23126
+ capScope: "system",
23127
+ addonId: null,
23128
+ access: "view"
23129
+ },
23028
23130
  "deviceManager.getLinkedDevices": {
23029
23131
  capName: "device-manager",
23030
23132
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -2,7 +2,7 @@ import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- //#region ../types/dist/event-category-BLcNejAE.mjs
5
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -273,6 +273,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
273
273
  */
274
274
  EventCategory["DeviceStateChanged"] = "device.state-changed";
275
275
  /**
276
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
277
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
278
+ *
279
+ * Emitted only on a change, so a steady scene is silent. It exists so a
280
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
281
+ * one live badge with no push signal at all, and it cost a request every
282
+ * four seconds per visible camera.
283
+ *
284
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
285
+ * keeps a slow reconcile rather than trusting it alone.
286
+ */
287
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
288
+ /**
276
289
  * Cap event fired by every device that registers the `battery`
277
290
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
278
291
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -9379,6 +9392,29 @@ var NcOccupancyConditionSchema = object({
9379
9392
  count: number().int().min(0).default(1),
9380
9393
  sustainSeconds: number().int().min(0).max(3600).default(15)
9381
9394
  });
9395
+ /**
9396
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9397
+ *
9398
+ * The values are not symmetric, and deliberately so — the absent value has to
9399
+ * mean exactly what every rule authored before this condition existed already
9400
+ * does:
9401
+ * - `enter` — entries and every NON-crossing record (movement state,
9402
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9403
+ * an operator who never asked for exits must not start receiving them.
9404
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9405
+ * fails closed, because "the car left the drive" is a question about a
9406
+ * boundary, not about a detection.
9407
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9408
+ *
9409
+ * A rule asking for a direction should normally also scope `zones`, which the
9410
+ * engine evaluates against the crossed zone as well as the current membership
9411
+ * (an exit's membership no longer contains the zone it just left).
9412
+ */
9413
+ var NcCrossingSchema = _enum([
9414
+ "enter",
9415
+ "exit",
9416
+ "any"
9417
+ ]);
9382
9418
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9383
9419
  var NcZoneConditionSchema = object({
9384
9420
  ids: array(string().min(1)).min(1),
@@ -9403,6 +9439,13 @@ var NcConditionsSchema = object({
9403
9439
  /** Veto zones — any hit fails the rule. */
9404
9440
  zonesExclude: array(string().min(1)).optional(),
9405
9441
  /**
9442
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9443
+ * and a closed track carries none, so a `track-end` rule asking for one
9444
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9445
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9446
+ */
9447
+ crossing: NcCrossingSchema.optional(),
9448
+ /**
9406
9449
  * Exact (case-insensitive) match on the record's collapsed `label`
9407
9450
  * (identity name / plate text / subclass).
9408
9451
  */
@@ -9591,11 +9634,31 @@ var NcMediaPolicySchema = object({
9591
9634
  */
9592
9635
  profile: CamProfileSchema.optional()
9593
9636
  });
9637
+ /**
9638
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9639
+ * notification suppresses.
9640
+ * - `shared` (default, and the absent value) — one window for the whole
9641
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9642
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9643
+ * at once and cat→cat still waits.
9644
+ *
9645
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9646
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9647
+ * see `cooldownKey` in the rule engine).
9648
+ */
9649
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9594
9650
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9595
9651
  var NcThrottleSchema = object({
9596
9652
  cooldownSec: number().int().min(0).max(86400).default(60),
9597
9653
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9598
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9654
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9655
+ /**
9656
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9657
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9658
+ * rule authored before this field simply carries none — and the engine
9659
+ * reads absent as `shared`, the pre-existing behaviour.
9660
+ */
9661
+ granularity: NcThrottleGranularitySchema.optional()
9599
9662
  });
9600
9663
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9601
9664
  var NcRuleInputSchema = object({
@@ -9703,6 +9766,7 @@ var NcConditionDescriptorSchema = object({
9703
9766
  "schedule",
9704
9767
  "plateMatcher",
9705
9768
  "packagePhase",
9769
+ "crossingSelect",
9706
9770
  "polygonDraw",
9707
9771
  "occupancy"
9708
9772
  ]),
@@ -16226,7 +16290,10 @@ method(object({
16226
16290
  }), method(object({
16227
16291
  deviceId: number(),
16228
16292
  caps: array(string()).readonly().optional()
16229
- }), record(string(), unknown().nullable()));
16293
+ }), record(string(), unknown().nullable())), method(object({
16294
+ deviceIds: array(number()).readonly(),
16295
+ caps: array(string()).readonly().optional()
16296
+ }), record(string(), record(string(), unknown().nullable())));
16230
16297
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
16231
16298
  deviceId: number(),
16232
16299
  capName: string()
@@ -17648,6 +17715,29 @@ var MotionEventSchema = object({
17648
17715
  * Absent on legacy rows ⇒ treat as `pipeline`.
17649
17716
  */
17650
17717
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
17718
+ /**
17719
+ * The confirmed zone crossing that produced an object event. Present ONLY on
17720
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
17721
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
17722
+ * appearance event carry none, so a rule asking for a direction fails closed
17723
+ * on them.
17724
+ *
17725
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
17726
+ * into its own event, so a frame in which a track enters A while leaving B
17727
+ * produces two events with two directions — never one ambiguous row.
17728
+ *
17729
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
17730
+ * membership the box has NOW, and by definition it no longer contains the zone
17731
+ * that was just left. Without the id here, a zone-scoped rule could never match
17732
+ * the exit it asked for.
17733
+ */
17734
+ var ZoneCrossingSchema = object({
17735
+ direction: _enum(["enter", "exit"]),
17736
+ /** Admin zone id crossed. */
17737
+ zoneId: string(),
17738
+ /** Zone display name at crossing time (falls back to the id). */
17739
+ zoneName: string().optional()
17740
+ });
17651
17741
  var ObjectEventSchema = object({
17652
17742
  ...BaseEventFields,
17653
17743
  kind: literal("object"),
@@ -17674,6 +17764,12 @@ var ObjectEventSchema = object({
17674
17764
  zones: array(string()).readonly().optional(),
17675
17765
  /** Omitted in slim projection. */
17676
17766
  state: TrackStateSchema.optional(),
17767
+ /**
17768
+ * The zone crossing this event IS, when it is one. Absent on every other
17769
+ * event kind (movement state, appearance, package) — see
17770
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
17771
+ */
17772
+ zoneCrossing: ZoneCrossingSchema.optional(),
17677
17773
  /** Detection-frame dimensions in pixels — let consumers normalize the
17678
17774
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
17679
17775
  frameWidth: number().optional(),
@@ -22998,6 +23094,12 @@ Object.freeze({
22998
23094
  addonId: null,
22999
23095
  access: "view"
23000
23096
  },
23097
+ "deviceManager.getDeviceStatusAggregateBatch": {
23098
+ capName: "device-manager",
23099
+ capScope: "system",
23100
+ addonId: null,
23101
+ access: "view"
23102
+ },
23001
23103
  "deviceManager.getLinkedDevices": {
23002
23104
  capName: "device-manager",
23003
23105
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",