@camstack/addon-advanced-notifier 0.1.12 → 0.1.14

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.d.mts CHANGED
@@ -1,8 +1,14 @@
1
- import { ICamstackAddon, AddonManifest, AddonContext, CapabilityProviderMap } from '@camstack/types';
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, ProviderRegistration } from '@camstack/types';
2
3
 
3
- declare class AdvancedNotifierAddon implements ICamstackAddon {
4
- readonly manifest: AddonManifest;
5
- private context;
4
+ interface NotifierConfig {
5
+ readonly defaultCooldownSeconds: number;
6
+ readonly maxHistoryEntries: number;
7
+ readonly auditLogRetentionDays: number;
8
+ readonly consoleOutputEnabled: boolean;
9
+ }
10
+ declare class AdvancedNotifierAddon extends BaseAddon<NotifierConfig> {
11
+ constructor();
6
12
  private ruleStore;
7
13
  private ruleEngine;
8
14
  private cooldown;
@@ -12,9 +18,9 @@ declare class AdvancedNotifierAddon implements ICamstackAddon {
12
18
  private auditLog;
13
19
  private unsubscribers;
14
20
  private readonly notifier;
15
- initialize(context: AddonContext): Promise<void>;
16
- shutdown(): Promise<void>;
17
- getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
21
+ protected onInitialize(): Promise<ProviderRegistration[]>;
22
+ protected onShutdown(): Promise<void>;
23
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
18
24
  private handleEvent;
19
25
  private buildTemplateVariables;
20
26
  private priorityToSeverity;
package/dist/addon.d.ts CHANGED
@@ -1,8 +1,14 @@
1
- import { ICamstackAddon, AddonManifest, AddonContext, CapabilityProviderMap } from '@camstack/types';
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, ProviderRegistration } from '@camstack/types';
2
3
 
3
- declare class AdvancedNotifierAddon implements ICamstackAddon {
4
- readonly manifest: AddonManifest;
5
- private context;
4
+ interface NotifierConfig {
5
+ readonly defaultCooldownSeconds: number;
6
+ readonly maxHistoryEntries: number;
7
+ readonly auditLogRetentionDays: number;
8
+ readonly consoleOutputEnabled: boolean;
9
+ }
10
+ declare class AdvancedNotifierAddon extends BaseAddon<NotifierConfig> {
11
+ constructor();
6
12
  private ruleStore;
7
13
  private ruleEngine;
8
14
  private cooldown;
@@ -12,9 +18,9 @@ declare class AdvancedNotifierAddon implements ICamstackAddon {
12
18
  private auditLog;
13
19
  private unsubscribers;
14
20
  private readonly notifier;
15
- initialize(context: AddonContext): Promise<void>;
16
- shutdown(): Promise<void>;
17
- getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
21
+ protected onInitialize(): Promise<ProviderRegistration[]>;
22
+ protected onShutdown(): Promise<void>;
23
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
18
24
  private handleEvent;
19
25
  private buildTemplateVariables;
20
26
  private priorityToSeverity;
package/dist/addon.js CHANGED
@@ -24,22 +24,23 @@ __export(addon_exports, {
24
24
  default: () => addon_default
25
25
  });
26
26
  module.exports = __toCommonJS(addon_exports);
27
+ var import_types = require("@camstack/types");
27
28
 
28
29
  // src/rules/rule-store.ts
29
30
  var COLLECTION = "addon-settings";
30
31
  var KEY = "advanced-notifier:rules";
31
32
  var RuleStore = class {
32
- backend;
33
- constructor(backend) {
34
- this.backend = backend;
33
+ api;
34
+ constructor(api) {
35
+ this.api = api;
35
36
  }
36
37
  async getRules() {
37
- const raw = await this.backend.get(COLLECTION, KEY);
38
+ const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: KEY });
38
39
  if (!raw || !Array.isArray(raw)) return [];
39
40
  return raw;
40
41
  }
41
42
  async saveRules(rules) {
42
- await this.backend.set(COLLECTION, KEY, [...rules]);
43
+ await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: KEY, value: [...rules] });
43
44
  }
44
45
  async upsertRule(rule) {
45
46
  const rules = await this.getRules();
@@ -71,8 +72,9 @@ var RuleEngine = class {
71
72
  const { conditions } = rule;
72
73
  const data = event.data;
73
74
  if (conditions.deviceIds?.length) {
74
- const deviceId = data["deviceId"] ?? event.source.id;
75
- if (!conditions.deviceIds.includes(deviceId)) return false;
75
+ const raw = data["deviceId"] ?? event.source.id;
76
+ const deviceId = typeof raw === "number" ? raw : Number(raw);
77
+ if (!Number.isFinite(deviceId) || !conditions.deviceIds.includes(deviceId)) return false;
76
78
  }
77
79
  if (conditions.classNames?.length) {
78
80
  const className = data["className"];
@@ -132,7 +134,8 @@ var ConsoleOutput = class {
132
134
  }
133
135
  async send(notification) {
134
136
  this.logger.info(
135
- `[${notification.severity}] ${notification.title}: ${notification.message}` + (notification.deviceId ? ` (device=${notification.deviceId})` : "")
137
+ `[${notification.severity}] ${notification.title}: ${notification.message}`,
138
+ notification.deviceId !== void 0 ? { tags: { deviceId: notification.deviceId } } : void 0
136
139
  );
137
140
  }
138
141
  async sendTest() {
@@ -151,17 +154,17 @@ var ConsoleOutput = class {
151
154
  var COLLECTION2 = "addon-settings";
152
155
  var LOG_KEY = "notifier-history:log";
153
156
  var AuditLog = class {
154
- backend;
157
+ api;
155
158
  maxEntries;
156
- constructor(backend, maxEntries = 1e3) {
157
- this.backend = backend;
159
+ constructor(api, maxEntries = 1e3) {
160
+ this.api = api;
158
161
  this.maxEntries = maxEntries;
159
162
  }
160
163
  async record(entry) {
161
164
  const entries = await this.getAll();
162
165
  const updated = [...entries, entry];
163
166
  const trimmed = updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated;
164
- await this.backend.set(COLLECTION2, LOG_KEY, trimmed);
167
+ await this.api.settingsStore.set.mutate({ collection: COLLECTION2, key: LOG_KEY, value: trimmed });
165
168
  }
166
169
  async getHistory(filter) {
167
170
  let entries = await this.getAll();
@@ -170,7 +173,7 @@ var AuditLog = class {
170
173
  }
171
174
  if (filter?.deviceId) {
172
175
  const deviceId = filter.deviceId;
173
- entries = entries.filter((e) => e["deviceId"] === deviceId);
176
+ entries = entries.filter((e) => e.deviceId === deviceId);
174
177
  }
175
178
  if (filter?.from !== void 0) {
176
179
  const from = filter.from;
@@ -186,7 +189,7 @@ var AuditLog = class {
186
189
  return entries;
187
190
  }
188
191
  async getAll() {
189
- const raw = await this.backend.get(COLLECTION2, LOG_KEY);
192
+ const raw = await this.api.settingsStore.get.query({ collection: COLLECTION2, key: LOG_KEY });
190
193
  if (!raw || !Array.isArray(raw)) return [];
191
194
  return raw;
192
195
  }
@@ -194,18 +197,21 @@ var AuditLog = class {
194
197
 
195
198
  // src/addon.ts
196
199
  var import_node_crypto = require("crypto");
197
- var EVENT_CATEGORIES = ["detection.raw", "detection.result", "detection.camera-native"];
198
- var AdvancedNotifierAddon = class {
199
- manifest = {
200
- id: "advanced-notifier",
201
- name: "Advanced Notifier",
202
- version: "0.1.0",
203
- capabilities: [
204
- { name: "advanced-notifier", mode: "singleton" },
205
- { name: "notification-output", mode: "collection" }
206
- ]
207
- };
208
- context;
200
+ var EVENT_CATEGORIES = [
201
+ import_types.EventCategory.DetectionRaw,
202
+ import_types.EventCategory.DetectionResult,
203
+ import_types.EventCategory.DetectionCameraNative
204
+ ];
205
+ var DEFAULT_NOTIFIER_CONFIG = {
206
+ defaultCooldownSeconds: 60,
207
+ maxHistoryEntries: 1e4,
208
+ auditLogRetentionDays: 30,
209
+ consoleOutputEnabled: true
210
+ };
211
+ var AdvancedNotifierAddon = class extends import_types.BaseAddon {
212
+ constructor() {
213
+ super({ ...DEFAULT_NOTIFIER_CONFIG });
214
+ }
209
215
  // Rule management
210
216
  ruleStore;
211
217
  ruleEngine;
@@ -234,21 +240,21 @@ var AdvancedNotifierAddon = class {
234
240
  this.cachedRules = [...this.cachedRules, rule];
235
241
  }
236
242
  }).catch((err) => {
237
- this.context.logger.error("Failed to upsert rule", { error: String(err) });
243
+ this.ctx.logger.error("Failed to upsert rule", { meta: { error: String(err) } });
238
244
  });
239
245
  },
240
246
  deleteRule: (ruleId) => {
241
247
  this.ruleStore.deleteRule(ruleId).then(() => {
242
248
  this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
243
249
  }).catch((err) => {
244
- this.context.logger.error("Failed to delete rule", { error: String(err) });
250
+ this.ctx.logger.error("Failed to delete rule", { tags: { ruleId }, meta: { error: String(err) } });
245
251
  });
246
252
  },
247
253
  testRule: async (ruleId, lookbackMinutes) => {
248
254
  const rule = this.cachedRules.find((r) => r.id === ruleId);
249
255
  if (!rule) return [];
250
256
  const since = new Date(Date.now() - lookbackMinutes * 60 * 1e3);
251
- const recentEvents = this.context.eventBus.getRecent(
257
+ const recentEvents = this.ctx.eventBus.getRecent(
252
258
  { category: void 0 },
253
259
  1e3
254
260
  ).filter((e) => e.timestamp >= since);
@@ -268,22 +274,22 @@ var AdvancedNotifierAddon = class {
268
274
  return this.auditLog.getHistory(filter);
269
275
  }
270
276
  };
271
- async initialize(context) {
272
- this.context = context;
277
+ async onInitialize() {
273
278
  this.ruleEngine = new RuleEngine();
274
279
  this.cooldown = new CooldownTracker();
275
- this.consoleOutput = new ConsoleOutput(context.logger.child("console-output"));
280
+ this.consoleOutput = new ConsoleOutput(this.ctx.logger.child("console-output"));
276
281
  this.outputs = [this.consoleOutput];
277
- if (context.settingsBackend) {
278
- this.ruleStore = new RuleStore(context.settingsBackend);
279
- this.auditLog = new AuditLog(context.settingsBackend);
282
+ const api = this.ctx.api;
283
+ if (api) {
284
+ this.ruleStore = new RuleStore(api);
285
+ this.auditLog = new AuditLog(api);
280
286
  this.cachedRules = await this.ruleStore.getRules();
281
- this.context.logger.info(`Loaded ${this.cachedRules.length} notification rules`);
287
+ this.ctx.logger.info("Loaded notification rules", { meta: { count: this.cachedRules.length } });
282
288
  } else {
283
- this.context.logger.warn("No settingsBackend \u2014 rules will not be persisted");
289
+ this.ctx.logger.warn("No ctx.api \u2014 rules will not be persisted");
284
290
  }
285
291
  for (const category of EVENT_CATEGORIES) {
286
- const unsubscribe = context.eventBus.subscribe(
292
+ const unsubscribe = this.ctx.eventBus.subscribe(
287
293
  { category },
288
294
  (event) => {
289
295
  void this.handleEvent(event);
@@ -291,42 +297,98 @@ var AdvancedNotifierAddon = class {
291
297
  );
292
298
  this.unsubscribers.push(unsubscribe);
293
299
  }
294
- this.context.logger.info("AdvancedNotifierAddon initialized");
300
+ this.ctx.logger.info("AdvancedNotifierAddon initialized");
301
+ return [
302
+ { capability: import_types.advancedNotifierCapability, provider: this.notifier },
303
+ { capability: import_types.notificationOutputCapability, provider: this.consoleOutput }
304
+ ];
295
305
  }
296
- async shutdown() {
306
+ async onShutdown() {
297
307
  for (const unsub of this.unsubscribers) {
298
308
  unsub();
299
309
  }
300
310
  this.unsubscribers = [];
301
- this.context.logger.info("AdvancedNotifierAddon shut down");
311
+ this.ctx.logger.info("AdvancedNotifierAddon shut down");
302
312
  }
303
- getCapabilityProvider(name) {
304
- if (name === "advanced-notifier") {
305
- return this.notifier;
306
- }
307
- if (name === "notification-output") {
308
- return this.consoleOutput;
309
- }
310
- return null;
313
+ // ── Config management (session 5 Sprint B7) ─────────────────────────
314
+ //
315
+ // Rules themselves are opaque JSON documents managed through the
316
+ // `upsertRule`/`deleteRule` cap methods — they do NOT belong in the
317
+ // schema-backed settings API. The schema below exposes addon-level
318
+ // tuning knobs only. All fields are global-scope because advanced-
319
+ // notifier is a system singleton with no per-device state.
320
+ // ── Three-level settings API (Phase 3) ──────────────────────────────
321
+ globalSettingsSchema() {
322
+ return this.schema({
323
+ sections: [{
324
+ id: "advanced-notifier-settings",
325
+ title: "Advanced Notifier",
326
+ columns: 2,
327
+ fields: [
328
+ this.field({
329
+ type: "number",
330
+ key: "defaultCooldownSeconds",
331
+ label: "Default Cooldown",
332
+ description: "Default cooldown between repeated notifications for the same rule.",
333
+ min: 0,
334
+ max: 3600,
335
+ step: 1,
336
+ default: 60,
337
+ unit: "s"
338
+ }),
339
+ this.field({
340
+ type: "number",
341
+ key: "maxHistoryEntries",
342
+ label: "Max History Entries",
343
+ description: "Maximum number of notification history entries to keep.",
344
+ min: 100,
345
+ max: 1e5,
346
+ step: 100,
347
+ default: 1e4
348
+ }),
349
+ this.field({
350
+ type: "number",
351
+ key: "auditLogRetentionDays",
352
+ label: "Audit Log Retention",
353
+ description: "How long to retain notification history entries.",
354
+ min: 1,
355
+ max: 365,
356
+ step: 1,
357
+ default: 30,
358
+ unit: "days"
359
+ }),
360
+ this.field({
361
+ type: "boolean",
362
+ key: "consoleOutputEnabled",
363
+ label: "Console Output Enabled",
364
+ description: "Emit matched rules to the server console.",
365
+ default: true
366
+ })
367
+ ]
368
+ }]
369
+ });
311
370
  }
312
371
  async handleEvent(event) {
313
372
  const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules);
314
373
  for (const rule of matchedRules) {
315
- const cooldownSeconds = rule.conditions.cooldownSeconds ?? 0;
374
+ const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds;
316
375
  if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {
317
- this.context.logger.debug(`Rule ${rule.id} skipped \u2014 in cooldown`);
376
+ this.ctx.logger.debug("Rule skipped \u2014 in cooldown", { meta: { ruleId: rule.id } });
318
377
  continue;
319
378
  }
320
379
  this.cooldown.recordFire(rule.id);
321
380
  const variables = this.buildTemplateVariables(event);
322
381
  const title = rule.template ? renderTemplate(rule.template.title, variables) : `CamStack Alert: ${rule.name}`;
323
382
  const message = rule.template ? renderTemplate(rule.template.body, variables) : `Event ${event.category} from ${event.source.id}`;
383
+ const rawDeviceId = event.data["deviceId"] ?? event.source.id;
384
+ const deviceIdNum = typeof rawDeviceId === "number" ? rawDeviceId : Number(rawDeviceId);
385
+ const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : void 0;
324
386
  const notification = {
325
387
  title,
326
388
  message,
327
389
  severity: this.priorityToSeverity(rule.priority),
328
390
  category: event.category,
329
- deviceId: event.data["deviceId"] ?? event.source.id,
391
+ deviceId,
330
392
  data: event.data,
331
393
  timestamp: event.timestamp.getTime()
332
394
  };
@@ -338,7 +400,7 @@ var AdvancedNotifierAddon = class {
338
400
  } catch (err) {
339
401
  success = false;
340
402
  dispatchError = String(err);
341
- this.context.logger.error(`Output ${output.id} failed`, { error: dispatchError });
403
+ this.ctx.logger.error("Output failed", { meta: { outputId: output.id, error: dispatchError } });
342
404
  }
343
405
  }
344
406
  if (this.auditLog) {
@@ -350,10 +412,11 @@ var AdvancedNotifierAddon = class {
350
412
  timestamp: event.timestamp.getTime(),
351
413
  outputs: this.outputs.map((o) => o.id),
352
414
  success,
353
- error: dispatchError
415
+ error: dispatchError,
416
+ deviceId
354
417
  };
355
418
  await this.auditLog.record(entry).catch((err) => {
356
- this.context.logger.error("Failed to record audit log entry", { error: String(err) });
419
+ this.ctx.logger.error("Failed to record audit log entry", { meta: { error: String(err) } });
357
420
  });
358
421
  }
359
422
  }
@@ -361,8 +424,8 @@ var AdvancedNotifierAddon = class {
361
424
  buildTemplateVariables(event) {
362
425
  const data = event.data;
363
426
  return {
364
- deviceId: data["deviceId"] ?? event.source.id,
365
- deviceName: data["deviceName"] ?? event.source.id,
427
+ deviceId: data["deviceId"] ?? String(event.source.id),
428
+ deviceName: data["deviceName"] ?? String(event.source.id),
366
429
  className: data["className"] ?? "",
367
430
  confidence: String(data["confidence"] ?? ""),
368
431
  zoneId: data["zoneId"] ?? "",
package/dist/addon.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/addon.ts","../src/rules/rule-store.ts","../src/rules/rule-engine.ts","../src/rules/cooldown.ts","../src/template/renderer.ts","../src/outputs/console.ts","../src/history/audit-log.ts"],"sourcesContent":["import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IAdvancedNotifier,\n INotificationOutput,\n NotificationRule,\n NotificationTestResult,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n SystemEvent,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { RuleStore } from './rules/rule-store.js'\nimport { RuleEngine } from './rules/rule-engine.js'\nimport { CooldownTracker } from './rules/cooldown.js'\nimport { renderTemplate } from './template/renderer.js'\nimport { ConsoleOutput } from './outputs/console.js'\nimport { AuditLog } from './history/audit-log.js'\nimport { randomUUID } from 'node:crypto'\n\nconst EVENT_CATEGORIES = ['detection.raw', 'detection.result', 'detection.camera-native'] as const\n\nexport class AdvancedNotifierAddon implements ICamstackAddon {\n readonly manifest: AddonManifest = {\n id: 'advanced-notifier',\n name: 'Advanced Notifier',\n version: '0.1.0',\n capabilities: [\n { name: 'advanced-notifier', mode: 'singleton' as const },\n { name: 'notification-output', mode: 'collection' as const },\n ],\n }\n\n private context!: AddonContext\n\n // Rule management\n private ruleStore!: RuleStore\n private ruleEngine!: RuleEngine\n private cooldown!: CooldownTracker\n private cachedRules: NotificationRule[] = []\n\n // Outputs\n private consoleOutput!: ConsoleOutput\n private outputs: INotificationOutput[] = []\n\n // Audit log\n private auditLog!: AuditLog\n\n // Event bus unsubscribe handles\n private unsubscribers: Array<() => void> = []\n\n // IAdvancedNotifier implementation (exposed as capability)\n private readonly notifier: IAdvancedNotifier = {\n getRules: () => this.cachedRules as readonly NotificationRule[],\n\n upsertRule: (rule: NotificationRule): void => {\n // Fire-and-forget with error logging\n this.ruleStore.upsertRule(rule).then(() => {\n const idx = this.cachedRules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n this.cachedRules = [\n ...this.cachedRules.slice(0, idx),\n rule,\n ...this.cachedRules.slice(idx + 1),\n ]\n } else {\n this.cachedRules = [...this.cachedRules, rule]\n }\n }).catch(err => {\n this.context.logger.error('Failed to upsert rule', { error: String(err) })\n })\n },\n\n deleteRule: (ruleId: string): void => {\n this.ruleStore.deleteRule(ruleId).then(() => {\n this.cachedRules = this.cachedRules.filter(r => r.id !== ruleId)\n }).catch(err => {\n this.context.logger.error('Failed to delete rule', { error: String(err) })\n })\n },\n\n testRule: async (ruleId: string, lookbackMinutes: number): Promise<NotificationTestResult[]> => {\n const rule = this.cachedRules.find(r => r.id === ruleId)\n if (!rule) return []\n const since = new Date(Date.now() - lookbackMinutes * 60 * 1000)\n const recentEvents = this.context.eventBus.getRecent(\n { category: undefined },\n 1000,\n ).filter(e => e.timestamp >= since)\n\n return recentEvents.map(event => {\n const matched = this.ruleEngine.evaluate(event, [rule])\n return {\n ruleId,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n wouldFire: matched.length > 0,\n reason: matched.length === 0 ? 'Conditions not met' : undefined,\n }\n })\n },\n\n getHistory: async (filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> => {\n if (!this.auditLog) return []\n return this.auditLog.getHistory(filter)\n },\n }\n\n async initialize(context: AddonContext): Promise<void> {\n this.context = context\n this.ruleEngine = new RuleEngine()\n this.cooldown = new CooldownTracker()\n this.consoleOutput = new ConsoleOutput(context.logger.child('console-output'))\n this.outputs = [this.consoleOutput]\n\n // Load rules from persistence if settings backend is available\n if (context.settingsBackend) {\n this.ruleStore = new RuleStore(context.settingsBackend)\n this.auditLog = new AuditLog(context.settingsBackend)\n this.cachedRules = await this.ruleStore.getRules()\n this.context.logger.info(`Loaded ${this.cachedRules.length} notification rules`)\n } else {\n this.context.logger.warn('No settingsBackend — rules will not be persisted')\n }\n\n // Subscribe to detection event categories\n for (const category of EVENT_CATEGORIES) {\n const unsubscribe = context.eventBus.subscribe(\n { category },\n (event: SystemEvent) => { void this.handleEvent(event) },\n )\n this.unsubscribers.push(unsubscribe)\n }\n\n this.context.logger.info('AdvancedNotifierAddon initialized')\n }\n\n async shutdown(): Promise<void> {\n for (const unsub of this.unsubscribers) {\n unsub()\n }\n this.unsubscribers = []\n this.context.logger.info('AdvancedNotifierAddon shut down')\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'advanced-notifier') {\n return this.notifier as CapabilityProviderMap[K]\n }\n if (name === 'notification-output') {\n return this.consoleOutput as CapabilityProviderMap[K]\n }\n return null\n }\n\n private async handleEvent(event: SystemEvent): Promise<void> {\n const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules)\n\n for (const rule of matchedRules) {\n const cooldownSeconds = rule.conditions.cooldownSeconds ?? 0\n if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {\n this.context.logger.debug(`Rule ${rule.id} skipped — in cooldown`)\n continue\n }\n this.cooldown.recordFire(rule.id)\n\n const variables = this.buildTemplateVariables(event)\n const title = rule.template\n ? renderTemplate(rule.template.title, variables)\n : `CamStack Alert: ${rule.name}`\n const message = rule.template\n ? renderTemplate(rule.template.body, variables)\n : `Event ${event.category} from ${event.source.id}`\n\n const notification = {\n title,\n message,\n severity: this.priorityToSeverity(rule.priority),\n category: event.category,\n deviceId: (event.data['deviceId'] as string | undefined) ?? event.source.id,\n data: event.data,\n timestamp: event.timestamp.getTime(),\n } as const\n\n let success = true\n let dispatchError: string | undefined\n\n for (const output of this.outputs) {\n try {\n await output.send(notification)\n } catch (err) {\n success = false\n dispatchError = String(err)\n this.context.logger.error(`Output ${output.id} failed`, { error: dispatchError })\n }\n }\n\n if (this.auditLog) {\n const entry: NotificationHistoryEntry = {\n id: randomUUID(),\n ruleId: rule.id,\n ruleName: rule.name,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n outputs: this.outputs.map(o => o.id),\n success,\n error: dispatchError,\n }\n await this.auditLog.record(entry).catch(err => {\n this.context.logger.error('Failed to record audit log entry', { error: String(err) })\n })\n }\n }\n }\n\n private buildTemplateVariables(event: SystemEvent): Record<string, string> {\n const data = event.data\n return {\n deviceId: (data['deviceId'] as string | undefined) ?? event.source.id,\n deviceName: (data['deviceName'] as string | undefined) ?? event.source.id,\n className: (data['className'] as string | undefined) ?? '',\n confidence: String(data['confidence'] ?? ''),\n zoneId: (data['zoneId'] as string | undefined) ?? '',\n zoneName: (data['zoneName'] as string | undefined) ?? '',\n category: event.category,\n timestamp: event.timestamp.toISOString(),\n }\n }\n\n private priorityToSeverity(\n priority: NotificationRule['priority'],\n ): 'info' | 'warning' | 'critical' {\n if (priority === 'critical') return 'critical'\n if (priority === 'high') return 'warning'\n return 'info'\n }\n}\n\nexport default AdvancedNotifierAddon\n","import type { ISettingsBackend, NotificationRule } from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst KEY = 'advanced-notifier:rules'\n\nexport class RuleStore {\n private readonly backend: ISettingsBackend\n\n constructor(backend: ISettingsBackend) {\n this.backend = backend\n }\n\n async getRules(): Promise<NotificationRule[]> {\n const raw = await this.backend.get(COLLECTION, KEY)\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationRule[]\n }\n\n async saveRules(rules: readonly NotificationRule[]): Promise<void> {\n await this.backend.set(COLLECTION, KEY, [...rules])\n }\n\n async upsertRule(rule: NotificationRule): Promise<void> {\n const rules = await this.getRules()\n const idx = rules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n rules[idx] = rule\n } else {\n rules.push(rule)\n }\n await this.saveRules(rules)\n }\n\n async deleteRule(ruleId: string): Promise<void> {\n const rules = await this.getRules()\n await this.saveRules(rules.filter(r => r.id !== ruleId))\n }\n}\n","import type { NotificationRule } from '@camstack/types'\n\ninterface EventData {\n readonly id: string\n readonly timestamp: Date\n readonly source: { readonly type: string; readonly id: string }\n readonly category: string\n readonly data: Record<string, unknown>\n}\n\nexport class RuleEngine {\n evaluate(event: EventData, rules: readonly NotificationRule[]): NotificationRule[] {\n return rules.filter(rule => this.matchesRule(event, rule))\n }\n\n private matchesRule(event: EventData, rule: NotificationRule): boolean {\n if (!rule.enabled) return false\n if (!rule.eventTypes.includes(event.category)) return false\n return this.evaluateConditions(event, rule)\n }\n\n private evaluateConditions(event: EventData, rule: NotificationRule): boolean {\n const { conditions } = rule\n const data = event.data\n\n if (conditions.deviceIds?.length) {\n const deviceId = (data['deviceId'] as string) ?? event.source.id\n if (!conditions.deviceIds.includes(deviceId)) return false\n }\n\n if (conditions.classNames?.length) {\n const className = data['className'] as string | undefined\n if (!className || !conditions.classNames.includes(className)) return false\n }\n\n if (conditions.zoneIds?.length) {\n const zoneId = data['zoneId'] as string | undefined\n if (!zoneId || !conditions.zoneIds.includes(zoneId)) return false\n }\n\n if (conditions.minConfidence !== undefined) {\n const confidence = data['confidence'] as number | undefined\n if (confidence === undefined || confidence < conditions.minConfidence) return false\n }\n\n if (conditions.schedule) {\n const now = new Date()\n const { days, startHour, endHour } = conditions.schedule\n if (!days.includes(now.getDay())) return false\n const hour = now.getHours()\n if (hour < startHour || hour >= endHour) return false\n }\n\n return true\n }\n}\n","export class CooldownTracker {\n private readonly lastFired = new Map<string, number>()\n\n canFire(ruleId: string, cooldownSeconds: number): boolean {\n const last = this.lastFired.get(ruleId)\n if (last === undefined) return true\n return (Date.now() - last) / 1000 >= cooldownSeconds\n }\n\n recordFire(ruleId: string): void {\n this.lastFired.set(ruleId, Date.now())\n }\n\n reset(): void {\n this.lastFired.clear()\n }\n}\n","export function renderTemplate(template: string, variables: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value: string | undefined = variables[key]\n return value !== undefined ? value : match\n })\n}\n","import type { INotificationOutput, Notification, IScopedLogger } from '@camstack/types'\n\nexport class ConsoleOutput implements INotificationOutput {\n readonly id = 'console'\n readonly name = 'Console Log'\n readonly icon = 'terminal'\n\n private readonly logger: IScopedLogger\n\n constructor(logger: IScopedLogger) {\n this.logger = logger\n }\n\n async send(notification: Notification): Promise<void> {\n this.logger.info(\n `[${notification.severity}] ${notification.title}: ${notification.message}` +\n (notification.deviceId ? ` (device=${notification.deviceId})` : ''),\n )\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n await this.send({\n title: 'Test',\n message: 'Console output working',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n }\n}\n","import type {\n ISettingsBackend,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n} from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst LOG_KEY = 'notifier-history:log'\n\nexport class AuditLog {\n private readonly backend: ISettingsBackend\n private readonly maxEntries: number\n\n constructor(backend: ISettingsBackend, maxEntries = 1000) {\n this.backend = backend\n this.maxEntries = maxEntries\n }\n\n async record(entry: NotificationHistoryEntry): Promise<void> {\n const entries = await this.getAll()\n const updated = [...entries, entry]\n const trimmed =\n updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated\n await this.backend.set(COLLECTION, LOG_KEY, trimmed)\n }\n\n async getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> {\n let entries = await this.getAll()\n if (filter?.ruleId) {\n entries = entries.filter(e => e.ruleId === filter.ruleId)\n }\n if (filter?.deviceId) {\n const deviceId = filter.deviceId\n entries = entries.filter(e => (e as unknown as Record<string, unknown>)['deviceId'] === deviceId)\n }\n if (filter?.from !== undefined) {\n const from = filter.from\n entries = entries.filter(e => e.timestamp >= from)\n }\n if (filter?.to !== undefined) {\n const to = filter.to\n entries = entries.filter(e => e.timestamp <= to)\n }\n if (filter?.limit) {\n entries = entries.slice(-filter.limit)\n }\n return entries\n }\n\n private async getAll(): Promise<NotificationHistoryEntry[]> {\n const raw = await this.backend.get(COLLECTION, LOG_KEY)\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationHistoryEntry[]\n }\n}\n"],"mappings":"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","names":["COLLECTION"]}
1
+ {"version":3,"sources":["../src/addon.ts","../src/rules/rule-store.ts","../src/rules/rule-engine.ts","../src/rules/cooldown.ts","../src/template/renderer.ts","../src/outputs/console.ts","../src/history/audit-log.ts"],"sourcesContent":["import type {\n ProviderRegistration,\n IAdvancedNotifier,\n INotificationOutput,\n NotificationRule,\n NotificationTestResult,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n SystemEvent,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, advancedNotifierCapability, notificationOutputCapability } from '@camstack/types'\nimport { RuleStore } from './rules/rule-store.js'\nimport { RuleEngine } from './rules/rule-engine.js'\nimport { CooldownTracker } from './rules/cooldown.js'\nimport { renderTemplate } from './template/renderer.js'\nimport { ConsoleOutput } from './outputs/console.js'\nimport { AuditLog } from './history/audit-log.js'\nimport { randomUUID } from 'node:crypto'\n\nconst EVENT_CATEGORIES = [\n EventCategory.DetectionRaw,\n EventCategory.DetectionResult,\n EventCategory.DetectionCameraNative,\n] as const\n\ninterface NotifierConfig {\n readonly defaultCooldownSeconds: number\n readonly maxHistoryEntries: number\n readonly auditLogRetentionDays: number\n readonly consoleOutputEnabled: boolean\n}\n\nconst DEFAULT_NOTIFIER_CONFIG: NotifierConfig = {\n defaultCooldownSeconds: 60,\n maxHistoryEntries: 10000,\n auditLogRetentionDays: 30,\n consoleOutputEnabled: true,\n}\n\nexport class AdvancedNotifierAddon extends BaseAddon<NotifierConfig> {\n constructor() {\n super({ ...DEFAULT_NOTIFIER_CONFIG })\n }\n\n // Rule management\n private ruleStore!: RuleStore\n private ruleEngine!: RuleEngine\n private cooldown!: CooldownTracker\n private cachedRules: NotificationRule[] = []\n\n // Outputs\n private consoleOutput!: ConsoleOutput\n private outputs: INotificationOutput[] = []\n\n // Audit log\n private auditLog!: AuditLog\n\n // Event bus unsubscribe handles\n private unsubscribers: Array<() => void> = []\n\n // IAdvancedNotifier implementation (exposed as capability)\n private readonly notifier: IAdvancedNotifier = {\n getRules: () => this.cachedRules as readonly NotificationRule[],\n\n upsertRule: (rule: NotificationRule): void => {\n // Fire-and-forget with error logging\n this.ruleStore.upsertRule(rule).then(() => {\n const idx = this.cachedRules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n this.cachedRules = [\n ...this.cachedRules.slice(0, idx),\n rule,\n ...this.cachedRules.slice(idx + 1),\n ]\n } else {\n this.cachedRules = [...this.cachedRules, rule]\n }\n }).catch(err => {\n this.ctx.logger.error('Failed to upsert rule', { meta: { error: String(err) } })\n })\n },\n\n deleteRule: (ruleId: string): void => {\n this.ruleStore.deleteRule(ruleId).then(() => {\n this.cachedRules = this.cachedRules.filter(r => r.id !== ruleId)\n }).catch(err => {\n this.ctx.logger.error('Failed to delete rule', { tags: { ruleId }, meta: { error: String(err) } })\n })\n },\n\n testRule: async (ruleId: string, lookbackMinutes: number): Promise<NotificationTestResult[]> => {\n const rule = this.cachedRules.find(r => r.id === ruleId)\n if (!rule) return []\n const since = new Date(Date.now() - lookbackMinutes * 60 * 1000)\n const recentEvents = this.ctx.eventBus.getRecent(\n { category: undefined },\n 1000,\n ).filter(e => e.timestamp >= since)\n\n return recentEvents.map(event => {\n const matched = this.ruleEngine.evaluate(event, [rule])\n return {\n ruleId,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n wouldFire: matched.length > 0,\n reason: matched.length === 0 ? 'Conditions not met' : undefined,\n }\n })\n },\n\n getHistory: async (filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> => {\n if (!this.auditLog) return []\n return this.auditLog.getHistory(filter)\n },\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ruleEngine = new RuleEngine()\n this.cooldown = new CooldownTracker()\n this.consoleOutput = new ConsoleOutput(this.ctx.logger.child('console-output'))\n this.outputs = [this.consoleOutput]\n\n const api = this.ctx.api\n if (api) {\n this.ruleStore = new RuleStore(api)\n this.auditLog = new AuditLog(api)\n this.cachedRules = await this.ruleStore.getRules()\n this.ctx.logger.info('Loaded notification rules', { meta: { count: this.cachedRules.length } })\n } else {\n this.ctx.logger.warn('No ctx.api — rules will not be persisted')\n }\n\n // Subscribe to detection event categories\n for (const category of EVENT_CATEGORIES) {\n const unsubscribe = this.ctx.eventBus.subscribe(\n { category },\n (event: SystemEvent) => { void this.handleEvent(event) },\n )\n this.unsubscribers.push(unsubscribe)\n }\n\n this.ctx.logger.info('AdvancedNotifierAddon initialized')\n\n return [\n { capability: advancedNotifierCapability, provider: this.notifier },\n { capability: notificationOutputCapability, provider: this.consoleOutput },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const unsub of this.unsubscribers) {\n unsub()\n }\n this.unsubscribers = []\n this.ctx.logger.info('AdvancedNotifierAddon shut down')\n }\n\n // ── Config management (session 5 Sprint B7) ─────────────────────────\n //\n // Rules themselves are opaque JSON documents managed through the\n // `upsertRule`/`deleteRule` cap methods — they do NOT belong in the\n // schema-backed settings API. The schema below exposes addon-level\n // tuning knobs only. All fields are global-scope because advanced-\n // notifier is a system singleton with no per-device state.\n\n // ── Three-level settings API (Phase 3) ──────────────────────────────\n protected globalSettingsSchema() {\n return this.schema({\n sections: [{\n id: 'advanced-notifier-settings',\n title: 'Advanced Notifier',\n columns: 2,\n fields: [\n this.field({\n type: 'number', key: 'defaultCooldownSeconds', label: 'Default Cooldown',\n description: 'Default cooldown between repeated notifications for the same rule.',\n min: 0, max: 3600, step: 1, default: 60, unit: 's',\n }),\n this.field({\n type: 'number', key: 'maxHistoryEntries', label: 'Max History Entries',\n description: 'Maximum number of notification history entries to keep.',\n min: 100, max: 100000, step: 100, default: 10000,\n }),\n this.field({\n type: 'number', key: 'auditLogRetentionDays', label: 'Audit Log Retention',\n description: 'How long to retain notification history entries.',\n min: 1, max: 365, step: 1, default: 30, unit: 'days',\n }),\n this.field({\n type: 'boolean', key: 'consoleOutputEnabled', label: 'Console Output Enabled',\n description: 'Emit matched rules to the server console.',\n default: true,\n }),\n ],\n }],\n })\n }\n\n private async handleEvent(event: SystemEvent): Promise<void> {\n const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules)\n\n for (const rule of matchedRules) {\n const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds\n if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {\n this.ctx.logger.debug('Rule skipped — in cooldown', { meta: { ruleId: rule.id } })\n continue\n }\n this.cooldown.recordFire(rule.id)\n\n const variables = this.buildTemplateVariables(event)\n const title = rule.template\n ? renderTemplate(rule.template.title, variables)\n : `CamStack Alert: ${rule.name}`\n const message = rule.template\n ? renderTemplate(rule.template.body, variables)\n : `Event ${event.category} from ${event.source.id}`\n\n const rawDeviceId = event.data['deviceId'] ?? event.source.id\n const deviceIdNum = typeof rawDeviceId === 'number' ? rawDeviceId : Number(rawDeviceId)\n const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : undefined\n const notification = {\n title,\n message,\n severity: this.priorityToSeverity(rule.priority),\n category: event.category,\n deviceId,\n data: event.data,\n timestamp: event.timestamp.getTime(),\n } as const\n\n let success = true\n let dispatchError: string | undefined\n\n for (const output of this.outputs) {\n try {\n await output.send(notification)\n } catch (err) {\n success = false\n dispatchError = String(err)\n this.ctx.logger.error('Output failed', { meta: { outputId: output.id, error: dispatchError } })\n }\n }\n\n if (this.auditLog) {\n const entry: NotificationHistoryEntry = {\n id: randomUUID(),\n ruleId: rule.id,\n ruleName: rule.name,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n outputs: this.outputs.map(o => o.id),\n success,\n error: dispatchError,\n deviceId,\n }\n await this.auditLog.record(entry).catch(err => {\n this.ctx.logger.error('Failed to record audit log entry', { meta: { error: String(err) } })\n })\n }\n }\n }\n\n private buildTemplateVariables(event: SystemEvent): Record<string, string> {\n const data = event.data\n return {\n deviceId: (data['deviceId'] as string | undefined) ?? String(event.source.id),\n deviceName: (data['deviceName'] as string | undefined) ?? String(event.source.id),\n className: (data['className'] as string | undefined) ?? '',\n confidence: String(data['confidence'] ?? ''),\n zoneId: (data['zoneId'] as string | undefined) ?? '',\n zoneName: (data['zoneName'] as string | undefined) ?? '',\n category: event.category,\n timestamp: event.timestamp.toISOString(),\n }\n }\n\n private priorityToSeverity(\n priority: NotificationRule['priority'],\n ): 'info' | 'warning' | 'critical' {\n if (priority === 'critical') return 'critical'\n if (priority === 'high') return 'warning'\n return 'info'\n }\n}\n\nexport default AdvancedNotifierAddon\n","import type { AddonApi, NotificationRule } from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst KEY = 'advanced-notifier:rules'\n\nexport class RuleStore {\n private readonly api: AddonApi\n\n constructor(api: AddonApi) {\n this.api = api\n }\n\n async getRules(): Promise<NotificationRule[]> {\n const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: KEY })\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationRule[]\n }\n\n async saveRules(rules: readonly NotificationRule[]): Promise<void> {\n await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: KEY, value: [...rules] })\n }\n\n async upsertRule(rule: NotificationRule): Promise<void> {\n const rules = await this.getRules()\n const idx = rules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n rules[idx] = rule\n } else {\n rules.push(rule)\n }\n await this.saveRules(rules)\n }\n\n async deleteRule(ruleId: string): Promise<void> {\n const rules = await this.getRules()\n await this.saveRules(rules.filter(r => r.id !== ruleId))\n }\n}\n","import type { NotificationRule } from '@camstack/types'\n\ninterface EventData {\n readonly id: string\n readonly timestamp: Date\n readonly source: { readonly type: string; readonly id: string | number }\n readonly category: string\n readonly data: Record<string, unknown>\n}\n\nexport class RuleEngine {\n evaluate(event: EventData, rules: readonly NotificationRule[]): NotificationRule[] {\n return rules.filter(rule => this.matchesRule(event, rule))\n }\n\n private matchesRule(event: EventData, rule: NotificationRule): boolean {\n if (!rule.enabled) return false\n if (!rule.eventTypes.includes(event.category)) return false\n return this.evaluateConditions(event, rule)\n }\n\n private evaluateConditions(event: EventData, rule: NotificationRule): boolean {\n const { conditions } = rule\n const data = event.data\n\n if (conditions.deviceIds?.length) {\n const raw = data['deviceId'] ?? event.source.id\n const deviceId = typeof raw === 'number' ? raw : Number(raw)\n if (!Number.isFinite(deviceId) || !conditions.deviceIds.includes(deviceId)) return false\n }\n\n if (conditions.classNames?.length) {\n const className = data['className'] as string | undefined\n if (!className || !conditions.classNames.includes(className)) return false\n }\n\n if (conditions.zoneIds?.length) {\n const zoneId = data['zoneId'] as string | undefined\n if (!zoneId || !conditions.zoneIds.includes(zoneId)) return false\n }\n\n if (conditions.minConfidence !== undefined) {\n const confidence = data['confidence'] as number | undefined\n if (confidence === undefined || confidence < conditions.minConfidence) return false\n }\n\n if (conditions.schedule) {\n const now = new Date()\n const { days, startHour, endHour } = conditions.schedule\n if (!days.includes(now.getDay())) return false\n const hour = now.getHours()\n if (hour < startHour || hour >= endHour) return false\n }\n\n return true\n }\n}\n","export class CooldownTracker {\n private readonly lastFired = new Map<string, number>()\n\n canFire(ruleId: string, cooldownSeconds: number): boolean {\n const last = this.lastFired.get(ruleId)\n if (last === undefined) return true\n return (Date.now() - last) / 1000 >= cooldownSeconds\n }\n\n recordFire(ruleId: string): void {\n this.lastFired.set(ruleId, Date.now())\n }\n\n reset(): void {\n this.lastFired.clear()\n }\n}\n","export function renderTemplate(template: string, variables: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value: string | undefined = variables[key]\n return value !== undefined ? value : match\n })\n}\n","import type { INotificationOutput, Notification, IScopedLogger } from '@camstack/types'\n\nexport class ConsoleOutput implements INotificationOutput {\n readonly id = 'console'\n readonly name = 'Console Log'\n readonly icon = 'terminal'\n\n private readonly logger: IScopedLogger\n\n constructor(logger: IScopedLogger) {\n this.logger = logger\n }\n\n async send(notification: Notification): Promise<void> {\n this.logger.info(\n `[${notification.severity}] ${notification.title}: ${notification.message}`,\n notification.deviceId !== undefined\n ? { tags: { deviceId: notification.deviceId } }\n : undefined,\n )\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n await this.send({\n title: 'Test',\n message: 'Console output working',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n }\n}\n","import type {\n AddonApi,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n} from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst LOG_KEY = 'notifier-history:log'\n\nexport class AuditLog {\n private readonly api: AddonApi\n private readonly maxEntries: number\n\n constructor(api: AddonApi, maxEntries = 1000) {\n this.api = api\n this.maxEntries = maxEntries\n }\n\n async record(entry: NotificationHistoryEntry): Promise<void> {\n const entries = await this.getAll()\n const updated = [...entries, entry]\n const trimmed =\n updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated\n await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: LOG_KEY, value: trimmed })\n }\n\n async getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> {\n let entries = await this.getAll()\n if (filter?.ruleId) {\n entries = entries.filter(e => e.ruleId === filter.ruleId)\n }\n if (filter?.deviceId) {\n const deviceId = filter.deviceId\n entries = entries.filter(e => e.deviceId === deviceId)\n }\n if (filter?.from !== undefined) {\n const from = filter.from\n entries = entries.filter(e => e.timestamp >= from)\n }\n if (filter?.to !== undefined) {\n const to = filter.to\n entries = entries.filter(e => e.timestamp <= to)\n }\n if (filter?.limit) {\n entries = entries.slice(-filter.limit)\n }\n return entries\n }\n\n private async getAll(): Promise<NotificationHistoryEntry[]> {\n const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: LOG_KEY })\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationHistoryEntry[]\n }\n}\n"],"mappings":"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","names":["COLLECTION"]}
package/dist/addon.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AdvancedNotifierAddon,
3
3
  addon_default
4
- } from "./chunk-HYUFV4O5.mjs";
4
+ } from "./chunk-MPVMHQWM.mjs";
5
5
  export {
6
6
  AdvancedNotifierAddon,
7
7
  addon_default as default