silgi 0.1.0-beta.13 → 0.1.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/core/context-bridge.ts
3
+ const ctxStorage = new AsyncLocalStorage();
4
+ function runWithCtx(ctx, fn) {
5
+ return ctxStorage.run(ctx, fn);
6
+ }
7
+ function getCtx() {
8
+ return ctxStorage.getStore();
9
+ }
10
+ //#endregion
11
+ export { getCtx, runWithCtx };
@@ -3,6 +3,7 @@ import { compileRouter, createContext, releaseContext } from "../compile.mjs";
3
3
  import { applyContext } from "./dispatch.mjs";
4
4
  import { analyticsTraceMap } from "../plugins/analytics.mjs";
5
5
  import { detectResponseFormat, encodeResponse, makeErrorResponse } from "./codec.mjs";
6
+ import { runWithCtx } from "./context-bridge.mjs";
6
7
  import { parseInput } from "./input.mjs";
7
8
  import { iteratorToEventStream } from "./sse.mjs";
8
9
  //#region src/core/handler.ts
@@ -128,7 +129,7 @@ function createFetchHandler(routerDef, contextFactory, hooks) {
128
129
  path: pathname,
129
130
  input: rawInput
130
131
  });
131
- const pipelineResult = route.handler(ctx, rawInput, request.signal);
132
+ const pipelineResult = runWithCtx(ctx, () => route.handler(ctx, rawInput, request.signal));
132
133
  const output = pipelineResult instanceof Promise ? await pipelineResult : pipelineResult;
133
134
  callHook("response", {
134
135
  path: pathname,
@@ -1,24 +1,4 @@
1
1
  //#region src/integrations/better-auth/index.d.ts
2
- /**
3
- * Silgi + Better Auth tracing integration.
4
- *
5
- * Provides a Better Auth plugin factory that auto-traces all auth operations
6
- * (sign-in, sign-up, OAuth, session management, etc.) into silgi analytics.
7
- *
8
- * The silgi request context is passed via `request.__silgiCtx`, set by
9
- * the silgi auth handler before calling `auth.handler(request)`.
10
- *
11
- * @example
12
- * ```ts
13
- * import { tracing } from 'silgi/better-auth'
14
- *
15
- * const auth = betterAuth({
16
- * plugins: [
17
- * tracing(), // auto-traces all auth operations
18
- * ],
19
- * })
20
- * ```
21
- */
22
2
  interface TracingConfig {
23
3
  /** Capture request body as span input (default: true) */
24
4
  captureInput?: boolean;
@@ -1,4 +1,4 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
1
+ import { getCtx, runWithCtx } from "../../core/context-bridge.mjs";
2
2
  //#region src/integrations/better-auth/index.ts
3
3
  /**
4
4
  * Silgi + Better Auth tracing integration.
@@ -20,7 +20,6 @@ import { AsyncLocalStorage } from "node:async_hooks";
20
20
  * })
21
21
  * ```
22
22
  */
23
- const ctxStorage = new AsyncLocalStorage();
24
23
  function matchOperation(path) {
25
24
  const normalized = path.replace(/^\/+/, "");
26
25
  if (normalized.endsWith("/sign-up/email") || normalized === "sign-up/email") return {
@@ -281,11 +280,11 @@ function instrumentBetterAuth(auth) {
281
280
  * Run a function with silgi context available to instrumented Better Auth API calls.
282
281
  */
283
282
  function withCtx(ctx, fn) {
284
- return ctxStorage.run(ctx, fn);
283
+ return runWithCtx(ctx, fn);
285
284
  }
286
285
  function wrapApiMethod(originalFn, operation, method) {
287
286
  return async function instrumented(...args) {
288
- const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
287
+ const reqTrace = getCtx()?.__analyticsTrace;
289
288
  if (!reqTrace) return originalFn.apply(this, args);
290
289
  const spanName = `auth.api.${operation}`;
291
290
  const start = performance.now();
@@ -1,4 +1,4 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
1
+ import { getCtx, runWithCtx } from "../../core/context-bridge.mjs";
2
2
  //#region src/integrations/drizzle/index.ts
3
3
  /**
4
4
  * Silgi + Drizzle ORM tracing integration.
@@ -31,7 +31,6 @@ import { AsyncLocalStorage } from "node:async_hooks";
31
31
  * })
32
32
  * ```
33
33
  */
34
- const ctxStorage = new AsyncLocalStorage();
35
34
  const INSTRUMENTED = "__silgiDrizzleInstrumented";
36
35
  const DEFAULT_DB_SYSTEM = "postgresql";
37
36
  const DEFAULT_MAX_QUERY_LENGTH = 1e3;
@@ -59,7 +58,7 @@ function instrumentDrizzle(db, config) {
59
58
  * All Drizzle queries inside `fn` will be recorded as trace spans.
60
59
  */
61
60
  function withCtx(ctx, fn) {
62
- return ctxStorage.run(ctx, fn);
61
+ return runWithCtx(ctx, fn);
63
62
  }
64
63
  function resolveConfig(config) {
65
64
  return {
@@ -84,7 +83,7 @@ function patchSession(session, cfg, isTx) {
84
83
  session.prepareQuery = function patchedPrepareQuery(...args) {
85
84
  const prepared = originalPrepareQuery.apply(this, args);
86
85
  if (!prepared || typeof prepared.execute !== "function") return prepared;
87
- const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
86
+ const reqTrace = getCtx()?.__analyticsTrace;
88
87
  if (!reqTrace) return prepared;
89
88
  const queryText = extractQueryText(args[0]) ?? prepared.rawQueryConfig?.text ?? prepared.queryConfig?.text ?? null;
90
89
  const originalExecute = prepared.execute.bind(prepared);
@@ -98,7 +97,7 @@ function patchSession(session, cfg, isTx) {
98
97
  if (typeof session.query === "function") {
99
98
  const originalQuery = session.query.bind(session);
100
99
  session.query = function patchedQuery(queryString, params) {
101
- const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
100
+ const reqTrace = getCtx()?.__analyticsTrace;
102
101
  if (!reqTrace) return originalQuery.call(this, queryString, params);
103
102
  return traceExecution(reqTrace, cfg, queryString ?? null, isTx, originalQuery, this, [queryString, params]);
104
103
  };
@@ -127,7 +126,7 @@ function patchRawClient(client, cfg) {
127
126
  if (!methodName) return false;
128
127
  const originalMethod = client[methodName].bind(client);
129
128
  client[methodName] = function patchedClientMethod(...args) {
130
- const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
129
+ const reqTrace = getCtx()?.__analyticsTrace;
131
130
  if (!reqTrace) return originalMethod.apply(this, args);
132
131
  return traceExecution(reqTrace, cfg, extractQueryText(args[0]) ?? null, false, originalMethod, this, args);
133
132
  };
@@ -141,7 +140,7 @@ function patchSessionExecute(session, cfg) {
141
140
  if (session[INSTRUMENTED]) return false;
142
141
  const originalExecute = session.execute.bind(session);
143
142
  session.execute = function patchedDeepExecute(...args) {
144
- const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
143
+ const reqTrace = getCtx()?.__analyticsTrace;
145
144
  if (!reqTrace) return originalExecute.apply(this, args);
146
145
  return traceExecution(reqTrace, cfg, extractQueryText(args[0]) ?? null, false, originalExecute, this, args);
147
146
  };
@@ -66,6 +66,7 @@ interface RequestEntry {
66
66
  timestamp: number;
67
67
  durationMs: number;
68
68
  method: string;
69
+ url: string;
69
70
  path: string;
70
71
  ip: string;
71
72
  headers: Record<string, string>;
@@ -93,12 +94,6 @@ interface AnalyticsOptions {
93
94
  bufferSize?: number;
94
95
  /** Time-series history in seconds (default: 120) */
95
96
  historySeconds?: number;
96
- /** Max error entries to keep (default: 100) */
97
- maxErrors?: number;
98
- /** Max recent request entries to keep (default: 200) */
99
- maxRequests?: number;
100
- /** Max task execution entries to keep (default: 200) */
101
- maxTasks?: number;
102
97
  /**
103
98
  * Protect dashboard access.
104
99
  * - `string` — secret token checked against `Authorization: Bearer <token>` header or `?token=` query param
@@ -108,6 +103,10 @@ interface AnalyticsOptions {
108
103
  auth?: string | ((req: Request) => boolean | Promise<boolean>);
109
104
  /** Interval in ms between storage flushes (default: 5000) */
110
105
  flushInterval?: number;
106
+ /** Days to retain entries in storage (default: 30). Entries older than this are pruned on flush. */
107
+ retentionDays?: number;
108
+ /** Path prefixes to exclude from tracking. Can also be managed at runtime via the dashboard or API. */
109
+ ignorePaths?: string[];
111
110
  }
112
111
  interface ProcedureSnapshot {
113
112
  count: number;
@@ -189,6 +188,13 @@ declare function trace<T>(ctx: Record<string, unknown>, name: string, fn: () =>
189
188
  declare class AnalyticsCollector {
190
189
  #private;
191
190
  constructor(options?: AnalyticsOptions);
191
+ /** Check if a path is server-side ignored (from config). */
192
+ isIgnored(pathname: string): boolean;
193
+ /** Check if a path is hidden in the dashboard (from runtime API). */
194
+ isHidden(pathname: string): boolean;
195
+ addHiddenPath(path: string): void;
196
+ removeHiddenPath(path: string): void;
197
+ getHiddenPaths(): string[];
192
198
  record(path: string, durationMs: number): void;
193
199
  recordError(path: string, durationMs: number, errorMsg: string): void;
194
200
  recordDetailedError(entry: Omit<ErrorEntry, 'id'>): void;
@@ -224,7 +230,7 @@ declare function sanitizeHeaders(headers: Headers): Record<string, string>;
224
230
  /** Return auth-failure response for analytics routes. */
225
231
  declare function analyticsAuthResponse(pathname: string): Response;
226
232
  /** Serve analytics dashboard and API routes. */
227
- declare function serveAnalyticsRoute(pathname: string, collector: AnalyticsCollector, dashboardHtml: string | undefined): Promise<Response>;
233
+ declare function serveAnalyticsRoute(pathname: string, request: Request, collector: AnalyticsCollector, dashboardHtml: string | undefined): Promise<Response>;
228
234
  /**
229
235
  * Wrap a fetch handler with analytics collection.
230
236
  * Intercepts analytics dashboard routes and instruments every request.