@adonis-agora/telescope 0.1.0 → 0.3.0

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 (120) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/providers/telescope_provider.d.ts +16 -0
  3. package/dist/providers/telescope_provider.d.ts.map +1 -1
  4. package/dist/providers/telescope_provider.js +33 -2
  5. package/dist/providers/telescope_provider.js.map +1 -1
  6. package/dist/providers/telescope_ui_provider.d.ts.map +1 -1
  7. package/dist/providers/telescope_ui_provider.js +87 -1
  8. package/dist/providers/telescope_ui_provider.js.map +1 -1
  9. package/dist/providers/telescope_watchers_provider.d.ts +11 -0
  10. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -1
  11. package/dist/providers/telescope_watchers_provider.js +58 -0
  12. package/dist/providers/telescope_watchers_provider.js.map +1 -1
  13. package/dist/src/define_config.d.ts +50 -0
  14. package/dist/src/define_config.d.ts.map +1 -1
  15. package/dist/src/define_config.js +9 -0
  16. package/dist/src/define_config.js.map +1 -1
  17. package/dist/src/index.d.ts +23 -2
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +17 -1
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/metrics/metrics_service.d.ts +63 -0
  22. package/dist/src/metrics/metrics_service.d.ts.map +1 -0
  23. package/dist/src/metrics/metrics_service.js +116 -0
  24. package/dist/src/metrics/metrics_service.js.map +1 -0
  25. package/dist/src/metrics/rollup.d.ts +61 -0
  26. package/dist/src/metrics/rollup.d.ts.map +1 -0
  27. package/dist/src/metrics/rollup.js +114 -0
  28. package/dist/src/metrics/rollup.js.map +1 -0
  29. package/dist/src/metrics/stats.d.ts +112 -0
  30. package/dist/src/metrics/stats.d.ts.map +1 -0
  31. package/dist/src/metrics/stats.js +255 -0
  32. package/dist/src/metrics/stats.js.map +1 -0
  33. package/dist/src/metrics/timeseries.d.ts +22 -0
  34. package/dist/src/metrics/timeseries.d.ts.map +1 -0
  35. package/dist/src/metrics/timeseries.js +34 -0
  36. package/dist/src/metrics/timeseries.js.map +1 -0
  37. package/dist/src/metrics/traces.d.ts +27 -0
  38. package/dist/src/metrics/traces.d.ts.map +1 -0
  39. package/dist/src/metrics/traces.js +71 -0
  40. package/dist/src/metrics/traces.js.map +1 -0
  41. package/dist/src/metrics/waterfall.d.ts +44 -0
  42. package/dist/src/metrics/waterfall.d.ts.map +1 -0
  43. package/dist/src/metrics/waterfall.js +99 -0
  44. package/dist/src/metrics/waterfall.js.map +1 -0
  45. package/dist/src/query/n_plus_one.d.ts +60 -0
  46. package/dist/src/query/n_plus_one.d.ts.map +1 -0
  47. package/dist/src/query/n_plus_one.js +102 -0
  48. package/dist/src/query/n_plus_one.js.map +1 -0
  49. package/dist/src/registry.d.ts +9 -0
  50. package/dist/src/registry.d.ts.map +1 -1
  51. package/dist/src/registry.js +6 -0
  52. package/dist/src/registry.js.map +1 -1
  53. package/dist/src/sampling/sampling.d.ts +52 -0
  54. package/dist/src/sampling/sampling.d.ts.map +1 -0
  55. package/dist/src/sampling/sampling.js +111 -0
  56. package/dist/src/sampling/sampling.js.map +1 -0
  57. package/dist/src/sampling/sampling_store.d.ts +33 -0
  58. package/dist/src/sampling/sampling_store.d.ts.map +1 -0
  59. package/dist/src/sampling/sampling_store.js +65 -0
  60. package/dist/src/sampling/sampling_store.js.map +1 -0
  61. package/dist/src/stream/entry_events.d.ts +45 -0
  62. package/dist/src/stream/entry_events.d.ts.map +1 -0
  63. package/dist/src/stream/entry_events.js +56 -0
  64. package/dist/src/stream/entry_events.js.map +1 -0
  65. package/dist/src/stream/index.d.ts +6 -0
  66. package/dist/src/stream/index.d.ts.map +1 -0
  67. package/dist/src/stream/index.js +5 -0
  68. package/dist/src/stream/index.js.map +1 -0
  69. package/dist/src/stream/stream_handler.d.ts +42 -0
  70. package/dist/src/stream/stream_handler.d.ts.map +1 -0
  71. package/dist/src/stream/stream_handler.js +48 -0
  72. package/dist/src/stream/stream_handler.js.map +1 -0
  73. package/dist/src/stream/streaming_store.d.ts +31 -0
  74. package/dist/src/stream/streaming_store.d.ts.map +1 -0
  75. package/dist/src/stream/streaming_store.js +49 -0
  76. package/dist/src/stream/streaming_store.js.map +1 -0
  77. package/dist/src/ui/api.d.ts +39 -1
  78. package/dist/src/ui/api.d.ts.map +1 -1
  79. package/dist/src/ui/api.js +116 -1
  80. package/dist/src/ui/api.js.map +1 -1
  81. package/dist/src/ui/define_config.d.ts +27 -0
  82. package/dist/src/ui/define_config.d.ts.map +1 -1
  83. package/dist/src/ui/define_config.js +6 -0
  84. package/dist/src/ui/define_config.js.map +1 -1
  85. package/dist/src/ui/http.d.ts +40 -0
  86. package/dist/src/ui/http.d.ts.map +1 -1
  87. package/dist/src/ui/http.js +43 -0
  88. package/dist/src/ui/http.js.map +1 -1
  89. package/dist/src/ui/index.d.ts +5 -3
  90. package/dist/src/ui/index.d.ts.map +1 -1
  91. package/dist/src/ui/index.js +3 -1
  92. package/dist/src/ui/index.js.map +1 -1
  93. package/dist/src/ui/request_replay.d.ts +56 -0
  94. package/dist/src/ui/request_replay.d.ts.map +1 -0
  95. package/dist/src/ui/request_replay.js +120 -0
  96. package/dist/src/ui/request_replay.js.map +1 -0
  97. package/dist/src/watchers/define_config.d.ts +17 -1
  98. package/dist/src/watchers/define_config.d.ts.map +1 -1
  99. package/dist/src/watchers/define_config.js +13 -0
  100. package/dist/src/watchers/define_config.js.map +1 -1
  101. package/dist/src/watchers/events_watcher.d.ts +63 -0
  102. package/dist/src/watchers/events_watcher.d.ts.map +1 -0
  103. package/dist/src/watchers/events_watcher.js +81 -0
  104. package/dist/src/watchers/events_watcher.js.map +1 -0
  105. package/dist/src/watchers/index.d.ts +6 -0
  106. package/dist/src/watchers/index.d.ts.map +1 -1
  107. package/dist/src/watchers/index.js +6 -0
  108. package/dist/src/watchers/index.js.map +1 -1
  109. package/dist/src/watchers/queue_watcher.d.ts +97 -0
  110. package/dist/src/watchers/queue_watcher.d.ts.map +1 -0
  111. package/dist/src/watchers/queue_watcher.js +114 -0
  112. package/dist/src/watchers/queue_watcher.js.map +1 -0
  113. package/dist/src/watchers/redis_watcher.d.ts +103 -0
  114. package/dist/src/watchers/redis_watcher.d.ts.map +1 -0
  115. package/dist/src/watchers/redis_watcher.js +161 -0
  116. package/dist/src/watchers/redis_watcher.js.map +1 -0
  117. package/dist/stubs/config/telescope.stub +9 -0
  118. package/dist/stubs/config/telescope_ui.stub +13 -0
  119. package/dist/stubs/config/telescope_watchers.stub +14 -3
  120. package/package.json +8 -8
@@ -0,0 +1,60 @@
1
+ import { type Entry } from '../entry.js';
2
+ /** A flat family-count N+1 insight — "query template X ran N times". */
3
+ export interface NPlusOneInsight {
4
+ familyHash: string;
5
+ count: number;
6
+ sql: string;
7
+ }
8
+ /**
9
+ * Flat N+1 detection — ported from `nestjs-telescope`'s `query/n-plus-one.ts`.
10
+ * Group query entries by `familyHash`; report templates that ran `>= threshold`
11
+ * times. Pure; order follows insertion (first-seen family first).
12
+ */
13
+ export declare function detectNPlusOne(entries: Entry[], threshold: number): NPlusOneInsight[];
14
+ /**
15
+ * A detected N+1 LOOP pattern within a single request/trace: one driving "parent"
16
+ * query (the SELECT that produced the rows being iterated) followed by N
17
+ * similarly-shaped "child" queries (the per-row lookups). Richer than the flat
18
+ * {@link detectNPlusOne} family-count: it attributes a likely parent and weights
19
+ * by the total time WASTED in the loop, so the worst offenders rank first.
20
+ */
21
+ export interface NPlusOnePattern {
22
+ /** The repeated child query's family hash. */
23
+ childFamilyHash: string;
24
+ /** A representative child SQL (template/text). */
25
+ childSql: string;
26
+ /** How many times the child query ran in the trace (>= threshold). */
27
+ count: number;
28
+ /** Sum of the child queries' durations (ms) — the "wasted" time, the rank key. */
29
+ totalDurationMs: number;
30
+ /**
31
+ * The family hash of the query that most likely drove the loop — the distinct
32
+ * query immediately preceding the loop in record order — or `null` when the
33
+ * loop is the first thing in the trace (no identifiable parent).
34
+ */
35
+ parentFamilyHash: string | null;
36
+ /** The likely-parent SQL, or `null` when there is no identifiable parent. */
37
+ parentSql: string | null;
38
+ /** A representative child entry id (deep-link / hydration seam). */
39
+ representativeId: string;
40
+ /** The trace the pattern was found in. */
41
+ traceId: string;
42
+ }
43
+ export interface NPlusOnePatternOptions {
44
+ /** Minimum repetitions of one child template to flag a loop. */
45
+ threshold: number;
46
+ }
47
+ /**
48
+ * Detect N+1 loop patterns in a request/trace's query entries. Ported from
49
+ * `nestjs-telescope`'s `query/n-plus-one-pattern.ts` (NestJS `batchId` becomes
50
+ * Adonis `traceId`). For each query family that repeats `>= threshold` times we
51
+ * emit a pattern weighted by the loop's total duration and attribute the likely
52
+ * driving parent (the distinct query that ran just before the loop began). Pure;
53
+ * ordered by total wasted duration desc.
54
+ *
55
+ * NOTE: callers pass entries in OLDEST-FIRST record order (ascending sequence) so
56
+ * "the query immediately preceding the loop" is meaningful. The store returns
57
+ * newest-first, so the service reverses before calling.
58
+ */
59
+ export declare function detectNPlusOnePatterns(entries: Entry[], options: NPlusOnePatternOptions): NPlusOnePattern[];
60
+ //# sourceMappingURL=n_plus_one.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"n_plus_one.d.ts","sourceRoot":"","sources":["../../../src/query/n_plus_one.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAa,MAAM,aAAa,CAAC;AAEpD,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAWD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAkBrF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,6EAA6E;IAC7E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,KAAK,EAAE,EAChB,OAAO,EAAE,sBAAsB,GAC9B,eAAe,EAAE,CA8CnB"}
@@ -0,0 +1,102 @@
1
+ import { EntryType } from '../entry.js';
2
+ /** Extract the `sql` string from a query entry's content, or `''`. */
3
+ function sqlOf(entry) {
4
+ const record = typeof entry.content === 'object' && entry.content !== null
5
+ ? entry.content
6
+ : null;
7
+ return record !== null && typeof record.sql === 'string' ? record.sql : '';
8
+ }
9
+ /**
10
+ * Flat N+1 detection — ported from `nestjs-telescope`'s `query/n-plus-one.ts`.
11
+ * Group query entries by `familyHash`; report templates that ran `>= threshold`
12
+ * times. Pure; order follows insertion (first-seen family first).
13
+ */
14
+ export function detectNPlusOne(entries, threshold) {
15
+ const groups = new Map();
16
+ for (const entry of entries) {
17
+ if (entry.type !== EntryType.Query || entry.familyHash === null)
18
+ continue;
19
+ const existing = groups.get(entry.familyHash);
20
+ if (existing) {
21
+ existing.count += 1;
22
+ }
23
+ else {
24
+ groups.set(entry.familyHash, { count: 1, sql: sqlOf(entry) });
25
+ }
26
+ }
27
+ const insights = [];
28
+ for (const [familyHash, group] of groups) {
29
+ if (group.count >= threshold) {
30
+ insights.push({ familyHash, count: group.count, sql: group.sql });
31
+ }
32
+ }
33
+ return insights;
34
+ }
35
+ /**
36
+ * Detect N+1 loop patterns in a request/trace's query entries. Ported from
37
+ * `nestjs-telescope`'s `query/n-plus-one-pattern.ts` (NestJS `batchId` becomes
38
+ * Adonis `traceId`). For each query family that repeats `>= threshold` times we
39
+ * emit a pattern weighted by the loop's total duration and attribute the likely
40
+ * driving parent (the distinct query that ran just before the loop began). Pure;
41
+ * ordered by total wasted duration desc.
42
+ *
43
+ * NOTE: callers pass entries in OLDEST-FIRST record order (ascending sequence) so
44
+ * "the query immediately preceding the loop" is meaningful. The store returns
45
+ * newest-first, so the service reverses before calling.
46
+ */
47
+ export function detectNPlusOnePatterns(entries, options) {
48
+ const queries = entries.filter((entry) => entry.type === EntryType.Query && entry.familyHash !== null);
49
+ const groups = new Map();
50
+ queries.forEach((entry, index) => {
51
+ const familyHash = entry.familyHash;
52
+ const duration = typeof entry.durationMs === 'number' ? entry.durationMs : 0;
53
+ const existing = groups.get(familyHash);
54
+ if (existing) {
55
+ existing.count += 1;
56
+ existing.totalDurationMs += duration;
57
+ }
58
+ else {
59
+ groups.set(familyHash, {
60
+ count: 1,
61
+ totalDurationMs: duration,
62
+ representativeId: entry.id,
63
+ sql: sqlOf(entry),
64
+ firstIndex: index,
65
+ });
66
+ }
67
+ });
68
+ const patterns = [];
69
+ for (const [childFamilyHash, group] of groups) {
70
+ if (group.count < options.threshold)
71
+ continue;
72
+ const parent = findParent(queries, group.firstIndex, childFamilyHash);
73
+ patterns.push({
74
+ childFamilyHash,
75
+ childSql: group.sql,
76
+ count: group.count,
77
+ totalDurationMs: group.totalDurationMs,
78
+ parentFamilyHash: parent?.familyHash ?? null,
79
+ parentSql: parent === null ? null : sqlOf(parent),
80
+ representativeId: group.representativeId,
81
+ traceId: queries[group.firstIndex]?.traceId ?? '',
82
+ });
83
+ }
84
+ return patterns.sort((a, b) => b.totalDurationMs - a.totalDurationMs ||
85
+ b.count - a.count ||
86
+ a.childFamilyHash.localeCompare(b.childFamilyHash));
87
+ }
88
+ /**
89
+ * The likely driving parent for a loop whose first child is at `firstIndex`:
90
+ * walking BACKWARDS from just before the loop, the first query of a DIFFERENT
91
+ * family. Returns `null` when none precedes it (the loop is the trace's start).
92
+ */
93
+ function findParent(queries, firstIndex, childFamilyHash) {
94
+ for (let i = firstIndex - 1; i >= 0; i--) {
95
+ const candidate = queries[i];
96
+ if (candidate !== undefined && candidate.familyHash !== childFamilyHash) {
97
+ return candidate;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ //# sourceMappingURL=n_plus_one.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"n_plus_one.js","sourceRoot":"","sources":["../../../src/query/n_plus_one.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionRegistry } from './extension/registry.js';
2
2
  import type { TelescopeStore } from './store.js';
3
+ import type { EntryEvents } from './stream/entry_events.js';
3
4
  /**
4
5
  * The live runtime telescope handles, published on a cross-copy-stable global slot
5
6
  * so the request middleware can reach the active store WITHOUT constructor wiring
@@ -14,6 +15,12 @@ export interface TelescopeRuntime {
14
15
  requestWatcherEnabled: boolean;
15
16
  /** The booted extension registry (entry types / dashboards / data providers), or `null`. */
16
17
  registry: ExtensionRegistry | null;
18
+ /**
19
+ * The live SSE entry-events bus the store's write path publishes persisted
20
+ * entries to and the UI stream route subscribes to, or `null` when live
21
+ * streaming is disabled / not booted.
22
+ */
23
+ entryEvents: EntryEvents | null;
17
24
  }
18
25
  /** The shared runtime handle. */
19
26
  export declare function getTelescopeRuntime(): TelescopeRuntime;
@@ -21,6 +28,8 @@ export declare function getTelescopeRuntime(): TelescopeRuntime;
21
28
  export declare function setTelescopeRuntime(store: TelescopeStore, requestWatcherEnabled: boolean): void;
22
29
  /** Publish the booted extension registry so the UI can serve its dashboards + providers. */
23
30
  export declare function setTelescopeExtensionRegistry(registry: ExtensionRegistry | null): void;
31
+ /** Publish the SSE entry-events bus so the UI stream route can subscribe to it. */
32
+ export declare function setTelescopeEntryEvents(entryEvents: EntryEvents | null): void;
24
33
  /** Tear down the runtime (called by the provider at shutdown). */
25
34
  export declare function resetTelescopeRuntime(): void;
26
35
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,iDAAiD;IACjD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4FAA4F;IAC5F,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpC;AAYD,iCAAiC;AACjC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,OAAO,GAAG,IAAI,CAG/F;AAED,4FAA4F;AAC5F,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAEtF;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,iDAAiD;IACjD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4FAA4F;IAC5F,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAaD,iCAAiC;AACjC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,OAAO,GAAG,IAAI,CAG/F;AAED,4FAA4F;AAC5F,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAEtF;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAE7E;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
@@ -4,6 +4,7 @@ const runtime = globalStore[RUNTIME_KEY] ?? {
4
4
  store: null,
5
5
  requestWatcherEnabled: false,
6
6
  registry: null,
7
+ entryEvents: null,
7
8
  };
8
9
  globalStore[RUNTIME_KEY] = runtime;
9
10
  /** The shared runtime handle. */
@@ -19,10 +20,15 @@ export function setTelescopeRuntime(store, requestWatcherEnabled) {
19
20
  export function setTelescopeExtensionRegistry(registry) {
20
21
  runtime.registry = registry;
21
22
  }
23
+ /** Publish the SSE entry-events bus so the UI stream route can subscribe to it. */
24
+ export function setTelescopeEntryEvents(entryEvents) {
25
+ runtime.entryEvents = entryEvents;
26
+ }
22
27
  /** Tear down the runtime (called by the provider at shutdown). */
23
28
  export function resetTelescopeRuntime() {
24
29
  runtime.store = null;
25
30
  runtime.requestWatcherEnabled = false;
26
31
  runtime.registry = null;
32
+ runtime.entryEvents = null;
27
33
  }
28
34
  //# sourceMappingURL=registry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC3D,MAAM,WAAW,GAAG,UAAsE,CAAC;AAE3F,MAAM,OAAO,GAAqB,WAAW,CAAC,WAAW,CAAC,IAAI;IAC5D,KAAK,EAAE,IAAI;IACX,qBAAqB,EAAE,KAAK;IAC5B,QAAQ,EAAE,IAAI;CACf,CAAC;AACF,WAAW,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC;AAEnC,iCAAiC;AACjC,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,KAAqB,EAAE,qBAA8B;IACvF,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,OAAO,CAAC,qBAAqB,GAAG,qBAAqB,CAAC;AACxD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,6BAA6B,CAAC,QAAkC;IAC9E,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,qBAAqB;IACnC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,qBAAqB,GAAG,KAAK,CAAC;IACtC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC3D,MAAM,WAAW,GAAG,UAAsE,CAAC;AAE3F,MAAM,OAAO,GAAqB,WAAW,CAAC,WAAW,CAAC,IAAI;IAC5D,KAAK,EAAE,IAAI;IACX,qBAAqB,EAAE,KAAK;IAC5B,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,IAAI;CAClB,CAAC;AACF,WAAW,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC;AAEnC,iCAAiC;AACjC,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,KAAqB,EAAE,qBAA8B;IACvF,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,OAAO,CAAC,qBAAqB,GAAG,qBAAqB,CAAC;AACxD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,6BAA6B,CAAC,QAAkC;IAC9E,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,uBAAuB,CAAC,WAA+B;IACrE,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;AACpC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,qBAAqB;IACnC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,qBAAqB,GAAG,KAAK,CAAC;IACtC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { RecordInput } from '../entry.js';
2
+ /**
3
+ * Tail-sampling rule for a single entry type. Keeps `rate` of the noise but
4
+ * always retains the entries that matter — errors and slow ones.
5
+ */
6
+ export interface SamplingRule {
7
+ /** Base keep-rate 0–1 applied to ordinary entries of this type. */
8
+ rate: number;
9
+ /** When true, always keep entries that look like errors (see {@link isErrorEntry}). */
10
+ keepErrors?: boolean;
11
+ /** When set, always keep entries whose `durationMs` is at least this value. */
12
+ keepSlowMs?: number;
13
+ }
14
+ /**
15
+ * Per-type sampling configuration. Each type maps to either a bare keep-rate
16
+ * (uniform down-sampling) or a {@link SamplingRule} object (tail-sampling: keep a
17
+ * fraction but always retain errors / slow entries). The reserved `default` key
18
+ * applies to every entry type that lacks a specific rate override.
19
+ */
20
+ export type SamplingConfig = Record<string, number | SamplingRule>;
21
+ /**
22
+ * Pragmatic, cheap structural error check used by tail-sampling's `keepErrors`.
23
+ * Reads only shallow, already-present fields — no deep walk — so it stays on the
24
+ * hot path. An entry "looks like an error" when:
25
+ * - its `tags` include `'failed'`, OR
26
+ * - `content.failed === true`, OR
27
+ * - `content.statusCode >= 500`, OR
28
+ * - `content.level` is `'warn'`, `'error'`, or `'fatal'` (a Log entry).
29
+ */
30
+ export declare function isErrorEntry(input: RecordInput): boolean;
31
+ /**
32
+ * Projects a {@link SamplingConfig} down to bare per-type keep-rates for the
33
+ * meta/dashboard contract, which only surfaces the headline rate.
34
+ */
35
+ export declare function samplingRates(sampling: SamplingConfig): Record<string, number>;
36
+ /**
37
+ * Tail-sampling decision for one input. Plain-number per-type config behaves
38
+ * exactly as a uniform keep-rate. A {@link SamplingRule} additionally always
39
+ * keeps errors (`keepErrors`) and slow entries (`durationMs >= keepSlowMs`)
40
+ * before falling back to the base rate. `random` is the injected 0–1 source.
41
+ *
42
+ * Default-neutral: a type with no rule (and no `default`) is always kept, so an
43
+ * unset `sampling` config records everything exactly as before.
44
+ */
45
+ export declare function passesSampling(sampling: SamplingConfig, input: RecordInput, random: () => number): boolean;
46
+ /**
47
+ * Normalize the author-facing `sampling` option (a bare number → `{ default }`,
48
+ * or a {@link SamplingConfig} as-is, or `undefined` → `{}` meaning record
49
+ * everything) into a {@link SamplingConfig}.
50
+ */
51
+ export declare function resolveSampling(sampling?: number | SamplingConfig): SamplingConfig;
52
+ //# sourceMappingURL=sampling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampling.d.ts","sourceRoot":"","sources":["../../../src/sampling/sampling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB/C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC;AAEnE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAwBxD;AAUD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,MAAM,GACnB,OAAO,CA4BT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,cAAc,CAIlF"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Tail-sampling for the WRITE path. Ported from `nestjs-telescope`'s
3
+ * `config/sampling.ts`: a per-entry-type keep `rate` plus `keepErrors` /
4
+ * `keepSlowMs` overrides so a host can sample noisy types aggressively while
5
+ * never dropping the entries that matter (errors, slow operations).
6
+ *
7
+ * Everything here is a PURE function with an injected RNG, so the sampling
8
+ * decision is deterministic in tests (no flaky `Math.random()` on the hot path).
9
+ */
10
+ /** Log levels that count as an error for `keepErrors` — a `warn` / `error` /
11
+ * `fatal` line is exactly what you never want sampled away. */
12
+ const ERROR_LOG_LEVELS = new Set(['warn', 'error', 'fatal']);
13
+ /**
14
+ * Pragmatic, cheap structural error check used by tail-sampling's `keepErrors`.
15
+ * Reads only shallow, already-present fields — no deep walk — so it stays on the
16
+ * hot path. An entry "looks like an error" when:
17
+ * - its `tags` include `'failed'`, OR
18
+ * - `content.failed === true`, OR
19
+ * - `content.statusCode >= 500`, OR
20
+ * - `content.level` is `'warn'`, `'error'`, or `'fatal'` (a Log entry).
21
+ */
22
+ export function isErrorEntry(input) {
23
+ if (input.tags?.includes('failed')) {
24
+ return true;
25
+ }
26
+ const { content } = input;
27
+ if (content === null || typeof content !== 'object') {
28
+ return false;
29
+ }
30
+ if ('failed' in content && content.failed === true) {
31
+ return true;
32
+ }
33
+ if ('statusCode' in content) {
34
+ const statusCode = content.statusCode;
35
+ if (typeof statusCode === 'number' && statusCode >= 500) {
36
+ return true;
37
+ }
38
+ }
39
+ if ('level' in content) {
40
+ const level = content.level;
41
+ if (typeof level === 'string' && ERROR_LOG_LEVELS.has(level)) {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Resolves the keep-rate for a sampling entry. Discriminated purely by `typeof`
49
+ * — a number is a bare rate, an object is a {@link SamplingRule}.
50
+ */
51
+ function ruleRate(rule) {
52
+ return typeof rule === 'number' ? rule : rule.rate;
53
+ }
54
+ /**
55
+ * Projects a {@link SamplingConfig} down to bare per-type keep-rates for the
56
+ * meta/dashboard contract, which only surfaces the headline rate.
57
+ */
58
+ export function samplingRates(sampling) {
59
+ const rates = {};
60
+ for (const [type, rule] of Object.entries(sampling)) {
61
+ rates[type] = ruleRate(rule);
62
+ }
63
+ return rates;
64
+ }
65
+ /**
66
+ * Tail-sampling decision for one input. Plain-number per-type config behaves
67
+ * exactly as a uniform keep-rate. A {@link SamplingRule} additionally always
68
+ * keeps errors (`keepErrors`) and slow entries (`durationMs >= keepSlowMs`)
69
+ * before falling back to the base rate. `random` is the injected 0–1 source.
70
+ *
71
+ * Default-neutral: a type with no rule (and no `default`) is always kept, so an
72
+ * unset `sampling` config records everything exactly as before.
73
+ */
74
+ export function passesSampling(sampling, input, random) {
75
+ const rule = sampling[input.type] ?? sampling.default;
76
+ if (rule === undefined) {
77
+ return true;
78
+ }
79
+ if (typeof rule === 'object') {
80
+ if (rule.keepErrors === true && isErrorEntry(input)) {
81
+ return true;
82
+ }
83
+ if (rule.keepSlowMs !== undefined &&
84
+ input.durationMs !== undefined &&
85
+ input.durationMs !== null &&
86
+ input.durationMs >= rule.keepSlowMs) {
87
+ return true;
88
+ }
89
+ }
90
+ const rate = ruleRate(rule);
91
+ if (rate >= 1) {
92
+ return true;
93
+ }
94
+ if (rate <= 0) {
95
+ return false;
96
+ }
97
+ return random() < rate;
98
+ }
99
+ /**
100
+ * Normalize the author-facing `sampling` option (a bare number → `{ default }`,
101
+ * or a {@link SamplingConfig} as-is, or `undefined` → `{}` meaning record
102
+ * everything) into a {@link SamplingConfig}.
103
+ */
104
+ export function resolveSampling(sampling) {
105
+ if (sampling === undefined)
106
+ return {};
107
+ if (typeof sampling === 'number')
108
+ return { default: sampling };
109
+ return sampling;
110
+ }
111
+ //# sourceMappingURL=sampling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampling.js","sourceRoot":"","sources":["../../../src/sampling/sampling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { Entry, RecordInput } from '../entry.js';
2
+ import type { EntryQuery, TelescopeStore } from '../store.js';
3
+ import { type SamplingConfig } from './sampling.js';
4
+ /**
5
+ * A {@link TelescopeStore} decorator that applies tail-sampling on the WRITE
6
+ * path: a dropped entry is NEVER persisted (its `record()` short-circuits before
7
+ * delegating to the inner store). Wiring sampling at the store boundary — the one
8
+ * place every watcher records through — guarantees no watcher can bypass it.
9
+ *
10
+ * The decision is made by the pure {@link passesSampling} using only the shallow
11
+ * fields already on the {@link RecordInput} (no deep walk), so the hot path stays
12
+ * cheap. The RNG is injected so the decision is deterministic in tests.
13
+ *
14
+ * Default-neutral: with an empty {@link SamplingConfig} (the default when the
15
+ * host sets no `sampling` option) every entry passes, so behavior is unchanged.
16
+ *
17
+ * A DROPPED entry still resolves to a synthetic {@link Entry} (never persisted,
18
+ * `sequence: -1`) so the fire-and-forget `record()` contract — callers `void` the
19
+ * promise — is honoured without surfacing the drop as an error.
20
+ */
21
+ export declare class SamplingTelescopeStore implements TelescopeStore {
22
+ private readonly inner;
23
+ private readonly sampling;
24
+ private readonly random;
25
+ constructor(inner: TelescopeStore, sampling: SamplingConfig, random?: () => number);
26
+ record<TContent>(input: RecordInput<TContent>): Promise<Entry<TContent>>;
27
+ get(id: string): Promise<Entry | null>;
28
+ list(query?: EntryQuery): Promise<Entry[]>;
29
+ count(): Promise<number>;
30
+ prune(olderThan: Date, keepLast?: number): Promise<number>;
31
+ clear(): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=sampling_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampling_store.d.ts","sourceRoot":"","sources":["../../../src/sampling/sampling_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,eAAe,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAEzD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,MAAM,MAAoB;IAG/C,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAO9E,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAItC,IAAI,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAI1C,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAIxB,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB"}
@@ -0,0 +1,65 @@
1
+ import { passesSampling } from './sampling.js';
2
+ /**
3
+ * A {@link TelescopeStore} decorator that applies tail-sampling on the WRITE
4
+ * path: a dropped entry is NEVER persisted (its `record()` short-circuits before
5
+ * delegating to the inner store). Wiring sampling at the store boundary — the one
6
+ * place every watcher records through — guarantees no watcher can bypass it.
7
+ *
8
+ * The decision is made by the pure {@link passesSampling} using only the shallow
9
+ * fields already on the {@link RecordInput} (no deep walk), so the hot path stays
10
+ * cheap. The RNG is injected so the decision is deterministic in tests.
11
+ *
12
+ * Default-neutral: with an empty {@link SamplingConfig} (the default when the
13
+ * host sets no `sampling` option) every entry passes, so behavior is unchanged.
14
+ *
15
+ * A DROPPED entry still resolves to a synthetic {@link Entry} (never persisted,
16
+ * `sequence: -1`) so the fire-and-forget `record()` contract — callers `void` the
17
+ * promise — is honoured without surfacing the drop as an error.
18
+ */
19
+ export class SamplingTelescopeStore {
20
+ inner;
21
+ sampling;
22
+ random;
23
+ constructor(inner, sampling, random = Math.random) {
24
+ this.inner = inner;
25
+ this.sampling = sampling;
26
+ this.random = random;
27
+ }
28
+ async record(input) {
29
+ if (!passesSampling(this.sampling, input, this.random)) {
30
+ return droppedEntry(input);
31
+ }
32
+ return this.inner.record(input);
33
+ }
34
+ get(id) {
35
+ return this.inner.get(id);
36
+ }
37
+ list(query) {
38
+ return this.inner.list(query);
39
+ }
40
+ count() {
41
+ return this.inner.count();
42
+ }
43
+ prune(olderThan, keepLast) {
44
+ return this.inner.prune(olderThan, keepLast);
45
+ }
46
+ clear() {
47
+ return this.inner.clear();
48
+ }
49
+ }
50
+ /** A non-persisted placeholder for a sampled-away entry (`sequence: -1`). */
51
+ function droppedEntry(input) {
52
+ return {
53
+ id: '',
54
+ type: input.type,
55
+ familyHash: input.familyHash ?? null,
56
+ content: input.content,
57
+ tags: input.tags ?? [],
58
+ sequence: -1,
59
+ durationMs: input.durationMs ?? null,
60
+ origin: input.origin ?? 'manual',
61
+ traceId: input.traceId ?? null,
62
+ createdAt: new Date(),
63
+ };
64
+ }
65
+ //# sourceMappingURL=sampling_store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampling_store.js","sourceRoot":"","sources":["../../../src/sampling/sampling_store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAuB,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,sBAAsB;IAEd;IACA;IACA;IAHnB,YACmB,KAAqB,EACrB,QAAwB,EACxB,SAAuB,IAAI,CAAC,MAAM;QAFlC,UAAK,GAAL,KAAK,CAAgB;QACrB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,WAAM,GAAN,MAAM,CAA4B;IAClD,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAW,KAA4B;QACjD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,KAAkB;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,SAAe,EAAE,QAAiB;QACtC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;CACF;AAED,6EAA6E;AAC7E,SAAS,YAAY,CAAW,KAA4B;IAC1D,OAAO;QACL,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;QACpC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACtB,QAAQ,EAAE,CAAC,CAAC;QACZ,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;QACpC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,QAAQ;QAChC,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI;QAC9B,SAAS,EAAE,IAAI,IAAI,EAAE;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { Entry } from '../entry.js';
2
+ /**
3
+ * A subscriber notified of each newly-persisted {@link Entry}. Run synchronously
4
+ * from {@link EntryEvents.publish}; it must not throw (the emitter swallows it
5
+ * regardless) and should not block — the SSE route's subscriber just enqueues a
6
+ * frame.
7
+ */
8
+ export type EntrySubscriber = (entry: Entry) => void;
9
+ /**
10
+ * Unsubscribe handle returned by {@link EntryEvents.subscribe}. Idempotent: calling
11
+ * it twice is a no-op.
12
+ */
13
+ export type Unsubscribe = () => void;
14
+ /**
15
+ * Process-local pub/sub of "an entry was just persisted". Fed from the store's
16
+ * write path (the {@link import('./streaming_store.js').StreamingTelescopeStore}
17
+ * decorator) AFTER redaction + sampling, so only entries that actually got stored
18
+ * — already scrubbed, never raw — are published. Consumed by the SSE stream route
19
+ * to push live entries to the dashboard.
20
+ *
21
+ * Port of the NestJS `EntryEvents` (an RxJS `Subject`), reshaped to a dependency-free
22
+ * synchronous emitter matching the Agora idiom (no RxJS). Stateless and best-effort:
23
+ * `publish` never throws into the flush path, and is a cheap no-op when there are no
24
+ * subscribers (`subscriberCount === 0`), so the hot write path pays nothing while the
25
+ * dashboard is closed.
26
+ */
27
+ export declare class EntryEvents {
28
+ private readonly subscribers;
29
+ /**
30
+ * Register a subscriber and get back an idempotent unsubscribe handle. The SSE
31
+ * route subscribes on connect and unsubscribes on client disconnect.
32
+ */
33
+ subscribe(subscriber: EntrySubscriber): Unsubscribe;
34
+ /**
35
+ * Publish a freshly-persisted entry to every subscriber. Best-effort: a throwing
36
+ * subscriber is isolated (logged-free swallow) so observability can never break
37
+ * the flush path. A no-op when there are no subscribers.
38
+ */
39
+ publish(entry: Entry): void;
40
+ /** Number of active subscribers — `0` means `publish` is a cheap no-op. */
41
+ get subscriberCount(): number;
42
+ /** Drop every subscriber (called by the provider at shutdown). */
43
+ clear(): void;
44
+ }
45
+ //# sourceMappingURL=entry_events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry_events.d.ts","sourceRoot":"","sources":["../../../src/stream/entry_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAE1D;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,eAAe,GAAG,WAAW;IAUnD;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAW3B,2EAA2E;IAC3E,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,kEAAkE;IAClE,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Process-local pub/sub of "an entry was just persisted". Fed from the store's
3
+ * write path (the {@link import('./streaming_store.js').StreamingTelescopeStore}
4
+ * decorator) AFTER redaction + sampling, so only entries that actually got stored
5
+ * — already scrubbed, never raw — are published. Consumed by the SSE stream route
6
+ * to push live entries to the dashboard.
7
+ *
8
+ * Port of the NestJS `EntryEvents` (an RxJS `Subject`), reshaped to a dependency-free
9
+ * synchronous emitter matching the Agora idiom (no RxJS). Stateless and best-effort:
10
+ * `publish` never throws into the flush path, and is a cheap no-op when there are no
11
+ * subscribers (`subscriberCount === 0`), so the hot write path pays nothing while the
12
+ * dashboard is closed.
13
+ */
14
+ export class EntryEvents {
15
+ subscribers = new Set();
16
+ /**
17
+ * Register a subscriber and get back an idempotent unsubscribe handle. The SSE
18
+ * route subscribes on connect and unsubscribes on client disconnect.
19
+ */
20
+ subscribe(subscriber) {
21
+ this.subscribers.add(subscriber);
22
+ let active = true;
23
+ return () => {
24
+ if (!active)
25
+ return;
26
+ active = false;
27
+ this.subscribers.delete(subscriber);
28
+ };
29
+ }
30
+ /**
31
+ * Publish a freshly-persisted entry to every subscriber. Best-effort: a throwing
32
+ * subscriber is isolated (logged-free swallow) so observability can never break
33
+ * the flush path. A no-op when there are no subscribers.
34
+ */
35
+ publish(entry) {
36
+ if (this.subscribers.size === 0)
37
+ return;
38
+ for (const subscriber of this.subscribers) {
39
+ try {
40
+ subscriber(entry);
41
+ }
42
+ catch {
43
+ // observability must never break the flush
44
+ }
45
+ }
46
+ }
47
+ /** Number of active subscribers — `0` means `publish` is a cheap no-op. */
48
+ get subscriberCount() {
49
+ return this.subscribers.size;
50
+ }
51
+ /** Drop every subscriber (called by the provider at shutdown). */
52
+ clear() {
53
+ this.subscribers.clear();
54
+ }
55
+ }
56
+ //# sourceMappingURL=entry_events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry_events.js","sourceRoot":"","sources":["../../../src/stream/entry_events.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,WAAW;IACL,WAAW,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE1D;;;OAGG;IACH,SAAS,CAAC,UAA2B;QACnC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACjC,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,GAAG,KAAK,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,KAAY;QAClB,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QACxC,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,UAAU,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,2CAA2C;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED,kEAAkE;IAClE,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ export { EntryEvents } from './entry_events.js';
2
+ export type { EntrySubscriber, Unsubscribe } from './entry_events.js';
3
+ export { StreamingTelescopeStore } from './streaming_store.js';
4
+ export { DEFAULT_HEARTBEAT_MS, streamEntries, } from './stream_handler.js';
5
+ export type { StreamOptions, StreamSession } from './stream_handler.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stream/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EACL,oBAAoB,EACpB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}