@adonis-agora/telescope 0.22.0 → 0.23.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 (48) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/src/ai/index.d.ts +1 -1
  3. package/dist/src/ai/index.js +1 -1
  4. package/dist/src/alerts/index.d.ts +1 -1
  5. package/dist/src/alerts/index.js +1 -1
  6. package/dist/src/index.d.ts +9 -2
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js +4 -1
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/mcp/index.d.ts +1 -1
  11. package/dist/src/mcp/index.js +1 -1
  12. package/dist/src/profiling/index.d.ts +1 -1
  13. package/dist/src/profiling/index.js +1 -1
  14. package/dist/src/stores/factory.d.ts +79 -0
  15. package/dist/src/stores/factory.d.ts.map +1 -1
  16. package/dist/src/stores/factory.js +71 -0
  17. package/dist/src/stores/factory.js.map +1 -1
  18. package/dist/src/stores/parquet.d.ts +184 -0
  19. package/dist/src/stores/parquet.d.ts.map +1 -0
  20. package/dist/src/stores/parquet.js +461 -0
  21. package/dist/src/stores/parquet.js.map +1 -0
  22. package/dist/src/stores/parquet_duckdb.d.ts +52 -0
  23. package/dist/src/stores/parquet_duckdb.d.ts.map +1 -0
  24. package/dist/src/stores/parquet_duckdb.js +266 -0
  25. package/dist/src/stores/parquet_duckdb.js.map +1 -0
  26. package/dist/src/stores/parquet_format.d.ts +108 -0
  27. package/dist/src/stores/parquet_format.d.ts.map +1 -0
  28. package/dist/src/stores/parquet_format.js +175 -0
  29. package/dist/src/stores/parquet_format.js.map +1 -0
  30. package/dist/src/stores/parquet_hyparquet.d.ts +34 -0
  31. package/dist/src/stores/parquet_hyparquet.d.ts.map +1 -0
  32. package/dist/src/stores/parquet_hyparquet.js +212 -0
  33. package/dist/src/stores/parquet_hyparquet.js.map +1 -0
  34. package/dist/src/stores/parquet_location.d.ts +88 -0
  35. package/dist/src/stores/parquet_location.d.ts.map +1 -0
  36. package/dist/src/stores/parquet_location.js +194 -0
  37. package/dist/src/stores/parquet_location.js.map +1 -0
  38. package/dist/src/stores/tiered.d.ts +91 -0
  39. package/dist/src/stores/tiered.d.ts.map +1 -0
  40. package/dist/src/stores/tiered.js +254 -0
  41. package/dist/src/stores/tiered.js.map +1 -0
  42. package/dist/src/ui/index.d.ts +1 -1
  43. package/dist/src/ui/index.js +1 -1
  44. package/dist/src/watchers/index.d.ts +1 -1
  45. package/dist/src/watchers/index.js +1 -1
  46. package/package.json +29 -4
  47. package/skills/telescope-setup/SKILL.md +1 -1
  48. package/skills/telescope-storage-retention/SKILL.md +7 -4
@@ -0,0 +1,254 @@
1
+ import { mergeNewestFirst } from './parquet.js';
2
+ import { resolvePage } from './parquet_format.js';
3
+ /**
4
+ * A two-tier {@link TelescopeStore}: a hot store kept minutes deep and a Parquet cold
5
+ * tier for everything older.
6
+ *
7
+ * - **Writes** only reach the hot store.
8
+ * - **`prune(olderThan, keepLast)`** first moves hot entries older than
9
+ * `now - hotFor` (and newer than `olderThan`) to cold, deleting them from hot only
10
+ * after the files are written; then applies the retention cutoff to the cold tier
11
+ * (whole hourly files); then compacts closed hours. A failed write deletes the files
12
+ * it wrote and leaves the hot store untouched, so the next cycle retries.
13
+ * - **Reads** go to hot first and to cold for whatever the answer still needs, merged
14
+ * newest-first without duplicate ids, so `{ page, size }` pages straight across the
15
+ * tier boundary. If the cold tier cannot be read, hot results are returned and a
16
+ * warning is logged; the dashboard never fails because of the cold tier.
17
+ */
18
+ export class TieredTelescopeStore {
19
+ hot;
20
+ cold;
21
+ hotForMs;
22
+ moveBatchSize;
23
+ moveFlushSize;
24
+ compactEnabled;
25
+ logger;
26
+ lastWarnAt = 0;
27
+ report = null;
28
+ constructor(options) {
29
+ if (!(options.hotForMs > 0)) {
30
+ throw new Error('@adonis-agora/telescope: storage.tiered() needs a positive `hotFor`.');
31
+ }
32
+ this.hot = options.hot;
33
+ this.cold = options.cold;
34
+ this.hotForMs = options.hotForMs;
35
+ this.moveBatchSize = Math.max(1, Math.floor(options.moveBatchSize ?? 5_000));
36
+ this.moveFlushSize = Math.max(this.moveBatchSize, Math.floor(options.moveFlushSize ?? 50_000));
37
+ this.compactEnabled = options.compact ?? true;
38
+ this.logger = options.logger ?? console;
39
+ }
40
+ record(input) {
41
+ return this.hot.record(input);
42
+ }
43
+ async get(id) {
44
+ const hot = await this.hot.get(id);
45
+ if (hot)
46
+ return hot;
47
+ return this.coldRead('get', () => this.cold.get(id), null);
48
+ }
49
+ async list(query = {}) {
50
+ const { offset, size } = resolvePage(query);
51
+ const { page: _page, size: _size, ...filters } = query;
52
+ if (size === undefined) {
53
+ const [hot, cold] = await Promise.all([
54
+ this.hot.list(filters),
55
+ this.coldRead('list', () => this.cold.top(filters, undefined), []),
56
+ ]);
57
+ return mergeNewestFirst(hot, cold);
58
+ }
59
+ // Each tier's newest `offset + size` is enough to produce the page from their merge.
60
+ const need = offset + size;
61
+ const hot = await this.hot.list({ ...filters, page: 1, size: need });
62
+ if (hot.length === need) {
63
+ const oldestHot = hot[hot.length - 1].createdAt;
64
+ const newestCold = await this.coldRead('list', () => this.cold.newestCreatedAt(filters), null);
65
+ if (newestCold === null || newestCold < oldestHot)
66
+ return hot.slice(offset, need);
67
+ }
68
+ const cold = await this.coldRead('list', () => this.cold.top(filters, need), []);
69
+ return mergeNewestFirst(hot, cold).slice(offset, need);
70
+ }
71
+ async count() {
72
+ const [hot, cold] = await Promise.all([
73
+ this.hot.count(),
74
+ this.coldRead('count', () => this.cold.count(), 0),
75
+ ]);
76
+ return hot + cold;
77
+ }
78
+ /**
79
+ * The union's top traces: each tier's top `offset + size` by last-seen, merged by the
80
+ * newer of the two timestamps. A trace in the union's top N is necessarily in the top
81
+ * N of the tier where it was last seen, so nothing is missed.
82
+ */
83
+ async listTraceIds(query) {
84
+ const size = Math.max(0, Math.floor(query.size));
85
+ const offset = (Math.max(1, Math.floor(query.page ?? 1)) - 1) * size;
86
+ const need = offset + size;
87
+ if (need === 0)
88
+ return [];
89
+ const window = {
90
+ ...(query.before !== undefined ? { before: query.before } : {}),
91
+ ...(query.after !== undefined ? { after: query.after } : {}),
92
+ };
93
+ const [hot, cold] = await Promise.all([
94
+ this.hot.listTraceIds
95
+ ? this.hot.listTraceIds({ ...window, size: need, page: 1 })
96
+ : traceIdsFromList(await this.hot.list(window), need),
97
+ this.coldRead('listTraceIds', () => this.cold.topTraceIds(window, need), []),
98
+ ]);
99
+ const lastAt = new Map();
100
+ for (const row of [...hot, ...cold]) {
101
+ const at = row.lastAt.getTime();
102
+ const seen = lastAt.get(row.traceId);
103
+ if (seen === undefined || at > seen)
104
+ lastAt.set(row.traceId, at);
105
+ }
106
+ return [...lastAt.entries()]
107
+ .sort((a, b) => b[1] - a[1])
108
+ .slice(offset, need)
109
+ .map(([traceId, at]) => ({ traceId, lastAt: new Date(at) }));
110
+ }
111
+ async countByBucket(query) {
112
+ const [hot, cold] = await Promise.all([
113
+ this.hot.countByBucket ? this.hot.countByBucket(query) : this.bucketsFromHotList(query),
114
+ this.coldRead('countByBucket', () => this.cold.countByBucket(query), []),
115
+ ]);
116
+ const tally = new Map();
117
+ for (const row of [...hot, ...cold]) {
118
+ const key = `${row.index}\u0000${row.type}`;
119
+ const seen = tally.get(key);
120
+ if (seen === undefined)
121
+ tally.set(key, { ...row });
122
+ else
123
+ seen.count += row.count;
124
+ }
125
+ return [...tally.values()];
126
+ }
127
+ async prune(olderThan, keepLast) {
128
+ const hotCutoff = new Date(Date.now() - this.hotForMs);
129
+ const report = {
130
+ moved: 0,
131
+ hotDeleted: 0,
132
+ coldExpired: 0,
133
+ compactedFiles: 0,
134
+ };
135
+ this.report = report;
136
+ if (hotCutoff > olderThan) {
137
+ // Move-then-delete. A failure in `moveToCold` leaves the hot store as it was.
138
+ // With `keepLast`, hot entries already past retention move too, so the cold
139
+ // tier's expiry applies the count across both tiers.
140
+ const moveAfter = keepLast === undefined ? olderThan : new Date(0);
141
+ report.moved = await this.moveToCold(moveAfter, hotCutoff);
142
+ report.hotDeleted = await this.hot.prune(hotCutoff);
143
+ }
144
+ else {
145
+ // Retention is shorter than the hot window: nothing ever reaches cold.
146
+ report.hotDeleted = await this.hot.prune(olderThan, keepLast);
147
+ }
148
+ try {
149
+ report.coldExpired = await this.cold.expire(olderThan, keepLast);
150
+ if (this.compactEnabled)
151
+ report.compactedFiles = await this.cold.compact(hotCutoff);
152
+ }
153
+ catch (error) {
154
+ report.error = message(error);
155
+ this.logger.warn(`Telescope cold tier maintenance failed: ${report.error}`);
156
+ }
157
+ return Math.max(0, report.hotDeleted - report.moved) + report.coldExpired;
158
+ }
159
+ /** What the last prune cycle moved, deleted and compacted (null before the first). */
160
+ lastPrune() {
161
+ return this.report ? { ...this.report } : null;
162
+ }
163
+ async clear() {
164
+ await Promise.all([this.hot.clear(), this.cold.clear()]);
165
+ }
166
+ /** Release the cold engine (DuckDB holds a native instance). */
167
+ async close() {
168
+ await this.cold.close();
169
+ }
170
+ /**
171
+ * Copy hot entries in `[after, before)` to cold. Pages are offset-based over a fixed
172
+ * window that nothing is deleted from until the whole move succeeded, so no entry is
173
+ * skipped; entries reach the window only by ageing past `before`, after this cycle's
174
+ * snapshot. Resolves to the number of entries written.
175
+ */
176
+ async moveToCold(after, before) {
177
+ const window = { after: new Date(after.getTime() - 1), before };
178
+ const written = [];
179
+ let buffered = [];
180
+ let moved = 0;
181
+ try {
182
+ for (let page = 1;; page++) {
183
+ const rows = await this.hot.list({ ...window, page, size: this.moveBatchSize });
184
+ buffered.push(...rows);
185
+ if (buffered.length >= this.moveFlushSize ||
186
+ (rows.length < this.moveBatchSize && buffered.length > 0)) {
187
+ written.push(...(await this.cold.write(buffered)));
188
+ moved += buffered.length;
189
+ buffered = [];
190
+ }
191
+ if (rows.length < this.moveBatchSize)
192
+ break;
193
+ }
194
+ }
195
+ catch (error) {
196
+ await this.cold.location.delete(written).catch(() => undefined);
197
+ throw error;
198
+ }
199
+ return moved;
200
+ }
201
+ async bucketsFromHotList(query) {
202
+ const startMs = query.after.getTime();
203
+ const bucketMs = Math.max(1, Math.floor(query.bucketMs));
204
+ const rows = await this.hot.list({
205
+ after: query.after,
206
+ before: new Date(query.before.getTime() + 1),
207
+ ...(query.type !== undefined ? { type: query.type } : {}),
208
+ });
209
+ const tally = new Map();
210
+ for (const entry of rows) {
211
+ const index = Math.floor((entry.createdAt.getTime() - startMs) / bucketMs);
212
+ const key = `${index}\u0000${entry.type}`;
213
+ const seen = tally.get(key);
214
+ if (seen === undefined)
215
+ tally.set(key, { index, type: entry.type, count: 1 });
216
+ else
217
+ seen.count += 1;
218
+ }
219
+ return [...tally.values()];
220
+ }
221
+ /** Run a cold read; on failure log (at most once a minute) and use `fallback`. */
222
+ async coldRead(what, read, fallback) {
223
+ try {
224
+ return await read();
225
+ }
226
+ catch (error) {
227
+ const now = Date.now();
228
+ if (now - this.lastWarnAt > 60_000) {
229
+ this.lastWarnAt = now;
230
+ this.logger.warn(`Telescope cold tier unavailable (${what}); serving hot entries only: ${message(error)}`);
231
+ }
232
+ return fallback;
233
+ }
234
+ }
235
+ }
236
+ function traceIdsFromList(entries, limit) {
237
+ const lastAt = new Map();
238
+ for (const entry of entries) {
239
+ if (entry.traceId === null)
240
+ continue;
241
+ const at = entry.createdAt.getTime();
242
+ const seen = lastAt.get(entry.traceId);
243
+ if (seen === undefined || at > seen)
244
+ lastAt.set(entry.traceId, at);
245
+ }
246
+ return [...lastAt.entries()]
247
+ .sort((a, b) => b[1] - a[1])
248
+ .slice(0, limit)
249
+ .map(([traceId, at]) => ({ traceId, lastAt: new Date(at) }));
250
+ }
251
+ function message(error) {
252
+ return error instanceof Error ? error.message : String(error);
253
+ }
254
+ //# sourceMappingURL=tiered.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tiered.js","sourceRoot":"","sources":["../../../src/stores/tiered.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.22.0";
2
+ export declare const VERSION = "0.23.0";
3
3
  export type { AccessDeniedInfo, AccessDeniedPageOptions, AccessDeniedReason, } from './access_denied_page.js';
4
4
  export { CONSOLE as ACCESS_DENIED_CONSOLE, escapeHtml, renderAccessDeniedPage, resolveAccessDeniedPage, } from './access_denied_page.js';
5
5
  export type { EntrySummary, RetentionInfo, RetentionOptions, RetentionPruneOptions, RetentionSamplingRate, } from './api.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.22.0';
2
+ export const VERSION = '0.23.0';
3
3
  // — the built-in "access denied" page (what a browser sees on a refused page navigation) —
4
4
  export { CONSOLE as ACCESS_DENIED_CONSOLE, escapeHtml, renderAccessDeniedPage, resolveAccessDeniedPage, } from './access_denied_page.js';
5
5
  // — JSON API —
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.22.0";
2
+ export declare const VERSION = "0.23.0";
3
3
  export type { CacheEntryContent, CacheEventLike, CacheOperation } from './cache_watcher.js';
4
4
  export { buildCacheEntry, CACHE_EVENTS, CacheWatcher } from './cache_watcher.js';
5
5
  export { nextCronRunMs } from './cron_next_run.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.22.0';
2
+ export const VERSION = '0.23.0';
3
3
  // — cache watcher —
4
4
  export { buildCacheEntry, CACHE_EVENTS, CacheWatcher } from './cache_watcher.js';
5
5
  export { nextCronRunMs } from './cron_next_run.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/telescope",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Laravel Telescope-style headless observability for AdonisJS — records HTTP requests and every Agora diagnostics event as queryable entries, plus opt-in subpaths for per-technology watchers, a web dashboard, AI exception diagnosis, and alerting.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -133,13 +133,18 @@
133
133
  "@adonisjs/lucid": "^22.4.0",
134
134
  "@adonisjs/mail": "^10.2.0",
135
135
  "@anthropic-ai/sdk": ">=0.32.0 <1.0.0",
136
+ "@aws-sdk/client-s3": "^3.400.0",
137
+ "@duckdb/node-api": ">=1.5.6-r.1 <2.0.0",
136
138
  "@opentelemetry/api": "^1.9.0",
137
139
  "@opentelemetry/exporter-logs-otlp-http": ">=0.200.0 <1.0.0",
138
140
  "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0 <1.0.0",
139
141
  "@opentelemetry/resources": "^2.0.0",
140
142
  "@opentelemetry/sdk-logs": ">=0.200.0 <1.0.0",
141
143
  "@opentelemetry/sdk-trace-base": "^2.0.0",
142
- "cron-parser": "^4.0.0 || ^5.0.0"
144
+ "cron-parser": "^4.0.0 || ^5.0.0",
145
+ "hyparquet": "^1.31.0",
146
+ "hyparquet-compressors": "^1.1.0",
147
+ "hyparquet-writer": ">=0.17.0 <1.0.0"
143
148
  },
144
149
  "peerDependenciesMeta": {
145
150
  "@adonisjs/lucid": {
@@ -174,13 +179,30 @@
174
179
  },
175
180
  "cron-parser": {
176
181
  "optional": true
182
+ },
183
+ "@aws-sdk/client-s3": {
184
+ "optional": true
185
+ },
186
+ "@duckdb/node-api": {
187
+ "optional": true
188
+ },
189
+ "hyparquet": {
190
+ "optional": true
191
+ },
192
+ "hyparquet-compressors": {
193
+ "optional": true
194
+ },
195
+ "hyparquet-writer": {
196
+ "optional": true
177
197
  }
178
198
  },
179
199
  "devDependencies": {
180
200
  "@adonisjs/assembler": "8.5.2",
181
201
  "@adonisjs/core": "7.6.0",
182
202
  "@adonisjs/lucid": "22.4.2",
183
- "@anthropic-ai/sdk": "0.133.0",
203
+ "@anthropic-ai/sdk": "0.135.0",
204
+ "@aws-sdk/client-s3": "3.1154.0",
205
+ "@duckdb/node-api": "1.5.6-r.1",
184
206
  "@opentelemetry/api": "1.9.1",
185
207
  "@opentelemetry/exporter-logs-otlp-http": "0.223.0",
186
208
  "@opentelemetry/exporter-trace-otlp-http": "0.223.0",
@@ -192,9 +214,12 @@
192
214
  "@types/node": "26.6.5",
193
215
  "better-sqlite3": "13.0.3",
194
216
  "cron-parser": "5.10.2",
217
+ "hyparquet": "1.31.5",
218
+ "hyparquet-compressors": "1.1.2",
219
+ "hyparquet-writer": "0.17.0",
195
220
  "luxon": "3.7.2",
196
221
  "typescript": "7.0.2",
197
- "vitest": "5.0.4"
222
+ "vitest": "5.0.5"
198
223
  },
199
224
  "engines": {
200
225
  "node": ">=24"
@@ -4,7 +4,7 @@ description: >-
4
4
  Set up @adonis-agora/telescope in an AdonisJS app. Covers node ace configure
5
5
  codemods (telescope_provider + TelescopeMiddleware on the server stack +
6
6
  config/telescope.ts), defineConfig keys (enabled/store/stores/watchers/redact),
7
- the storage.memory and storage.lucid drivers, the headless TelescopeService query
7
+ the storage.memory, storage.lucid and storage.tiered drivers, the headless TelescopeService query
8
8
  API (list/find/byTrace/count/topFamilies/topTags/getHealth), recordException for
9
9
  non-HTTP paths, opt-in requestCapture body gates, and why /telescope 404s without
10
10
  @adonis-agora/telescope-ui. Use for "install telescope", "telemetry is empty",
@@ -2,7 +2,8 @@
2
2
  name: telescope-storage-retention
3
3
  description: >-
4
4
  Storage, retention, redaction and sampling in @adonis-agora/telescope — the
5
- storage.memory ring buffer vs the storage.lucid SQL driver (optional peer,
5
+ storage.memory ring buffer vs the storage.lucid SQL driver vs the
6
+ storage.tiered hot-plus-Parquet driver (optional peers,
6
7
  create_telescope_entries_table migration), the TelescopeStore contract
7
8
  (record/get/list/count/prune/clear), prune {after,keepLast,intervalMs}, the
8
9
  overload guard + setTelescopePaused, redact {keys,perType}, tail-sampling rules
@@ -27,9 +28,11 @@ sources:
27
28
 
28
29
  A store sits between capture and query: watchers `record` into it, and
29
30
  `TelescopeService` reads out of it. Storage is **config-driven** — build named
30
- drivers with the `storage` factory, pick one with `store`. Two ship in the box:
31
- `memory` (bounded ring buffer) and `lucid` (persistent SQL via an optional
32
- `@adonisjs/lucid` peer imported lazily only when selected).
31
+ drivers with the `storage` factory, pick one with `store`. Three ship in the box:
32
+ `memory` (bounded ring buffer), `lucid` (persistent SQL via an optional
33
+ `@adonisjs/lucid` peer imported lazily only when selected) and `tiered` (a hot
34
+ driver kept minutes deep plus a Parquet cold tier for everything older — see the
35
+ Scaling storage docs).
33
36
 
34
37
  ## Setup
35
38