silgi 0.0.14 → 0.1.0-beta.10
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/README.md +102 -1
- package/dist/_virtual/_rolldown/runtime.mjs +5 -0
- package/dist/adapters/_fetch-adapter.d.mts +18 -0
- package/dist/adapters/_fetch-adapter.mjs +53 -0
- package/dist/adapters/astro.d.mts +15 -0
- package/dist/adapters/astro.mjs +31 -0
- package/dist/adapters/aws-lambda.d.mts +42 -0
- package/dist/adapters/aws-lambda.mjs +92 -0
- package/dist/adapters/express.d.mts +16 -0
- package/dist/adapters/express.mjs +120 -0
- package/dist/adapters/message-port.d.mts +42 -0
- package/dist/adapters/message-port.mjs +132 -0
- package/dist/adapters/nestjs.d.mts +25 -0
- package/dist/adapters/nestjs.mjs +75 -0
- package/dist/adapters/nextjs.d.mts +14 -0
- package/dist/adapters/nextjs.mjs +29 -0
- package/dist/adapters/peer.d.mts +27 -0
- package/dist/adapters/peer.mjs +36 -0
- package/dist/adapters/remix.d.mts +15 -0
- package/dist/adapters/remix.mjs +30 -0
- package/dist/adapters/solidstart.d.mts +12 -0
- package/dist/adapters/solidstart.mjs +29 -0
- package/dist/adapters/sveltekit.d.mts +14 -0
- package/dist/adapters/sveltekit.mjs +30 -0
- package/dist/broker/index.d.mts +62 -0
- package/dist/broker/index.mjs +153 -0
- package/dist/broker/nats.d.mts +33 -0
- package/dist/broker/nats.mjs +31 -0
- package/dist/broker/redis.d.mts +51 -0
- package/dist/broker/redis.mjs +92 -0
- package/dist/builder.d.mts +55 -0
- package/dist/builder.mjs +70 -0
- package/dist/callable.d.mts +19 -0
- package/dist/callable.mjs +42 -0
- package/dist/caller.mjs +90 -0
- package/dist/client/adapters/fetch/index.d.mts +19 -0
- package/dist/client/adapters/fetch/index.mjs +82 -0
- package/dist/client/adapters/ofetch/index.d.mts +57 -0
- package/dist/client/adapters/ofetch/index.mjs +100 -0
- package/dist/client/adapters/websocket/index.d.mts +20 -0
- package/dist/client/adapters/websocket/index.mjs +101 -0
- package/dist/client/client.d.mts +37 -0
- package/dist/client/client.mjs +80 -0
- package/dist/client/consume.d.mts +50 -0
- package/dist/client/consume.mjs +66 -0
- package/dist/client/dynamic-link.d.mts +16 -0
- package/dist/client/dynamic-link.mjs +19 -0
- package/dist/client/index.d.mts +6 -0
- package/dist/client/index.mjs +5 -0
- package/dist/client/interceptor.d.mts +31 -0
- package/dist/client/interceptor.mjs +34 -0
- package/dist/client/openapi.d.mts +29 -0
- package/dist/client/openapi.mjs +89 -0
- package/dist/client/plugins/batch.d.mts +26 -0
- package/dist/client/plugins/batch.mjs +64 -0
- package/dist/client/plugins/circuit-breaker.d.mts +24 -0
- package/dist/client/plugins/circuit-breaker.mjs +60 -0
- package/dist/client/plugins/csrf.d.mts +13 -0
- package/dist/client/plugins/csrf.mjs +20 -0
- package/dist/client/plugins/dedupe.d.mts +10 -0
- package/dist/client/plugins/dedupe.mjs +28 -0
- package/dist/client/plugins/index.d.mts +8 -0
- package/dist/client/plugins/index.mjs +8 -0
- package/dist/client/plugins/otel.d.mts +12 -0
- package/dist/client/plugins/otel.mjs +27 -0
- package/dist/client/plugins/retry.d.mts +34 -0
- package/dist/client/plugins/retry.mjs +79 -0
- package/dist/client/plugins/timeout.d.mts +10 -0
- package/dist/client/plugins/timeout.mjs +14 -0
- package/dist/client/server.d.mts +16 -0
- package/dist/client/server.mjs +62 -0
- package/dist/client/types.d.mts +29 -0
- package/dist/codec/devalue.d.mts +21 -0
- package/dist/codec/devalue.mjs +33 -0
- package/dist/codec/msgpack.d.mts +18 -0
- package/dist/codec/msgpack.mjs +45 -0
- package/dist/codec/sanitize.mjs +38 -0
- package/dist/compile.d.mts +46 -0
- package/dist/compile.mjs +332 -0
- package/dist/contract.d.mts +36 -0
- package/dist/contract.mjs +40 -0
- package/dist/core/codec.mjs +67 -0
- package/dist/core/dispatch.mjs +62 -0
- package/dist/core/error.d.mts +104 -0
- package/dist/core/error.mjs +139 -0
- package/dist/core/handler.d.mts +6 -0
- package/dist/core/handler.mjs +148 -0
- package/dist/core/input.mjs +49 -0
- package/dist/core/iterator.d.mts +17 -0
- package/dist/core/iterator.mjs +79 -0
- package/dist/core/router-utils.d.mts +25 -0
- package/dist/core/router-utils.mjs +98 -0
- package/dist/core/schema.d.mts +20 -0
- package/dist/core/schema.mjs +33 -0
- package/dist/core/serve.d.mts +51 -0
- package/dist/core/serve.mjs +76 -0
- package/dist/core/sse.d.mts +18 -0
- package/dist/core/sse.mjs +98 -0
- package/dist/core/storage.d.mts +20 -0
- package/dist/core/storage.mjs +61 -0
- package/dist/core/task.d.mts +62 -0
- package/dist/core/task.mjs +165 -0
- package/dist/core/utils.mjs +24 -0
- package/dist/index.d.mts +18 -37
- package/dist/index.mjs +15 -7
- package/dist/integrations/ai/index.d.mts +25 -0
- package/dist/integrations/ai/index.mjs +117 -0
- 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/integrations/hey-api/index.d.mts +2 -0
- package/dist/integrations/hey-api/index.mjs +2 -0
- package/dist/integrations/hey-api/to-client.d.mts +20 -0
- package/dist/integrations/hey-api/to-client.mjs +39 -0
- package/dist/integrations/pinia-colada/general-utils.d.mts +13 -0
- package/dist/integrations/pinia-colada/general-utils.mjs +9 -0
- package/dist/integrations/pinia-colada/index.d.mts +6 -0
- package/dist/integrations/pinia-colada/index.mjs +5 -0
- package/dist/integrations/pinia-colada/key.d.mts +11 -0
- package/dist/integrations/pinia-colada/key.mjs +11 -0
- package/dist/integrations/pinia-colada/procedure-utils.d.mts +25 -0
- package/dist/integrations/pinia-colada/procedure-utils.mjs +33 -0
- package/dist/integrations/pinia-colada/router-utils.d.mts +17 -0
- package/dist/integrations/pinia-colada/router-utils.mjs +30 -0
- package/dist/integrations/pinia-colada/types.d.mts +25 -0
- package/dist/integrations/react/index.d.mts +83 -0
- package/dist/integrations/react/index.mjs +196 -0
- package/dist/integrations/tanstack-query/index.d.mts +120 -0
- package/dist/integrations/tanstack-query/index.mjs +100 -0
- package/dist/integrations/tanstack-query/ssr.d.mts +60 -0
- package/dist/integrations/tanstack-query/ssr.mjs +102 -0
- package/dist/integrations/zod/converter.d.mts +75 -0
- package/dist/integrations/zod/converter.mjs +345 -0
- package/dist/integrations/zod/index.d.mts +2 -0
- package/dist/integrations/zod/index.mjs +2 -0
- package/dist/lazy.d.mts +22 -0
- package/dist/lazy.mjs +34 -0
- package/dist/lifecycle.d.mts +36 -0
- package/dist/lifecycle.mjs +46 -0
- package/dist/map-input.d.mts +17 -0
- package/dist/map-input.mjs +47 -0
- package/dist/plugins/analytics.d.mts +236 -0
- package/dist/plugins/analytics.mjs +816 -0
- package/dist/plugins/batch-server.d.mts +20 -0
- package/dist/plugins/batch-server.mjs +91 -0
- package/dist/plugins/body-limit.d.mts +19 -0
- package/dist/plugins/body-limit.mjs +49 -0
- package/dist/plugins/cache.d.mts +170 -0
- package/dist/plugins/cache.mjs +212 -0
- package/dist/plugins/coerce.d.mts +24 -0
- package/dist/plugins/coerce.mjs +70 -0
- package/dist/plugins/cookies.d.mts +14 -0
- package/dist/plugins/cookies.mjs +48 -0
- package/dist/plugins/cors.d.mts +43 -0
- package/dist/plugins/cors.mjs +62 -0
- package/dist/plugins/file-upload.d.mts +38 -0
- package/dist/plugins/file-upload.mjs +102 -0
- package/dist/plugins/index.d.mts +14 -0
- package/dist/plugins/index.mjs +14 -0
- package/dist/plugins/otel.d.mts +35 -0
- package/dist/plugins/otel.mjs +40 -0
- package/dist/plugins/pino.d.mts +60 -0
- package/dist/plugins/pino.mjs +42 -0
- package/dist/plugins/pubsub.d.mts +50 -0
- package/dist/plugins/pubsub.mjs +53 -0
- package/dist/plugins/ratelimit.d.mts +53 -0
- package/dist/plugins/ratelimit.mjs +92 -0
- package/dist/plugins/signing.d.mts +41 -0
- package/dist/plugins/signing.mjs +118 -0
- package/dist/plugins/strict-get.d.mts +10 -0
- package/dist/plugins/strict-get.mjs +33 -0
- package/dist/scalar.d.mts +49 -0
- package/dist/scalar.mjs +311 -0
- package/dist/silgi.d.mts +144 -0
- package/dist/silgi.mjs +156 -0
- package/dist/trpc-interop.d.mts +22 -0
- package/dist/trpc-interop.mjs +68 -0
- package/dist/types.d.mts +108 -0
- package/dist/ws.d.mts +88 -0
- package/dist/ws.mjs +205 -0
- package/lib/dashboard/index.html +123 -0
- package/lib/ocache.d.mts +1 -0
- package/lib/ocache.mjs +1 -0
- package/lib/ofetch.d.mts +1 -0
- package/lib/ofetch.mjs +1 -0
- package/lib/srvx.d.mts +1 -0
- package/lib/srvx.mjs +1 -0
- package/lib/unstorage.d.mts +1 -0
- package/lib/unstorage.mjs +1 -0
- package/package.json +324 -65
- package/bin/silgi.mjs +0 -3
- package/dist/chunks/generate.mjs +0 -933
- package/dist/chunks/init.mjs +0 -21
- package/dist/cli/config.d.mts +0 -19
- package/dist/cli/config.d.ts +0 -19
- package/dist/cli/config.mjs +0 -5
- package/dist/cli/index.d.mts +0 -2
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +0 -119
- package/dist/index.d.ts +0 -37
- package/dist/plugins/openapi.d.mts +0 -138
- package/dist/plugins/openapi.d.ts +0 -138
- package/dist/plugins/openapi.mjs +0 -204
- package/dist/plugins/scalar.d.mts +0 -14
- package/dist/plugins/scalar.d.ts +0 -14
- package/dist/plugins/scalar.mjs +0 -66
- package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
- package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
- package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { compileProcedure } from "../../compile.mjs";
|
|
2
|
+
import { jsonSchema, tool } from "ai";
|
|
3
|
+
//#region src/integrations/ai/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Vercel AI SDK integration — expose silgi procedures as AI tools.
|
|
6
|
+
*
|
|
7
|
+
* Converts silgi router procedures into AI SDK tools that can be
|
|
8
|
+
* called by LLMs via function calling / tool use.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { generateText } from "ai"
|
|
13
|
+
* import { openai } from "@ai-sdk/openai"
|
|
14
|
+
* import { routerToTools } from "silgi/ai"
|
|
15
|
+
*
|
|
16
|
+
* const tools = routerToTools(appRouter)
|
|
17
|
+
*
|
|
18
|
+
* const { text } = await generateText({
|
|
19
|
+
* model: openai("gpt-4o"),
|
|
20
|
+
* tools,
|
|
21
|
+
* prompt: "List all users with limit 5",
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Convert a single silgi procedure into an AI SDK tool.
|
|
27
|
+
*/
|
|
28
|
+
function procedureToTool(name, procedure, options) {
|
|
29
|
+
const handler = compileProcedure(procedure);
|
|
30
|
+
return tool({
|
|
31
|
+
description: options?.description ?? procedure.route?.description ?? procedure.route?.summary ?? `Call ${name}`,
|
|
32
|
+
parameters: jsonSchema(procedure.input ? zodToJsonSchemaSimple(procedure.input) : {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {}
|
|
35
|
+
}),
|
|
36
|
+
execute: async (input, execOptions) => {
|
|
37
|
+
const ctx = Object.create(null);
|
|
38
|
+
const signal = execOptions?.abortSignal ?? new AbortController().signal;
|
|
39
|
+
const result = handler(ctx, input ?? {}, signal);
|
|
40
|
+
return result instanceof Promise ? await result : result;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convert all procedures in a silgi router into AI SDK tools.
|
|
46
|
+
* Nested routers are flattened with underscore separators.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // Router: { users: { list, create }, health }
|
|
51
|
+
* // Tools: { users_list, users_create, health }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function routerToTools(router, options) {
|
|
55
|
+
const tools = {};
|
|
56
|
+
collectProcedures(router, [], (path, proc) => {
|
|
57
|
+
const flatName = path.join("_");
|
|
58
|
+
if (options?.filter && !options.filter(flatName, proc)) return;
|
|
59
|
+
tools[flatName] = procedureToTool(flatName, proc, { description: options?.descriptions?.[flatName] });
|
|
60
|
+
});
|
|
61
|
+
return tools;
|
|
62
|
+
}
|
|
63
|
+
function isProcedureDef(v) {
|
|
64
|
+
return typeof v === "object" && v !== null && "type" in v && "resolve" in v;
|
|
65
|
+
}
|
|
66
|
+
function collectProcedures(node, path, cb) {
|
|
67
|
+
if (isProcedureDef(node)) {
|
|
68
|
+
cb(path, node);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (typeof node === "object" && node !== null) for (const [key, child] of Object.entries(node)) collectProcedures(child, [...path, key], cb);
|
|
72
|
+
}
|
|
73
|
+
/** Simple Zod → JSON Schema for AI tool parameters */
|
|
74
|
+
function zodToJsonSchemaSimple(schema) {
|
|
75
|
+
const zod = schema?._zod ?? schema?._def;
|
|
76
|
+
if (!zod) return {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {}
|
|
79
|
+
};
|
|
80
|
+
return convertDef(zod.def ?? zod);
|
|
81
|
+
}
|
|
82
|
+
function convertDef(def) {
|
|
83
|
+
if (!def) return {};
|
|
84
|
+
switch (def.type ?? def.typeName) {
|
|
85
|
+
case "string": return { type: "string" };
|
|
86
|
+
case "number":
|
|
87
|
+
case "float": return { type: "number" };
|
|
88
|
+
case "int": return { type: "integer" };
|
|
89
|
+
case "boolean": return { type: "boolean" };
|
|
90
|
+
case "object": {
|
|
91
|
+
const props = {};
|
|
92
|
+
const required = [];
|
|
93
|
+
if (def.shape) for (const [k, v] of Object.entries(def.shape)) {
|
|
94
|
+
props[k] = zodToJsonSchemaSimple(v);
|
|
95
|
+
const fz = v?._zod?.def ?? v?._def;
|
|
96
|
+
if (fz?.type !== "optional" && fz?.typeName !== "ZodOptional") required.push(k);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: props,
|
|
101
|
+
...required.length ? { required } : {}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
case "array": return {
|
|
105
|
+
type: "array",
|
|
106
|
+
...def.element ? { items: zodToJsonSchemaSimple(def.element) } : {}
|
|
107
|
+
};
|
|
108
|
+
case "optional": return zodToJsonSchemaSimple(def.innerType ?? def.inner);
|
|
109
|
+
case "enum": return {
|
|
110
|
+
type: "string",
|
|
111
|
+
enum: def.values ?? def.entries
|
|
112
|
+
};
|
|
113
|
+
default: return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { procedureToTool, routerToTools };
|
|
@@ -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 { tracing } from 'silgi/better-auth'
|
|
14
|
+
*
|
|
15
|
+
* const auth = betterAuth({
|
|
16
|
+
* plugins: [
|
|
17
|
+
* tracing(), // auto-traces all auth operations
|
|
18
|
+
* ],
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface TracingConfig {
|
|
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 tracing(config?: TracingConfig): any;
|
|
38
|
+
/**
|
|
39
|
+
* Instrument a Better Auth instance to trace all `auth.api.*` method calls.
|
|
40
|
+
* Works with `withCtx` — 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, withCtx } 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 withCtx(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 withCtx<T>(ctx: Record<string, unknown>, fn: () => T): T;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { TracingConfig, instrumentBetterAuth, tracing, withCtx };
|
|
@@ -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 { tracing } from 'silgi/better-auth'
|
|
15
|
+
*
|
|
16
|
+
* const auth = betterAuth({
|
|
17
|
+
* plugins: [
|
|
18
|
+
* tracing(), // 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 tracing(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 `withCtx` — 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, withCtx } 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 withCtx(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 withCtx(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, tracing, withCtx };
|
|
@@ -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 withCtx<T>(ctx: Record<string, unknown>, fn: () => T): T;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { InstrumentDrizzleConfig, instrumentDrizzle, withCtx };
|