better auth package

This commit is contained in:
Alex Holovach
2025-10-02 18:18:02 -05:00
parent d000ea8efc
commit 024f3a06ff
11 changed files with 1706 additions and 25 deletions

View File

@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}

View File

@@ -0,0 +1,40 @@
---
"@kubiks/otel-better-auth": major
---
Initial release of @kubiks/otel-better-auth
🎉 First release of OpenTelemetry instrumentation for Better Auth!
## Features
- **Plugin-based integration**: Clean one-line setup using Better Auth's native plugin system
- **Comprehensive auth event tracing**: Automatic instrumentation for signup, signin, OAuth, password reset, and more
- **Privacy-first design**: Email capture is opt-in, passwords never captured
- **Rich telemetry**: Captures user IDs, session IDs, auth methods, and success/failure status
- **Semantic conventions**: Follows OpenTelemetry standards with meaningful span attributes
- **Zero configuration**: Works out of the box with sensible defaults
## Supported Auth Operations
- User signup (email/password, OAuth, magic link)
- User signin (all authentication methods)
- Password reset flows (forgot password, reset password)
- Email verification
- Session management
- OAuth callbacks (Google, GitHub, Facebook, and more)
- Sign out
## Usage
```typescript
import { betterAuth } from "better-auth";
import { otelPlugin } from "@kubiks/otel-better-auth";
export const auth = betterAuth({
database: db,
emailAndPassword: { enabled: true },
}).use(otelPlugin());
```
That's it! All your authentication operations are now traced with OpenTelemetry.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Kubiks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,301 @@
# @kubiks/otel-better-auth
OpenTelemetry instrumentation for [Better Auth](https://better-auth.com/). Add distributed tracing to your authentication flows with a single line of code.
## 🚀 Features
- **🔌 Plugin-based**: Clean integration using Better Auth's native plugin system
- **📊 Comprehensive Coverage**: Traces all auth operations (signup, signin, OAuth, password reset, etc.)
- **🎯 Semantic Conventions**: Follows OpenTelemetry standards with meaningful attributes
- **🔐 Privacy-First**: Email capture is opt-in by default
- **⚡ Zero Config**: Works out of the box with sensible defaults
- **🎨 Rich Telemetry**: Captures user IDs, session IDs, auth methods, and success/failure status
## Installation
```bash
npm install @kubiks/otel-better-auth
# or
pnpm add @kubiks/otel-better-auth
# or
yarn add @kubiks/otel-better-auth
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `better-auth` >= 0.1.0
## Usage
### Basic Setup (One Line!)
Simply add the plugin to your Better Auth configuration:
```typescript
import { betterAuth } from "better-auth";
import { otelPlugin } from "@kubiks/otel-better-auth";
export const auth = betterAuth({
database: db,
emailAndPassword: {
enabled: true,
},
}).use(otelPlugin());
// That's it! All auth operations are now traced automatically ✨
```
### With Custom Configuration
```typescript
import { betterAuth } from "better-auth";
import { otelPlugin } from "@kubiks/otel-better-auth";
export const auth = betterAuth({
database: db,
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
},
}).use(
otelPlugin({
tracerName: "my-app-auth", // Custom tracer name
captureEmail: true, // Include email addresses in traces (default: false)
captureErrors: true, // Capture detailed error messages (default: true)
})
);
```
### Full Example with OpenTelemetry Setup
```typescript
// instrumentation.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
// auth.ts
import { betterAuth } from "better-auth";
import { otelPlugin } from "@kubiks/otel-better-auth";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);
export const auth = betterAuth({
database: db,
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
}).use(otelPlugin());
// app.ts
import "./instrumentation"; // Must be imported first!
import { auth } from "./auth";
// Your auth is now fully instrumented!
```
## What You Get
The plugin automatically traces the following authentication events:
### 📝 Signup Operations
- Email/password signup
- OAuth provider signup
- Magic link signup
- Automatic user ID capture
**Span name**: `auth.signup` or `auth.signup.email`
### 🔑 Signin Operations
- Email/password signin
- OAuth provider signin (Google, GitHub, Facebook, etc.)
- Magic link signin
- Session creation tracking
**Span name**: `auth.signin`, `auth.signin.email`, or `auth.oauth.{provider}`
### 🔒 Password Management
- Forgot password requests
- Password reset flows
- Email verification
**Span names**: `auth.forgot_password`, `auth.reset_password`, `auth.verify_email`
### 🚪 Signout
- Session termination tracking
**Span name**: `auth.signout`
### 🌐 OAuth Flows
- Automatic provider detection (Google, GitHub, Facebook, etc.)
- Callback tracking
- Success/failure monitoring
**Span name**: `auth.oauth.{provider}`
## Span Attributes
Each traced operation includes rich telemetry data following OpenTelemetry semantic conventions:
| Attribute | Description | Example |
| ------------------ | ------------------------------ | ------------------------ |
| `auth.operation` | Type of auth operation | `signin`, `signup` |
| `auth.method` | Authentication method | `password`, `oauth` |
| `auth.provider` | OAuth provider (if applicable) | `google`, `github` |
| `auth.success` | Whether operation succeeded | `true`, `false` |
| `auth.error` | Error message (if failed) | `Invalid credentials` |
| `user.id` | User identifier | `user_123456` |
| `user.email` | User email (opt-in) | `user@example.com` |
| `session.id` | Session identifier | `session_789012` |
## Configuration Options
### `tracerName`
- **Type**: `string`
- **Default**: `"@kubiks/otel-better-auth"`
- **Description**: Custom name for the tracer
### `captureEmail`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Whether to include user email addresses in span attributes. **Note**: Email addresses are PII (Personally Identifiable Information). Only enable this if your tracing backend is compliant with your privacy requirements.
### `captureErrors`
- **Type**: `boolean`
- **Default**: `true`
- **Description**: Whether to capture detailed error messages in spans
### `tracer`
- **Type**: `Tracer`
- **Default**: `undefined`
- **Description**: Custom OpenTelemetry tracer instance. If not provided, the plugin will obtain a tracer using `trace.getTracer(tracerName)`.
## Privacy & Security
By default, the plugin is designed with privacy in mind:
- ✅ User emails are **NOT** captured by default
- ✅ Passwords are **NEVER** captured
- ✅ Only operation metadata and success/failure status are traced
- ⚠️ Enable `captureEmail: true` only if your infrastructure is compliant with privacy regulations (GDPR, CCPA, etc.)
## Architecture
The plugin leverages Better Auth's powerful plugin API to hook into:
1. **Lifecycle Hooks**: `user.create`, `session.create` for core auth events
2. **Endpoint Hooks**: All auth endpoints (`signInEmail`, `signUpEmail`, `forgetPassword`, etc.)
3. **Request/Response Hooks**: For OAuth callback detection and tracing
This provides comprehensive coverage of all authentication flows without any code changes to your application.
## Visualizing Traces
When integrated with a tracing backend (Jaeger, Zipkin, Honeycomb, Datadog, etc.), you'll see:
- 📊 End-to-end auth flow visualization
- ⏱️ Performance metrics for each auth operation
- 🔍 Detailed attributes for debugging
- 🚨 Error tracking with stack traces
- 📈 Success/failure rates across auth methods
## Examples
### Next.js App Router
```typescript
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth.handler);
```
### Express
```typescript
import express from "express";
import { auth } from "./auth";
const app = express();
app.all("/api/auth/*", auth.handler);
app.listen(3000);
```
### SvelteKit
```typescript
// src/hooks.server.ts
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export const handle = svelteKitHandler(auth);
```
## Compatibility
- ✅ Works with all Better Auth adapters (Drizzle, Prisma, Kysely, etc.)
- ✅ Compatible with all Better Auth plugins
- ✅ Framework agnostic (Next.js, Express, SvelteKit, etc.)
- ✅ Supports all authentication methods (email/password, OAuth, magic link)
## Best Practices
1. **Initialize OpenTelemetry early**: Import your instrumentation file before any other code
2. **Use environment-based configuration**: Enable `captureEmail` only in development/staging
3. **Combine with other instrumentation**: Use alongside `@kubiks/otel-drizzle` for database query tracing
4. **Monitor performance**: Set up alerts for slow auth operations or high failure rates
5. **Respect privacy**: Be mindful of what PII you capture in production traces
## Related Packages
- [`@kubiks/otel-drizzle`](https://www.npmjs.com/package/@kubiks/otel-drizzle) - OpenTelemetry instrumentation for Drizzle ORM
- [`better-auth`](https://better-auth.com/) - The best authentication library for TypeScript
## Contributing
We welcome contributions! Please check out our [GitHub repository](https://github.com/kubiks-inc/otel) for issues and pull requests.
## License
MIT
---
Made with ❤️ by [Kubiks](https://github.com/kubiks-inc)

View File

@@ -0,0 +1,70 @@
import { stat } from "node:fs/promises";
import type { Plugin } from "esbuild";
import { build } from "esbuild";
const MINIFY = true;
const SOURCEMAP = true;
const MAX_SIZE = 50_000; // 50KB max for instrumentation package
type ExternalPluginFactory = (external: string[]) => Plugin;
const externalCjsToEsmPlugin: ExternalPluginFactory = (external) => ({
name: "external",
setup(builder): void {
const escape = (text: string): string =>
`^${text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")}$`;
const filter = new RegExp(external.map(escape).join("|"));
builder.onResolve({ filter: /.*/, namespace: "external" }, (args) => ({
path: args.path,
external: true,
}));
builder.onResolve({ filter }, (args) => ({
path: args.path,
namespace: "external",
}));
builder.onLoad({ filter: /.*/, namespace: "external" }, (args) => ({
contents: `export * from ${JSON.stringify(args.path)}`,
}));
},
});
/** Adds support for require, __filename, and __dirname to ESM / Node. */
const esmNodeSupportBanner = {
js: `import { fileURLToPath } from 'url';
import { createRequire as topLevelCreateRequire } from 'module';
import _nPath from 'path'
const require = topLevelCreateRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = _nPath.dirname(__filename);`,
};
const peerDependencies = ["@opentelemetry/api", "better-auth"];
async function buildAll(): Promise<void> {
await build({
platform: "node",
format: "esm",
splitting: false,
entryPoints: ["src/index.ts"],
outdir: "dist",
bundle: true,
minify: MINIFY,
sourcemap: SOURCEMAP,
banner: esmNodeSupportBanner,
external: peerDependencies,
plugins: [externalCjsToEsmPlugin(peerDependencies)],
});
// Check max size.
const outputFile = "dist/index.js";
const s = await stat(outputFile);
if (s.size > MAX_SIZE) {
// eslint-disable-next-line no-console
console.error(
`${outputFile}: the size of ${s.size} is over the maximum allowed size of ${MAX_SIZE}`,
);
process.exit(1);
}
}
void buildAll();

View File

@@ -0,0 +1,58 @@
{
"name": "@kubiks/otel-better-auth",
"version": "1.0.0",
"private": false,
"publishConfig": {
"access": "public"
},
"description": "OpenTelemetry instrumentation for Better Auth - Add distributed tracing to your authentication flows with a single line of code",
"author": "Kubiks",
"license": "MIT",
"repository": "kubiks-inc/otel",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist",
"LICENSE",
"README.md"
],
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"scripts": {
"build": "pnpm clean && pnpm build-only && pnpm build-types",
"build-only": "pnpm tsx build.ts",
"build-types": "tsc --skipLibCheck --noEmit false --declaration --emitDeclarationOnly --stripInternal --declarationDir dist/types src/index.ts",
"clean": "rimraf dist",
"prepublishOnly": "pnpm build",
"type-check": "tsc --noEmit",
"unit-test": "vitest --run",
"unit-test-watch": "vitest"
},
"dependencies": {},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@opentelemetry/sdk-trace-node": "^1.28.0",
"@types/node": "18.15.11",
"better-auth": "^1.0.0",
"esbuild": "^0.19.4",
"rimraf": "3.0.2",
"tsx": "^4.6.2",
"typescript": "^5",
"vitest": "0.33.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.9.0 <2.0.0",
"better-auth": ">=0.1.0"
}
}

View File

@@ -0,0 +1,358 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { trace, SpanStatusCode } from "@opentelemetry/api";
import { InMemorySpanExporter } from "@opentelemetry/sdk-trace-base";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import {
otelPlugin,
SEMATTRS_AUTH_OPERATION,
SEMATTRS_AUTH_METHOD,
SEMATTRS_AUTH_PROVIDER,
SEMATTRS_USER_ID,
SEMATTRS_USER_EMAIL,
SEMATTRS_AUTH_SUCCESS,
} from "./index.js";
describe("otel-better-auth", () => {
let exporter: InMemorySpanExporter;
let provider: NodeTracerProvider;
beforeEach(() => {
exporter = new InMemorySpanExporter();
provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
});
afterEach(() => {
exporter.reset();
provider.shutdown();
});
describe("otelPlugin", () => {
it("should create a plugin with correct id", () => {
const plugin = otelPlugin();
expect(plugin.id).toBe("otel");
});
it("should have before and after hooks", () => {
const plugin = otelPlugin();
expect(plugin.hooks?.before).toBeDefined();
expect(plugin.hooks?.after).toBeDefined();
expect(Array.isArray(plugin.hooks?.before)).toBe(true);
expect(Array.isArray(plugin.hooks?.after)).toBe(true);
});
it("should have multiple before hooks for different endpoints", () => {
const plugin = otelPlugin();
expect(plugin.hooks?.before?.length).toBeGreaterThan(3);
});
it("should have after hook for span finalization", () => {
const plugin = otelPlugin();
expect(plugin.hooks?.after?.length).toBe(1);
});
});
describe("Hook matchers", () => {
it("should match signup endpoints", () => {
const plugin = otelPlugin();
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
expect(signupHook).toBeDefined();
});
it("should match signin endpoints", () => {
const plugin = otelPlugin();
const signinHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-in/email", request: {} } as any);
});
expect(signinHook).toBeDefined();
});
it("should match forgot password endpoints", () => {
const plugin = otelPlugin();
const forgotHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/forget-password", request: {} } as any);
});
expect(forgotHook).toBeDefined();
});
it("should match OAuth callback endpoints", () => {
const plugin = otelPlugin();
const oauthHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/callback/google", request: {} } as any);
});
expect(oauthHook).toBeDefined();
});
});
describe("Configuration", () => {
it("should use custom tracer name", () => {
const customName = "my-custom-auth-tracer";
const plugin = otelPlugin({ tracerName: customName });
expect(plugin.id).toBe("otel");
});
it("should accept custom tracer instance", () => {
const customTracer = trace.getTracer("custom-tracer");
const plugin = otelPlugin({ tracer: customTracer });
expect(plugin.id).toBe("otel");
});
it("should respect captureEmail setting", () => {
const plugin = otelPlugin({ captureEmail: true });
expect(plugin.id).toBe("otel");
});
it("should respect captureErrors setting", () => {
const plugin = otelPlugin({ captureErrors: false });
expect(plugin.id).toBe("otel");
});
});
describe("Span creation", () => {
it("should create span for signup", async () => {
const plugin = otelPlugin();
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
const ctx = {
path: "/sign-up/email",
body: { email: "test@example.com" },
request: {},
};
await signupHook?.handler(ctx as any);
// Span is created but not finalized yet
expect((ctx as any).__otelSpan).toBeDefined();
});
it("should capture email when enabled", async () => {
const plugin = otelPlugin({ captureEmail: true });
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
const ctx = {
path: "/sign-up/email",
body: { email: "test@example.com" },
request: {},
};
await signupHook?.handler(ctx as any);
const span = (ctx as any).__otelSpan;
expect(span).toBeDefined();
});
it("should create span for OAuth callback", async () => {
const plugin = otelPlugin();
const oauthHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/callback/google", request: {} } as any);
});
const ctx = {
path: "/callback/google?code=abc123",
request: {},
};
await oauthHook?.handler(ctx as any);
expect((ctx as any).__otelSpan).toBeDefined();
});
});
describe("Span creation and finalization", () => {
it("should attach span to context in before hook", async () => {
const plugin = otelPlugin();
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
const ctx = {
path: "/sign-up/email",
body: { email: "test@example.com" },
request: {},
returned: { status: 200 },
};
await signupHook?.handler(ctx as any);
// Verify span was attached
expect((ctx as any).__otelSpan).toBeDefined();
expect((ctx as any).__otelContext).toBeDefined();
});
it("should cleanup span in after hook", async () => {
const plugin = otelPlugin();
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
const ctx = {
path: "/sign-up/email",
body: { email: "test@example.com" },
request: {},
returned: { status: 200 },
};
await signupHook?.handler(ctx as any);
const afterHook = plugin.hooks?.after?.[0];
await afterHook?.handler(ctx as any);
// Verify span was cleaned up
expect((ctx as any).__otelSpan).toBeUndefined();
expect((ctx as any).__otelContext).toBeUndefined();
});
it("should handle error contexts", async () => {
const plugin = otelPlugin();
const signinHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-in/email", request: {} } as any);
});
const ctx = {
path: "/sign-in/email",
body: { email: "test@example.com" },
request: {},
error: new Error("Invalid credentials"),
returned: { status: 401 },
};
await signinHook?.handler(ctx as any);
const afterHook = plugin.hooks?.after?.[0];
// Should not throw
expect(async () => await afterHook?.handler(ctx as any)).not.toThrow();
});
});
describe("Semantic conventions", () => {
it("should export semantic attribute constants", () => {
expect(SEMATTRS_AUTH_OPERATION).toBe("auth.operation");
expect(SEMATTRS_AUTH_METHOD).toBe("auth.method");
expect(SEMATTRS_AUTH_PROVIDER).toBe("auth.provider");
expect(SEMATTRS_USER_ID).toBe("user.id");
expect(SEMATTRS_USER_EMAIL).toBe("user.email");
expect(SEMATTRS_AUTH_SUCCESS).toBe("auth.success");
});
it("should create spans with correct operation types", async () => {
const plugin = otelPlugin({ captureEmail: true });
const signupHook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: "/sign-up/email", request: {} } as any);
});
const ctx = {
path: "/sign-up/email",
body: { email: "test@example.com" },
request: {},
};
await signupHook?.handler(ctx as any);
// Span should be created and attached
expect((ctx as any).__otelSpan).toBeDefined();
});
it("should support different OAuth providers", async () => {
const plugin = otelPlugin();
const providers = ["google", "github", "facebook"];
for (const provider of providers) {
const hook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: `/callback/${provider}`, request: {} } as any);
});
const ctx = {
path: `/callback/${provider}?code=abc`,
request: {},
};
await hook?.handler(ctx as any);
expect((ctx as any).__otelSpan).toBeDefined();
const afterHook = plugin.hooks?.after?.[0];
await afterHook?.handler(ctx as any);
}
});
});
describe("Multiple operations", () => {
it("should handle multiple auth operations sequentially", async () => {
const plugin = otelPlugin();
// Simulate multiple auth operations
const operations = [
{ path: "/sign-up/email", matcher: "signup" },
{ path: "/sign-in/email", matcher: "signin" },
{ path: "/forget-password", matcher: "forgot" },
];
for (const op of operations) {
const hook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: op.path, request: {} } as any);
});
expect(hook).toBeDefined();
const ctx = {
path: op.path,
body: { email: "test@example.com" },
request: {},
returned: { status: 200 },
};
await hook?.handler(ctx as any);
expect((ctx as any).__otelSpan).toBeDefined();
await plugin.hooks?.after?.[0].handler(ctx as any);
expect((ctx as any).__otelSpan).toBeUndefined();
}
});
it("should handle concurrent operations", async () => {
const plugin = otelPlugin();
const operations = [
{ path: "/sign-up/email" },
{ path: "/sign-in/email" },
{ path: "/sign-out" },
];
const promises = operations.map(async (op) => {
const hook = plugin.hooks?.before?.find(h => {
return h.matcher({ path: op.path, request: {} } as any);
});
const ctx = {
path: op.path,
body: { email: "test@example.com" },
request: {},
returned: { status: 200 },
};
await hook?.handler(ctx as any);
await plugin.hooks?.after?.[0].handler(ctx as any);
return ctx;
});
const results = await Promise.all(promises);
expect(results).toHaveLength(3);
});
});
describe("Default export", () => {
it("should export otelPlugin as default", async () => {
const { default: defaultExport } = await import("./index.js");
expect(defaultExport).toBe(otelPlugin);
});
});
});

View File

@@ -0,0 +1,351 @@
import {
context,
SpanKind,
SpanStatusCode,
trace,
type Span,
type Tracer,
} from "@opentelemetry/api";
import type { BetterAuthPlugin } from "better-auth/plugins";
const DEFAULT_TRACER_NAME = "@kubiks/otel-better-auth";
// Semantic conventions for auth attributes
export const SEMATTRS_AUTH_OPERATION = "auth.operation";
export const SEMATTRS_AUTH_METHOD = "auth.method";
export const SEMATTRS_AUTH_PROVIDER = "auth.provider";
export const SEMATTRS_USER_ID = "user.id";
export const SEMATTRS_USER_EMAIL = "user.email";
export const SEMATTRS_SESSION_ID = "session.id";
export const SEMATTRS_AUTH_SUCCESS = "auth.success";
export const SEMATTRS_AUTH_ERROR = "auth.error";
/**
* Configuration options for Better Auth OpenTelemetry instrumentation.
*/
export interface OtelBetterAuthConfig {
/**
* Custom tracer name. Defaults to "@kubiks/otel-better-auth".
*/
tracerName?: string;
/**
* Whether to capture user email in spans.
* Defaults to false for privacy.
*/
captureEmail?: boolean;
/**
* Whether to capture detailed error messages in spans.
* Defaults to true.
*/
captureErrors?: boolean;
/**
* Custom tracer instance. If not provided, will use trace.getTracer().
*/
tracer?: Tracer;
}
/**
* Finalizes a span with status, timing, and optional error.
*/
function finalizeSpan(span: Span, error?: unknown, success = true): void {
span.setAttribute(SEMATTRS_AUTH_SUCCESS, success);
if (error) {
if (error instanceof Error) {
span.recordException(error);
span.setAttribute(SEMATTRS_AUTH_ERROR, error.message);
} else {
const errorMsg = String(error);
span.recordException(new Error(errorMsg));
span.setAttribute(SEMATTRS_AUTH_ERROR, errorMsg);
}
span.setStatus({ code: SpanStatusCode.ERROR });
} else {
span.setStatus({ code: SpanStatusCode.OK });
}
span.end();
}
/**
* Creates a Better Auth plugin that adds OpenTelemetry tracing to all auth operations.
*
* This plugin automatically instruments key authentication events including:
* - User signup (password, OAuth, magic link, etc.)
* - User signin (all methods)
* - Password reset flows
* - Email verification
* - Session creation and management
*
* @param config - Optional configuration for instrumentation behavior
* @returns A Better Auth plugin that can be added via .use()
*
* @example
* ```typescript
* import { betterAuth } from "better-auth";
* import { otelPlugin } from "@kubiks/otel-better-auth";
*
* export const auth = betterAuth({
* database: db,
* // ... other config
* }).use(otelPlugin());
*
* // Or with custom configuration
* export const auth = betterAuth({
* database: db,
* }).use(otelPlugin({
* tracerName: "my-app-auth",
* captureEmail: true,
* captureErrors: true,
* }));
* ```
*/
export function otelPlugin(config?: OtelBetterAuthConfig): BetterAuthPlugin {
const {
tracerName = DEFAULT_TRACER_NAME,
captureEmail = false,
captureErrors = true,
tracer: customTracer,
} = config ?? {};
const tracer = customTracer ?? trace.getTracer(tracerName);
return {
id: "otel",
hooks: {
before: [
{
matcher: (ctx) => {
// Match signup endpoints
return (
ctx.path === "/sign-up/email" ||
ctx.path === "/sign-up" ||
ctx.request?.url?.includes("/sign-up")
);
},
handler: async (ctx) => {
const attributes: Record<string, string | boolean> = {
[SEMATTRS_AUTH_OPERATION]: "signup",
[SEMATTRS_AUTH_METHOD]: "password",
};
if (captureEmail && (ctx.body as any)?.email) {
attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email;
}
const span = tracer.startSpan("auth.signup.email", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match signin endpoints
return (
ctx.path === "/sign-in/email" ||
ctx.path === "/sign-in" ||
ctx.request?.url?.includes("/sign-in")
);
},
handler: async (ctx) => {
const attributes: Record<string, string | boolean> = {
[SEMATTRS_AUTH_OPERATION]: "signin",
[SEMATTRS_AUTH_METHOD]: "password",
};
if (captureEmail && (ctx.body as any)?.email) {
attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email;
}
const span = tracer.startSpan("auth.signin.email", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match forgot password endpoints
return (
ctx.path === "/forget-password" ||
ctx.request?.url?.includes("/forget-password")
);
},
handler: async (ctx) => {
const attributes: Record<string, string> = {
[SEMATTRS_AUTH_OPERATION]: "forgot_password",
};
if (captureEmail && (ctx.body as any)?.email) {
attributes[SEMATTRS_USER_EMAIL] = (ctx.body as any).email;
}
const span = tracer.startSpan("auth.forgot_password", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match reset password endpoints
return (
ctx.path === "/reset-password" ||
ctx.request?.url?.includes("/reset-password")
);
},
handler: async (ctx) => {
const attributes: Record<string, string> = {
[SEMATTRS_AUTH_OPERATION]: "reset_password",
};
const span = tracer.startSpan("auth.reset_password", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match signout endpoints
return (
ctx.path === "/sign-out" || ctx.request?.url?.includes("/sign-out")
);
},
handler: async (ctx) => {
const attributes: Record<string, string> = {
[SEMATTRS_AUTH_OPERATION]: "signout",
};
const span = tracer.startSpan("auth.signout", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match verify email endpoints
return (
ctx.path === "/verify-email" ||
ctx.request?.url?.includes("/verify-email")
);
},
handler: async (ctx) => {
const attributes: Record<string, string> = {
[SEMATTRS_AUTH_OPERATION]: "verify_email",
};
const span = tracer.startSpan("auth.verify_email", {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
return ctx;
},
},
{
matcher: (ctx) => {
// Match OAuth callback endpoints
return (
(ctx as any).path?.includes("/callback/") ||
ctx.request?.url?.includes("/callback/")
);
},
handler: async (ctx) => {
const url = ctx.request?.url || (ctx as any).path;
const provider = url?.split("/callback/")[1]?.split("/")[0]?.split("?")[0];
if (provider) {
const attributes: Record<string, string> = {
[SEMATTRS_AUTH_OPERATION]: "signin",
[SEMATTRS_AUTH_METHOD]: "oauth",
[SEMATTRS_AUTH_PROVIDER]: provider,
};
const span = tracer.startSpan(`auth.oauth.${provider}`, {
kind: SpanKind.INTERNAL,
attributes,
});
const activeContext = trace.setSpan(context.active(), span);
(ctx as any).__otelSpan = span;
(ctx as any).__otelContext = activeContext;
}
return ctx;
},
},
],
after: [
{
matcher: () => true, // Match all requests
handler: async (ctx) => {
const span = (ctx as any).__otelSpan;
if (span) {
const ctxAny = ctx as any;
const success =
!ctxAny.error &&
(!ctxAny.returned ||
(ctxAny.returned.status >= 200 && ctxAny.returned.status < 300));
// Add user/session info if available
if (ctxAny.context?.session?.userId) {
span.setAttribute(SEMATTRS_USER_ID, ctxAny.context.session.userId);
}
if (ctxAny.context?.session?.sessionId) {
span.setAttribute(SEMATTRS_SESSION_ID, ctxAny.context.session.sessionId);
}
finalizeSpan(span, ctxAny.error, success);
delete (ctx as any).__otelSpan;
delete (ctx as any).__otelContext;
}
return ctx;
},
},
],
},
};
}
// Re-export for convenience
export { otelPlugin as default };

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"incremental": true,
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

492
pnpm-lock.yaml generated
View File

@@ -21,6 +21,39 @@ importers:
specifier: ^1.11.3
version: 1.11.3
packages/otel-better-auth:
devDependencies:
'@opentelemetry/api':
specifier: ^1.9.0
version: 1.9.0
'@opentelemetry/sdk-trace-base':
specifier: ^2.1.0
version: 2.1.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-node':
specifier: ^1.28.0
version: 1.30.1(@opentelemetry/api@1.9.0)
'@types/node':
specifier: 18.15.11
version: 18.15.11
better-auth:
specifier: ^1.0.0
version: 1.3.25
esbuild:
specifier: ^0.19.4
version: 0.19.11
rimraf:
specifier: 3.0.2
version: 3.0.2
tsx:
specifier: ^4.6.2
version: 4.7.0
typescript:
specifier: ^5
version: 5.3.3
vitest:
specifier: 0.33.0
version: 0.33.0(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
packages/otel-drizzle:
devDependencies:
'@opentelemetry/api':
@@ -37,7 +70,7 @@ importers:
version: 8.15.5
drizzle-orm:
specifier: ^0.36.4
version: 0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(postgres@3.4.7)(react@18.2.0)
version: 0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(kysely@0.28.7)(postgres@3.4.7)(react@18.2.0)
esbuild:
specifier: ^0.19.4
version: 0.19.11
@@ -78,6 +111,15 @@ packages:
resolution: {integrity: sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==}
engines: {node: '>=6.9.0'}
'@better-auth/core@1.3.25':
resolution: {integrity: sha512-+Z09RkrpufX8+skD05pAoMSNo95Cl6nwfqrcEHfXJPaiSGJNXoo4/0GNtxZAgsW89xfRdsmbVAwwEd1AlCrHZA==}
'@better-auth/utils@0.3.0':
resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==}
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
'@changesets/apply-release-plan@7.0.0':
resolution: {integrity: sha512-vfi69JR416qC9hWmFGSxj7N6wA5J222XNBmezSVATPWDVPIF7gkd4d8CpbEbXmRWbVrkoli3oerGS6dcL/BGsQ==}
@@ -409,6 +451,9 @@ packages:
cpu: [x64]
os: [win32]
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@jest/schemas@29.6.3':
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -416,12 +461,23 @@ packages:
'@jridgewell/sourcemap-codec@1.4.15':
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@manypkg/find-root@1.1.0':
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
'@manypkg/get-packages@1.1.3':
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
'@noble/ciphers@2.0.1':
resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==}
engines: {node: '>= 20.19.0'}
'@noble/hashes@2.0.1':
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -438,28 +494,117 @@ packages:
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@opentelemetry/context-async-hooks@1.30.1':
resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/core@1.30.1':
resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/core@2.1.0':
resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/propagator-b3@1.30.1':
resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/propagator-jaeger@1.30.1':
resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/resources@1.30.1':
resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/resources@2.1.0':
resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/sdk-trace-base@1.30.1':
resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/sdk-trace-base@2.1.0':
resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/sdk-trace-node@1.30.1':
resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/semantic-conventions@1.28.0':
resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==}
engines: {node: '>=14'}
'@opentelemetry/semantic-conventions@1.37.0':
resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==}
engines: {node: '>=14'}
'@peculiar/asn1-android@2.5.0':
resolution: {integrity: sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==}
'@peculiar/asn1-cms@2.5.0':
resolution: {integrity: sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==}
'@peculiar/asn1-csr@2.5.0':
resolution: {integrity: sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==}
'@peculiar/asn1-ecc@2.5.0':
resolution: {integrity: sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==}
'@peculiar/asn1-pfx@2.5.0':
resolution: {integrity: sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==}
'@peculiar/asn1-pkcs8@2.5.0':
resolution: {integrity: sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==}
'@peculiar/asn1-pkcs9@2.5.0':
resolution: {integrity: sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==}
'@peculiar/asn1-rsa@2.5.0':
resolution: {integrity: sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==}
'@peculiar/asn1-schema@2.5.0':
resolution: {integrity: sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==}
'@peculiar/asn1-x509-attr@2.5.0':
resolution: {integrity: sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==}
'@peculiar/asn1-x509@2.5.0':
resolution: {integrity: sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==}
'@peculiar/x509@1.14.0':
resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==}
'@simplewebauthn/browser@13.2.0':
resolution: {integrity: sha512-N3fuA1AAnTo5gCStYoIoiasPccC+xPLx2YU88Dv0GeAmPQTWHETlZQq5xZ0DgUq1H9loXMWQH5qqUjcI7BHJ1A==}
'@simplewebauthn/server@13.2.1':
resolution: {integrity: sha512-Inmfye5opZXe3HI0GaksqBnQiM7glcNySoG6DH1GgkO1Lh9dvuV4XSV9DK02DReUVX39HpcDob9nxHELjECoQw==}
engines: {node: '>=20.0.0'}
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -478,9 +623,6 @@ packages:
'@types/node@18.15.11':
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
'@types/node@20.11.0':
resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==}
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@@ -569,6 +711,10 @@ packages:
resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==}
engines: {node: '>=0.10.0'}
asn1js@3.0.6:
resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==}
engines: {node: '>=12.0.0'}
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -579,6 +725,38 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
better-auth@1.3.25:
resolution: {integrity: sha512-prTPitTvhIbYPeO/M1QrkveT5afbkyV1OAfnSxYe3WfgTdKgRiEO3tMIPzwmKA54zSWw6XBYKtSD9uIuAAfvuQ==}
peerDependencies:
'@lynx-js/react': '*'
'@sveltejs/kit': '*'
next: '*'
react: '*'
react-dom: '*'
solid-js: '*'
svelte: '*'
vue: '*'
peerDependenciesMeta:
'@lynx-js/react':
optional: true
'@sveltejs/kit':
optional: true
next:
optional: true
react:
optional: true
react-dom:
optional: true
solid-js:
optional: true
svelte:
optional: true
vue:
optional: true
better-call@1.0.19:
resolution: {integrity: sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==}
better-path-resolve@1.0.0:
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
engines: {node: '>=4'}
@@ -722,6 +900,9 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -1152,6 +1333,9 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jose@6.1.0:
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -1176,6 +1360,10 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
kysely@0.28.7:
resolution: {integrity: sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==}
engines: {node: '>=20.0.0'}
less@4.2.0:
resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
engines: {node: '>=6'}
@@ -1276,6 +1464,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanostores@1.0.1:
resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==}
engines: {node: ^20.0.0 || >=22.0.0}
needle@3.3.1:
resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
engines: {node: '>= 4.4.x'}
@@ -1456,6 +1648,13 @@ packages:
pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -1490,6 +1689,9 @@ packages:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@@ -1529,6 +1731,9 @@ packages:
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -1565,6 +1770,9 @@ packages:
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
set-function-length@1.1.1:
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
engines: {node: '>= 0.4'}
@@ -1713,14 +1921,21 @@ packages:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.7.0:
resolution: {integrity: sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==}
engines: {node: '>=18.0.0'}
hasBin: true
tsyringe@4.10.0:
resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
engines: {node: '>= 6.0.0'}
tty-table@4.2.3:
resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==}
engines: {node: '>=8.0.0'}
@@ -1802,8 +2017,8 @@ packages:
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
@@ -1960,6 +2175,9 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
zod@4.1.11:
resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==}
snapshots:
'@adobe/css-tools@4.3.2':
@@ -1982,6 +2200,15 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@better-auth/core@1.3.25':
dependencies:
better-call: 1.0.19
zod: 4.1.11
'@better-auth/utils@0.3.0': {}
'@better-fetch/fetch@1.1.18': {}
'@changesets/apply-release-plan@7.0.0':
dependencies:
'@babel/runtime': 7.23.7
@@ -2282,12 +2509,16 @@ snapshots:
'@esbuild/win32-x64@0.19.11':
optional: true
'@hexagon/base64@1.1.28': {}
'@jest/schemas@29.6.3':
dependencies:
'@sinclair/typebox': 0.27.8
'@jridgewell/sourcemap-codec@1.4.15': {}
'@levischuck/tiny-cbor@0.2.11': {}
'@manypkg/find-root@1.1.0':
dependencies:
'@babel/runtime': 7.23.7
@@ -2304,6 +2535,10 @@ snapshots:
globby: 11.1.0
read-yaml-file: 1.1.0
'@noble/ciphers@2.0.1': {}
'@noble/hashes@2.0.1': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2318,17 +2553,49 @@ snapshots:
'@opentelemetry/api@1.9.0': {}
'@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.37.0
'@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.37.0
'@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
@@ -2336,8 +2603,129 @@ snapshots:
'@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.37.0
'@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
semver: 7.5.4
'@opentelemetry/semantic-conventions@1.28.0': {}
'@opentelemetry/semantic-conventions@1.37.0': {}
'@peculiar/asn1-android@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-cms@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
'@peculiar/asn1-x509-attr': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-csr@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-ecc@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-pfx@2.5.0':
dependencies:
'@peculiar/asn1-cms': 2.5.0
'@peculiar/asn1-pkcs8': 2.5.0
'@peculiar/asn1-rsa': 2.5.0
'@peculiar/asn1-schema': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-pkcs8@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-pkcs9@2.5.0':
dependencies:
'@peculiar/asn1-cms': 2.5.0
'@peculiar/asn1-pfx': 2.5.0
'@peculiar/asn1-pkcs8': 2.5.0
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
'@peculiar/asn1-x509-attr': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-rsa@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-schema@2.5.0':
dependencies:
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509-attr@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-x509@2.5.0':
dependencies:
'@peculiar/asn1-schema': 2.5.0
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/x509@1.14.0':
dependencies:
'@peculiar/asn1-cms': 2.5.0
'@peculiar/asn1-csr': 2.5.0
'@peculiar/asn1-ecc': 2.5.0
'@peculiar/asn1-pkcs9': 2.5.0
'@peculiar/asn1-rsa': 2.5.0
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
pvtsutils: 1.3.6
reflect-metadata: 0.2.2
tslib: 2.8.1
tsyringe: 4.10.0
'@simplewebauthn/browser@13.2.0': {}
'@simplewebauthn/server@13.2.1':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
'@peculiar/asn1-android': 2.5.0
'@peculiar/asn1-ecc': 2.5.0
'@peculiar/asn1-rsa': 2.5.0
'@peculiar/asn1-schema': 2.5.0
'@peculiar/asn1-x509': 2.5.0
'@peculiar/x509': 1.14.0
'@sinclair/typebox@0.27.8': {}
'@types/chai-subset@1.3.5':
@@ -2352,15 +2740,11 @@ snapshots:
'@types/node@18.15.11': {}
'@types/node@20.11.0':
dependencies:
undici-types: 5.26.5
'@types/normalize-package-data@2.4.4': {}
'@types/pg@8.15.5':
dependencies:
'@types/node': 20.11.0
'@types/node': 18.15.11
pg-protocol: 1.10.3
pg-types: 2.2.0
@@ -2461,12 +2845,42 @@ snapshots:
arrify@1.0.1: {}
asn1js@3.0.6:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.3
tslib: 2.8.1
assertion-error@1.1.0: {}
available-typed-arrays@1.0.5: {}
balanced-match@1.0.2: {}
better-auth@1.3.25:
dependencies:
'@better-auth/core': 1.3.25
'@better-auth/utils': 0.3.0
'@better-fetch/fetch': 1.1.18
'@noble/ciphers': 2.0.1
'@noble/hashes': 2.0.1
'@simplewebauthn/browser': 13.2.0
'@simplewebauthn/server': 13.2.1
better-call: 1.0.19
defu: 6.1.4
jose: 6.1.0
kysely: 0.28.7
nanostores: 1.0.1
zod: 4.1.11
better-call@1.0.19:
dependencies:
'@better-auth/utils': 0.3.0
'@better-fetch/fetch': 1.1.18
rou3: 0.5.1
set-cookie-parser: 2.7.1
uncrypto: 0.1.3
better-path-resolve@1.0.0:
dependencies:
is-windows: 1.0.2
@@ -2633,6 +3047,8 @@ snapshots:
has-property-descriptors: 1.0.1
object-keys: 1.1.1
defu@6.1.4: {}
detect-indent@6.1.0: {}
diff-sequences@29.6.3: {}
@@ -2643,11 +3059,12 @@ snapshots:
dotenv@8.6.0: {}
drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(postgres@3.4.7)(react@18.2.0):
drizzle-orm@0.36.4(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(@types/react@18.2.46)(kysely@0.28.7)(postgres@3.4.7)(react@18.2.0):
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/pg': 8.15.5
'@types/react': 18.2.46
kysely: 0.28.7
postgres: 3.4.7
react: 18.2.0
@@ -3055,6 +3472,8 @@ snapshots:
isexe@2.0.0: {}
jose@6.1.0: {}
js-tokens@4.0.0: {}
js-yaml@3.14.1:
@@ -3074,11 +3493,13 @@ snapshots:
kleur@4.1.5: {}
kysely@0.28.7: {}
less@4.2.0:
dependencies:
copy-anything: 2.0.6
parse-node-version: 1.0.1
tslib: 2.6.2
tslib: 2.8.1
optionalDependencies:
errno: 0.1.8
graceful-fs: 4.2.11
@@ -3191,6 +3612,8 @@ snapshots:
nanoid@3.3.7: {}
nanostores@1.0.1: {}
needle@3.3.1:
dependencies:
iconv-lite: 0.6.3
@@ -3348,6 +3771,12 @@ snapshots:
pseudomap@1.0.2: {}
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
queue-microtask@1.2.3: {}
quick-lru@4.0.1: {}
@@ -3389,6 +3818,8 @@ snapshots:
indent-string: 4.0.0
strip-indent: 3.0.0
reflect-metadata@0.2.2: {}
regenerator-runtime@0.14.1: {}
regexp.prototype.flags@1.5.1:
@@ -3421,6 +3852,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
rou3@0.5.1: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -3461,6 +3894,8 @@ snapshots:
set-blocking@2.0.0: {}
set-cookie-parser@2.7.1: {}
set-function-length@1.1.1:
dependencies:
define-data-property: 1.1.1
@@ -3617,8 +4052,9 @@ snapshots:
trim-newlines@3.0.1: {}
tslib@2.6.2:
optional: true
tslib@1.14.1: {}
tslib@2.8.1: {}
tsx@4.7.0:
dependencies:
@@ -3627,6 +4063,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
tsyringe@4.10.0:
dependencies:
tslib: 1.14.1
tty-table@4.2.3:
dependencies:
chalk: 4.1.2
@@ -3710,7 +4150,7 @@ snapshots:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
undici-types@5.26.5: {}
uncrypto@0.1.3: {}
universalify@0.1.2: {}
@@ -3719,14 +4159,14 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vite-node@0.33.0(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0):
vite-node@0.33.0(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0):
dependencies:
cac: 6.7.14
debug: 4.3.4
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
vite: 4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
vite: 4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -3737,13 +4177,13 @@ snapshots:
- supports-color
- terser
vite@4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0):
vite@4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0):
dependencies:
esbuild: 0.18.20
postcss: 8.4.33
rollup: 3.29.4
optionalDependencies:
'@types/node': 20.11.0
'@types/node': 18.15.11
fsevents: 2.3.3
less: 4.2.0
sass: 1.69.7
@@ -3753,7 +4193,7 @@ snapshots:
dependencies:
'@types/chai': 4.3.11
'@types/chai-subset': 1.3.5
'@types/node': 20.11.0
'@types/node': 18.15.11
'@vitest/expect': 0.33.0
'@vitest/runner': 0.33.0
'@vitest/snapshot': 0.33.0
@@ -3772,8 +4212,8 @@ snapshots:
strip-literal: 1.3.0
tinybench: 2.5.1
tinypool: 0.6.0
vite: 4.5.1(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
vite-node: 0.33.0(@types/node@20.11.0)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
vite: 4.5.1(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
vite-node: 0.33.0(@types/node@18.15.11)(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@@ -3885,3 +4325,5 @@ snapshots:
yocto-queue@0.1.0: {}
yocto-queue@1.0.0: {}
zod@4.1.11: {}