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

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 (161) hide show
  1. package/dist/rules/__fakes__/fakeClock.d.ts +7 -0
  2. package/dist/rules/__fakes__/fakeClock.d.ts.map +1 -0
  3. package/dist/rules/__fakes__/fakeClock.js +13 -0
  4. package/dist/rules/__fakes__/fakeClock.js.map +1 -0
  5. package/dist/rules/__fakes__/fakeEngine.d.ts +12 -0
  6. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -0
  7. package/dist/rules/__fakes__/fakeEngine.js +33 -0
  8. package/dist/rules/__fakes__/fakeEngine.js.map +1 -0
  9. package/dist/rules/__fakes__/fakeStorage.d.ts +7 -0
  10. package/dist/rules/__fakes__/fakeStorage.d.ts.map +1 -0
  11. package/dist/rules/__fakes__/fakeStorage.js +22 -0
  12. package/dist/rules/__fakes__/fakeStorage.js.map +1 -0
  13. package/dist/rules/compiler-cache.d.ts +16 -0
  14. package/dist/rules/compiler-cache.d.ts.map +1 -0
  15. package/dist/rules/compiler-cache.js +29 -0
  16. package/dist/rules/compiler-cache.js.map +1 -0
  17. package/dist/rules/compiler.d.ts +27 -0
  18. package/dist/rules/compiler.d.ts.map +1 -0
  19. package/dist/rules/compiler.js +210 -0
  20. package/dist/rules/compiler.js.map +1 -0
  21. package/dist/rules/defaults/index.d.ts +13 -0
  22. package/dist/rules/defaults/index.d.ts.map +1 -0
  23. package/dist/rules/defaults/index.js +13 -0
  24. package/dist/rules/defaults/index.js.map +1 -0
  25. package/dist/rules/defaults/loader.d.ts +18 -0
  26. package/dist/rules/defaults/loader.d.ts.map +1 -0
  27. package/dist/rules/defaults/loader.js +37 -0
  28. package/dist/rules/defaults/loader.js.map +1 -0
  29. package/dist/rules/defaults/luminx.d.ts +6 -0
  30. package/dist/rules/defaults/luminx.d.ts.map +1 -0
  31. package/dist/rules/defaults/luminx.js +64 -0
  32. package/dist/rules/defaults/luminx.js.map +1 -0
  33. package/dist/rules/defaults/viva.d.ts +6 -0
  34. package/dist/rules/defaults/viva.d.ts.map +1 -0
  35. package/dist/rules/defaults/viva.js +45 -0
  36. package/dist/rules/defaults/viva.js.map +1 -0
  37. package/dist/rules/defaults/yogaring.d.ts +6 -0
  38. package/dist/rules/defaults/yogaring.d.ts.map +1 -0
  39. package/dist/rules/defaults/yogaring.js +45 -0
  40. package/dist/rules/defaults/yogaring.js.map +1 -0
  41. package/dist/rules/defaults/ziva.d.ts +10 -0
  42. package/dist/rules/defaults/ziva.d.ts.map +1 -0
  43. package/dist/rules/defaults/ziva.js +87 -0
  44. package/dist/rules/defaults/ziva.js.map +1 -0
  45. package/dist/rules/emitter.d.ts +20 -0
  46. package/dist/rules/emitter.d.ts.map +1 -0
  47. package/dist/rules/emitter.js +32 -0
  48. package/dist/rules/emitter.js.map +1 -0
  49. package/dist/rules/evaluator.d.ts +44 -0
  50. package/dist/rules/evaluator.d.ts.map +1 -0
  51. package/dist/rules/evaluator.js +127 -0
  52. package/dist/rules/evaluator.js.map +1 -0
  53. package/dist/rules/factory.d.ts +13 -0
  54. package/dist/rules/factory.d.ts.map +1 -0
  55. package/dist/rules/factory.js +56 -0
  56. package/dist/rules/factory.js.map +1 -0
  57. package/dist/rules/hooks/useRuleRegistry.d.ts +16 -0
  58. package/dist/rules/hooks/useRuleRegistry.d.ts.map +1 -0
  59. package/dist/rules/hooks/useRuleRegistry.js +26 -0
  60. package/dist/rules/hooks/useRuleRegistry.js.map +1 -0
  61. package/dist/rules/hooks/useRuleViolations.d.ts +21 -0
  62. package/dist/rules/hooks/useRuleViolations.d.ts.map +1 -0
  63. package/dist/rules/hooks/useRuleViolations.js +32 -0
  64. package/dist/rules/hooks/useRuleViolations.js.map +1 -0
  65. package/dist/rules/index.d.ts +30 -1
  66. package/dist/rules/index.d.ts.map +1 -1
  67. package/dist/rules/index.js +36 -1
  68. package/dist/rules/index.js.map +1 -1
  69. package/dist/rules/registry.d.ts +32 -0
  70. package/dist/rules/registry.d.ts.map +1 -0
  71. package/dist/rules/registry.js +98 -0
  72. package/dist/rules/registry.js.map +1 -0
  73. package/dist/rules/schema.d.ts +221 -0
  74. package/dist/rules/schema.d.ts.map +1 -0
  75. package/dist/rules/schema.js +80 -0
  76. package/dist/rules/schema.js.map +1 -0
  77. package/dist/rules/throttle.d.ts +27 -0
  78. package/dist/rules/throttle.d.ts.map +1 -0
  79. package/dist/rules/throttle.js +76 -0
  80. package/dist/rules/throttle.js.map +1 -0
  81. package/dist/rules/types.d.ts +88 -0
  82. package/dist/rules/types.d.ts.map +1 -0
  83. package/dist/rules/types.js +9 -0
  84. package/dist/rules/types.js.map +1 -0
  85. package/dist/rules/validator.d.ts +27 -0
  86. package/dist/rules/validator.d.ts.map +1 -0
  87. package/dist/rules/validator.js +97 -0
  88. package/dist/rules/validator.js.map +1 -0
  89. package/dist/sync/factory.d.ts +8 -0
  90. package/dist/sync/factory.d.ts.map +1 -1
  91. package/dist/sync/factory.js +31 -1
  92. package/dist/sync/factory.js.map +1 -1
  93. package/dist/sync/flusher.d.ts +6 -0
  94. package/dist/sync/flusher.d.ts.map +1 -1
  95. package/dist/sync/flusher.js +13 -1
  96. package/dist/sync/flusher.js.map +1 -1
  97. package/dist/tools/__fakes__/engine.d.ts +25 -0
  98. package/dist/tools/__fakes__/engine.d.ts.map +1 -0
  99. package/dist/tools/__fakes__/engine.js +37 -0
  100. package/dist/tools/__fakes__/engine.js.map +1 -0
  101. package/dist/tools/audit.d.ts +31 -0
  102. package/dist/tools/audit.d.ts.map +1 -0
  103. package/dist/tools/audit.js +157 -0
  104. package/dist/tools/audit.js.map +1 -0
  105. package/dist/tools/authorize.d.ts +36 -0
  106. package/dist/tools/authorize.d.ts.map +1 -0
  107. package/dist/tools/authorize.js +87 -0
  108. package/dist/tools/authorize.js.map +1 -0
  109. package/dist/tools/budget.d.ts +15 -0
  110. package/dist/tools/budget.d.ts.map +1 -0
  111. package/dist/tools/budget.js +64 -0
  112. package/dist/tools/budget.js.map +1 -0
  113. package/dist/tools/factory.d.ts +43 -0
  114. package/dist/tools/factory.d.ts.map +1 -0
  115. package/dist/tools/factory.js +137 -0
  116. package/dist/tools/factory.js.map +1 -0
  117. package/dist/tools/formatters.d.ts +17 -0
  118. package/dist/tools/formatters.d.ts.map +1 -0
  119. package/dist/tools/formatters.js +30 -0
  120. package/dist/tools/formatters.js.map +1 -0
  121. package/dist/tools/impls/activity.d.ts +15 -0
  122. package/dist/tools/impls/activity.d.ts.map +1 -0
  123. package/dist/tools/impls/activity.js +61 -0
  124. package/dist/tools/impls/activity.js.map +1 -0
  125. package/dist/tools/impls/anomaly.d.ts +21 -0
  126. package/dist/tools/impls/anomaly.d.ts.map +1 -0
  127. package/dist/tools/impls/anomaly.js +70 -0
  128. package/dist/tools/impls/anomaly.js.map +1 -0
  129. package/dist/tools/impls/compare.d.ts +21 -0
  130. package/dist/tools/impls/compare.d.ts.map +1 -0
  131. package/dist/tools/impls/compare.js +68 -0
  132. package/dist/tools/impls/compare.js.map +1 -0
  133. package/dist/tools/impls/hrv.d.ts +15 -0
  134. package/dist/tools/impls/hrv.d.ts.map +1 -0
  135. package/dist/tools/impls/hrv.js +36 -0
  136. package/dist/tools/impls/hrv.js.map +1 -0
  137. package/dist/tools/impls/insights.d.ts +18 -0
  138. package/dist/tools/impls/insights.d.ts.map +1 -0
  139. package/dist/tools/impls/insights.js +41 -0
  140. package/dist/tools/impls/insights.js.map +1 -0
  141. package/dist/tools/impls/sleep.d.ts +15 -0
  142. package/dist/tools/impls/sleep.d.ts.map +1 -0
  143. package/dist/tools/impls/sleep.js +43 -0
  144. package/dist/tools/impls/sleep.js.map +1 -0
  145. package/dist/tools/index.d.ts +13 -1
  146. package/dist/tools/index.d.ts.map +1 -1
  147. package/dist/tools/index.js +21 -1
  148. package/dist/tools/index.js.map +1 -1
  149. package/dist/tools/rate-limit.d.ts +26 -0
  150. package/dist/tools/rate-limit.d.ts.map +1 -0
  151. package/dist/tools/rate-limit.js +67 -0
  152. package/dist/tools/rate-limit.js.map +1 -0
  153. package/dist/tools/types.d.ts +121 -0
  154. package/dist/tools/types.d.ts.map +1 -0
  155. package/dist/tools/types.js +16 -0
  156. package/dist/tools/types.js.map +1 -0
  157. package/dist/tools/wrap.d.ts +53 -0
  158. package/dist/tools/wrap.d.ts.map +1 -0
  159. package/dist/tools/wrap.js +135 -0
  160. package/dist/tools/wrap.js.map +1 -0
  161. package/package.json +5 -1
@@ -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"}
@@ -0,0 +1,76 @@
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
+ const KEY_PREFIX = 'analytics:rules:throttle:';
15
+ function lastKey(ruleId, userId) {
16
+ return `${KEY_PREFIX}${ruleId}:${userId}:last`;
17
+ }
18
+ function countKey(ruleId, userId, ymd) {
19
+ return `${KEY_PREFIX}${ruleId}:${userId}:count:${ymd}`;
20
+ }
21
+ /** yyyy-mm-dd in UTC. */
22
+ function ymdOf(d) {
23
+ const y = d.getUTCFullYear();
24
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
25
+ const day = String(d.getUTCDate()).padStart(2, '0');
26
+ return `${y}-${m}-${day}`;
27
+ }
28
+ export function createThrottleStore({ storage, clock = () => new Date(), logger, }) {
29
+ return {
30
+ async isThrottled(ruleId, userId, throttle) {
31
+ const now = clock();
32
+ const ymd = ymdOf(now);
33
+ // Min-gap check.
34
+ const lastRaw = await storage.get(lastKey(ruleId, userId));
35
+ if (lastRaw) {
36
+ const last = new Date(lastRaw);
37
+ const gapMs = now.getTime() - last.getTime();
38
+ const minGapMs = throttle.minGapMinutes * 60 * 1000;
39
+ if (gapMs < minGapMs) {
40
+ logger?.debug('rules.throttle: gap', {
41
+ ruleId,
42
+ userId,
43
+ gapMs,
44
+ minGapMs,
45
+ });
46
+ return true;
47
+ }
48
+ // Purge stale daily-count keys from prior days (best-effort).
49
+ const lastYmd = ymdOf(last);
50
+ if (lastYmd !== ymd) {
51
+ await storage.delete(countKey(ruleId, userId, lastYmd));
52
+ }
53
+ }
54
+ // Daily cap check.
55
+ const count = (await storage.get(countKey(ruleId, userId, ymd))) ?? 0;
56
+ if (count >= throttle.maxPerDay) {
57
+ logger?.debug('rules.throttle: daily cap', {
58
+ ruleId,
59
+ userId,
60
+ count,
61
+ maxPerDay: throttle.maxPerDay,
62
+ });
63
+ return true;
64
+ }
65
+ return false;
66
+ },
67
+ async recordFire(ruleId, userId) {
68
+ const now = clock();
69
+ const ymd = ymdOf(now);
70
+ await storage.set(lastKey(ruleId, userId), now.toISOString());
71
+ const count = (await storage.get(countKey(ruleId, userId, ymd))) ?? 0;
72
+ await storage.set(countKey(ruleId, userId, ymd), count + 1);
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/rules/throttle.ts"],"names":[],"mappings":"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"}