@mongrov/analytics 0.1.0-alpha.7 → 0.1.0-alpha.8

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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/rules/__fakes__/fakeClock.d.ts +7 -0
  3. package/dist/rules/__fakes__/fakeClock.d.ts.map +1 -0
  4. package/dist/rules/__fakes__/fakeClock.js +13 -0
  5. package/dist/rules/__fakes__/fakeClock.js.map +1 -0
  6. package/dist/rules/__fakes__/fakeEngine.d.ts +12 -0
  7. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -0
  8. package/dist/rules/__fakes__/fakeEngine.js +33 -0
  9. package/dist/rules/__fakes__/fakeEngine.js.map +1 -0
  10. package/dist/rules/__fakes__/fakeStorage.d.ts +7 -0
  11. package/dist/rules/__fakes__/fakeStorage.d.ts.map +1 -0
  12. package/dist/rules/__fakes__/fakeStorage.js +22 -0
  13. package/dist/rules/__fakes__/fakeStorage.js.map +1 -0
  14. package/dist/rules/compiler-cache.d.ts +16 -0
  15. package/dist/rules/compiler-cache.d.ts.map +1 -0
  16. package/dist/rules/compiler-cache.js +29 -0
  17. package/dist/rules/compiler-cache.js.map +1 -0
  18. package/dist/rules/compiler.d.ts +27 -0
  19. package/dist/rules/compiler.d.ts.map +1 -0
  20. package/dist/rules/compiler.js +210 -0
  21. package/dist/rules/compiler.js.map +1 -0
  22. package/dist/rules/defaults/index.d.ts +13 -0
  23. package/dist/rules/defaults/index.d.ts.map +1 -0
  24. package/dist/rules/defaults/index.js +13 -0
  25. package/dist/rules/defaults/index.js.map +1 -0
  26. package/dist/rules/defaults/loader.d.ts +18 -0
  27. package/dist/rules/defaults/loader.d.ts.map +1 -0
  28. package/dist/rules/defaults/loader.js +37 -0
  29. package/dist/rules/defaults/loader.js.map +1 -0
  30. package/dist/rules/defaults/luminx.d.ts +6 -0
  31. package/dist/rules/defaults/luminx.d.ts.map +1 -0
  32. package/dist/rules/defaults/luminx.js +64 -0
  33. package/dist/rules/defaults/luminx.js.map +1 -0
  34. package/dist/rules/defaults/viva.d.ts +6 -0
  35. package/dist/rules/defaults/viva.d.ts.map +1 -0
  36. package/dist/rules/defaults/viva.js +45 -0
  37. package/dist/rules/defaults/viva.js.map +1 -0
  38. package/dist/rules/defaults/yogaring.d.ts +6 -0
  39. package/dist/rules/defaults/yogaring.d.ts.map +1 -0
  40. package/dist/rules/defaults/yogaring.js +45 -0
  41. package/dist/rules/defaults/yogaring.js.map +1 -0
  42. package/dist/rules/defaults/ziva.d.ts +10 -0
  43. package/dist/rules/defaults/ziva.d.ts.map +1 -0
  44. package/dist/rules/defaults/ziva.js +87 -0
  45. package/dist/rules/defaults/ziva.js.map +1 -0
  46. package/dist/rules/emitter.d.ts +20 -0
  47. package/dist/rules/emitter.d.ts.map +1 -0
  48. package/dist/rules/emitter.js +32 -0
  49. package/dist/rules/emitter.js.map +1 -0
  50. package/dist/rules/evaluator.d.ts +44 -0
  51. package/dist/rules/evaluator.d.ts.map +1 -0
  52. package/dist/rules/evaluator.js +127 -0
  53. package/dist/rules/evaluator.js.map +1 -0
  54. package/dist/rules/factory.d.ts +13 -0
  55. package/dist/rules/factory.d.ts.map +1 -0
  56. package/dist/rules/factory.js +56 -0
  57. package/dist/rules/factory.js.map +1 -0
  58. package/dist/rules/hooks/useRuleRegistry.d.ts +16 -0
  59. package/dist/rules/hooks/useRuleRegistry.d.ts.map +1 -0
  60. package/dist/rules/hooks/useRuleRegistry.js +26 -0
  61. package/dist/rules/hooks/useRuleRegistry.js.map +1 -0
  62. package/dist/rules/hooks/useRuleViolations.d.ts +21 -0
  63. package/dist/rules/hooks/useRuleViolations.d.ts.map +1 -0
  64. package/dist/rules/hooks/useRuleViolations.js +32 -0
  65. package/dist/rules/hooks/useRuleViolations.js.map +1 -0
  66. package/dist/rules/index.d.ts +30 -1
  67. package/dist/rules/index.d.ts.map +1 -1
  68. package/dist/rules/index.js +36 -1
  69. package/dist/rules/index.js.map +1 -1
  70. package/dist/rules/registry.d.ts +32 -0
  71. package/dist/rules/registry.d.ts.map +1 -0
  72. package/dist/rules/registry.js +98 -0
  73. package/dist/rules/registry.js.map +1 -0
  74. package/dist/rules/schema.d.ts +221 -0
  75. package/dist/rules/schema.d.ts.map +1 -0
  76. package/dist/rules/schema.js +80 -0
  77. package/dist/rules/schema.js.map +1 -0
  78. package/dist/rules/throttle.d.ts +27 -0
  79. package/dist/rules/throttle.d.ts.map +1 -0
  80. package/dist/rules/throttle.js +76 -0
  81. package/dist/rules/throttle.js.map +1 -0
  82. package/dist/rules/types.d.ts +88 -0
  83. package/dist/rules/types.d.ts.map +1 -0
  84. package/dist/rules/types.js +9 -0
  85. package/dist/rules/types.js.map +1 -0
  86. package/dist/rules/validator.d.ts +27 -0
  87. package/dist/rules/validator.d.ts.map +1 -0
  88. package/dist/rules/validator.js +97 -0
  89. package/dist/rules/validator.js.map +1 -0
  90. package/dist/sync/factory.d.ts +8 -0
  91. package/dist/sync/factory.d.ts.map +1 -1
  92. package/dist/sync/factory.js +31 -1
  93. package/dist/sync/factory.js.map +1 -1
  94. package/dist/sync/flusher.d.ts +6 -0
  95. package/dist/sync/flusher.d.ts.map +1 -1
  96. package/dist/sync/flusher.js +13 -1
  97. package/dist/sync/flusher.js.map +1 -1
  98. package/package.json +17 -16
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `useRuleViolations(engine, options?)` — session-scoped violation feed.
3
+ *
4
+ * Subscribes to `engine.on('violation', ...)` for the lifetime of the
5
+ * component and buffers the most-recent `limit` violations in state. On
6
+ * unmount the subscription tears down.
7
+ *
8
+ * React is an optional peer of `@mongrov/analytics`; this file is only
9
+ * imported when the app pulls the `/rules` subpath.
10
+ */
11
+ import { useEffect, useState, useCallback } from 'react';
12
+ const DEFAULT_LIMIT = 50;
13
+ export function useRuleViolations(engine, options) {
14
+ const limit = options?.limit ?? DEFAULT_LIMIT;
15
+ const [violations, setViolations] = useState([]);
16
+ useEffect(() => {
17
+ const unsub = engine.on('violation', (v) => {
18
+ setViolations((prev) => {
19
+ const next = [v, ...prev];
20
+ if (next.length > limit)
21
+ next.length = limit;
22
+ return next;
23
+ });
24
+ });
25
+ return unsub;
26
+ }, [engine, limit]);
27
+ const clear = useCallback(() => {
28
+ setViolations([]);
29
+ }, []);
30
+ return { violations, clear };
31
+ }
32
+ //# sourceMappingURL=useRuleViolations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRuleViolations.js","sourceRoot":"","sources":["../../../src/rules/hooks/useRuleViolations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGxD,MAAM,aAAa,GAAG,EAAE,CAAA;AAYxB,MAAM,UAAU,iBAAiB,CAC/B,MAAmB,EACnB,OAAkC;IAElC,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,aAAa,CAAA;IAC7C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAkB,EAAE,CAAC,CAAA;IAEjE,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;YACzC,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE;gBACrB,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAA;gBACzB,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK;oBAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;gBAC5C,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,OAAO,KAAK,CAAA;IACd,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;IAEnB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,aAAa,CAAC,EAAE,CAAC,CAAA;IACnB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAA;AAC9B,CAAC"}
@@ -1,2 +1,31 @@
1
- export {};
1
+ /**
2
+ * @mongrov/analytics/rules — structured threshold rule engine.
3
+ *
4
+ * See `README.md` for authoring rules, sampling minimums, brand default
5
+ * catalogs, and hooks. Consumers typically:
6
+ *
7
+ * ```ts
8
+ * import {
9
+ * createRulesEngine,
10
+ * defaults,
11
+ * } from '@mongrov/analytics/rules';
12
+ *
13
+ * const engine = createRulesEngine({ analytics, storage, brand: 'ziva', ... });
14
+ * await engine.register(defaults.zivaDefaults);
15
+ * engine.on('violation', (v) => notify(v));
16
+ * ```
17
+ */
18
+ export { RuleSchema, RuleValidationError, TargetSchema, ThrottleSchema, AGGREGATIONS, COMPARES, SEVERITIES, WINDOWS, } from './schema';
19
+ export type { Aggregation, Compare, Rule, Severity, Target, Throttle, Window, } from './schema';
20
+ export type { Clock, CompiledRule, FlushSummary, RuleSeverity, RuleViolation, RulesEngine, RulesEngineConfig, RulesLogger, Unsubscribe, } from './types';
21
+ export { allowedWindowsFor, validateRule } from './validator';
22
+ export { compileRule, sanitizeIdent } from './compiler';
23
+ export { createCompilerCache, type CompilerCache } from './compiler-cache';
24
+ export { createRulesRegistry, type CreateRegistryConfig, type RulesRegistry, } from './registry';
25
+ export { createThrottleStore, type CreateThrottleConfig, type ThrottleStore, } from './throttle';
26
+ export { createEmitter, type CreateEmitterConfig, type RulesEmitter, } from './emitter';
27
+ export { createRulesEngine } from './factory';
28
+ export * as defaults from './defaults';
29
+ export { useRuleViolations, type UseRuleViolationsOptions, type UseRuleViolationsResult, } from './hooks/useRuleViolations';
30
+ export { useRuleRegistry, type UseRuleRegistryResult, } from './hooks/useRuleRegistry';
2
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,OAAO,GACR,MAAM,UAAU,CAAA;AACjB,YAAY,EACV,WAAW,EACX,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,GACP,MAAM,UAAU,CAAA;AAEjB,YAAY,EACV,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,WAAW,GACZ,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAI1E,OAAO,EACL,mBAAmB,EACnB,KAAK,oBAAoB,EACzB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,mBAAmB,EACnB,KAAK,oBAAoB,EACzB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,YAAY,GAClB,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG7C,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAGtC,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,GAC3B,MAAM,yBAAyB,CAAA"}
@@ -1,2 +1,37 @@
1
- export {};
1
+ /**
2
+ * @mongrov/analytics/rules — structured threshold rule engine.
3
+ *
4
+ * See `README.md` for authoring rules, sampling minimums, brand default
5
+ * catalogs, and hooks. Consumers typically:
6
+ *
7
+ * ```ts
8
+ * import {
9
+ * createRulesEngine,
10
+ * defaults,
11
+ * } from '@mongrov/analytics/rules';
12
+ *
13
+ * const engine = createRulesEngine({ analytics, storage, brand: 'ziva', ... });
14
+ * await engine.register(defaults.zivaDefaults);
15
+ * engine.on('violation', (v) => notify(v));
16
+ * ```
17
+ */
18
+ // R1 — schema + types
19
+ export { RuleSchema, RuleValidationError, TargetSchema, ThrottleSchema, AGGREGATIONS, COMPARES, SEVERITIES, WINDOWS, } from './schema';
20
+ // R2 — validator
21
+ export { allowedWindowsFor, validateRule } from './validator';
22
+ // R3 — compiler + cache
23
+ export { compileRule, sanitizeIdent } from './compiler';
24
+ export { createCompilerCache } from './compiler-cache';
25
+ // R4-R6 — registry / throttle / emitter (exposed for advanced composition
26
+ // or test doubles; typical apps go through `createRulesEngine`).
27
+ export { createRulesRegistry, } from './registry';
28
+ export { createThrottleStore, } from './throttle';
29
+ export { createEmitter, } from './emitter';
30
+ // R9 — factory
31
+ export { createRulesEngine } from './factory';
32
+ // R8 — brand defaults
33
+ export * as defaults from './defaults';
34
+ // R10 — hooks
35
+ export { useRuleViolations, } from './hooks/useRuleViolations';
36
+ export { useRuleRegistry, } from './hooks/useRuleRegistry';
2
37
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,sBAAsB;AACtB,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,OAAO,GACR,MAAM,UAAU,CAAA;AAuBjB,iBAAiB;AACjB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE7D,wBAAwB;AACxB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,kBAAkB,CAAA;AAE1E,0EAA0E;AAC1E,iEAAiE;AACjE,OAAO,EACL,mBAAmB,GAGpB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,mBAAmB,GAGpB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,aAAa,GAGd,MAAM,WAAW,CAAA;AAElB,eAAe;AACf,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAE7C,sBAAsB;AACtB,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,cAAc;AACd,OAAO,EACL,iBAAiB,GAGlB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,eAAe,GAEhB,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * In-memory rule registry with KV-persisted `enabled` state.
3
+ *
4
+ * Every register/enable/disable bumps `rev` — the compiler cache keys on
5
+ * `(ruleId, rev)`, so mutations transparently invalidate compiled SQL.
6
+ *
7
+ * The `subscribe(listener)` surface backs the `useRuleRegistry` hook via
8
+ * `useSyncExternalStore`.
9
+ */
10
+ import type { KVStore, Unsubscribe } from '../core/types';
11
+ import type { MetricId } from '../core/metric_metadata';
12
+ import { type Rule } from './schema';
13
+ import type { RulesLogger } from './types';
14
+ export interface RulesRegistry {
15
+ register(rules: Rule[]): Promise<void>;
16
+ enable(ruleId: string): Promise<void>;
17
+ disable(ruleId: string): Promise<void>;
18
+ list(): Rule[];
19
+ getActive(): Rule[];
20
+ getByMetric(metric: MetricId): Rule[];
21
+ getByBrand(brand: string): Rule[];
22
+ subscribe(listener: () => void): Unsubscribe;
23
+ readonly rev: number;
24
+ }
25
+ export interface CreateRegistryConfig {
26
+ storage: KVStore;
27
+ logger?: RulesLogger;
28
+ /** Brand assigned to rules with no explicit `brand` field. Default `ziva`. */
29
+ defaultBrand?: string;
30
+ }
31
+ export declare function createRulesRegistry({ storage, logger, defaultBrand, }: CreateRegistryConfig): RulesRegistry;
32
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAmC,KAAK,IAAI,EAAE,MAAM,UAAU,CAAA;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAQ1C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,IAAI,IAAI,EAAE,CAAA;IACd,SAAS,IAAI,IAAI,EAAE,CAAA;IACnB,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,EAAE,CAAA;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,CAAA;IACjC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAA;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,MAAM,EACN,YAAqB,GACtB,EAAE,oBAAoB,GAAG,aAAa,CA2FtC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * In-memory rule registry with KV-persisted `enabled` state.
3
+ *
4
+ * Every register/enable/disable bumps `rev` — the compiler cache keys on
5
+ * `(ruleId, rev)`, so mutations transparently invalidate compiled SQL.
6
+ *
7
+ * The `subscribe(listener)` surface backs the `useRuleRegistry` hook via
8
+ * `useSyncExternalStore`.
9
+ */
10
+ import { RuleSchema, RuleValidationError } from './schema';
11
+ import { validateRule } from './validator';
12
+ const KV_ENABLED_PREFIX = 'analytics:rules:';
13
+ function enabledKey(ruleId) {
14
+ return `${KV_ENABLED_PREFIX}${ruleId}:enabled`;
15
+ }
16
+ export function createRulesRegistry({ storage, logger, defaultBrand = 'ziva', }) {
17
+ const entries = new Map();
18
+ const listeners = new Set();
19
+ let rev = 0;
20
+ function notify() {
21
+ rev += 1;
22
+ for (const listener of listeners) {
23
+ try {
24
+ listener();
25
+ }
26
+ catch (err) {
27
+ logger?.error('rules.registry: listener threw', {
28
+ err: err.message,
29
+ });
30
+ }
31
+ }
32
+ }
33
+ return {
34
+ async register(rules) {
35
+ // Validate everything up-front — fail fast on first bad rule.
36
+ const validated = [];
37
+ for (const raw of rules) {
38
+ const parsed = RuleSchema.parse(raw);
39
+ validateRule(parsed, logger);
40
+ validated.push(parsed);
41
+ }
42
+ for (const rule of validated) {
43
+ const persisted = await storage.get(enabledKey(rule.id));
44
+ entries.set(rule.id, {
45
+ rule,
46
+ enabled: persisted ?? true,
47
+ });
48
+ }
49
+ logger?.info('rules.registry: registered', { count: validated.length });
50
+ notify();
51
+ },
52
+ async enable(ruleId) {
53
+ const entry = entries.get(ruleId);
54
+ if (!entry) {
55
+ throw new RuleValidationError(`Rule ${ruleId} not registered.`);
56
+ }
57
+ entry.enabled = true;
58
+ await storage.set(enabledKey(ruleId), true);
59
+ notify();
60
+ },
61
+ async disable(ruleId) {
62
+ const entry = entries.get(ruleId);
63
+ if (!entry) {
64
+ throw new RuleValidationError(`Rule ${ruleId} not registered.`);
65
+ }
66
+ entry.enabled = false;
67
+ await storage.set(enabledKey(ruleId), false);
68
+ notify();
69
+ },
70
+ list() {
71
+ return Array.from(entries.values()).map(e => e.rule);
72
+ },
73
+ getActive() {
74
+ const active = [];
75
+ for (const entry of entries.values()) {
76
+ if (entry.enabled)
77
+ active.push(entry.rule);
78
+ }
79
+ return active;
80
+ },
81
+ getByMetric(metric) {
82
+ return this.getActive().filter(r => r.metric === metric);
83
+ },
84
+ getByBrand(brand) {
85
+ return this.getActive().filter(r => (r.brand ?? defaultBrand) === brand);
86
+ },
87
+ subscribe(listener) {
88
+ listeners.add(listener);
89
+ return () => {
90
+ listeners.delete(listener);
91
+ };
92
+ },
93
+ get rev() {
94
+ return rev;
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Zod schema for structured threshold rules (spec §Rule schema).
3
+ *
4
+ * The `metric` enum is generated at module load from the frozen
5
+ * `METRIC_METADATA` registry — only metrics with `exposure: 'full'` are
6
+ * accepted. Rules referencing `collected_only` metrics can still ship
7
+ * via `rawSql` + `exposureOverride: true` (see validator.ts).
8
+ */
9
+ import { z } from 'zod';
10
+ /** Custom error class for rule-schema / validator failures. */
11
+ export declare class RuleValidationError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ export declare const WINDOWS: readonly ["1h", "6h", "24h", "3d", "7d", "30d"];
15
+ export type Window = (typeof WINDOWS)[number];
16
+ export declare const AGGREGATIONS: readonly ["avg", "min", "max", "sum", "last", "count"];
17
+ export type Aggregation = (typeof AGGREGATIONS)[number];
18
+ export declare const COMPARES: readonly ["less_than", "greater_than", "equals", "not_equals", "between"];
19
+ export type Compare = (typeof COMPARES)[number];
20
+ export declare const SEVERITIES: readonly ["info", "warn", "critical"];
21
+ export type Severity = (typeof SEVERITIES)[number];
22
+ export declare const TargetSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
23
+ type: z.ZodLiteral<"absolute">;
24
+ value: z.ZodNumber;
25
+ }, "strip", z.ZodTypeAny, {
26
+ type: "absolute";
27
+ value: number;
28
+ }, {
29
+ type: "absolute";
30
+ value: number;
31
+ }>, z.ZodObject<{
32
+ type: z.ZodLiteral<"baseline_percent">;
33
+ windowDays: z.ZodNumber;
34
+ percent: z.ZodNumber;
35
+ }, "strip", z.ZodTypeAny, {
36
+ type: "baseline_percent";
37
+ windowDays: number;
38
+ percent: number;
39
+ }, {
40
+ type: "baseline_percent";
41
+ windowDays: number;
42
+ percent: number;
43
+ }>, z.ZodObject<{
44
+ type: z.ZodLiteral<"baseline_stddev">;
45
+ windowDays: z.ZodNumber;
46
+ stddevs: z.ZodNumber;
47
+ }, "strip", z.ZodTypeAny, {
48
+ type: "baseline_stddev";
49
+ windowDays: number;
50
+ stddevs: number;
51
+ }, {
52
+ type: "baseline_stddev";
53
+ windowDays: number;
54
+ stddevs: number;
55
+ }>, z.ZodObject<{
56
+ type: z.ZodLiteral<"range">;
57
+ min: z.ZodNumber;
58
+ max: z.ZodNumber;
59
+ }, "strip", z.ZodTypeAny, {
60
+ type: "range";
61
+ min: number;
62
+ max: number;
63
+ }, {
64
+ type: "range";
65
+ min: number;
66
+ max: number;
67
+ }>]>;
68
+ export type Target = z.infer<typeof TargetSchema>;
69
+ export declare const ThrottleSchema: z.ZodDefault<z.ZodObject<{
70
+ minGapMinutes: z.ZodDefault<z.ZodNumber>;
71
+ maxPerDay: z.ZodDefault<z.ZodNumber>;
72
+ }, "strip", z.ZodTypeAny, {
73
+ minGapMinutes: number;
74
+ maxPerDay: number;
75
+ }, {
76
+ minGapMinutes?: number | undefined;
77
+ maxPerDay?: number | undefined;
78
+ }>>;
79
+ export type Throttle = z.infer<typeof ThrottleSchema>;
80
+ export declare const RuleSchema: z.ZodObject<{
81
+ id: z.ZodString;
82
+ brand: z.ZodOptional<z.ZodString>;
83
+ name: z.ZodString;
84
+ description: z.ZodOptional<z.ZodString>;
85
+ metric: z.ZodEnum<["spo2" | "hrv_ms" | "stress" | "systolic_bp" | "diastolic_bp" | "vascular_aging" | "hr_bpm" | "temp_c" | "activity_steps" | "calories" | "distance_km" | "sleep_total_minutes" | "sleep_score" | "device_battery", ...("spo2" | "hrv_ms" | "stress" | "systolic_bp" | "diastolic_bp" | "vascular_aging" | "hr_bpm" | "temp_c" | "activity_steps" | "calories" | "distance_km" | "sleep_total_minutes" | "sleep_score" | "device_battery")[]]>;
86
+ window: z.ZodEnum<["1h", "6h", "24h", "3d", "7d", "30d"]>;
87
+ aggregation: z.ZodDefault<z.ZodEnum<["avg", "min", "max", "sum", "last", "count"]>>;
88
+ compare: z.ZodEnum<["less_than", "greater_than", "equals", "not_equals", "between"]>;
89
+ target: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
90
+ type: z.ZodLiteral<"absolute">;
91
+ value: z.ZodNumber;
92
+ }, "strip", z.ZodTypeAny, {
93
+ type: "absolute";
94
+ value: number;
95
+ }, {
96
+ type: "absolute";
97
+ value: number;
98
+ }>, z.ZodObject<{
99
+ type: z.ZodLiteral<"baseline_percent">;
100
+ windowDays: z.ZodNumber;
101
+ percent: z.ZodNumber;
102
+ }, "strip", z.ZodTypeAny, {
103
+ type: "baseline_percent";
104
+ windowDays: number;
105
+ percent: number;
106
+ }, {
107
+ type: "baseline_percent";
108
+ windowDays: number;
109
+ percent: number;
110
+ }>, z.ZodObject<{
111
+ type: z.ZodLiteral<"baseline_stddev">;
112
+ windowDays: z.ZodNumber;
113
+ stddevs: z.ZodNumber;
114
+ }, "strip", z.ZodTypeAny, {
115
+ type: "baseline_stddev";
116
+ windowDays: number;
117
+ stddevs: number;
118
+ }, {
119
+ type: "baseline_stddev";
120
+ windowDays: number;
121
+ stddevs: number;
122
+ }>, z.ZodObject<{
123
+ type: z.ZodLiteral<"range">;
124
+ min: z.ZodNumber;
125
+ max: z.ZodNumber;
126
+ }, "strip", z.ZodTypeAny, {
127
+ type: "range";
128
+ min: number;
129
+ max: number;
130
+ }, {
131
+ type: "range";
132
+ min: number;
133
+ max: number;
134
+ }>]>;
135
+ severity: z.ZodEnum<["info", "warn", "critical"]>;
136
+ throttle: z.ZodDefault<z.ZodObject<{
137
+ minGapMinutes: z.ZodDefault<z.ZodNumber>;
138
+ maxPerDay: z.ZodDefault<z.ZodNumber>;
139
+ }, "strip", z.ZodTypeAny, {
140
+ minGapMinutes: number;
141
+ maxPerDay: number;
142
+ }, {
143
+ minGapMinutes?: number | undefined;
144
+ maxPerDay?: number | undefined;
145
+ }>>;
146
+ rawSql: z.ZodOptional<z.ZodString>;
147
+ rawSqlParams: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
148
+ /**
149
+ * Required-true when `rawSql` references `collected_only` columns
150
+ * (BP, vascular aging). Validator enforces.
151
+ */
152
+ exposureOverride: z.ZodOptional<z.ZodBoolean>;
153
+ }, "strip", z.ZodTypeAny, {
154
+ id: string;
155
+ target: {
156
+ type: "absolute";
157
+ value: number;
158
+ } | {
159
+ type: "baseline_percent";
160
+ windowDays: number;
161
+ percent: number;
162
+ } | {
163
+ type: "baseline_stddev";
164
+ windowDays: number;
165
+ stddevs: number;
166
+ } | {
167
+ type: "range";
168
+ min: number;
169
+ max: number;
170
+ };
171
+ name: string;
172
+ metric: "spo2" | "hrv_ms" | "stress" | "systolic_bp" | "diastolic_bp" | "vascular_aging" | "hr_bpm" | "temp_c" | "activity_steps" | "calories" | "distance_km" | "sleep_total_minutes" | "sleep_score" | "device_battery";
173
+ window: "1h" | "6h" | "24h" | "3d" | "7d" | "30d";
174
+ aggregation: "avg" | "min" | "max" | "sum" | "last" | "count";
175
+ compare: "less_than" | "greater_than" | "equals" | "not_equals" | "between";
176
+ severity: "info" | "warn" | "critical";
177
+ throttle: {
178
+ minGapMinutes: number;
179
+ maxPerDay: number;
180
+ };
181
+ description?: string | undefined;
182
+ brand?: string | undefined;
183
+ rawSql?: string | undefined;
184
+ rawSqlParams?: string[] | undefined;
185
+ exposureOverride?: boolean | undefined;
186
+ }, {
187
+ id: string;
188
+ target: {
189
+ type: "absolute";
190
+ value: number;
191
+ } | {
192
+ type: "baseline_percent";
193
+ windowDays: number;
194
+ percent: number;
195
+ } | {
196
+ type: "baseline_stddev";
197
+ windowDays: number;
198
+ stddevs: number;
199
+ } | {
200
+ type: "range";
201
+ min: number;
202
+ max: number;
203
+ };
204
+ name: string;
205
+ metric: "spo2" | "hrv_ms" | "stress" | "systolic_bp" | "diastolic_bp" | "vascular_aging" | "hr_bpm" | "temp_c" | "activity_steps" | "calories" | "distance_km" | "sleep_total_minutes" | "sleep_score" | "device_battery";
206
+ window: "1h" | "6h" | "24h" | "3d" | "7d" | "30d";
207
+ compare: "less_than" | "greater_than" | "equals" | "not_equals" | "between";
208
+ severity: "info" | "warn" | "critical";
209
+ description?: string | undefined;
210
+ brand?: string | undefined;
211
+ aggregation?: "avg" | "min" | "max" | "sum" | "last" | "count" | undefined;
212
+ throttle?: {
213
+ minGapMinutes?: number | undefined;
214
+ maxPerDay?: number | undefined;
215
+ } | undefined;
216
+ rawSql?: string | undefined;
217
+ rawSqlParams?: string[] | undefined;
218
+ exposureOverride?: boolean | undefined;
219
+ }>;
220
+ export type Rule = z.infer<typeof RuleSchema>;
221
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/rules/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,+DAA+D;AAC/D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAID,eAAO,MAAM,OAAO,iDAAkD,CAAA;AACtE,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7C,eAAO,MAAM,YAAY,wDAAyD,CAAA;AAClF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD,eAAO,MAAM,QAAQ,2EAMX,CAAA;AACV,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/C,eAAO,MAAM,UAAU,uCAAwC,CAAA;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAyBlD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAKvB,CAAA;AACF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEjD,eAAO,MAAM,cAAc;;;;;;;;;GAKb,CAAA;AAEd,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAErD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAcrB;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEH,CAAA;AAEF,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAA"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Zod schema for structured threshold rules (spec §Rule schema).
3
+ *
4
+ * The `metric` enum is generated at module load from the frozen
5
+ * `METRIC_METADATA` registry — only metrics with `exposure: 'full'` are
6
+ * accepted. Rules referencing `collected_only` metrics can still ship
7
+ * via `rawSql` + `exposureOverride: true` (see validator.ts).
8
+ */
9
+ import { z } from 'zod';
10
+ import { getExposedMetricIds } from '../core/metric_metadata';
11
+ /** Custom error class for rule-schema / validator failures. */
12
+ export class RuleValidationError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'RuleValidationError';
16
+ }
17
+ }
18
+ const exposedMetricIds = getExposedMetricIds();
19
+ export const WINDOWS = ['1h', '6h', '24h', '3d', '7d', '30d'];
20
+ export const AGGREGATIONS = ['avg', 'min', 'max', 'sum', 'last', 'count'];
21
+ export const COMPARES = [
22
+ 'less_than',
23
+ 'greater_than',
24
+ 'equals',
25
+ 'not_equals',
26
+ 'between',
27
+ ];
28
+ export const SEVERITIES = ['info', 'warn', 'critical'];
29
+ const TargetAbsolute = z.object({
30
+ type: z.literal('absolute'),
31
+ value: z.number(),
32
+ });
33
+ const TargetBaselinePercent = z.object({
34
+ type: z.literal('baseline_percent'),
35
+ windowDays: z.number().int().positive(),
36
+ percent: z.number().positive(),
37
+ });
38
+ const TargetBaselineStddev = z.object({
39
+ type: z.literal('baseline_stddev'),
40
+ windowDays: z.number().int().positive(),
41
+ stddevs: z.number(),
42
+ });
43
+ const TargetRange = z.object({
44
+ type: z.literal('range'),
45
+ min: z.number(),
46
+ max: z.number(),
47
+ });
48
+ export const TargetSchema = z.discriminatedUnion('type', [
49
+ TargetAbsolute,
50
+ TargetBaselinePercent,
51
+ TargetBaselineStddev,
52
+ TargetRange,
53
+ ]);
54
+ export const ThrottleSchema = z
55
+ .object({
56
+ minGapMinutes: z.number().nonnegative().default(60),
57
+ maxPerDay: z.number().int().positive().default(3),
58
+ })
59
+ .default({});
60
+ export const RuleSchema = z.object({
61
+ id: z.string().min(1),
62
+ brand: z.string().optional(),
63
+ name: z.string().min(1),
64
+ description: z.string().optional(),
65
+ metric: z.enum(exposedMetricIds),
66
+ window: z.enum(WINDOWS),
67
+ aggregation: z.enum(AGGREGATIONS).default('avg'),
68
+ compare: z.enum(COMPARES),
69
+ target: TargetSchema,
70
+ severity: z.enum(SEVERITIES),
71
+ throttle: ThrottleSchema,
72
+ rawSql: z.string().optional(),
73
+ rawSqlParams: z.array(z.string()).optional(),
74
+ /**
75
+ * Required-true when `rawSql` references `collected_only` columns
76
+ * (BP, vascular aging). Validator enforces.
77
+ */
78
+ exposureOverride: z.boolean().optional(),
79
+ });
80
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/rules/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * KV-backed throttle store for rule fires.
3
+ *
4
+ * Two keys per (rule, user):
5
+ * - `analytics:rules:throttle:{ruleId}:{userId}:last` → ISO date string
6
+ * - `analytics:rules:throttle:{ruleId}:{userId}:count:{yyyy-mm-dd}` → number
7
+ *
8
+ * Order of checks: min-gap first, then daily cap. Stale daily-count
9
+ * entries (from earlier days) are best-effort deleted on next-day reads.
10
+ *
11
+ * All time reads go through an injected `clock: () => Date` so tests can
12
+ * pin the wall clock. Production defaults to `() => new Date()`.
13
+ */
14
+ import type { KVStore } from '../core/types';
15
+ import type { Clock, RulesLogger } from './types';
16
+ import type { Throttle } from './schema';
17
+ export interface ThrottleStore {
18
+ isThrottled(ruleId: string, userId: string, throttle: Throttle): Promise<boolean>;
19
+ recordFire(ruleId: string, userId: string): Promise<void>;
20
+ }
21
+ export interface CreateThrottleConfig {
22
+ storage: KVStore;
23
+ clock?: Clock;
24
+ logger?: RulesLogger;
25
+ }
26
+ export declare function createThrottleStore({ storage, clock, logger, }: CreateThrottleConfig): ThrottleStore;
27
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/rules/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAoBxC,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACjF,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,KAAwB,EACxB,MAAM,GACP,EAAE,oBAAoB,GAAG,aAAa,CAmDtC"}