silgi 0.1.0-beta.3 → 0.1.0-beta.4
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.
- package/dist/core/handler.mjs +4 -10
- package/dist/integrations/better-auth/index.d.mts +61 -0
- package/dist/integrations/better-auth/index.mjs +332 -0
- package/dist/integrations/drizzle/index.d.mts +27 -0
- package/dist/integrations/drizzle/index.mjs +286 -0
- package/dist/plugins/analytics.d.mts +22 -1
- package/dist/plugins/analytics.mjs +20 -6
- package/lib/dashboard/index.html +3 -3
- package/package.json +11 -7
- package/dist/adapters/elysia.d.mts +0 -17
- package/dist/adapters/elysia.mjs +0 -76
package/dist/core/handler.mjs
CHANGED
|
@@ -232,7 +232,7 @@ location.reload();
|
|
|
232
232
|
});
|
|
233
233
|
const route = match.data;
|
|
234
234
|
const method = request.method;
|
|
235
|
-
if (ctxFactoryIsSync && (method === "GET" || !request.body
|
|
235
|
+
if (ctxFactoryIsSync && (method === "GET" || !request.body) && !route.passthrough) {
|
|
236
236
|
const usePool = !ctxFactoryIsEmpty || match.params || collector;
|
|
237
237
|
const ctx = usePool ? ctxPool.borrow() : emptyCtx;
|
|
238
238
|
let t0 = 0;
|
|
@@ -376,13 +376,7 @@ location.reload();
|
|
|
376
376
|
ctx.__analyticsTrace = reqTrace;
|
|
377
377
|
ctx.trace = reqTrace.trace.bind(reqTrace);
|
|
378
378
|
}
|
|
379
|
-
if (route.passthrough) {
|
|
380
|
-
if (collector && request.body && request.method !== "GET") try {
|
|
381
|
-
const cloned = request.clone();
|
|
382
|
-
const ct = cloned.headers.get("content-type");
|
|
383
|
-
if (ct && ct.includes("json")) rawInput = await cloned.json();
|
|
384
|
-
} catch {}
|
|
385
|
-
} else if (request.method === "GET") {
|
|
379
|
+
if (route.passthrough) {} else if (request.method === "GET") {
|
|
386
380
|
if (qMark !== -1) {
|
|
387
381
|
const searchStr = url.slice(qMark + 1);
|
|
388
382
|
const dataIdx = searchStr.indexOf("data=");
|
|
@@ -435,8 +429,8 @@ location.reload();
|
|
|
435
429
|
procedure: pathname,
|
|
436
430
|
durationMs,
|
|
437
431
|
status: output.status,
|
|
438
|
-
input: rawInput,
|
|
439
|
-
output: null,
|
|
432
|
+
input: rawInput ?? reqTrace?.procedureInput ?? null,
|
|
433
|
+
output: reqTrace?.procedureOutput ?? null,
|
|
440
434
|
spans: reqTrace?.spans ?? []
|
|
441
435
|
});
|
|
442
436
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { silgiTracing } from 'silgi/better-auth'
|
|
14
|
+
*
|
|
15
|
+
* const auth = betterAuth({
|
|
16
|
+
* plugins: [
|
|
17
|
+
* silgiTracing(), // auto-traces all auth operations
|
|
18
|
+
* ],
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface SilgiTracingConfig {
|
|
23
|
+
/** Capture request body as span input (default: true) */
|
|
24
|
+
captureInput?: boolean;
|
|
25
|
+
/** Capture response data as span output (default: true) */
|
|
26
|
+
captureOutput?: boolean;
|
|
27
|
+
/** Pass `createAuthMiddleware` from `better-auth/api` to wrap hooks handler */
|
|
28
|
+
createAuthMiddleware?: (handler: any) => any;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a Better Auth plugin that auto-traces all auth operations
|
|
32
|
+
* into silgi analytics spans.
|
|
33
|
+
*
|
|
34
|
+
* @param config - Optional configuration
|
|
35
|
+
* @returns A Better Auth plugin (typed as `any` to avoid requiring better-auth types at build time)
|
|
36
|
+
*/
|
|
37
|
+
declare function silgiTracing(config?: SilgiTracingConfig): any;
|
|
38
|
+
/**
|
|
39
|
+
* Instrument a Better Auth instance to trace all `auth.api.*` method calls.
|
|
40
|
+
* Works with `withSilgiCtx` — programmatic calls from background jobs,
|
|
41
|
+
* server-side session fetches etc. are traced when context is available.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { instrumentBetterAuth, withSilgiCtx } from 'silgi/better-auth'
|
|
46
|
+
*
|
|
47
|
+
* const auth = instrumentBetterAuth(betterAuth({ ... }))
|
|
48
|
+
*
|
|
49
|
+
* // In a silgi procedure:
|
|
50
|
+
* const me = s.$resolve(async ({ ctx }) => {
|
|
51
|
+
* return withSilgiCtx(ctx, () => auth.api.getSession({ headers: ctx.headers }))
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function instrumentBetterAuth<T extends Record<string, any>>(auth: T): T;
|
|
56
|
+
/**
|
|
57
|
+
* Run a function with silgi context available to instrumented Better Auth API calls.
|
|
58
|
+
*/
|
|
59
|
+
declare function withSilgiCtx<T>(ctx: Record<string, unknown>, fn: () => T): T;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { SilgiTracingConfig, instrumentBetterAuth, silgiTracing, withSilgiCtx };
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region src/integrations/better-auth/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Silgi + Better Auth tracing integration.
|
|
5
|
+
*
|
|
6
|
+
* Provides a Better Auth plugin factory that auto-traces all auth operations
|
|
7
|
+
* (sign-in, sign-up, OAuth, session management, etc.) into silgi analytics.
|
|
8
|
+
*
|
|
9
|
+
* The silgi request context is passed via `request.__silgiCtx`, set by
|
|
10
|
+
* the silgi auth handler before calling `auth.handler(request)`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { silgiTracing } from 'silgi/better-auth'
|
|
15
|
+
*
|
|
16
|
+
* const auth = betterAuth({
|
|
17
|
+
* plugins: [
|
|
18
|
+
* silgiTracing(), // auto-traces all auth operations
|
|
19
|
+
* ],
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
const ctxStorage = new AsyncLocalStorage();
|
|
24
|
+
function matchOperation(path) {
|
|
25
|
+
const normalized = path.replace(/^\/+/, "");
|
|
26
|
+
if (normalized.endsWith("/sign-up/email") || normalized === "sign-up/email") return {
|
|
27
|
+
spanName: "auth.signup.email",
|
|
28
|
+
operation: "signup",
|
|
29
|
+
method: "email"
|
|
30
|
+
};
|
|
31
|
+
if (normalized.endsWith("/sign-in/email") || normalized === "sign-in/email") return {
|
|
32
|
+
spanName: "auth.signin.email",
|
|
33
|
+
operation: "signin",
|
|
34
|
+
method: "email"
|
|
35
|
+
};
|
|
36
|
+
if (normalized.endsWith("/sign-out") || normalized === "sign-out") return {
|
|
37
|
+
spanName: "auth.signout",
|
|
38
|
+
operation: "signout"
|
|
39
|
+
};
|
|
40
|
+
if (normalized.endsWith("/get-session") || normalized === "get-session") return {
|
|
41
|
+
spanName: "auth.get_session",
|
|
42
|
+
operation: "get_session"
|
|
43
|
+
};
|
|
44
|
+
if (normalized.endsWith("/update-user") || normalized === "update-user") return {
|
|
45
|
+
spanName: "auth.update_user",
|
|
46
|
+
operation: "update_user"
|
|
47
|
+
};
|
|
48
|
+
if (normalized.endsWith("/delete-user") || normalized === "delete-user") return {
|
|
49
|
+
spanName: "auth.delete_user",
|
|
50
|
+
operation: "delete_user"
|
|
51
|
+
};
|
|
52
|
+
if (normalized.endsWith("/change-password") || normalized === "change-password") return {
|
|
53
|
+
spanName: "auth.change_password",
|
|
54
|
+
operation: "change_password"
|
|
55
|
+
};
|
|
56
|
+
if (normalized.endsWith("/change-email") || normalized === "change-email") return {
|
|
57
|
+
spanName: "auth.change_email",
|
|
58
|
+
operation: "change_email"
|
|
59
|
+
};
|
|
60
|
+
if (normalized.endsWith("/verify-email") || normalized === "verify-email") return {
|
|
61
|
+
spanName: "auth.verify_email",
|
|
62
|
+
operation: "verify_email"
|
|
63
|
+
};
|
|
64
|
+
if (normalized.endsWith("/forget-password") || normalized === "forget-password") return {
|
|
65
|
+
spanName: "auth.forgot_password",
|
|
66
|
+
operation: "forgot_password"
|
|
67
|
+
};
|
|
68
|
+
if (normalized.endsWith("/reset-password") || normalized === "reset-password") return {
|
|
69
|
+
spanName: "auth.reset_password",
|
|
70
|
+
operation: "reset_password"
|
|
71
|
+
};
|
|
72
|
+
if (normalized.endsWith("/list-sessions") || normalized === "list-sessions") return {
|
|
73
|
+
spanName: "auth.list_sessions",
|
|
74
|
+
operation: "list_sessions"
|
|
75
|
+
};
|
|
76
|
+
if (normalized.endsWith("/revoke-session") || normalized === "revoke-session") return {
|
|
77
|
+
spanName: "auth.revoke_session",
|
|
78
|
+
operation: "revoke_session"
|
|
79
|
+
};
|
|
80
|
+
const callbackMatch = normalized.match(/\/callback\/([^/?]+)/);
|
|
81
|
+
if (callbackMatch) {
|
|
82
|
+
const provider = callbackMatch[1];
|
|
83
|
+
return {
|
|
84
|
+
spanName: `auth.oauth.callback.${provider}`,
|
|
85
|
+
operation: "oauth_callback",
|
|
86
|
+
method: "oauth",
|
|
87
|
+
provider
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const signinMatch = normalized.match(/\/sign-in\/([^/?]+)$/);
|
|
91
|
+
if (signinMatch && signinMatch[1] !== "email") {
|
|
92
|
+
const provider = signinMatch[1];
|
|
93
|
+
return {
|
|
94
|
+
spanName: `auth.oauth.initiate.${provider}`,
|
|
95
|
+
operation: "oauth_initiate",
|
|
96
|
+
method: "oauth",
|
|
97
|
+
provider
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const segments = normalized.split("/");
|
|
101
|
+
return {
|
|
102
|
+
spanName: `auth.${(segments[segments.length - 1] || "unknown").replace(/-/g, "_")}`,
|
|
103
|
+
operation: "unknown"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function round(n) {
|
|
107
|
+
return Math.round(n * 100) / 100;
|
|
108
|
+
}
|
|
109
|
+
function extractUserData(returned) {
|
|
110
|
+
const result = {};
|
|
111
|
+
if (!returned || typeof returned !== "object") return result;
|
|
112
|
+
const data = returned.data ?? returned;
|
|
113
|
+
if (data.user?.id) result.userId = String(data.user.id);
|
|
114
|
+
if (data.user?.email) result.userEmail = String(data.user.email);
|
|
115
|
+
if (data.session?.id) result.sessionId = String(data.session.id);
|
|
116
|
+
if (!result.userId && returned.id && returned.email) {
|
|
117
|
+
result.userId = String(returned.id);
|
|
118
|
+
result.userEmail = String(returned.email);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
const requestMetas = /* @__PURE__ */ new WeakMap();
|
|
123
|
+
/**
|
|
124
|
+
* Creates a Better Auth plugin that auto-traces all auth operations
|
|
125
|
+
* into silgi analytics spans.
|
|
126
|
+
*
|
|
127
|
+
* @param config - Optional configuration
|
|
128
|
+
* @returns A Better Auth plugin (typed as `any` to avoid requiring better-auth types at build time)
|
|
129
|
+
*/
|
|
130
|
+
function silgiTracing(config) {
|
|
131
|
+
const captureInput = config?.captureInput ?? true;
|
|
132
|
+
const captureOutput = config?.captureOutput ?? true;
|
|
133
|
+
return {
|
|
134
|
+
id: "silgi-tracing",
|
|
135
|
+
onRequest: async (request, _ctx) => {
|
|
136
|
+
try {
|
|
137
|
+
const path = new URL(request.url).pathname;
|
|
138
|
+
const match = matchOperation(path);
|
|
139
|
+
requestMetas.set(request, {
|
|
140
|
+
startTime: performance.now(),
|
|
141
|
+
path,
|
|
142
|
+
operation: match.operation,
|
|
143
|
+
method: match.method,
|
|
144
|
+
provider: match.provider,
|
|
145
|
+
spanName: match.spanName
|
|
146
|
+
});
|
|
147
|
+
} catch {}
|
|
148
|
+
},
|
|
149
|
+
hooks: { after: [{
|
|
150
|
+
matcher: () => true,
|
|
151
|
+
handler: (config?.createAuthMiddleware ?? ((fn) => fn))(async (ctx) => {
|
|
152
|
+
try {
|
|
153
|
+
const request = ctx.request;
|
|
154
|
+
if (!request) return;
|
|
155
|
+
const silgiCtx = request.__silgiCtx;
|
|
156
|
+
if (!silgiCtx) return;
|
|
157
|
+
const reqTrace = silgiCtx.__analyticsTrace;
|
|
158
|
+
if (!reqTrace) return;
|
|
159
|
+
const meta = requestMetas.get(request);
|
|
160
|
+
requestMetas.delete(request);
|
|
161
|
+
const path = ctx.path || "";
|
|
162
|
+
const match = meta ? {
|
|
163
|
+
spanName: meta.spanName,
|
|
164
|
+
operation: meta.operation,
|
|
165
|
+
method: meta.method,
|
|
166
|
+
provider: meta.provider
|
|
167
|
+
} : matchOperation(path);
|
|
168
|
+
const startTime = meta?.startTime ?? performance.now();
|
|
169
|
+
const durationMs = round(performance.now() - startTime);
|
|
170
|
+
const returned = ctx.context?.returned;
|
|
171
|
+
const newSession = ctx.context?.newSession;
|
|
172
|
+
const userData = extractUserData(returned);
|
|
173
|
+
if (!userData.userId && newSession?.user?.id) userData.userId = String(newSession.user.id);
|
|
174
|
+
if (!userData.userEmail && newSession?.user?.email) userData.userEmail = String(newSession.user.email);
|
|
175
|
+
if (!userData.sessionId && newSession?.session?.id) userData.sessionId = String(newSession.session.id);
|
|
176
|
+
const success = !returned?.error && !ctx.context?.error;
|
|
177
|
+
const attributes = {
|
|
178
|
+
"auth.operation": match.operation,
|
|
179
|
+
"auth.success": success
|
|
180
|
+
};
|
|
181
|
+
if (match.method) attributes["auth.method"] = match.method;
|
|
182
|
+
if (match.provider) attributes["auth.provider"] = match.provider;
|
|
183
|
+
if (userData.userId) attributes["user.id"] = userData.userId;
|
|
184
|
+
if (userData.userEmail) attributes["user.email"] = userData.userEmail;
|
|
185
|
+
if (userData.sessionId) attributes["session.id"] = userData.sessionId;
|
|
186
|
+
const span = {
|
|
187
|
+
name: match.spanName,
|
|
188
|
+
kind: "http",
|
|
189
|
+
durationMs,
|
|
190
|
+
startOffsetMs: round(startTime - reqTrace.t0),
|
|
191
|
+
attributes
|
|
192
|
+
};
|
|
193
|
+
if (captureInput && ctx.body) span.input = ctx.body;
|
|
194
|
+
if (captureOutput && returned && typeof returned === "object") span.output = returned;
|
|
195
|
+
if (!success && returned?.error) span.error = typeof returned.error === "string" ? returned.error : returned.error?.message ?? "error";
|
|
196
|
+
reqTrace.spans.push(span);
|
|
197
|
+
if (captureInput && ctx.body) reqTrace.procedureInput = ctx.body;
|
|
198
|
+
if (captureOutput && returned && typeof returned === "object") reqTrace.procedureOutput = returned;
|
|
199
|
+
} catch {}
|
|
200
|
+
})
|
|
201
|
+
}] }
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const API_METHOD_METADATA = {
|
|
205
|
+
getSession: { operation: "get_session" },
|
|
206
|
+
signOut: { operation: "signout" },
|
|
207
|
+
signInEmail: {
|
|
208
|
+
operation: "signin",
|
|
209
|
+
method: "email"
|
|
210
|
+
},
|
|
211
|
+
signUpEmail: {
|
|
212
|
+
operation: "signup",
|
|
213
|
+
method: "email"
|
|
214
|
+
},
|
|
215
|
+
signInSocial: {
|
|
216
|
+
operation: "signin",
|
|
217
|
+
method: "oauth"
|
|
218
|
+
},
|
|
219
|
+
callbackOAuth: {
|
|
220
|
+
operation: "oauth_callback",
|
|
221
|
+
method: "oauth"
|
|
222
|
+
},
|
|
223
|
+
linkSocialAccount: {
|
|
224
|
+
operation: "link_social_account",
|
|
225
|
+
method: "oauth"
|
|
226
|
+
},
|
|
227
|
+
unlinkAccount: { operation: "unlink_account" },
|
|
228
|
+
listUserAccounts: { operation: "list_user_accounts" },
|
|
229
|
+
updateUser: { operation: "update_user" },
|
|
230
|
+
deleteUser: { operation: "delete_user" },
|
|
231
|
+
changePassword: { operation: "change_password" },
|
|
232
|
+
setPassword: { operation: "set_password" },
|
|
233
|
+
changeEmail: { operation: "change_email" },
|
|
234
|
+
verifyEmail: { operation: "verify_email" },
|
|
235
|
+
sendVerificationEmail: { operation: "send_verification_email" },
|
|
236
|
+
forgetPassword: { operation: "forget_password" },
|
|
237
|
+
resetPassword: { operation: "reset_password" },
|
|
238
|
+
listSessions: { operation: "list_sessions" },
|
|
239
|
+
revokeSession: { operation: "revoke_session" },
|
|
240
|
+
revokeSessions: { operation: "revoke_sessions" },
|
|
241
|
+
revokeOtherSessions: { operation: "revoke_other_sessions" },
|
|
242
|
+
refreshToken: { operation: "refresh_token" },
|
|
243
|
+
getAccessToken: { operation: "get_access_token" }
|
|
244
|
+
};
|
|
245
|
+
const AUTH_INSTRUMENTED = "__silgiBetterAuthInstrumented";
|
|
246
|
+
/**
|
|
247
|
+
* Instrument a Better Auth instance to trace all `auth.api.*` method calls.
|
|
248
|
+
* Works with `withSilgiCtx` — programmatic calls from background jobs,
|
|
249
|
+
* server-side session fetches etc. are traced when context is available.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* import { instrumentBetterAuth, withSilgiCtx } from 'silgi/better-auth'
|
|
254
|
+
*
|
|
255
|
+
* const auth = instrumentBetterAuth(betterAuth({ ... }))
|
|
256
|
+
*
|
|
257
|
+
* // In a silgi procedure:
|
|
258
|
+
* const me = s.$resolve(async ({ ctx }) => {
|
|
259
|
+
* return withSilgiCtx(ctx, () => auth.api.getSession({ headers: ctx.headers }))
|
|
260
|
+
* })
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
function instrumentBetterAuth(auth) {
|
|
264
|
+
if (!auth || auth[AUTH_INSTRUMENTED]) return auth;
|
|
265
|
+
const api = auth.api;
|
|
266
|
+
if (!api || typeof api !== "object") return auth;
|
|
267
|
+
const instrumented = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const [methodName, metadata] of Object.entries(API_METHOD_METADATA)) if (typeof api[methodName] === "function") {
|
|
269
|
+
api[methodName] = wrapApiMethod(api[methodName], metadata.operation, metadata.method);
|
|
270
|
+
instrumented.add(methodName);
|
|
271
|
+
}
|
|
272
|
+
for (const key of Object.keys(api)) if (typeof api[key] === "function" && !instrumented.has(key) && !key.startsWith("$") && !key.startsWith("_")) {
|
|
273
|
+
const operation = key.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
274
|
+
api[key] = wrapApiMethod(api[key], operation);
|
|
275
|
+
instrumented.add(key);
|
|
276
|
+
}
|
|
277
|
+
auth[AUTH_INSTRUMENTED] = true;
|
|
278
|
+
return auth;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Run a function with silgi context available to instrumented Better Auth API calls.
|
|
282
|
+
*/
|
|
283
|
+
function withSilgiCtx(ctx, fn) {
|
|
284
|
+
return ctxStorage.run(ctx, fn);
|
|
285
|
+
}
|
|
286
|
+
function wrapApiMethod(originalFn, operation, method) {
|
|
287
|
+
return async function instrumented(...args) {
|
|
288
|
+
const reqTrace = ctxStorage.getStore()?.__analyticsTrace;
|
|
289
|
+
if (!reqTrace) return originalFn.apply(this, args);
|
|
290
|
+
const spanName = `auth.api.${operation}`;
|
|
291
|
+
const start = performance.now();
|
|
292
|
+
const attributes = {
|
|
293
|
+
"auth.operation": operation,
|
|
294
|
+
"auth.success": true
|
|
295
|
+
};
|
|
296
|
+
if (method) attributes["auth.method"] = method;
|
|
297
|
+
try {
|
|
298
|
+
const result = await originalFn.apply(this, args);
|
|
299
|
+
const data = result?.data ?? result;
|
|
300
|
+
if (data?.user?.id) attributes["user.id"] = String(data.user.id);
|
|
301
|
+
if (data?.user?.email) attributes["user.email"] = String(data.user.email);
|
|
302
|
+
if (data?.session?.id) attributes["session.id"] = String(data.session.id);
|
|
303
|
+
if (result?.error) {
|
|
304
|
+
attributes["auth.success"] = false;
|
|
305
|
+
attributes["auth.error"] = typeof result.error === "string" ? result.error : result.error?.message ?? "unknown";
|
|
306
|
+
}
|
|
307
|
+
reqTrace.spans.push({
|
|
308
|
+
name: spanName,
|
|
309
|
+
kind: "http",
|
|
310
|
+
durationMs: round(performance.now() - start),
|
|
311
|
+
startOffsetMs: round(start - reqTrace.t0),
|
|
312
|
+
attributes,
|
|
313
|
+
output: result && typeof result === "object" ? result : void 0
|
|
314
|
+
});
|
|
315
|
+
return result;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
attributes["auth.success"] = false;
|
|
318
|
+
attributes["auth.error"] = error instanceof Error ? error.message : String(error);
|
|
319
|
+
reqTrace.spans.push({
|
|
320
|
+
name: spanName,
|
|
321
|
+
kind: "http",
|
|
322
|
+
durationMs: round(performance.now() - start),
|
|
323
|
+
startOffsetMs: round(start - reqTrace.t0),
|
|
324
|
+
attributes,
|
|
325
|
+
error: error instanceof Error ? error.stack ?? error.message : String(error)
|
|
326
|
+
});
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
export { instrumentBetterAuth, silgiTracing, withSilgiCtx };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/integrations/drizzle/index.d.ts
|
|
2
|
+
interface InstrumentDrizzleConfig {
|
|
3
|
+
/** Logical database name (e.g. 'auth', 'ecommerce') */
|
|
4
|
+
dbName?: string;
|
|
5
|
+
/** Database system identifier. Default: 'postgresql' */
|
|
6
|
+
dbSystem?: string;
|
|
7
|
+
/** Capture SQL query text in spans. Default: true */
|
|
8
|
+
captureQueryText?: boolean;
|
|
9
|
+
/** Max query text length before truncation. Default: 1000 */
|
|
10
|
+
maxQueryTextLength?: number;
|
|
11
|
+
/** Database host */
|
|
12
|
+
peerName?: string;
|
|
13
|
+
/** Database port */
|
|
14
|
+
peerPort?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Instrument a Drizzle db instance to record query spans in silgi analytics.
|
|
18
|
+
* Returns the same db instance (mutated). Safe to call multiple times.
|
|
19
|
+
*/
|
|
20
|
+
declare function instrumentDrizzle<T extends Record<string, any>>(db: T, config?: InstrumentDrizzleConfig): T;
|
|
21
|
+
/**
|
|
22
|
+
* Run a function with silgi context available to instrumented Drizzle instances.
|
|
23
|
+
* All Drizzle queries inside `fn` will be recorded as trace spans.
|
|
24
|
+
*/
|
|
25
|
+
declare function withSilgiCtx<T>(ctx: Record<string, unknown>, fn: () => T): T;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { InstrumentDrizzleConfig, instrumentDrizzle, withSilgiCtx };
|