From 8594308146123646d55cbde137e5434257b3e0fc Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Wed, 15 Jul 2026 09:48:34 +0200 Subject: [PATCH 1/7] feat: add error-tracking adapter seam with sentry reference impl Add the ERROR_TRACKING port + token so the runtime can report unhandled errors to a vendor, replaceable via an overlay (PostHog, Rollbar, ...). - new ErrorTrackingAdapter contract (fire-and-forget captureException) - NoopErrorTracker default; createSentryErrorTracker auto-binds when SENTRY_DSN is set, dynamically importing @sentry/node (optional dep, tracing gated on SENTRY_TRACES_SAMPLE_RATE), falling back to no-op if the SDK is absent so boot never crashes - capture at the three previously log-only seams: oRPC onError interceptor, event-subscriber failures, outbox-relay drops - docs/adapters/error-tracking.md + regenerated catalog --- docs/adapters/error-tracking.md | 81 +++ docs/catalog.json | 64 +- packages/core/package.json | 3 + .../src/contracts/adapters/error-tracking.ts | 24 + packages/core/src/contracts/adapters/index.ts | 3 + .../kernel/__tests__/error-tracker.test.ts | 59 ++ .../server/kernel/__tests__/event-bus.test.ts | 18 + .../core/src/server/kernel/error-tracker.ts | 74 +++ packages/core/src/server/kernel/event-bus.ts | 6 + packages/core/src/server/kernel/index.ts | 3 + .../core/src/server/runtime/create-app.ts | 24 +- pnpm-lock.yaml | 574 ++++++++++++++---- 12 files changed, 755 insertions(+), 178 deletions(-) create mode 100644 docs/adapters/error-tracking.md create mode 100644 packages/core/src/contracts/adapters/error-tracking.ts create mode 100644 packages/core/src/server/kernel/__tests__/error-tracker.test.ts create mode 100644 packages/core/src/server/kernel/error-tracker.ts diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md new file mode 100644 index 00000000..14358695 --- /dev/null +++ b/docs/adapters/error-tracking.md @@ -0,0 +1,81 @@ +# Error-Tracking Adapter + +## Interface + +```ts +// packages/core/src/contracts/adapters/error-tracking.ts +import { createToken, type Token } from './token.js'; + +export type ErrorContext = { + userId?: string; + traceId?: string; + tags?: Record; + extra?: Record; +}; + +export type ErrorTrackingAdapter = { + captureException(error: unknown, context?: ErrorContext): void; +}; + +export const ERROR_TRACKING: Token = createToken('ERROR_TRACKING'); +``` + +`captureException` is fire-and-forget (returns `void`) - a reporter failure must never break +the request or background path that reported the error. + +## Where the runtime captures + +The engine (`createApp`) reports at the three seams that previously only logged to pino: + +- oRPC unhandled-error interceptor - every route error that is not an expected `ORPCError` + (mapped 4xx domain errors are skipped on purpose). Carries `userId` + `traceId` from the + active request context. +- EventBus subscriber failures - a handler that throws is isolated and reported with a + `path: 'event-subscriber'` tag plus the event topic. +- OutboxRelay drain failures - background publish failures, tagged `path: 'outbox-relay'`. + +## Default binding + +`ERROR_TRACKING` defaults to `NoopErrorTracker` - pino already logs at every seam, so the +reporter is purely additive and the platform runs unchanged without one. + +When `SENTRY_DSN` is set, core auto-binds a Sentry reference tracker instead (same env-driven +auto-bind treatment as the Redis/BullMQ reference drivers). It dynamically imports +`@sentry/node`, so the vendor SDK is only loaded when a DSN is configured; `@sentry/node` is +an `optionalDependency` of `@openora/core` - install it in your app to activate the binding. +If the DSN is set but `@sentry/node` is missing, it logs a warning and falls back to the +no-op (boot never crashes). + +Env vars: + +- `SENTRY_DSN` - the project DSN. Required to activate. Lives in env, never in code. +- `SENTRY_TRACES_SAMPLE_RATE` - optional. `0..1`; unset means tracing is off (errors only). + +`captureException` maps `ErrorContext` onto the Sentry scope: `userId -> user.id`, +`traceId -> tags.trace_id`, and `tags`/`extra` pass through. + +> Deep auto-instrumentation (HTTP/DB spans) needs Sentry initialized via a `--import` +> preload hook before the app's other imports. That is a Sentry-specific consumer opt-in, +> deliberately outside this vendor-neutral seam. The in-process init here covers error +> capture and manual tracing. + +## Override with another vendor (PostHog, Rollbar, ...) + +Ship an impl of `ErrorTrackingAdapter` and bind it in an overlay plugin loaded AFTER core +(last registration wins). No DSN needed - your binding replaces the default outright. + +```ts +// extensions/posthog-errors/plugin.ts +import { ERROR_TRACKING } from '@openora/core/contracts'; +import { definePlugin } from '@openora/core/server'; +import { PostHogErrorTracker } from './src/posthog-error-tracker.js'; + +export default definePlugin({ + id: 'posthog-errors', + register(ctx) { + ctx.provide(ERROR_TRACKING, () => new PostHogErrorTracker()); + }, +}); +``` + +Register it in `extensions.config.ts` as the last entry (an `kind: 'infra'` overlay). diff --git a/docs/catalog.json b/docs/catalog.json index f7c5faef..8c128b14 100644 --- a/docs/catalog.json +++ b/docs/catalog.json @@ -143,7 +143,6 @@ "tables": [ "account", "session", - "sms_otp_session", "two_factor", "user", "verification" @@ -157,8 +156,6 @@ "identity.login", "identity.logout", "identity.me", - "identity.phoneLoginRequest", - "identity.phoneLoginVerify", "identity.register", "identity.requestPasswordReset", "identity.resetPassword", @@ -169,8 +166,7 @@ "identity.unlockUser", "identity.updateProfile", "identity.verify2fa", - "identity.verifyEmail", - "identity.verifyPasswordResetOtp" + "identity.verifyEmail" ] }, { @@ -365,12 +361,12 @@ ] }, { - "category": "email-template", - "interface": "EmailTemplateKey", - "token": "EMAIL_TEMPLATE_RENDERER", + "category": "error-tracking", + "interface": "ErrorTrackingAdapter", + "token": "ERROR_TRACKING", "status": "wired", "boundIn": [ - "packages/core/src/pam/identity/plugin.ts" + "packages/core/src/server/runtime/create-app.ts" ] }, { @@ -480,6 +476,16 @@ "packages/core/src/wallet/plugin.ts" ] }, + { + "category": "rate-limit", + "interface": "RateLimiterAdapter", + "token": "RATE_LIMITER", + "status": "wired", + "boundIn": [ + "packages/core/src/pam/identity/plugin.ts", + "packages/core/src/wallet/plugin.ts" + ] + }, { "category": "realtime", "interface": "RealtimePresence", @@ -498,16 +504,6 @@ "packages/core/src/casino/gaming/plugin.ts" ] }, - { - "category": "sms", - "interface": "SmsAdapter", - "token": "SMS_ADAPTER", - "status": "wired", - "boundIn": [ - "packages/core/src/pam/identity/adapters/mock/mock-sms-adapter.ts", - "packages/core/src/pam/identity/plugin.ts" - ] - }, { "category": "wallet-commands", "interface": "WalletDebitArgs", @@ -558,8 +554,6 @@ "identity.2fa.enabled", "identity.email.verified", "identity.password.reset", - "identity.phone_otp.cancelled", - "identity.phone_otp.requested", "identity.profile.updated", "identity.session.revoked", "identity.sessions.revoked_all", @@ -568,7 +562,6 @@ "identity.user.login", "identity.user.login.failed", "identity.user.logout", - "identity.user.phone_login", "identity.user.reactivated", "identity.user.registered", "identity.user.unlocked", @@ -754,10 +747,6 @@ "name": "Disable2faInputSchema", "file": "packages/core/src/contracts/schemas/identity.ts" }, - { - "name": "E164PhoneSchema", - "file": "packages/core/src/contracts/schemas/identity.ts" - }, { "name": "EffectivePermissionsSchema", "file": "packages/core/src/iam/contract/index.ts" @@ -954,18 +943,6 @@ "name": "PermissionLevelSchema", "file": "packages/core/src/contracts/schemas/iam.ts" }, - { - "name": "PhoneLoginRequestInputSchema", - "file": "packages/core/src/contracts/schemas/identity.ts" - }, - { - "name": "PhoneLoginRequestOutputSchema", - "file": "packages/core/src/contracts/schemas/identity.ts" - }, - { - "name": "PhoneLoginVerifyInputSchema", - "file": "packages/core/src/contracts/schemas/identity.ts" - }, { "name": "PlatformConfigSchema", "file": "packages/core/src/contracts/schemas/platform-config.ts" @@ -1086,10 +1063,6 @@ "name": "SessionItemSchema", "file": "packages/core/src/pam/identity/contract/index.ts" }, - { - "name": "SessionSchema", - "file": "packages/core/src/pam/identity/contract/index.ts" - }, { "name": "SessionTimeDetailSchema", "file": "packages/core/src/compliance/contract/rg.ts" @@ -1190,10 +1163,6 @@ "name": "VerifyEmailInputSchema", "file": "packages/core/src/contracts/schemas/identity.ts" }, - { - "name": "VerifyPasswordResetOtpInputSchema", - "file": "packages/core/src/contracts/schemas/identity.ts" - }, { "name": "WalletBalanceSchema", "file": "packages/core/src/wallet/contract/index.ts" @@ -1360,9 +1329,6 @@ "POST /identity/password/change", "POST /identity/password/forgot", "POST /identity/password/reset", - "POST /identity/password/verify-otp", - "POST /identity/phone-login/request", - "POST /identity/phone-login/verify", "POST /identity/register", "POST /identity/sessions/revoke", "POST /identity/sessions/revoke-all", diff --git a/packages/core/package.json b/packages/core/package.json index 901a3afa..39da4741 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -565,5 +565,8 @@ "react-dom": { "optional": true } + }, + "optionalDependencies": { + "@sentry/node": "10.65.0" } } diff --git a/packages/core/src/contracts/adapters/error-tracking.ts b/packages/core/src/contracts/adapters/error-tracking.ts new file mode 100644 index 00000000..41e346a7 --- /dev/null +++ b/packages/core/src/contracts/adapters/error-tracking.ts @@ -0,0 +1,24 @@ +// Error-tracking seam. The runtime reports unhandled errors here - the oRPC +// interceptor, event-subscriber failures, and outbox-relay drops. The default is +// a no-op; core ships a Sentry reference impl that auto-binds when SENTRY_DSN is +// set (mirroring the Redis/BullMQ reference drivers). Operators swap it for any +// vendor (PostHog, Rollbar, ...) via an overlay: +// ctx.provide(ERROR_TRACKING, () => new MyErrorTracker()) +// Load your overlay AFTER core so its binding wins (last registration wins). +import { createToken, type Token } from './token.js'; + +export type ErrorContext = { + userId?: string; + traceId?: string; + tags?: Record; + extra?: Record; +}; + +// captureException is fire-and-forget (returns void) - a reporter failure must +// never break the request or background path that called it. +export type ErrorTrackingAdapter = { + captureException(error: unknown, context?: ErrorContext): void; +}; + +export const ERROR_TRACKING: Token = + createToken('ERROR_TRACKING'); diff --git a/packages/core/src/contracts/adapters/index.ts b/packages/core/src/contracts/adapters/index.ts index f4cc7296..6de964f7 100644 --- a/packages/core/src/contracts/adapters/index.ts +++ b/packages/core/src/contracts/adapters/index.ts @@ -108,6 +108,9 @@ export type { } from './email-template.js'; export { EMAIL_TEMPLATE_RENDERER, DEFAULT_EMAIL_TEMPLATES } from './email-template.js'; +export type { ErrorTrackingAdapter, ErrorContext } from './error-tracking.js'; +export { ERROR_TRACKING } from './error-tracking.js'; + export type { AdminGrant, AdminPermissionResolver } from './admin-permission.js'; export { ADMIN_PERMISSION_RESOLVER } from './admin-permission.js'; diff --git a/packages/core/src/server/kernel/__tests__/error-tracker.test.ts b/packages/core/src/server/kernel/__tests__/error-tracker.test.ts new file mode 100644 index 00000000..cab874ec --- /dev/null +++ b/packages/core/src/server/kernel/__tests__/error-tracker.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { NoopErrorTracker, createSentryErrorTracker } from '../error-tracker.js'; + +describe('NoopErrorTracker', () => { + it('is inert', () => { + const tracker = new NoopErrorTracker(); + expect(() => tracker.captureException(new Error('x'))).not.toThrow(); + }); +}); + +const init = vi.fn(); +const captureException = vi.fn(); + +// The adapter loads '@sentry/node' via a runtime dynamic import(); vitest's module +// registry intercepts it regardless of the specifier being non-literal. +vi.mock('@sentry/node', () => ({ init, captureException })); + +describe('createSentryErrorTracker', () => { + beforeEach(() => { + init.mockReset(); + captureException.mockReset(); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it('inits Sentry with the dsn and no tracing by default', async () => { + await createSentryErrorTracker({ dsn: 'https://k@example/1' }); + expect(init).toHaveBeenCalledWith({ dsn: 'https://k@example/1' }); + }); + + it('passes tracesSampleRate through when set', async () => { + await createSentryErrorTracker({ dsn: 'https://k@example/1', tracesSampleRate: 0.5 }); + expect(init).toHaveBeenCalledWith({ dsn: 'https://k@example/1', tracesSampleRate: 0.5 }); + }); + + it('maps userId, traceId and tags onto the Sentry scope', async () => { + const tracker = await createSentryErrorTracker({ dsn: 'https://k@example/1' }); + const err = new Error('boom'); + tracker.captureException(err, { + userId: 'u1', + traceId: 't1', + tags: { path: 'orpc' }, + extra: { route: '/x' }, + }); + expect(captureException).toHaveBeenCalledWith(err, { + user: { id: 'u1' }, + tags: { path: 'orpc', trace_id: 't1' }, + extra: { route: '/x' }, + }); + }); + + it('omits user when no userId is given', async () => { + const tracker = await createSentryErrorTracker({ dsn: 'https://k@example/1' }); + tracker.captureException(new Error('boom')); + const [, ctx] = captureException.mock.calls[0] ?? []; + expect(ctx).not.toHaveProperty('user'); + }); +}); diff --git a/packages/core/src/server/kernel/__tests__/event-bus.test.ts b/packages/core/src/server/kernel/__tests__/event-bus.test.ts index cc976825..68afb35f 100644 --- a/packages/core/src/server/kernel/__tests__/event-bus.test.ts +++ b/packages/core/src/server/kernel/__tests__/event-bus.test.ts @@ -105,6 +105,24 @@ describe('createEventBus', () => { ); }); + it('reports a throwing subscriber to the error tracker when one is bound', async () => { + const reporter = { captureException: vi.fn() }; + const bus = createEventBus(new InMemoryBroker(), fakeLogger(), undefined, reporter); + + bus.on('identity.user.registered', () => { + throw new Error('boom'); + }); + bus.emit('identity.user.registered', { userId: 'u1' }); + await flush(); + + expect(reporter.captureException).toHaveBeenCalledWith( + expect.any(Error), + expect.objectContaining({ + tags: { event: 'identity.user.registered', path: 'event-subscriber' }, + }), + ); + }); + it('logs a validation error for a bad payload but still delivers', async () => { const logger = fakeLogger(); const bus = createEventBus(new InMemoryBroker(), logger); diff --git a/packages/core/src/server/kernel/error-tracker.ts b/packages/core/src/server/kernel/error-tracker.ts new file mode 100644 index 00000000..81208821 --- /dev/null +++ b/packages/core/src/server/kernel/error-tracker.ts @@ -0,0 +1,74 @@ +import type { ErrorTrackingAdapter, ErrorContext } from '@openora/core/contracts'; +import { createLogger } from './logger.js'; + +// Default binding for ERROR_TRACKING. pino already logs at every failure seam, so +// the no-op tracker is purely additive - the platform runs unchanged without a +// reporter overlay. +export class NoopErrorTracker implements ErrorTrackingAdapter { + captureException(_error?: unknown, _context?: ErrorContext): void {} +} + +type SentryCaptureContext = { + user?: { id: string }; + tags?: Record; + extra?: Record; +}; + +// The slice of @sentry/node this adapter touches. The module is imported +// dynamically (optional dependency), so we type only what we call. +type SentryModule = { + init(options: { dsn: string; tracesSampleRate?: number }): void; + captureException(error: unknown, context?: SentryCaptureContext): void; +}; + +export type SentryErrorTrackerConfig = { + dsn: string; + tracesSampleRate?: number; +}; + +/** + * Sentry reference error tracker. Dynamically imports @sentry/node so the vendor + * dep is only pulled in when a DSN is configured, and tracing only activates when + * tracesSampleRate is set. Falls back to a no-op if @sentry/node is not installed, + * so a misconfigured DSN never crashes boot. + */ +// ponytail: in-process init gives error capture + manual tracing; deep +// auto-instrumentation (http/pg spans) needs a --import preload hook, opt-in on +// the consumer side. Add when APM spans are actually wanted. +export async function createSentryErrorTracker( + config: SentryErrorTrackerConfig, +): Promise { + const log = createLogger('sentry'); + // Non-literal specifier: keep the optional dep out of static module resolution + // so typecheck/build never require @sentry/node to be present. + const specifier: string = '@sentry/node'; + let sentry: SentryModule; + try { + sentry = await import(specifier); + } catch { + log.warn( + 'SENTRY_DSN is set but @sentry/node is not installed - error tracking disabled. ' + + 'Add @sentry/node to your app dependencies to enable it.', + ); + return new NoopErrorTracker(); + } + + sentry.init({ + dsn: config.dsn, + ...(config.tracesSampleRate !== undefined ? { tracesSampleRate: config.tracesSampleRate } : {}), + }); + log.info('sentry error tracking active.'); + + return { + captureException(error: unknown, context?: ErrorContext): void { + sentry.captureException(error, { + ...(context?.userId ? { user: { id: context.userId } } : {}), + tags: { + ...context?.tags, + ...(context?.traceId ? { trace_id: context.traceId } : {}), + }, + ...(context?.extra ? { extra: context.extra } : {}), + }); + }, + }; +} diff --git a/packages/core/src/server/kernel/event-bus.ts b/packages/core/src/server/kernel/event-bus.ts index 9a79d086..c9b86057 100644 --- a/packages/core/src/server/kernel/event-bus.ts +++ b/packages/core/src/server/kernel/event-bus.ts @@ -5,6 +5,7 @@ import { type OutboxWriter, type EventEnvelope, type SubscribeOptions, + type ErrorTrackingAdapter, domainEventSchemas, getEventVersion, type DomainEventName, @@ -110,6 +111,7 @@ export function createEventBus( broker: MessageBrokerAdapter, logger: Logger = createLogger('event-bus'), outbox?: OutboxWriter, + reporter?: ErrorTrackingAdapter, ): EventBus { function validate(event: string, payload: unknown): void { if (isKnownEvent(event)) { @@ -147,6 +149,10 @@ export function createEventBus( await handler(envelope.payload, envelope); } catch (err) { logger.error({ event, err }, 'event subscriber threw'); + reporter?.captureException(err, { + traceId: getCurrentTraceId(), + tags: { event, path: 'event-subscriber' }, + }); } }); }, diff --git a/packages/core/src/server/kernel/index.ts b/packages/core/src/server/kernel/index.ts index 01d9363d..fe2eebfa 100644 --- a/packages/core/src/server/kernel/index.ts +++ b/packages/core/src/server/kernel/index.ts @@ -24,6 +24,9 @@ export type { Factory } from './container.js'; export { createLogger } from './logger.js'; +export { NoopErrorTracker, createSentryErrorTracker } from './error-tracker.js'; +export type { SentryErrorTrackerConfig } from './error-tracker.js'; + export { getUserId } from './router-utils.js'; export type { OssContext, AuthContext, NodeHeaders } from './router-utils.js'; export { diff --git a/packages/core/src/server/runtime/create-app.ts b/packages/core/src/server/runtime/create-app.ts index e5b6d6e2..fbe3b29b 100644 --- a/packages/core/src/server/runtime/create-app.ts +++ b/packages/core/src/server/runtime/create-app.ts @@ -23,6 +23,9 @@ import { createLogger, createRedisClient, withRequestContext, + getCurrentRequestContext, + NoopErrorTracker, + createSentryErrorTracker, EVENT_BUS, type OssContext, } from '../kernel/index.js'; @@ -36,6 +39,7 @@ import { ADMIN_PERMISSION_RESOLVER, RATE_LIMITER, CACHE, + ERROR_TRACKING, composeContract, healthContract, IGAMING_CONFIG, @@ -182,6 +186,19 @@ export async function createApp(config: CreateAppConfig): Promise { } const container = new Container(); + // Registered before the event-bus/outbox so every downstream c.get(ERROR_TRACKING) + // resolves synchronously. Sentry inits here (async dynamic import) when SENTRY_DSN + // is set; otherwise a no-op. A consumer overlay re-providing ERROR_TRACKING later + // still wins (last registration wins). + const sentryDsn = process.env['SENTRY_DSN']; + const tracesSampleRate = process.env['SENTRY_TRACES_SAMPLE_RATE']; + const errorTracker = sentryDsn + ? await createSentryErrorTracker({ + dsn: sentryDsn, + ...(tracesSampleRate ? { tracesSampleRate: Number(tracesSampleRate) } : {}), + }) + : new NoopErrorTracker(); + container.register(ERROR_TRACKING, () => errorTracker); container.register(DRIZZLE, () => { const svc = new DrizzleService(); container.onDispose(() => svc.dispose()); @@ -206,6 +223,7 @@ export async function createApp(config: CreateAppConfig): Promise { c.get(MESSAGE_BROKER), createLogger('event-bus'), outboxEnabled ? c.get(OUTBOX) : undefined, + c.get(ERROR_TRACKING), ), ); // Default in-process fan-out served as SSE; an overlay rebinds to a managed @@ -293,7 +311,10 @@ export async function createApp(config: CreateAppConfig): Promise { if (outboxEnabled) { const relay = new OutboxRelay(drizzle.db, container.get(MESSAGE_BROKER), { - onError: (err) => createLogger('outbox-relay').error({ err }, 'outbox drain failed'), + onError: (err) => { + createLogger('outbox-relay').error({ err }, 'outbox drain failed'); + errorTracker.captureException(err, { tags: { path: 'outbox-relay' } }); + }, }); relay.start(); container.onDispose(() => relay.stop()); @@ -320,6 +341,7 @@ export async function createApp(config: CreateAppConfig): Promise { onError((error) => { if (!(error instanceof ORPCError)) { createLogger('orpc').error({ err: error }, 'unhandled error'); + container.get(ERROR_TRACKING).captureException(error, getCurrentRequestContext()); } }), ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8abe431..43bdfcfe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,7 @@ importers: version: link:packages/testing '@orpc/contract': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@turbo/gen': specifier: 2.9.16 version: 2.9.16(@types/node@25.9.2) @@ -102,7 +102,7 @@ importers: version: 4.1.8(vitest@4.1.8) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) packages/core: dependencies: @@ -114,37 +114,37 @@ importers: version: 2.0.4(hono@4.12.25) '@orpc/client': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@orpc/contract': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@orpc/openapi': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@orpc/openapi-client': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@orpc/server': specifier: 1.14.5 - version: 1.14.5 + version: 1.14.5(@opentelemetry/api@1.9.1) '@orpc/tanstack-query': specifier: 1.14.5 - version: 1.14.5(@orpc/client@1.14.5)(@tanstack/query-core@5.101.0) + version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/client@1.14.5(@opentelemetry/api@1.9.1))(@tanstack/query-core@5.101.0) '@orpc/zod': specifier: 1.14.5 - version: 1.14.5(@orpc/contract@1.14.5)(@orpc/server@1.14.5)(zod@4.4.3) + version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1))(zod@4.4.3) better-auth: specifier: 1.6.15 - version: 1.6.15(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) + version: 1.6.15(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) bullmq: specifier: 5.79.2 - version: 5.79.2(redis@5.12.1) + version: 5.79.2(redis@5.12.1(@opentelemetry/api@1.9.1)) dotenv: specifier: 17.4.2 version: 17.4.2 drizzle-orm: specifier: 0.45.2 - version: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) hono: specifier: 4.12.25 version: 4.12.25 @@ -162,7 +162,7 @@ importers: version: 19.2.7(react@19.2.7) redis: specifier: 5.12.1 - version: 5.12.1 + version: 5.12.1(@opentelemetry/api@1.9.1) zod: specifier: 4.4.3 version: 4.4.3 @@ -193,7 +193,11 @@ importers: version: 6.0.3 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + optionalDependencies: + '@sentry/node': + specifier: 10.65.0 + version: 10.65.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)) packages/mcp: dependencies: @@ -221,7 +225,7 @@ importers: version: link:../core drizzle-orm: specifier: 0.45.2 - version: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) hono: specifier: 4.12.25 version: 4.12.25 @@ -240,7 +244,7 @@ importers: version: 6.0.3 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) packages: @@ -248,6 +252,17 @@ packages: resolution: {integrity: sha512-hBptHbB6Regs5AgtHiLr/WfgHRh6ZGdA9EexOWU14n2eNjQekvK9/gg18iFUYvL2WrNb4X3Ci0bzgzJXhzmpNQ==} engines: {node: '>=12'} + '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': + resolution: {integrity: sha512-YxLBY5nGlurL7QeJLq6e5g0ouBpAp0pwgyA/5rHXEXwhiPLn9ZHbT+Y2LlP90GT872cSocfjWRYu/fnpuBudNQ==} + engines: {node: '>=18.0.0'} + + '@apm-js-collab/code-transformer@0.15.0': + resolution: {integrity: sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww==} + hasBin: true + + '@apm-js-collab/tracing-hooks@0.10.1': + resolution: {integrity: sha512-w2OWXR7FWrKqSziuE9+QclaZrStxO/8+OwbXM635s/zs0Eez1Qo3ivSPdB2WsaPY/iznKTytONPx/PitD7IXcA==} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -1176,6 +1191,44 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@2.9.0': + resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation@0.220.0': + resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.9.0': + resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.9.0': + resolution: {integrity: sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.9.0': + resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.41.1': resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} @@ -1640,6 +1693,51 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sentry/conventions@0.15.1': + resolution: {integrity: sha512-ZLP8bRdMON3prWE2tJyImuYscCxdcJeIPIhrOs/rgyFm3C1nCh1B6gdvPj3AZ5zW08oSFFCsq7T+tYEW3h8MNA==} + engines: {node: '>=14'} + + '@sentry/core@10.65.0': + resolution: {integrity: sha512-3aqtmM5NgNGo45BNaaBzi0LPQZAw//NEL4HKS5fXm12pJMa4KEkze8DEKnkTEIrGnWaOJKamecHKlnNg/Mqf/Q==} + engines: {node: '>=18'} + + '@sentry/node-core@10.65.0': + resolution: {integrity: sha512-U01X9mPT+jZnsLPmPWfBU67Ka+t/Sdd9RGAuvGoKdrI6N47a/9PDkM9oCW+kj0fmZwogZHTgSnzJU5oi3pImgA==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/exporter-trace-otlp-http': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + + '@sentry/node@10.65.0': + resolution: {integrity: sha512-t35dcdyksysVch/m/XdLgGJqGKJhr9eMD30Ctn3TeQ8yMB0wNXySfjPR5Yg93fpjmfaHtzc6iYIXRAvgNVfrvA==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.65.0': + resolution: {integrity: sha512-8C6FPvm3XBvUrkM52dX3Gz0p2H0Ij8t4sahUA+GTiCz0WM0fnyPeQPGC/b6I4jamV9UXyCZRnE1UEEGCoD+c7A==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + + '@sentry/server-utils@10.65.0': + resolution: {integrity: sha512-80toEFD6s+0Le7jrYB6pHWLF703WSg0WyavAWqrBGWG8JkREHgedAxzFYgoY5GlMI756qk6Ea7UzhJTHd2zAXA==} + engines: {node: '>=18'} + '@simple-libs/child-process-utils@1.0.2': resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} engines: {node: '>=18'} @@ -1869,6 +1967,10 @@ packages: ast-v8-to-istanbul@1.0.3: resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2023,6 +2125,9 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} @@ -2356,6 +2461,9 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -2391,6 +2499,14 @@ packages: engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -2665,6 +2781,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@3.3.1: + resolution: {integrity: sha512-0rymlHSFLwZ0ixx8DaQkoIyZojJPY2a0K2nEYslhKJ6jIYO/m0IcCb7iQsFPmS7WmKwISZiIrv5Icstrw/CmqA==} + engines: {node: '>=18'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2980,6 +3100,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meriyah@6.1.4: + resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} + engines: {node: '>=18.0.0'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3007,6 +3131,9 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -3391,6 +3518,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3448,6 +3579,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + semifies@1.0.0: + resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} + semver@7.8.1: resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} @@ -3943,6 +4077,33 @@ snapshots: '@2toad/profanity@3.3.0': {} + '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + es-module-lexer: 2.1.0 + magic-string: 0.30.21 + module-details-from-path: 1.0.4 + optional: true + + '@apm-js-collab/code-transformer@0.15.0': + dependencies: + '@types/estree': 1.0.9 + astring: 1.9.0 + esquery: 1.7.0 + meriyah: 6.1.4 + semifies: 1.0.0 + source-map: 0.6.1 + optional: true + + '@apm-js-collab/tracing-hooks@0.10.1': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + optional: true + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -3966,7 +4127,7 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0)': + '@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0)': dependencies: '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 @@ -3977,39 +4138,41 @@ snapshots: kysely: 0.29.2 nanostores: 1.3.0 zod: 4.4.3 + optionalDependencies: + '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))': + '@better-auth/drizzle-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 optionalDependencies: - drizzle-orm: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) - '@better-auth/kysely-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)': + '@better-auth/kysely-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 optionalDependencies: kysely: 0.29.2 - '@better-auth/memory-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/memory-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/mongo-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/mongo-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/prisma-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/prisma-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/telemetry@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 @@ -4822,21 +4985,69 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 + '@opentelemetry/api-logs@0.220.0': + dependencies: + '@opentelemetry/api': 1.9.1 + optional: true + + '@opentelemetry/api@1.9.1': + optional: true + + '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + optional: true + + '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + import-in-the-middle: 3.3.1 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + optional: true + + '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + optional: true + + '@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + optional: true + + '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + optional: true + '@opentelemetry/semantic-conventions@1.41.1': {} - '@orpc/client@1.14.5': + '@orpc/client@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 - '@orpc/standard-server-fetch': 1.14.5 - '@orpc/standard-server-peer': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-peer': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/contract@1.14.5': + '@orpc/contract@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/client': 1.14.5 - '@orpc/shared': 1.14.5 + '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) '@standard-schema/spec': 1.1.0 openapi-types: 12.1.3 transitivePeerDependencies: @@ -4844,13 +5055,13 @@ snapshots: '@orpc/interop@1.14.5': {} - '@orpc/json-schema@1.14.5': + '@orpc/json-schema@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/contract': 1.14.5 + '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) '@orpc/interop': 1.14.5 - '@orpc/openapi': 1.14.5 - '@orpc/server': 1.14.5 - '@orpc/shared': 1.14.5 + '@orpc/openapi': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) json-schema-typed: 8.0.2 transitivePeerDependencies: - '@opentelemetry/api' @@ -4858,24 +5069,24 @@ snapshots: - fastify - ws - '@orpc/openapi-client@1.14.5': + '@orpc/openapi-client@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/client': 1.14.5 - '@orpc/contract': 1.14.5 - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 + '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/openapi@1.14.5': + '@orpc/openapi@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/client': 1.14.5 - '@orpc/contract': 1.14.5 + '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) '@orpc/interop': 1.14.5 - '@orpc/openapi-client': 1.14.5 - '@orpc/server': 1.14.5 - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 + '@orpc/openapi-client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) json-schema-typed: 8.0.2 rou3: 0.7.12 transitivePeerDependencies: @@ -4884,88 +5095,90 @@ snapshots: - fastify - ws - '@orpc/server@1.14.5': + '@orpc/server@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/client': 1.14.5 - '@orpc/contract': 1.14.5 + '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) '@orpc/interop': 1.14.5 - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 - '@orpc/standard-server-aws-lambda': 1.14.5 - '@orpc/standard-server-fastify': 1.14.5 - '@orpc/standard-server-fetch': 1.14.5 - '@orpc/standard-server-node': 1.14.5 - '@orpc/standard-server-peer': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-aws-lambda': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-fastify': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-peer': 1.14.5(@opentelemetry/api@1.9.1) cookie: 1.1.1 transitivePeerDependencies: - '@opentelemetry/api' - fastify - '@orpc/shared@1.14.5': + '@orpc/shared@1.14.5(@opentelemetry/api@1.9.1)': dependencies: radash: 12.1.1 type-fest: 5.7.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 - '@orpc/standard-server-aws-lambda@1.14.5': + '@orpc/standard-server-aws-lambda@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 - '@orpc/standard-server-fetch': 1.14.5 - '@orpc/standard-server-node': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-fastify@1.14.5': + '@orpc/standard-server-fastify@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 - '@orpc/standard-server-node': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-fetch@1.14.5': + '@orpc/standard-server-fetch@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-node@1.14.5': + '@orpc/standard-server-node@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 - '@orpc/standard-server-fetch': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-peer@1.14.5': + '@orpc/standard-server-peer@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 - '@orpc/standard-server': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server@1.14.5': + '@orpc/standard-server@1.14.5(@opentelemetry/api@1.9.1)': dependencies: - '@orpc/shared': 1.14.5 + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/tanstack-query@1.14.5(@orpc/client@1.14.5)(@tanstack/query-core@5.101.0)': + '@orpc/tanstack-query@1.14.5(@opentelemetry/api@1.9.1)(@orpc/client@1.14.5(@opentelemetry/api@1.9.1))(@tanstack/query-core@5.101.0)': dependencies: - '@orpc/client': 1.14.5 - '@orpc/shared': 1.14.5 + '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) '@tanstack/query-core': 5.101.0 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/zod@1.14.5(@orpc/contract@1.14.5)(@orpc/server@1.14.5)(zod@4.4.3)': + '@orpc/zod@1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1))(zod@4.4.3)': dependencies: - '@orpc/contract': 1.14.5 - '@orpc/json-schema': 1.14.5 - '@orpc/openapi': 1.14.5 - '@orpc/server': 1.14.5 - '@orpc/shared': 1.14.5 + '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/json-schema': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/openapi': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) escape-string-regexp: 5.0.0 wildcard-match: 5.1.4 zod: 4.4.3 @@ -5093,25 +5306,27 @@ snapshots: '@pinojs/redact@0.4.0': {} - '@redis/bloom@5.12.1(@redis/client@5.12.1)': + '@redis/bloom@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': dependencies: - '@redis/client': 5.12.1 + '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) - '@redis/client@5.12.1': + '@redis/client@5.12.1(@opentelemetry/api@1.9.1)': dependencies: cluster-key-slot: 1.1.2 + optionalDependencies: + '@opentelemetry/api': 1.9.1 - '@redis/json@5.12.1(@redis/client@5.12.1)': + '@redis/json@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': dependencies: - '@redis/client': 5.12.1 + '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) - '@redis/search@5.12.1(@redis/client@5.12.1)': + '@redis/search@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': dependencies: - '@redis/client': 5.12.1 + '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) - '@redis/time-series@5.12.1(@redis/client@5.12.1)': + '@redis/time-series@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': dependencies: - '@redis/client': 5.12.1 + '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) '@rolldown/binding-android-arm64@1.0.3': optional: true @@ -5166,6 +5381,65 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@sentry/conventions@0.15.1': + optional: true + + '@sentry/core@10.65.0': + dependencies: + '@sentry/conventions': 0.15.1 + optional: true + + '@sentry/node-core@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1))': + dependencies: + '@sentry/conventions': 0.15.1 + '@sentry/core': 10.65.0 + '@sentry/opentelemetry': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) + import-in-the-middle: 3.3.1 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) + optional: true + + '@sentry/node@10.65.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) + '@sentry/conventions': 0.15.1 + '@sentry/core': 10.65.0 + '@sentry/node-core': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) + '@sentry/opentelemetry': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) + '@sentry/server-utils': 10.65.0 + import-in-the-middle: 3.3.1 + transitivePeerDependencies: + - '@opentelemetry/core' + - '@opentelemetry/exporter-trace-otlp-http' + - supports-color + optional: true + + '@sentry/opentelemetry@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1))': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) + '@sentry/conventions': 0.15.1 + '@sentry/core': 10.65.0 + optional: true + + '@sentry/server-utils@10.65.0': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + '@apm-js-collab/code-transformer-bundler-plugins': 0.5.0 + '@apm-js-collab/tracing-hooks': 0.10.1 + '@sentry/conventions': 0.15.1 + '@sentry/core': 10.65.0 + magic-string: 0.30.21 + transitivePeerDependencies: + - supports-color + optional: true + '@simple-libs/child-process-utils@1.0.2': dependencies: '@simple-libs/stream-utils': 1.2.0 @@ -5280,7 +5554,7 @@ snapshots: obug: 2.1.2 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) '@vitest/expect@4.1.8': dependencies: @@ -5396,6 +5670,9 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + astring@1.9.0: + optional: true + asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -5417,15 +5694,15 @@ snapshots: before-after-hook@4.0.0: {} - better-auth@1.6.15(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8): + better-auth@1.6.15(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8): dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0)) - '@better-auth/kysely-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/mongo-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/prisma-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/telemetry': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0)) + '@better-auth/kysely-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2) + '@better-auth/memory-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/mongo-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/prisma-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/telemetry': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.2.0 @@ -5438,12 +5715,12 @@ snapshots: zod: 4.4.3 optionalDependencies: drizzle-kit: 0.31.10 - drizzle-orm: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) - next: 16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + next: 16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) pg: 8.21.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -5485,7 +5762,7 @@ snapshots: buffer-from@1.1.2: {} - bullmq@5.79.2(redis@5.12.1): + bullmq@5.79.2(redis@5.12.1(@opentelemetry/api@1.9.1)): dependencies: cron-parser: 4.9.0 ioredis: 5.10.1 @@ -5494,7 +5771,7 @@ snapshots: semver: 7.8.5 tslib: 2.8.1 optionalDependencies: - redis: 5.12.1 + redis: 5.12.1(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color @@ -5526,6 +5803,9 @@ snapshots: chardet@2.1.1: {} + cjs-module-lexer@2.2.0: + optional: true + cli-table3@0.6.5: dependencies: string-width: 4.2.3 @@ -5700,8 +5980,9 @@ snapshots: esbuild: 0.28.1 tsx: 4.22.4 - drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0): + drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0): optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/pg': 8.20.0 kysely: 0.29.2 pg: 8.21.0 @@ -5751,6 +6032,9 @@ snapshots: es-module-lexer@2.1.0: {} + es-module-lexer@2.3.1: + optional: true + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -5803,6 +6087,14 @@ snapshots: esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + optional: true + + estraverse@5.3.0: + optional: true + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -6143,6 +6435,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@3.3.1: + dependencies: + cjs-module-lexer: 2.2.0 + es-module-lexer: 2.3.1 + module-details-from-path: 1.0.4 + optional: true + inherits@2.0.4: {} ini@4.1.1: {} @@ -6402,6 +6701,9 @@ snapshots: merge2@1.4.1: {} + meriyah@6.1.4: + optional: true + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -6425,6 +6727,9 @@ snapshots: minimist@1.2.8: {} + module-details-from-path@1.0.4: + optional: true + mri@1.2.0: {} ms@2.1.3: {} @@ -6455,7 +6760,7 @@ snapshots: negotiator@1.0.0: {} - next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.9 '@swc/helpers': 0.5.15 @@ -6474,6 +6779,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.9 '@next/swc-win32-arm64-msvc': 16.2.9 '@next/swc-win32-x64-msvc': 16.2.9 + '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -6788,13 +7094,13 @@ snapshots: dependencies: redis-errors: 1.2.0 - redis@5.12.1: + redis@5.12.1(@opentelemetry/api@1.9.1): dependencies: - '@redis/bloom': 5.12.1(@redis/client@5.12.1) - '@redis/client': 5.12.1 - '@redis/json': 5.12.1(@redis/client@5.12.1) - '@redis/search': 5.12.1(@redis/client@5.12.1) - '@redis/time-series': 5.12.1(@redis/client@5.12.1) + '@redis/bloom': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) + '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) + '@redis/json': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) + '@redis/search': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) + '@redis/time-series': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) transitivePeerDependencies: - '@node-rs/xxhash' - '@opentelemetry/api' @@ -6803,6 +7109,14 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + optional: true + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -6896,6 +7210,9 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + semifies@1.0.0: + optional: true + semver@7.8.1: {} semver@7.8.2: {} @@ -7213,7 +7530,7 @@ snapshots: jiti: 2.7.0 tsx: 4.22.4 - vitest@4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)): dependencies: '@vitest/expect': 4.1.8 '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) @@ -7236,6 +7553,7 @@ snapshots: vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 25.9.2 '@vitest/coverage-v8': 4.1.8(vitest@4.1.8) transitivePeerDependencies: From 7ffe93f243096ece796bb964a6eb4529c62e1b0d Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Wed, 15 Jul 2026 15:51:35 +0200 Subject: [PATCH 2/7] refactor: keep error-tracking seam vendor-neutral, report via logger sink Core named a specific vendor (Sentry) in the engine - createApp imported createSentryErrorTracker and auto-bound on SENTRY_DSN. That ships Sentry code to every consumer, even a PostHog shop. Make the seam truly neutral. - drop the Sentry reference impl + @sentry/node optional dep from core; the engine imports no vendor SDK and has no SENTRY_DSN of its own - report through the logger: createLogger's pino hook forwards any error-level log carrying `err` to a process-wide error sink, so one logger.error({ err }) both logs and reports - no separate captureException at each site, and all error logs are covered (not just three seams) - createApp bridges the sink to whatever ERROR_TRACKING an overlay bound, after plugins load; unbound -> error logs stay logs-only - revert the createEventBus reporter param and the per-site capture calls - vendors (Sentry, PostHog, ...) become consumer overlays - doc shows the Sentry overlay recipe, mirroring the payment/KYC vendor overlays --- docs/adapters/error-tracking.md | 83 +-- docs/catalog.json | 65 +- packages/core/package.json | 3 - .../src/contracts/adapters/error-tracking.ts | 10 +- .../kernel/__tests__/error-tracker.test.ts | 59 -- .../server/kernel/__tests__/event-bus.test.ts | 18 - .../server/kernel/__tests__/logger.test.ts | 45 ++ packages/core/src/server/kernel/error-sink.ts | 25 + .../core/src/server/kernel/error-tracker.ts | 74 --- packages/core/src/server/kernel/event-bus.ts | 6 - packages/core/src/server/kernel/index.ts | 4 +- packages/core/src/server/kernel/logger.ts | 34 +- .../core/src/server/runtime/create-app.ts | 34 +- pnpm-lock.yaml | 574 ++++-------------- 14 files changed, 347 insertions(+), 687 deletions(-) delete mode 100644 packages/core/src/server/kernel/__tests__/error-tracker.test.ts create mode 100644 packages/core/src/server/kernel/__tests__/logger.test.ts create mode 100644 packages/core/src/server/kernel/error-sink.ts delete mode 100644 packages/core/src/server/kernel/error-tracker.ts diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md index 14358695..908ef628 100644 --- a/docs/adapters/error-tracking.md +++ b/docs/adapters/error-tracking.md @@ -20,62 +20,65 @@ export type ErrorTrackingAdapter = { export const ERROR_TRACKING: Token = createToken('ERROR_TRACKING'); ``` -`captureException` is fire-and-forget (returns `void`) - a reporter failure must never break -the request or background path that reported the error. +`captureException` is fire-and-forget (returns `void`) - a reporter failure never breaks the +path that reported the error. Core names **no vendor**: the port is the only seam. Sentry, +PostHog, Rollbar, etc. are consumer overlays. -## Where the runtime captures +## How reporting flows (no per-call-site capture) -The engine (`createApp`) reports at the three seams that previously only logged to pino: +Core does not call `captureException` at each failure site. Instead the logger is the bridge: +every `logger.error({ err, ... }, 'message')` is mirrored to the bound tracker. So one log call +both logs and reports, and **all** error logs are covered - the oRPC unhandled-error +interceptor, EventBus subscriber failures, OutboxRelay drops, and anything else that logs an +error with an `err` field. Error logs without an `err` (validation notices) are not reported. -- oRPC unhandled-error interceptor - every route error that is not an expected `ORPCError` - (mapped 4xx domain errors are skipped on purpose). Carries `userId` + `traceId` from the - active request context. -- EventBus subscriber failures - a handler that throws is isolated and reported with a - `path: 'event-subscriber'` tag plus the event topic. -- OutboxRelay drain failures - background publish failures, tagged `path: 'outbox-relay'`. +Wiring, in `createApp`, after plugins load: -## Default binding - -`ERROR_TRACKING` defaults to `NoopErrorTracker` - pino already logs at every seam, so the -reporter is purely additive and the platform runs unchanged without one. - -When `SENTRY_DSN` is set, core auto-binds a Sentry reference tracker instead (same env-driven -auto-bind treatment as the Redis/BullMQ reference drivers). It dynamically imports -`@sentry/node`, so the vendor SDK is only loaded when a DSN is configured; `@sentry/node` is -an `optionalDependency` of `@openora/core` - install it in your app to activate the binding. -If the DSN is set but `@sentry/node` is missing, it logs a warning and falls back to the -no-op (boot never crashes). - -Env vars: - -- `SENTRY_DSN` - the project DSN. Required to activate. Lives in env, never in code. -- `SENTRY_TRACES_SAMPLE_RATE` - optional. `0..1`; unset means tracing is off (errors only). +```ts +if (container.has(ERROR_TRACKING)) { + const tracker = container.get(ERROR_TRACKING); + setErrorSink((error, context) => tracker.captureException(error, context)); +} +``` -`captureException` maps `ErrorContext` onto the Sentry scope: `userId -> user.id`, -`traceId -> tags.trace_id`, and `tags`/`extra` pass through. +No overlay binds `ERROR_TRACKING` -> no sink -> error logs stay logs-only. The engine imports +no vendor SDK and has no `SENTRY_DSN`/vendor env of its own. -> Deep auto-instrumentation (HTTP/DB spans) needs Sentry initialized via a `--import` -> preload hook before the app's other imports. That is a Sentry-specific consumer opt-in, -> deliberately outside this vendor-neutral seam. The in-process init here covers error -> capture and manual tracing. +The forwarded `ErrorContext` carries `userId`/`traceId` from the active request context plus the +log's structured fields (event topic, path, message) as `extra`. -## Override with another vendor (PostHog, Rollbar, ...) +## Bind a vendor (consumer overlay) -Ship an impl of `ErrorTrackingAdapter` and bind it in an overlay plugin loaded AFTER core -(last registration wins). No DSN needed - your binding replaces the default outright. +Real vendor adapters live in the consumer, bound via a self-disabling overlay plugin - the same +pattern as the payment/KYC/realtime vendor overlays. Ship an `ErrorTrackingAdapter` impl and +provide it; loading the overlay last makes its binding win. ```ts -// extensions/posthog-errors/plugin.ts +// extensions/sentry/plugin.ts (consumer) import { ERROR_TRACKING } from '@openora/core/contracts'; import { definePlugin } from '@openora/core/server'; -import { PostHogErrorTracker } from './src/posthog-error-tracker.js'; +import * as Sentry from '@sentry/node'; export default definePlugin({ - id: 'posthog-errors', + id: 'sentry', register(ctx) { - ctx.provide(ERROR_TRACKING, () => new PostHogErrorTracker()); + const dsn = process.env['SENTRY_DSN']; + if (!dsn) { + return; // self-disabling: no DSN -> platform stays logs-only + } + Sentry.init({ dsn }); + ctx.provide(ERROR_TRACKING, () => ({ + captureException(error, context) { + Sentry.captureException(error, { + ...(context?.userId ? { user: { id: context.userId } } : {}), + tags: { ...context?.tags, ...(context?.traceId ? { trace_id: context.traceId } : {}) }, + ...(context?.extra ? { extra: context.extra } : {}), + }); + }, + })); }, }); ``` -Register it in `extensions.config.ts` as the last entry (an `kind: 'infra'` overlay). +Register it last in `extensions.config.ts` (an `kind: 'infra'` overlay). A PostHog/Rollbar shop +writes the equivalent overlay against the same port - core is unchanged. diff --git a/docs/catalog.json b/docs/catalog.json index 8c128b14..d8343dc2 100644 --- a/docs/catalog.json +++ b/docs/catalog.json @@ -143,6 +143,7 @@ "tables": [ "account", "session", + "sms_otp_session", "two_factor", "user", "verification" @@ -156,6 +157,8 @@ "identity.login", "identity.logout", "identity.me", + "identity.phoneLoginRequest", + "identity.phoneLoginVerify", "identity.register", "identity.requestPasswordReset", "identity.resetPassword", @@ -166,7 +169,8 @@ "identity.unlockUser", "identity.updateProfile", "identity.verify2fa", - "identity.verifyEmail" + "identity.verifyEmail", + "identity.verifyPasswordResetOtp" ] }, { @@ -360,6 +364,15 @@ "packages/core/src/pam/identity/plugin.ts" ] }, + { + "category": "email-template", + "interface": "EmailTemplateKey", + "token": "EMAIL_TEMPLATE_RENDERER", + "status": "wired", + "boundIn": [ + "packages/core/src/pam/identity/plugin.ts" + ] + }, { "category": "error-tracking", "interface": "ErrorTrackingAdapter", @@ -476,16 +489,6 @@ "packages/core/src/wallet/plugin.ts" ] }, - { - "category": "rate-limit", - "interface": "RateLimiterAdapter", - "token": "RATE_LIMITER", - "status": "wired", - "boundIn": [ - "packages/core/src/pam/identity/plugin.ts", - "packages/core/src/wallet/plugin.ts" - ] - }, { "category": "realtime", "interface": "RealtimePresence", @@ -504,6 +507,16 @@ "packages/core/src/casino/gaming/plugin.ts" ] }, + { + "category": "sms", + "interface": "SmsAdapter", + "token": "SMS_ADAPTER", + "status": "wired", + "boundIn": [ + "packages/core/src/pam/identity/adapters/mock/mock-sms-adapter.ts", + "packages/core/src/pam/identity/plugin.ts" + ] + }, { "category": "wallet-commands", "interface": "WalletDebitArgs", @@ -554,6 +567,8 @@ "identity.2fa.enabled", "identity.email.verified", "identity.password.reset", + "identity.phone_otp.cancelled", + "identity.phone_otp.requested", "identity.profile.updated", "identity.session.revoked", "identity.sessions.revoked_all", @@ -562,6 +577,7 @@ "identity.user.login", "identity.user.login.failed", "identity.user.logout", + "identity.user.phone_login", "identity.user.reactivated", "identity.user.registered", "identity.user.unlocked", @@ -747,6 +763,10 @@ "name": "Disable2faInputSchema", "file": "packages/core/src/contracts/schemas/identity.ts" }, + { + "name": "E164PhoneSchema", + "file": "packages/core/src/contracts/schemas/identity.ts" + }, { "name": "EffectivePermissionsSchema", "file": "packages/core/src/iam/contract/index.ts" @@ -943,6 +963,18 @@ "name": "PermissionLevelSchema", "file": "packages/core/src/contracts/schemas/iam.ts" }, + { + "name": "PhoneLoginRequestInputSchema", + "file": "packages/core/src/contracts/schemas/identity.ts" + }, + { + "name": "PhoneLoginRequestOutputSchema", + "file": "packages/core/src/contracts/schemas/identity.ts" + }, + { + "name": "PhoneLoginVerifyInputSchema", + "file": "packages/core/src/contracts/schemas/identity.ts" + }, { "name": "PlatformConfigSchema", "file": "packages/core/src/contracts/schemas/platform-config.ts" @@ -1063,6 +1095,10 @@ "name": "SessionItemSchema", "file": "packages/core/src/pam/identity/contract/index.ts" }, + { + "name": "SessionSchema", + "file": "packages/core/src/pam/identity/contract/index.ts" + }, { "name": "SessionTimeDetailSchema", "file": "packages/core/src/compliance/contract/rg.ts" @@ -1163,6 +1199,10 @@ "name": "VerifyEmailInputSchema", "file": "packages/core/src/contracts/schemas/identity.ts" }, + { + "name": "VerifyPasswordResetOtpInputSchema", + "file": "packages/core/src/contracts/schemas/identity.ts" + }, { "name": "WalletBalanceSchema", "file": "packages/core/src/wallet/contract/index.ts" @@ -1329,6 +1369,9 @@ "POST /identity/password/change", "POST /identity/password/forgot", "POST /identity/password/reset", + "POST /identity/password/verify-otp", + "POST /identity/phone-login/request", + "POST /identity/phone-login/verify", "POST /identity/register", "POST /identity/sessions/revoke", "POST /identity/sessions/revoke-all", diff --git a/packages/core/package.json b/packages/core/package.json index 39da4741..901a3afa 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -565,8 +565,5 @@ "react-dom": { "optional": true } - }, - "optionalDependencies": { - "@sentry/node": "10.65.0" } } diff --git a/packages/core/src/contracts/adapters/error-tracking.ts b/packages/core/src/contracts/adapters/error-tracking.ts index 41e346a7..242749f7 100644 --- a/packages/core/src/contracts/adapters/error-tracking.ts +++ b/packages/core/src/contracts/adapters/error-tracking.ts @@ -1,8 +1,8 @@ -// Error-tracking seam. The runtime reports unhandled errors here - the oRPC -// interceptor, event-subscriber failures, and outbox-relay drops. The default is -// a no-op; core ships a Sentry reference impl that auto-binds when SENTRY_DSN is -// set (mirroring the Redis/BullMQ reference drivers). Operators swap it for any -// vendor (PostHog, Rollbar, ...) via an overlay: +// Error-tracking seam. Core names no vendor: every error-level log carrying an +// `err` is forwarded here (wired through the logger), so binding this port reports +// all error logs - the oRPC interceptor, event-subscriber failures, outbox-relay +// drops, and any other logger.error({ err }). Unbound -> error logs stay logs-only. +// A consumer binds a vendor (Sentry, PostHog, Rollbar, ...) via an overlay plugin: // ctx.provide(ERROR_TRACKING, () => new MyErrorTracker()) // Load your overlay AFTER core so its binding wins (last registration wins). import { createToken, type Token } from './token.js'; diff --git a/packages/core/src/server/kernel/__tests__/error-tracker.test.ts b/packages/core/src/server/kernel/__tests__/error-tracker.test.ts deleted file mode 100644 index cab874ec..00000000 --- a/packages/core/src/server/kernel/__tests__/error-tracker.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { NoopErrorTracker, createSentryErrorTracker } from '../error-tracker.js'; - -describe('NoopErrorTracker', () => { - it('is inert', () => { - const tracker = new NoopErrorTracker(); - expect(() => tracker.captureException(new Error('x'))).not.toThrow(); - }); -}); - -const init = vi.fn(); -const captureException = vi.fn(); - -// The adapter loads '@sentry/node' via a runtime dynamic import(); vitest's module -// registry intercepts it regardless of the specifier being non-literal. -vi.mock('@sentry/node', () => ({ init, captureException })); - -describe('createSentryErrorTracker', () => { - beforeEach(() => { - init.mockReset(); - captureException.mockReset(); - }); - afterEach(() => { - vi.unstubAllEnvs(); - }); - - it('inits Sentry with the dsn and no tracing by default', async () => { - await createSentryErrorTracker({ dsn: 'https://k@example/1' }); - expect(init).toHaveBeenCalledWith({ dsn: 'https://k@example/1' }); - }); - - it('passes tracesSampleRate through when set', async () => { - await createSentryErrorTracker({ dsn: 'https://k@example/1', tracesSampleRate: 0.5 }); - expect(init).toHaveBeenCalledWith({ dsn: 'https://k@example/1', tracesSampleRate: 0.5 }); - }); - - it('maps userId, traceId and tags onto the Sentry scope', async () => { - const tracker = await createSentryErrorTracker({ dsn: 'https://k@example/1' }); - const err = new Error('boom'); - tracker.captureException(err, { - userId: 'u1', - traceId: 't1', - tags: { path: 'orpc' }, - extra: { route: '/x' }, - }); - expect(captureException).toHaveBeenCalledWith(err, { - user: { id: 'u1' }, - tags: { path: 'orpc', trace_id: 't1' }, - extra: { route: '/x' }, - }); - }); - - it('omits user when no userId is given', async () => { - const tracker = await createSentryErrorTracker({ dsn: 'https://k@example/1' }); - tracker.captureException(new Error('boom')); - const [, ctx] = captureException.mock.calls[0] ?? []; - expect(ctx).not.toHaveProperty('user'); - }); -}); diff --git a/packages/core/src/server/kernel/__tests__/event-bus.test.ts b/packages/core/src/server/kernel/__tests__/event-bus.test.ts index 68afb35f..cc976825 100644 --- a/packages/core/src/server/kernel/__tests__/event-bus.test.ts +++ b/packages/core/src/server/kernel/__tests__/event-bus.test.ts @@ -105,24 +105,6 @@ describe('createEventBus', () => { ); }); - it('reports a throwing subscriber to the error tracker when one is bound', async () => { - const reporter = { captureException: vi.fn() }; - const bus = createEventBus(new InMemoryBroker(), fakeLogger(), undefined, reporter); - - bus.on('identity.user.registered', () => { - throw new Error('boom'); - }); - bus.emit('identity.user.registered', { userId: 'u1' }); - await flush(); - - expect(reporter.captureException).toHaveBeenCalledWith( - expect.any(Error), - expect.objectContaining({ - tags: { event: 'identity.user.registered', path: 'event-subscriber' }, - }), - ); - }); - it('logs a validation error for a bad payload but still delivers', async () => { const logger = fakeLogger(); const bus = createEventBus(new InMemoryBroker(), logger); diff --git a/packages/core/src/server/kernel/__tests__/logger.test.ts b/packages/core/src/server/kernel/__tests__/logger.test.ts new file mode 100644 index 00000000..ae9e9531 --- /dev/null +++ b/packages/core/src/server/kernel/__tests__/logger.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { createLogger } from '../logger.js'; +import { setErrorSink } from '../error-sink.js'; + +afterEach(() => { + setErrorSink(undefined); +}); + +describe('createLogger error-sink hook', () => { + it('forwards an error-level log carrying `err` to the bound sink', () => { + const sink = vi.fn(); + setErrorSink(sink); + + const err = new Error('boom'); + createLogger('test').error({ err, event: 'x' }, 'something failed'); + + expect(sink).toHaveBeenCalledTimes(1); + expect(sink).toHaveBeenCalledWith( + err, + expect.objectContaining({ extra: { event: 'x', message: 'something failed' } }), + ); + }); + + it('does not report error logs without an `err`', () => { + const sink = vi.fn(); + setErrorSink(sink); + + createLogger('test').error({ issues: [] }, 'validation failed'); + + expect(sink).not.toHaveBeenCalled(); + }); + + it('does not report non-error levels', () => { + const sink = vi.fn(); + setErrorSink(sink); + + createLogger('test').warn({ err: new Error('x') }, 'a warning'); + + expect(sink).not.toHaveBeenCalled(); + }); + + it('is inert when no sink is bound', () => { + expect(() => createLogger('test').error({ err: new Error('x') }, 'no sink')).not.toThrow(); + }); +}); diff --git a/packages/core/src/server/kernel/error-sink.ts b/packages/core/src/server/kernel/error-sink.ts new file mode 100644 index 00000000..7d09f366 --- /dev/null +++ b/packages/core/src/server/kernel/error-sink.ts @@ -0,0 +1,25 @@ +import type { ErrorContext } from '@openora/core/contracts'; + +// The process-wide bridge from the logger to a bound ERROR_TRACKING adapter. +// createApp sets it once after plugins load (when an overlay bound the port); +// createLogger forwards every error-level log carrying an `err` here, so a single +// logger.error(...) both logs and reports - no separate capture call at each site. +export type ErrorSink = (error: unknown, context?: ErrorContext) => void; + +// ponytail: module-global, single sink. Fine for the single-process, single-tenant +// runtime (ADR-0026); if the engine ever runs multiple isolated apps in one process, +// move this onto the container. +let sink: ErrorSink | undefined; + +export function setErrorSink(next: ErrorSink | undefined): void { + sink = next; +} + +export function reportError(error: unknown, context?: ErrorContext): void { + // Fire-and-forget: a reporter failure must never break the log call that drove it. + try { + sink?.(error, context); + } catch { + /* swallow - reporting is best-effort */ + } +} diff --git a/packages/core/src/server/kernel/error-tracker.ts b/packages/core/src/server/kernel/error-tracker.ts deleted file mode 100644 index 81208821..00000000 --- a/packages/core/src/server/kernel/error-tracker.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ErrorTrackingAdapter, ErrorContext } from '@openora/core/contracts'; -import { createLogger } from './logger.js'; - -// Default binding for ERROR_TRACKING. pino already logs at every failure seam, so -// the no-op tracker is purely additive - the platform runs unchanged without a -// reporter overlay. -export class NoopErrorTracker implements ErrorTrackingAdapter { - captureException(_error?: unknown, _context?: ErrorContext): void {} -} - -type SentryCaptureContext = { - user?: { id: string }; - tags?: Record; - extra?: Record; -}; - -// The slice of @sentry/node this adapter touches. The module is imported -// dynamically (optional dependency), so we type only what we call. -type SentryModule = { - init(options: { dsn: string; tracesSampleRate?: number }): void; - captureException(error: unknown, context?: SentryCaptureContext): void; -}; - -export type SentryErrorTrackerConfig = { - dsn: string; - tracesSampleRate?: number; -}; - -/** - * Sentry reference error tracker. Dynamically imports @sentry/node so the vendor - * dep is only pulled in when a DSN is configured, and tracing only activates when - * tracesSampleRate is set. Falls back to a no-op if @sentry/node is not installed, - * so a misconfigured DSN never crashes boot. - */ -// ponytail: in-process init gives error capture + manual tracing; deep -// auto-instrumentation (http/pg spans) needs a --import preload hook, opt-in on -// the consumer side. Add when APM spans are actually wanted. -export async function createSentryErrorTracker( - config: SentryErrorTrackerConfig, -): Promise { - const log = createLogger('sentry'); - // Non-literal specifier: keep the optional dep out of static module resolution - // so typecheck/build never require @sentry/node to be present. - const specifier: string = '@sentry/node'; - let sentry: SentryModule; - try { - sentry = await import(specifier); - } catch { - log.warn( - 'SENTRY_DSN is set but @sentry/node is not installed - error tracking disabled. ' + - 'Add @sentry/node to your app dependencies to enable it.', - ); - return new NoopErrorTracker(); - } - - sentry.init({ - dsn: config.dsn, - ...(config.tracesSampleRate !== undefined ? { tracesSampleRate: config.tracesSampleRate } : {}), - }); - log.info('sentry error tracking active.'); - - return { - captureException(error: unknown, context?: ErrorContext): void { - sentry.captureException(error, { - ...(context?.userId ? { user: { id: context.userId } } : {}), - tags: { - ...context?.tags, - ...(context?.traceId ? { trace_id: context.traceId } : {}), - }, - ...(context?.extra ? { extra: context.extra } : {}), - }); - }, - }; -} diff --git a/packages/core/src/server/kernel/event-bus.ts b/packages/core/src/server/kernel/event-bus.ts index c9b86057..9a79d086 100644 --- a/packages/core/src/server/kernel/event-bus.ts +++ b/packages/core/src/server/kernel/event-bus.ts @@ -5,7 +5,6 @@ import { type OutboxWriter, type EventEnvelope, type SubscribeOptions, - type ErrorTrackingAdapter, domainEventSchemas, getEventVersion, type DomainEventName, @@ -111,7 +110,6 @@ export function createEventBus( broker: MessageBrokerAdapter, logger: Logger = createLogger('event-bus'), outbox?: OutboxWriter, - reporter?: ErrorTrackingAdapter, ): EventBus { function validate(event: string, payload: unknown): void { if (isKnownEvent(event)) { @@ -149,10 +147,6 @@ export function createEventBus( await handler(envelope.payload, envelope); } catch (err) { logger.error({ event, err }, 'event subscriber threw'); - reporter?.captureException(err, { - traceId: getCurrentTraceId(), - tags: { event, path: 'event-subscriber' }, - }); } }); }, diff --git a/packages/core/src/server/kernel/index.ts b/packages/core/src/server/kernel/index.ts index fe2eebfa..05952975 100644 --- a/packages/core/src/server/kernel/index.ts +++ b/packages/core/src/server/kernel/index.ts @@ -24,8 +24,8 @@ export type { Factory } from './container.js'; export { createLogger } from './logger.js'; -export { NoopErrorTracker, createSentryErrorTracker } from './error-tracker.js'; -export type { SentryErrorTrackerConfig } from './error-tracker.js'; +export { setErrorSink, reportError } from './error-sink.js'; +export type { ErrorSink } from './error-sink.js'; export { getUserId } from './router-utils.js'; export type { OssContext, AuthContext, NodeHeaders } from './router-utils.js'; diff --git a/packages/core/src/server/kernel/logger.ts b/packages/core/src/server/kernel/logger.ts index 566882fe..b80f316f 100644 --- a/packages/core/src/server/kernel/logger.ts +++ b/packages/core/src/server/kernel/logger.ts @@ -1,5 +1,37 @@ import { pino, type Logger } from 'pino'; +import { reportError } from './error-sink.js'; +import { getCurrentRequestContext } from './request-context.js'; +const ERROR_LEVEL = 50; + +// Mirror every error-level log carrying an `err` to the bound error sink +// (Sentry/PostHog/... via an ERROR_TRACKING overlay). Wiring it into the logger +// means one logger.error({ err }, msg) both logs and reports - callers never make a +// separate capture call, and all error logs are covered, not just a hand-picked few. +// The hook fires for child loggers too. Logs without an `err` (validation notices, +// etc.) are not reported. export function createLogger(name: string): Logger { - return pino({ name }); + return pino({ + name, + hooks: { + logMethod(inputArgs, method, level) { + method.apply(this, inputArgs); + if (level < ERROR_LEVEL) { + return; + } + const [first, second] = inputArgs; + if (!first || typeof first !== 'object' || !('err' in first)) { + return; + } + const { err, ...rest } = first as { err: unknown } & Record; + const ctx = getCurrentRequestContext(); + const message = typeof second === 'string' ? second : undefined; + reportError(err, { + ...(ctx?.userId ? { userId: ctx.userId } : {}), + ...(ctx?.traceId ? { traceId: ctx.traceId } : {}), + extra: message ? { ...rest, message } : rest, + }); + }, + }, + }); } diff --git a/packages/core/src/server/runtime/create-app.ts b/packages/core/src/server/runtime/create-app.ts index fbe3b29b..e2a7c4fd 100644 --- a/packages/core/src/server/runtime/create-app.ts +++ b/packages/core/src/server/runtime/create-app.ts @@ -23,9 +23,7 @@ import { createLogger, createRedisClient, withRequestContext, - getCurrentRequestContext, - NoopErrorTracker, - createSentryErrorTracker, + setErrorSink, EVENT_BUS, type OssContext, } from '../kernel/index.js'; @@ -186,19 +184,6 @@ export async function createApp(config: CreateAppConfig): Promise { } const container = new Container(); - // Registered before the event-bus/outbox so every downstream c.get(ERROR_TRACKING) - // resolves synchronously. Sentry inits here (async dynamic import) when SENTRY_DSN - // is set; otherwise a no-op. A consumer overlay re-providing ERROR_TRACKING later - // still wins (last registration wins). - const sentryDsn = process.env['SENTRY_DSN']; - const tracesSampleRate = process.env['SENTRY_TRACES_SAMPLE_RATE']; - const errorTracker = sentryDsn - ? await createSentryErrorTracker({ - dsn: sentryDsn, - ...(tracesSampleRate ? { tracesSampleRate: Number(tracesSampleRate) } : {}), - }) - : new NoopErrorTracker(); - container.register(ERROR_TRACKING, () => errorTracker); container.register(DRIZZLE, () => { const svc = new DrizzleService(); container.onDispose(() => svc.dispose()); @@ -223,7 +208,6 @@ export async function createApp(config: CreateAppConfig): Promise { c.get(MESSAGE_BROKER), createLogger('event-bus'), outboxEnabled ? c.get(OUTBOX) : undefined, - c.get(ERROR_TRACKING), ), ); // Default in-process fan-out served as SSE; an overlay rebinds to a managed @@ -294,6 +278,16 @@ export async function createApp(config: CreateAppConfig): Promise { const registry = await loadPlugins(config.plugins, container); await config.configure?.(container); + // Bridge the logger's error-report hook to whatever ERROR_TRACKING an overlay + // bound (Sentry/PostHog/...). No overlay -> no binding -> error logs stay + // logs-only. Run after plugins/configure so the final binding wins. Core names + // no vendor; the port is the only seam. + if (container.has(ERROR_TRACKING)) { + const tracker = container.get(ERROR_TRACKING); + setErrorSink((error, context) => tracker.captureException(error, context)); + container.onDispose(() => setErrorSink(undefined)); + } + const bus = container.get(EVENT_BUS); for (const [event, handlers] of registry.events.getAll()) { for (const handler of handlers) { @@ -311,10 +305,7 @@ export async function createApp(config: CreateAppConfig): Promise { if (outboxEnabled) { const relay = new OutboxRelay(drizzle.db, container.get(MESSAGE_BROKER), { - onError: (err) => { - createLogger('outbox-relay').error({ err }, 'outbox drain failed'); - errorTracker.captureException(err, { tags: { path: 'outbox-relay' } }); - }, + onError: (err) => createLogger('outbox-relay').error({ err }, 'outbox drain failed'), }); relay.start(); container.onDispose(() => relay.stop()); @@ -341,7 +332,6 @@ export async function createApp(config: CreateAppConfig): Promise { onError((error) => { if (!(error instanceof ORPCError)) { createLogger('orpc').error({ err: error }, 'unhandled error'); - container.get(ERROR_TRACKING).captureException(error, getCurrentRequestContext()); } }), ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43bdfcfe..c8abe431 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,7 @@ importers: version: link:packages/testing '@orpc/contract': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@turbo/gen': specifier: 2.9.16 version: 2.9.16(@types/node@25.9.2) @@ -102,7 +102,7 @@ importers: version: 4.1.8(vitest@4.1.8) vitest: specifier: 4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) packages/core: dependencies: @@ -114,37 +114,37 @@ importers: version: 2.0.4(hono@4.12.25) '@orpc/client': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@orpc/contract': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@orpc/openapi': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@orpc/openapi-client': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@orpc/server': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1) + version: 1.14.5 '@orpc/tanstack-query': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/client@1.14.5(@opentelemetry/api@1.9.1))(@tanstack/query-core@5.101.0) + version: 1.14.5(@orpc/client@1.14.5)(@tanstack/query-core@5.101.0) '@orpc/zod': specifier: 1.14.5 - version: 1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1))(zod@4.4.3) + version: 1.14.5(@orpc/contract@1.14.5)(@orpc/server@1.14.5)(zod@4.4.3) better-auth: specifier: 1.6.15 - version: 1.6.15(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) + version: 1.6.15(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) bullmq: specifier: 5.79.2 - version: 5.79.2(redis@5.12.1(@opentelemetry/api@1.9.1)) + version: 5.79.2(redis@5.12.1) dotenv: specifier: 17.4.2 version: 17.4.2 drizzle-orm: specifier: 0.45.2 - version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + version: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) hono: specifier: 4.12.25 version: 4.12.25 @@ -162,7 +162,7 @@ importers: version: 19.2.7(react@19.2.7) redis: specifier: 5.12.1 - version: 5.12.1(@opentelemetry/api@1.9.1) + version: 5.12.1 zod: specifier: 4.4.3 version: 4.4.3 @@ -193,11 +193,7 @@ importers: version: 6.0.3 vitest: specifier: 4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) - optionalDependencies: - '@sentry/node': - specifier: 10.65.0 - version: 10.65.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)) + version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) packages/mcp: dependencies: @@ -225,7 +221,7 @@ importers: version: link:../core drizzle-orm: specifier: 0.45.2 - version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + version: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) hono: specifier: 4.12.25 version: 4.12.25 @@ -244,7 +240,7 @@ importers: version: 6.0.3 vitest: specifier: 4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) packages: @@ -252,17 +248,6 @@ packages: resolution: {integrity: sha512-hBptHbB6Regs5AgtHiLr/WfgHRh6ZGdA9EexOWU14n2eNjQekvK9/gg18iFUYvL2WrNb4X3Ci0bzgzJXhzmpNQ==} engines: {node: '>=12'} - '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': - resolution: {integrity: sha512-YxLBY5nGlurL7QeJLq6e5g0ouBpAp0pwgyA/5rHXEXwhiPLn9ZHbT+Y2LlP90GT872cSocfjWRYu/fnpuBudNQ==} - engines: {node: '>=18.0.0'} - - '@apm-js-collab/code-transformer@0.15.0': - resolution: {integrity: sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww==} - hasBin: true - - '@apm-js-collab/tracing-hooks@0.10.1': - resolution: {integrity: sha512-w2OWXR7FWrKqSziuE9+QclaZrStxO/8+OwbXM635s/zs0Eez1Qo3ivSPdB2WsaPY/iznKTytONPx/PitD7IXcA==} - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -1191,44 +1176,6 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} - '@opentelemetry/api-logs@0.220.0': - resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/api@1.9.1': - resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/core@2.9.0': - resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - - '@opentelemetry/instrumentation@0.220.0': - resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/resources@2.9.0': - resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - - '@opentelemetry/sdk-trace-base@2.9.0': - resolution: {integrity: sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - - '@opentelemetry/sdk-trace@2.9.0': - resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.41.1': resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} @@ -1693,51 +1640,6 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sentry/conventions@0.15.1': - resolution: {integrity: sha512-ZLP8bRdMON3prWE2tJyImuYscCxdcJeIPIhrOs/rgyFm3C1nCh1B6gdvPj3AZ5zW08oSFFCsq7T+tYEW3h8MNA==} - engines: {node: '>=14'} - - '@sentry/core@10.65.0': - resolution: {integrity: sha512-3aqtmM5NgNGo45BNaaBzi0LPQZAw//NEL4HKS5fXm12pJMa4KEkze8DEKnkTEIrGnWaOJKamecHKlnNg/Mqf/Q==} - engines: {node: '>=18'} - - '@sentry/node-core@10.65.0': - resolution: {integrity: sha512-U01X9mPT+jZnsLPmPWfBU67Ka+t/Sdd9RGAuvGoKdrI6N47a/9PDkM9oCW+kj0fmZwogZHTgSnzJU5oi3pImgA==} - engines: {node: '>=18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 - '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' - '@opentelemetry/instrumentation': '>=0.57.1 <1' - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@opentelemetry/core': - optional: true - '@opentelemetry/exporter-trace-otlp-http': - optional: true - '@opentelemetry/instrumentation': - optional: true - '@opentelemetry/sdk-trace-base': - optional: true - - '@sentry/node@10.65.0': - resolution: {integrity: sha512-t35dcdyksysVch/m/XdLgGJqGKJhr9eMD30Ctn3TeQ8yMB0wNXySfjPR5Yg93fpjmfaHtzc6iYIXRAvgNVfrvA==} - engines: {node: '>=18'} - - '@sentry/opentelemetry@10.65.0': - resolution: {integrity: sha512-8C6FPvm3XBvUrkM52dX3Gz0p2H0Ij8t4sahUA+GTiCz0WM0fnyPeQPGC/b6I4jamV9UXyCZRnE1UEEGCoD+c7A==} - engines: {node: '>=18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - - '@sentry/server-utils@10.65.0': - resolution: {integrity: sha512-80toEFD6s+0Le7jrYB6pHWLF703WSg0WyavAWqrBGWG8JkREHgedAxzFYgoY5GlMI756qk6Ea7UzhJTHd2zAXA==} - engines: {node: '>=18'} - '@simple-libs/child-process-utils@1.0.2': resolution: {integrity: sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==} engines: {node: '>=18'} @@ -1967,10 +1869,6 @@ packages: ast-v8-to-istanbul@1.0.3: resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==} - astring@1.9.0: - resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} - hasBin: true - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2125,9 +2023,6 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - cjs-module-lexer@2.2.0: - resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} @@ -2461,9 +2356,6 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} - es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -2499,14 +2391,6 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -2781,10 +2665,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@3.3.1: - resolution: {integrity: sha512-0rymlHSFLwZ0ixx8DaQkoIyZojJPY2a0K2nEYslhKJ6jIYO/m0IcCb7iQsFPmS7WmKwISZiIrv5Icstrw/CmqA==} - engines: {node: '>=18'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3100,10 +2980,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - meriyah@6.1.4: - resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} - engines: {node: '>=18.0.0'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3131,9 +3007,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - module-details-from-path@1.0.4: - resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -3518,10 +3391,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@8.0.1: - resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} - engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3579,9 +3448,6 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} - semifies@1.0.0: - resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} - semver@7.8.1: resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} @@ -4077,33 +3943,6 @@ snapshots: '@2toad/profanity@3.3.0': {} - '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': - dependencies: - '@apm-js-collab/code-transformer': 0.15.0 - es-module-lexer: 2.1.0 - magic-string: 0.30.21 - module-details-from-path: 1.0.4 - optional: true - - '@apm-js-collab/code-transformer@0.15.0': - dependencies: - '@types/estree': 1.0.9 - astring: 1.9.0 - esquery: 1.7.0 - meriyah: 6.1.4 - semifies: 1.0.0 - source-map: 0.6.1 - optional: true - - '@apm-js-collab/tracing-hooks@0.10.1': - dependencies: - '@apm-js-collab/code-transformer': 0.15.0 - debug: 4.4.3 - module-details-from-path: 1.0.4 - transitivePeerDependencies: - - supports-color - optional: true - '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -4127,7 +3966,7 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0)': + '@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0)': dependencies: '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 @@ -4138,41 +3977,39 @@ snapshots: kysely: 0.29.2 nanostores: 1.3.0 zod: 4.4.3 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))': + '@better-auth/drizzle-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 optionalDependencies: - drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + drizzle-orm: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) - '@better-auth/kysely-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)': + '@better-auth/kysely-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 optionalDependencies: kysely: 0.29.2 - '@better-auth/memory-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/memory-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/mongo-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/mongo-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/prisma-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/prisma-adapter@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 - '@better-auth/telemetry@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 @@ -4985,69 +4822,21 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 - '@opentelemetry/api-logs@0.220.0': - dependencies: - '@opentelemetry/api': 1.9.1 - optional: true - - '@opentelemetry/api@1.9.1': - optional: true - - '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.41.1 - optional: true - - '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.220.0 - import-in-the-middle: 3.3.1 - require-in-the-middle: 8.0.1 - transitivePeerDependencies: - - supports-color - optional: true - - '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - optional: true - - '@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - optional: true - - '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - optional: true - '@opentelemetry/semantic-conventions@1.41.1': {} - '@orpc/client@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/client@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-peer': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 + '@orpc/standard-server-fetch': 1.14.5 + '@orpc/standard-server-peer': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/contract@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/contract@1.14.5': dependencies: - '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/client': 1.14.5 + '@orpc/shared': 1.14.5 '@standard-schema/spec': 1.1.0 openapi-types: 12.1.3 transitivePeerDependencies: @@ -5055,13 +4844,13 @@ snapshots: '@orpc/interop@1.14.5': {} - '@orpc/json-schema@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/json-schema@1.14.5': dependencies: - '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/contract': 1.14.5 '@orpc/interop': 1.14.5 - '@orpc/openapi': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/openapi': 1.14.5 + '@orpc/server': 1.14.5 + '@orpc/shared': 1.14.5 json-schema-typed: 8.0.2 transitivePeerDependencies: - '@opentelemetry/api' @@ -5069,24 +4858,24 @@ snapshots: - fastify - ws - '@orpc/openapi-client@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/openapi-client@1.14.5': dependencies: - '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/client': 1.14.5 + '@orpc/contract': 1.14.5 + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/openapi@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/openapi@1.14.5': dependencies: - '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/client': 1.14.5 + '@orpc/contract': 1.14.5 '@orpc/interop': 1.14.5 - '@orpc/openapi-client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/openapi-client': 1.14.5 + '@orpc/server': 1.14.5 + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 json-schema-typed: 8.0.2 rou3: 0.7.12 transitivePeerDependencies: @@ -5095,90 +4884,88 @@ snapshots: - fastify - ws - '@orpc/server@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/server@1.14.5': dependencies: - '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/client': 1.14.5 + '@orpc/contract': 1.14.5 '@orpc/interop': 1.14.5 - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-aws-lambda': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-fastify': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-peer': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 + '@orpc/standard-server-aws-lambda': 1.14.5 + '@orpc/standard-server-fastify': 1.14.5 + '@orpc/standard-server-fetch': 1.14.5 + '@orpc/standard-server-node': 1.14.5 + '@orpc/standard-server-peer': 1.14.5 cookie: 1.1.1 transitivePeerDependencies: - '@opentelemetry/api' - fastify - '@orpc/shared@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/shared@1.14.5': dependencies: radash: 12.1.1 type-fest: 5.7.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@orpc/standard-server-aws-lambda@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server-aws-lambda@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 + '@orpc/standard-server-fetch': 1.14.5 + '@orpc/standard-server-node': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-fastify@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server-fastify@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-node': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 + '@orpc/standard-server-node': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-fetch@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server-fetch@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-node@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server-node@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server-fetch': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 + '@orpc/standard-server-fetch': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-peer@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server-peer@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/standard-server': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 + '@orpc/standard-server': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server@1.14.5(@opentelemetry/api@1.9.1)': + '@orpc/standard-server@1.14.5': dependencies: - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/shared': 1.14.5 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/tanstack-query@1.14.5(@opentelemetry/api@1.9.1)(@orpc/client@1.14.5(@opentelemetry/api@1.9.1))(@tanstack/query-core@5.101.0)': + '@orpc/tanstack-query@1.14.5(@orpc/client@1.14.5)(@tanstack/query-core@5.101.0)': dependencies: - '@orpc/client': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/client': 1.14.5 + '@orpc/shared': 1.14.5 '@tanstack/query-core': 5.101.0 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/zod@1.14.5(@opentelemetry/api@1.9.1)(@orpc/contract@1.14.5(@opentelemetry/api@1.9.1))(@orpc/server@1.14.5(@opentelemetry/api@1.9.1))(zod@4.4.3)': + '@orpc/zod@1.14.5(@orpc/contract@1.14.5)(@orpc/server@1.14.5)(zod@4.4.3)': dependencies: - '@orpc/contract': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/json-schema': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/openapi': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/server': 1.14.5(@opentelemetry/api@1.9.1) - '@orpc/shared': 1.14.5(@opentelemetry/api@1.9.1) + '@orpc/contract': 1.14.5 + '@orpc/json-schema': 1.14.5 + '@orpc/openapi': 1.14.5 + '@orpc/server': 1.14.5 + '@orpc/shared': 1.14.5 escape-string-regexp: 5.0.0 wildcard-match: 5.1.4 zod: 4.4.3 @@ -5306,27 +5093,25 @@ snapshots: '@pinojs/redact@0.4.0': {} - '@redis/bloom@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': + '@redis/bloom@5.12.1(@redis/client@5.12.1)': dependencies: - '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) + '@redis/client': 5.12.1 - '@redis/client@5.12.1(@opentelemetry/api@1.9.1)': + '@redis/client@5.12.1': dependencies: cluster-key-slot: 1.1.2 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@redis/json@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': + '@redis/json@5.12.1(@redis/client@5.12.1)': dependencies: - '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) + '@redis/client': 5.12.1 - '@redis/search@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': + '@redis/search@5.12.1(@redis/client@5.12.1)': dependencies: - '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) + '@redis/client': 5.12.1 - '@redis/time-series@5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1))': + '@redis/time-series@5.12.1(@redis/client@5.12.1)': dependencies: - '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) + '@redis/client': 5.12.1 '@rolldown/binding-android-arm64@1.0.3': optional: true @@ -5381,65 +5166,6 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@sentry/conventions@0.15.1': - optional: true - - '@sentry/core@10.65.0': - dependencies: - '@sentry/conventions': 0.15.1 - optional: true - - '@sentry/node-core@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1))': - dependencies: - '@sentry/conventions': 0.15.1 - '@sentry/core': 10.65.0 - '@sentry/opentelemetry': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) - import-in-the-middle: 3.3.1 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) - optional: true - - '@sentry/node@10.65.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) - '@sentry/conventions': 0.15.1 - '@sentry/core': 10.65.0 - '@sentry/node-core': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) - '@sentry/opentelemetry': 10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)) - '@sentry/server-utils': 10.65.0 - import-in-the-middle: 3.3.1 - transitivePeerDependencies: - - '@opentelemetry/core' - - '@opentelemetry/exporter-trace-otlp-http' - - supports-color - optional: true - - '@sentry/opentelemetry@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1))': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) - '@sentry/conventions': 0.15.1 - '@sentry/core': 10.65.0 - optional: true - - '@sentry/server-utils@10.65.0': - dependencies: - '@apm-js-collab/code-transformer': 0.15.0 - '@apm-js-collab/code-transformer-bundler-plugins': 0.5.0 - '@apm-js-collab/tracing-hooks': 0.10.1 - '@sentry/conventions': 0.15.1 - '@sentry/core': 10.65.0 - magic-string: 0.30.21 - transitivePeerDependencies: - - supports-color - optional: true - '@simple-libs/child-process-utils@1.0.2': dependencies: '@simple-libs/stream-utils': 1.2.0 @@ -5554,7 +5280,7 @@ snapshots: obug: 2.1.2 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + vitest: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) '@vitest/expect@4.1.8': dependencies: @@ -5670,9 +5396,6 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 - astring@1.9.0: - optional: true - asynckit@0.4.0: {} atomic-sleep@1.0.0: {} @@ -5694,15 +5417,15 @@ snapshots: before-after-hook@4.0.0: {} - better-auth@1.6.15(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8): + better-auth@1.6.15(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(pg@8.21.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8): dependencies: - '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0)) - '@better-auth/kysely-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2) - '@better-auth/memory-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/mongo-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/prisma-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/telemetry': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) + '@better-auth/core': 1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0)) + '@better-auth/kysely-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.29.2) + '@better-auth/memory-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/mongo-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/prisma-adapter': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1) + '@better-auth/telemetry': 1.6.15(@better-auth/core@1.6.15(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) '@better-auth/utils': 0.4.1 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.2.0 @@ -5715,12 +5438,12 @@ snapshots: zod: 4.4.3 optionalDependencies: drizzle-kit: 0.31.10 - drizzle-orm: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) - next: 16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + drizzle-orm: 0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0) + next: 16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) pg: 8.21.0 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) + vitest: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -5762,7 +5485,7 @@ snapshots: buffer-from@1.1.2: {} - bullmq@5.79.2(redis@5.12.1(@opentelemetry/api@1.9.1)): + bullmq@5.79.2(redis@5.12.1): dependencies: cron-parser: 4.9.0 ioredis: 5.10.1 @@ -5771,7 +5494,7 @@ snapshots: semver: 7.8.5 tslib: 2.8.1 optionalDependencies: - redis: 5.12.1(@opentelemetry/api@1.9.1) + redis: 5.12.1 transitivePeerDependencies: - supports-color @@ -5803,9 +5526,6 @@ snapshots: chardet@2.1.1: {} - cjs-module-lexer@2.2.0: - optional: true - cli-table3@0.6.5: dependencies: string-width: 4.2.3 @@ -5980,9 +5700,8 @@ snapshots: esbuild: 0.28.1 tsx: 4.22.4 - drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0): + drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.2)(pg@8.21.0): optionalDependencies: - '@opentelemetry/api': 1.9.1 '@types/pg': 8.20.0 kysely: 0.29.2 pg: 8.21.0 @@ -6032,9 +5751,6 @@ snapshots: es-module-lexer@2.1.0: {} - es-module-lexer@2.3.1: - optional: true - es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -6087,14 +5803,6 @@ snapshots: esprima@4.0.1: {} - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - optional: true - - estraverse@5.3.0: - optional: true - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -6435,13 +6143,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@3.3.1: - dependencies: - cjs-module-lexer: 2.2.0 - es-module-lexer: 2.3.1 - module-details-from-path: 1.0.4 - optional: true - inherits@2.0.4: {} ini@4.1.1: {} @@ -6701,9 +6402,6 @@ snapshots: merge2@1.4.1: {} - meriyah@6.1.4: - optional: true - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -6727,9 +6425,6 @@ snapshots: minimist@1.2.8: {} - module-details-from-path@1.0.4: - optional: true - mri@1.2.0: {} ms@2.1.3: {} @@ -6760,7 +6455,7 @@ snapshots: negotiator@1.0.0: {} - next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@next/env': 16.2.9 '@swc/helpers': 0.5.15 @@ -6779,7 +6474,6 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.9 '@next/swc-win32-arm64-msvc': 16.2.9 '@next/swc-win32-x64-msvc': 16.2.9 - '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -7094,13 +6788,13 @@ snapshots: dependencies: redis-errors: 1.2.0 - redis@5.12.1(@opentelemetry/api@1.9.1): + redis@5.12.1: dependencies: - '@redis/bloom': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) - '@redis/client': 5.12.1(@opentelemetry/api@1.9.1) - '@redis/json': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) - '@redis/search': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) - '@redis/time-series': 5.12.1(@redis/client@5.12.1(@opentelemetry/api@1.9.1)) + '@redis/bloom': 5.12.1(@redis/client@5.12.1) + '@redis/client': 5.12.1 + '@redis/json': 5.12.1(@redis/client@5.12.1) + '@redis/search': 5.12.1(@redis/client@5.12.1) + '@redis/time-series': 5.12.1(@redis/client@5.12.1) transitivePeerDependencies: - '@node-rs/xxhash' - '@opentelemetry/api' @@ -7109,14 +6803,6 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@8.0.1: - dependencies: - debug: 4.4.3 - module-details-from-path: 1.0.4 - transitivePeerDependencies: - - supports-color - optional: true - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -7210,9 +6896,6 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 - semifies@1.0.0: - optional: true - semver@7.8.1: {} semver@7.8.2: {} @@ -7530,7 +7213,7 @@ snapshots: jiti: 2.7.0 tsx: 4.22.4 - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)): + vitest@4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)): dependencies: '@vitest/expect': 4.1.8 '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)) @@ -7553,7 +7236,6 @@ snapshots: vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4) why-is-node-running: 2.3.0 optionalDependencies: - '@opentelemetry/api': 1.9.1 '@types/node': 25.9.2 '@vitest/coverage-v8': 4.1.8(vitest@4.1.8) transitivePeerDependencies: From 1f435ee60dc55c250716c0f1fb0a96760ec55c90 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 16 Jul 2026 00:35:08 +0200 Subject: [PATCH 3/7] fix: guard null err before reporting to error sink --- docs/adapters/error-tracking.md | 2 +- packages/core/src/server/kernel/__tests__/logger.test.ts | 8 ++++++++ packages/core/src/server/kernel/logger.ts | 5 ++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md index 908ef628..ff33040e 100644 --- a/docs/adapters/error-tracking.md +++ b/docs/adapters/error-tracking.md @@ -80,5 +80,5 @@ export default definePlugin({ }); ``` -Register it last in `extensions.config.ts` (an `kind: 'infra'` overlay). A PostHog/Rollbar shop +Register it last in `extensions.config.ts` (a `kind: 'infra'` overlay). A PostHog/Rollbar shop writes the equivalent overlay against the same port - core is unchanged. diff --git a/packages/core/src/server/kernel/__tests__/logger.test.ts b/packages/core/src/server/kernel/__tests__/logger.test.ts index ae9e9531..54ce4658 100644 --- a/packages/core/src/server/kernel/__tests__/logger.test.ts +++ b/packages/core/src/server/kernel/__tests__/logger.test.ts @@ -39,6 +39,14 @@ describe('createLogger error-sink hook', () => { expect(sink).not.toHaveBeenCalled(); }); + it('swallows sink failures', () => { + setErrorSink(() => { + throw new Error('sink failed'); + }); + + expect(() => createLogger('test').error({ err: new Error('x') }, 'sink failure')).not.toThrow(); + }); + it('is inert when no sink is bound', () => { expect(() => createLogger('test').error({ err: new Error('x') }, 'no sink')).not.toThrow(); }); diff --git a/packages/core/src/server/kernel/logger.ts b/packages/core/src/server/kernel/logger.ts index b80f316f..4ed5aefb 100644 --- a/packages/core/src/server/kernel/logger.ts +++ b/packages/core/src/server/kernel/logger.ts @@ -23,7 +23,10 @@ export function createLogger(name: string): Logger { if (!first || typeof first !== 'object' || !('err' in first)) { return; } - const { err, ...rest } = first as { err: unknown } & Record; + const { err, ...rest } = first as { err?: unknown } & Record; + if (err === null || err === undefined) { + return; + } const ctx = getCurrentRequestContext(); const message = typeof second === 'string' ? second : undefined; reportError(err, { From a7e86bb891677e0e30d9d343b3e6cbb5309937a4 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 16 Jul 2026 00:38:27 +0200 Subject: [PATCH 4/7] docs: link adapter docs to source instead of inlining interfaces --- docs/adapters/error-tracking.md | 18 +---------- docs/adapters/kyc.md | 51 ++--------------------------- docs/adapters/notification.md | 9 +----- docs/adapters/payment.md | 57 ++------------------------------- 4 files changed, 8 insertions(+), 127 deletions(-) diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md index ff33040e..cdec380e 100644 --- a/docs/adapters/error-tracking.md +++ b/docs/adapters/error-tracking.md @@ -2,23 +2,7 @@ ## Interface -```ts -// packages/core/src/contracts/adapters/error-tracking.ts -import { createToken, type Token } from './token.js'; - -export type ErrorContext = { - userId?: string; - traceId?: string; - tags?: Record; - extra?: Record; -}; - -export type ErrorTrackingAdapter = { - captureException(error: unknown, context?: ErrorContext): void; -}; - -export const ERROR_TRACKING: Token = createToken('ERROR_TRACKING'); -``` +Source of truth: [`packages/core/src/contracts/adapters/error-tracking.ts`](../../packages/core/src/contracts/adapters/error-tracking.ts) - `ErrorTrackingAdapter`, `ErrorContext`, and the `ERROR_TRACKING` token. `captureException` is fire-and-forget (returns `void`) - a reporter failure never breaks the path that reported the error. Core names **no vendor**: the port is the only seam. Sentry, diff --git a/docs/adapters/kyc.md b/docs/adapters/kyc.md index 73f3c0ff..d7961218 100644 --- a/docs/adapters/kyc.md +++ b/docs/adapters/kyc.md @@ -2,58 +2,13 @@ ## Interface -```ts -// packages/core/src/contracts/adapters/kyc.ts -export type KycAdapter = { - // True when the adapter rubber-stamps every submission (the default MockKycAdapter). - // A boot guard refuses/warns if withdrawals are KYC-gated while this is bound, so an - // operator can't enable the gate yet leave verification a no-op. Real providers omit it. - readonly autoApproves?: boolean; - submit(userId: string, documents: KycDocument[]): Promise; - getStatus(userId: string): Promise; - // Provider-specific normalization of a raw webhook into a vendor decision, or null when - // the body is not a reconcilable decision. Optional - MockKycAdapter omits it. - parseWebhook?( - rawBody: string, - headers: Record, - ): KycResult | null; -}; - -export type KycDocument = { - type: 'passport' | 'drivers_license' | 'national_id'; - frontUrl: string; - backUrl?: string; -}; - -export type KycVendorStatus = 'pending' | 'approved' | 'rejected' | 'not_started'; - -export type KycResult = { - referenceId: string; - status: KycVendorStatus; - // Hosted verification URL to redirect the end user to, for vendors whose flow collects - // documents on their own hosted page rather than accepting them from our backend. - // Omitted by document-forwarding vendors (eg SumSub, MockKycAdapter). - verificationUrl?: string; -}; - -export const KYC_ADAPTER: Token = createToken('KYC_ADAPTER'); -``` +Source of truth: [`packages/core/src/contracts/adapters/kyc.ts`](../../packages/core/src/contracts/adapters/kyc.ts) - `KycAdapter`, `KycDocument`, `KycVendorStatus`, `KycResult`, and the `KYC_ADAPTER` token. -`KYC_ADAPTER` is a `Token` created via `createToken` (`packages/core/src/contracts/adapters/token.ts`), -not a bare `Symbol` - the `Container` uses it to type both `provide` and `get` calls. +Two fields drive behavior beyond their types: `KycAdapter.autoApproves` marks a rubber-stamp adapter (the default `MockKycAdapter`) - a boot guard refuses/warns if withdrawals are KYC-gated while it's bound, so the gate can't be on with verification a no-op; real providers omit it. `KycResult.verificationUrl` is set only by hosted-session vendors whose own page collects documents (see recipe 2), and omitted by document-forwarding vendors. ## Webhook verifier -A second port authenticates the vendor's async webhook before `KycAdapter.parseWebhook` -ever sees the body: - -```ts -export type KycWebhookVerifier = { - verify(rawBody: string, headers: Record): boolean; -}; - -export const KYC_WEBHOOK_VERIFIER: Token = createToken('KYC_WEBHOOK_VERIFIER'); -``` +A second port, `KycWebhookVerifier` + the `KYC_WEBHOOK_VERIFIER` token (same source file), authenticates the vendor's async webhook before `KycAdapter.parseWebhook` ever sees the body. `verify` takes the full headers map, not a single pre-extracted value - the signing header name is vendor-specific (SumSub, a hosted-session vendor, and any future provider each name diff --git a/docs/adapters/notification.md b/docs/adapters/notification.md index cf8e7312..53ed11a9 100644 --- a/docs/adapters/notification.md +++ b/docs/adapters/notification.md @@ -2,14 +2,7 @@ ## Interface -```ts -// packages/core/src/contracts/adapters/src/notification.ts -export interface NotificationDeliveryAdapter { - sendEmail(to: string, subject: string, body: string): Promise; -} - -export const NOTIFICATION_DELIVERY_ADAPTER = Symbol('NOTIFICATION_DELIVERY_ADAPTER'); -``` +Source of truth: [`packages/core/src/contracts/adapters/notification.ts`](../../packages/core/src/contracts/adapters/notification.ts) - `NotificationDeliveryAdapter` and the `NOTIFICATION_DELIVERY_ADAPTER` token. ## Default binding diff --git a/docs/adapters/payment.md b/docs/adapters/payment.md index 9eb80980..ee2e2bdb 100644 --- a/docs/adapters/payment.md +++ b/docs/adapters/payment.md @@ -2,60 +2,9 @@ ## Interface -```ts -// packages/core/src/contracts/adapters/payment.ts -import { createToken, type Token } from './token.js'; - -export type PaymentAdapter = { - processDeposit( - amount: string, - currency: string, - metadata: Record, - ): Promise<{ externalId: string; status: string }>; - - processWithdrawal( - amount: string, - currency: string, - metadata: Record, - ): Promise<{ externalId: string; status: string }>; - - // Address-based deposit vendors only (eg a custody rail). Omit entirely for a - // synchronous PSP. - issueDepositAddress?(userId: string, currency: string): Promise<{ address: string }>; - - // Async vendors only. Normalizes a raw webhook body into a reconcilable event. - parseWebhook?( - rawBody: string, - headers: Record, - ): PaymentWebhookEvent | null; -}; - -export type PaymentWebhookEvent = - | { - kind: 'deposit'; - address: string; - amount: string; - currency: string; - txHash: string; - externalId: string; - } - | { - kind: 'withdrawal'; - externalId: string; - status: 'processing' | 'completed' | 'failed'; - txHash?: string; - }; - -export const PAYMENT_ADAPTER: Token = createToken('PAYMENT_ADAPTER'); - -export type PaymentWebhookVerifier = { - verify(rawBody: string, headers: Record): boolean; -}; - -export const PAYMENT_WEBHOOK_VERIFIER: Token = createToken( - 'PAYMENT_WEBHOOK_VERIFIER', -); -``` +Source of truth: [`packages/core/src/contracts/adapters/payment.ts`](../../packages/core/src/contracts/adapters/payment.ts) - `PaymentAdapter`, `PaymentWebhookEvent`, `PaymentWebhookVerifier`, and the `PAYMENT_ADAPTER` / `PAYMENT_WEBHOOK_VERIFIER` tokens. + +`issueDepositAddress` and `parseWebhook` are optional - present only on address-based/async vendors, omitted by a synchronous PSP (see the two vendor shapes below). ## Default binding From 98661410b5eb5e0f4063018106c0350fd88e79dc Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 16 Jul 2026 01:08:58 +0200 Subject: [PATCH 5/7] feat: add report:false log opt-out and global http error catch - logger hook honors report:false to skip tracker forwarding for expected/noisy errors (still logged), keeping vendor quota under control - Hono app.onError catches errors thrown in the middleware chain before oRPC runs (session resolution, raw-body capture, etag/cache) - the one seam oRPC's onError interceptor never sees; logs (so it reports) and returns 500, skipping HTTPException - document both plus the three global catch seams in the error-tracking adapter doc --- docs/adapters/error-tracking.md | 13 +++++++++++++ .../src/server/kernel/__tests__/logger.test.ts | 9 +++++++++ packages/core/src/server/kernel/logger.ts | 10 +++++++--- packages/core/src/server/runtime/create-app.ts | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md index cdec380e..9cff806a 100644 --- a/docs/adapters/error-tracking.md +++ b/docs/adapters/error-tracking.md @@ -16,6 +16,19 @@ both logs and reports, and **all** error logs are covered - the oRPC unhandled-e interceptor, EventBus subscriber failures, OutboxRelay drops, and anything else that logs an error with an `err` field. Error logs without an `err` (validation notices) are not reported. +Every unhandled error is caught at one of three global seams - never per-call-site in a module: + +- **oRPC `onError` interceptor** - every route/service error; `handler.handle` maps it to a + response, and non-`ORPCError`s are logged (expected `ORPCError`s are not reported). +- **Hono `app.onError`** - anything thrown in the middleware chain before oRPC runs (session + resolution, raw-body capture, etag/cache); returns a 500 and reports non-`HTTPException` errors. +- **EventBus / OutboxRelay** - async subscriber and outbox-drain failures. + +A module never writes tracker-specific code: it throws a typed error (caught by the interceptor) +or logs with `logger.error({ err })`. To opt an expected/noisy error out of forwarding - a caught +third-party timeout you log-and-retry - pass `report: false`: `logger.error({ err, report: false }, +'psp timeout, retrying')` is still logged, just not sent to the tracker (so it doesn't burn quota). + Wiring, in `createApp`, after plugins load: ```ts diff --git a/packages/core/src/server/kernel/__tests__/logger.test.ts b/packages/core/src/server/kernel/__tests__/logger.test.ts index 54ce4658..7bf1956e 100644 --- a/packages/core/src/server/kernel/__tests__/logger.test.ts +++ b/packages/core/src/server/kernel/__tests__/logger.test.ts @@ -30,6 +30,15 @@ describe('createLogger error-sink hook', () => { expect(sink).not.toHaveBeenCalled(); }); + it('does not report an error log opted out with `report: false`', () => { + const sink = vi.fn(); + setErrorSink(sink); + + createLogger('test').error({ err: new Error('boom'), report: false }, 'expected, retrying'); + + expect(sink).not.toHaveBeenCalled(); + }); + it('does not report non-error levels', () => { const sink = vi.fn(); setErrorSink(sink); diff --git a/packages/core/src/server/kernel/logger.ts b/packages/core/src/server/kernel/logger.ts index 4ed5aefb..60c6b8b4 100644 --- a/packages/core/src/server/kernel/logger.ts +++ b/packages/core/src/server/kernel/logger.ts @@ -9,7 +9,8 @@ const ERROR_LEVEL = 50; // means one logger.error({ err }, msg) both logs and reports - callers never make a // separate capture call, and all error logs are covered, not just a hand-picked few. // The hook fires for child loggers too. Logs without an `err` (validation notices, -// etc.) are not reported. +// etc.) are not reported, and `logger.error({ err, report: false }, msg)` opts an +// expected/noisy error out of forwarding - still logged, just not sent to the tracker. export function createLogger(name: string): Logger { return pino({ name, @@ -23,8 +24,11 @@ export function createLogger(name: string): Logger { if (!first || typeof first !== 'object' || !('err' in first)) { return; } - const { err, ...rest } = first as { err?: unknown } & Record; - if (err === null || err === undefined) { + const { err, report, ...rest } = first as { + err?: unknown; + report?: boolean; + } & Record; + if (err === null || err === undefined || report === false) { return; } const ctx = getCurrentRequestContext(); diff --git a/packages/core/src/server/runtime/create-app.ts b/packages/core/src/server/runtime/create-app.ts index e2a7c4fd..40adaa50 100644 --- a/packages/core/src/server/runtime/create-app.ts +++ b/packages/core/src/server/runtime/create-app.ts @@ -5,6 +5,7 @@ import type { ContractRouter } from '@orpc/contract'; import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { etag } from 'hono/etag'; +import { HTTPException } from 'hono/http-exception'; import { serve, type ServerType } from '@hono/node-server'; import { resolve } from 'node:path'; import { generateOpenApiSpec } from './openapi.js'; @@ -339,6 +340,23 @@ export async function createApp(config: CreateAppConfig): Promise { const app = new Hono(); + // Outermost catch for anything thrown in the Hono middleware chain (session + // resolution, raw-body capture, etag/cache) - oRPC's onError interceptor only + // sees route/service errors, which handler.handle turns into responses and never + // rethrows. The logger.error({ err }) here flows to the bound ERROR_TRACKING sink, + // so no capture call. HTTPExceptions are deliberate HTTP outcomes - returned as-is, + // not reported (mirrors skipping ORPCError in the interceptor). + app.onError((err, c) => { + if (err instanceof HTTPException) { + return err.getResponse(); + } + createLogger('http').error( + { err, method: c.req.method, path: c.req.path }, + 'unhandled request error', + ); + return c.json({ error: 'Internal Server Error' }, 500); + }); + if (config.cors !== false) { const origins = config.cors === true || config.cors === undefined ? undefined : config.cors.origins; From 17ba91ac67369656b98031c72cfed06ca93bf2a0 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 16 Jul 2026 01:21:23 +0200 Subject: [PATCH 6/7] refactor: trim redundant comments in error-tracking seam --- .../src/contracts/adapters/error-tracking.ts | 13 ++++--------- packages/core/src/server/kernel/error-sink.ts | 6 ++---- packages/core/src/server/kernel/logger.ts | 12 +++++------- packages/core/src/server/runtime/create-app.ts | 17 +++++++---------- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/core/src/contracts/adapters/error-tracking.ts b/packages/core/src/contracts/adapters/error-tracking.ts index 242749f7..edb4251b 100644 --- a/packages/core/src/contracts/adapters/error-tracking.ts +++ b/packages/core/src/contracts/adapters/error-tracking.ts @@ -1,10 +1,6 @@ -// Error-tracking seam. Core names no vendor: every error-level log carrying an -// `err` is forwarded here (wired through the logger), so binding this port reports -// all error logs - the oRPC interceptor, event-subscriber failures, outbox-relay -// drops, and any other logger.error({ err }). Unbound -> error logs stay logs-only. -// A consumer binds a vendor (Sentry, PostHog, Rollbar, ...) via an overlay plugin: -// ctx.provide(ERROR_TRACKING, () => new MyErrorTracker()) -// Load your overlay AFTER core so its binding wins (last registration wins). +// Error-tracking seam. Core names no vendor; a consumer binds one (Sentry, PostHog, +// Rollbar, ...) via an overlay plugin. Bound -> every error-level log carrying an +// `err` is reported (forwarded through the logger); unbound -> logs stay logs-only. import { createToken, type Token } from './token.js'; export type ErrorContext = { @@ -14,8 +10,7 @@ export type ErrorContext = { extra?: Record; }; -// captureException is fire-and-forget (returns void) - a reporter failure must -// never break the request or background path that called it. +// Fire-and-forget - a reporter failure must never break the path that called it. export type ErrorTrackingAdapter = { captureException(error: unknown, context?: ErrorContext): void; }; diff --git a/packages/core/src/server/kernel/error-sink.ts b/packages/core/src/server/kernel/error-sink.ts index 7d09f366..3a022513 100644 --- a/packages/core/src/server/kernel/error-sink.ts +++ b/packages/core/src/server/kernel/error-sink.ts @@ -1,9 +1,7 @@ import type { ErrorContext } from '@openora/core/contracts'; -// The process-wide bridge from the logger to a bound ERROR_TRACKING adapter. -// createApp sets it once after plugins load (when an overlay bound the port); -// createLogger forwards every error-level log carrying an `err` here, so a single -// logger.error(...) both logs and reports - no separate capture call at each site. +// The process-wide bridge from the logger to a bound ERROR_TRACKING adapter; +// createApp sets it once after plugins load, when an overlay bound the port. export type ErrorSink = (error: unknown, context?: ErrorContext) => void; // ponytail: module-global, single sink. Fine for the single-process, single-tenant diff --git a/packages/core/src/server/kernel/logger.ts b/packages/core/src/server/kernel/logger.ts index 60c6b8b4..f463bbe8 100644 --- a/packages/core/src/server/kernel/logger.ts +++ b/packages/core/src/server/kernel/logger.ts @@ -4,13 +4,11 @@ import { getCurrentRequestContext } from './request-context.js'; const ERROR_LEVEL = 50; -// Mirror every error-level log carrying an `err` to the bound error sink -// (Sentry/PostHog/... via an ERROR_TRACKING overlay). Wiring it into the logger -// means one logger.error({ err }, msg) both logs and reports - callers never make a -// separate capture call, and all error logs are covered, not just a hand-picked few. -// The hook fires for child loggers too. Logs without an `err` (validation notices, -// etc.) are not reported, and `logger.error({ err, report: false }, msg)` opts an -// expected/noisy error out of forwarding - still logged, just not sent to the tracker. +// One logger.error({ err }, msg) both logs and reports: this hook forwards every +// error-level log carrying an `err` to the bound error sink (an ERROR_TRACKING +// overlay - Sentry/PostHog/...), so no call site makes a separate capture call and +// child loggers are covered too. `report: false` opts a noisy/expected error out of +// forwarding (still logged); a log without `err` is never reported. export function createLogger(name: string): Logger { return pino({ name, diff --git a/packages/core/src/server/runtime/create-app.ts b/packages/core/src/server/runtime/create-app.ts index 40adaa50..b891c750 100644 --- a/packages/core/src/server/runtime/create-app.ts +++ b/packages/core/src/server/runtime/create-app.ts @@ -279,10 +279,8 @@ export async function createApp(config: CreateAppConfig): Promise { const registry = await loadPlugins(config.plugins, container); await config.configure?.(container); - // Bridge the logger's error-report hook to whatever ERROR_TRACKING an overlay - // bound (Sentry/PostHog/...). No overlay -> no binding -> error logs stay - // logs-only. Run after plugins/configure so the final binding wins. Core names - // no vendor; the port is the only seam. + // Bridge the logger's error sink to whatever ERROR_TRACKING an overlay bound, + // after plugins/configure so the final binding wins. Unbound -> logs stay logs-only. if (container.has(ERROR_TRACKING)) { const tracker = container.get(ERROR_TRACKING); setErrorSink((error, context) => tracker.captureException(error, context)); @@ -340,12 +338,11 @@ export async function createApp(config: CreateAppConfig): Promise { const app = new Hono(); - // Outermost catch for anything thrown in the Hono middleware chain (session - // resolution, raw-body capture, etag/cache) - oRPC's onError interceptor only - // sees route/service errors, which handler.handle turns into responses and never - // rethrows. The logger.error({ err }) here flows to the bound ERROR_TRACKING sink, - // so no capture call. HTTPExceptions are deliberate HTTP outcomes - returned as-is, - // not reported (mirrors skipping ORPCError in the interceptor). + // Outermost catch for errors thrown in the Hono middleware chain (session + // resolution, raw-body capture, etag/cache) - the one seam oRPC's onError + // interceptor never sees, since handler.handle turns route/service errors into + // responses. logger.error reports via the sink; HTTPExceptions are deliberate + // outcomes, returned without reporting. app.onError((err, c) => { if (err instanceof HTTPException) { return err.getResponse(); From 92a9cfbc6d9c16f1ac24093c3736cdc9261d3dde Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 16 Jul 2026 09:10:22 +0200 Subject: [PATCH 7/7] fix: rename sink to reporter --- docs/adapters/error-tracking.md | 4 +- .../src/contracts/adapters/error-tracking.ts | 4 -- .../server/kernel/__tests__/logger.test.ts | 48 ++++++++++--------- .../core/src/server/kernel/error-reporter.ts | 26 ++++++++++ packages/core/src/server/kernel/error-sink.ts | 23 --------- packages/core/src/server/kernel/index.ts | 4 +- packages/core/src/server/kernel/logger.ts | 4 +- .../core/src/server/runtime/create-app.ts | 10 ++-- 8 files changed, 61 insertions(+), 62 deletions(-) create mode 100644 packages/core/src/server/kernel/error-reporter.ts delete mode 100644 packages/core/src/server/kernel/error-sink.ts diff --git a/docs/adapters/error-tracking.md b/docs/adapters/error-tracking.md index 9cff806a..829d0698 100644 --- a/docs/adapters/error-tracking.md +++ b/docs/adapters/error-tracking.md @@ -34,11 +34,11 @@ Wiring, in `createApp`, after plugins load: ```ts if (container.has(ERROR_TRACKING)) { const tracker = container.get(ERROR_TRACKING); - setErrorSink((error, context) => tracker.captureException(error, context)); + setErrorReporter((error, context) => tracker.captureException(error, context)); } ``` -No overlay binds `ERROR_TRACKING` -> no sink -> error logs stay logs-only. The engine imports +No overlay binds `ERROR_TRACKING` -> no reporter -> error logs stay logs-only. The engine imports no vendor SDK and has no `SENTRY_DSN`/vendor env of its own. The forwarded `ErrorContext` carries `userId`/`traceId` from the active request context plus the diff --git a/packages/core/src/contracts/adapters/error-tracking.ts b/packages/core/src/contracts/adapters/error-tracking.ts index edb4251b..3c601fc6 100644 --- a/packages/core/src/contracts/adapters/error-tracking.ts +++ b/packages/core/src/contracts/adapters/error-tracking.ts @@ -1,6 +1,3 @@ -// Error-tracking seam. Core names no vendor; a consumer binds one (Sentry, PostHog, -// Rollbar, ...) via an overlay plugin. Bound -> every error-level log carrying an -// `err` is reported (forwarded through the logger); unbound -> logs stay logs-only. import { createToken, type Token } from './token.js'; export type ErrorContext = { @@ -10,7 +7,6 @@ export type ErrorContext = { extra?: Record; }; -// Fire-and-forget - a reporter failure must never break the path that called it. export type ErrorTrackingAdapter = { captureException(error: unknown, context?: ErrorContext): void; }; diff --git a/packages/core/src/server/kernel/__tests__/logger.test.ts b/packages/core/src/server/kernel/__tests__/logger.test.ts index 7bf1956e..0941a9d4 100644 --- a/packages/core/src/server/kernel/__tests__/logger.test.ts +++ b/packages/core/src/server/kernel/__tests__/logger.test.ts @@ -1,62 +1,64 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { createLogger } from '../logger.js'; -import { setErrorSink } from '../error-sink.js'; +import { setErrorReporter } from '../error-reporter.js'; afterEach(() => { - setErrorSink(undefined); + setErrorReporter(undefined); }); -describe('createLogger error-sink hook', () => { - it('forwards an error-level log carrying `err` to the bound sink', () => { - const sink = vi.fn(); - setErrorSink(sink); +describe('createLogger error-reporter hook', () => { + it('forwards an error-level log carrying `err` to the bound reporter', () => { + const reporter = vi.fn(); + setErrorReporter(reporter); const err = new Error('boom'); createLogger('test').error({ err, event: 'x' }, 'something failed'); - expect(sink).toHaveBeenCalledTimes(1); - expect(sink).toHaveBeenCalledWith( + expect(reporter).toHaveBeenCalledTimes(1); + expect(reporter).toHaveBeenCalledWith( err, expect.objectContaining({ extra: { event: 'x', message: 'something failed' } }), ); }); it('does not report error logs without an `err`', () => { - const sink = vi.fn(); - setErrorSink(sink); + const reporter = vi.fn(); + setErrorReporter(reporter); createLogger('test').error({ issues: [] }, 'validation failed'); - expect(sink).not.toHaveBeenCalled(); + expect(reporter).not.toHaveBeenCalled(); }); it('does not report an error log opted out with `report: false`', () => { - const sink = vi.fn(); - setErrorSink(sink); + const reporter = vi.fn(); + setErrorReporter(reporter); createLogger('test').error({ err: new Error('boom'), report: false }, 'expected, retrying'); - expect(sink).not.toHaveBeenCalled(); + expect(reporter).not.toHaveBeenCalled(); }); it('does not report non-error levels', () => { - const sink = vi.fn(); - setErrorSink(sink); + const reporter = vi.fn(); + setErrorReporter(reporter); createLogger('test').warn({ err: new Error('x') }, 'a warning'); - expect(sink).not.toHaveBeenCalled(); + expect(reporter).not.toHaveBeenCalled(); }); - it('swallows sink failures', () => { - setErrorSink(() => { - throw new Error('sink failed'); + it('swallows reporter failures', () => { + setErrorReporter(() => { + throw new Error('reporter failed'); }); - expect(() => createLogger('test').error({ err: new Error('x') }, 'sink failure')).not.toThrow(); + expect(() => + createLogger('test').error({ err: new Error('x') }, 'reporter failure'), + ).not.toThrow(); }); - it('is inert when no sink is bound', () => { - expect(() => createLogger('test').error({ err: new Error('x') }, 'no sink')).not.toThrow(); + it('is inert when no reporter is bound', () => { + expect(() => createLogger('test').error({ err: new Error('x') }, 'no reporter')).not.toThrow(); }); }); diff --git a/packages/core/src/server/kernel/error-reporter.ts b/packages/core/src/server/kernel/error-reporter.ts new file mode 100644 index 00000000..037fb53f --- /dev/null +++ b/packages/core/src/server/kernel/error-reporter.ts @@ -0,0 +1,26 @@ +import type { ErrorContext } from '@openora/core/contracts'; + +// The process-wide bridge from the logger to a bound ERROR_TRACKING adapter; +// createApp sets it once after plugins load, when an overlay bound the port. +export type ErrorReporter = (error: unknown, context?: ErrorContext) => void; + +// ponytail: process-global single reporter. Correct under horizontal scaling - each +// replica is its own process with its own reporter, reporting independently (the vendor +// aggregates across instances). The only limit is multiple createApp instances in ONE +// process (multi-tenant hosting, or a test harness booting several apps): they'd share +// this reporter and one app's dispose would clear it for the others. Move it onto the +// container then. See ADR-0026. +let reporter: ErrorReporter | undefined; + +export function setErrorReporter(next: ErrorReporter | undefined): void { + reporter = next; +} + +export function reportError(error: unknown, context?: ErrorContext): void { + // Fire-and-forget: a reporter failure must never break the log call that drove it. + try { + reporter?.(error, context); + } catch { + /* swallow - reporting is best-effort */ + } +} diff --git a/packages/core/src/server/kernel/error-sink.ts b/packages/core/src/server/kernel/error-sink.ts deleted file mode 100644 index 3a022513..00000000 --- a/packages/core/src/server/kernel/error-sink.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ErrorContext } from '@openora/core/contracts'; - -// The process-wide bridge from the logger to a bound ERROR_TRACKING adapter; -// createApp sets it once after plugins load, when an overlay bound the port. -export type ErrorSink = (error: unknown, context?: ErrorContext) => void; - -// ponytail: module-global, single sink. Fine for the single-process, single-tenant -// runtime (ADR-0026); if the engine ever runs multiple isolated apps in one process, -// move this onto the container. -let sink: ErrorSink | undefined; - -export function setErrorSink(next: ErrorSink | undefined): void { - sink = next; -} - -export function reportError(error: unknown, context?: ErrorContext): void { - // Fire-and-forget: a reporter failure must never break the log call that drove it. - try { - sink?.(error, context); - } catch { - /* swallow - reporting is best-effort */ - } -} diff --git a/packages/core/src/server/kernel/index.ts b/packages/core/src/server/kernel/index.ts index 05952975..00000548 100644 --- a/packages/core/src/server/kernel/index.ts +++ b/packages/core/src/server/kernel/index.ts @@ -24,8 +24,8 @@ export type { Factory } from './container.js'; export { createLogger } from './logger.js'; -export { setErrorSink, reportError } from './error-sink.js'; -export type { ErrorSink } from './error-sink.js'; +export { setErrorReporter, reportError } from './error-reporter.js'; +export type { ErrorReporter } from './error-reporter.js'; export { getUserId } from './router-utils.js'; export type { OssContext, AuthContext, NodeHeaders } from './router-utils.js'; diff --git a/packages/core/src/server/kernel/logger.ts b/packages/core/src/server/kernel/logger.ts index f463bbe8..707e8d69 100644 --- a/packages/core/src/server/kernel/logger.ts +++ b/packages/core/src/server/kernel/logger.ts @@ -1,11 +1,11 @@ import { pino, type Logger } from 'pino'; -import { reportError } from './error-sink.js'; +import { reportError } from './error-reporter.js'; import { getCurrentRequestContext } from './request-context.js'; const ERROR_LEVEL = 50; // One logger.error({ err }, msg) both logs and reports: this hook forwards every -// error-level log carrying an `err` to the bound error sink (an ERROR_TRACKING +// error-level log carrying an `err` to the bound error reporter (an ERROR_TRACKING // overlay - Sentry/PostHog/...), so no call site makes a separate capture call and // child loggers are covered too. `report: false` opts a noisy/expected error out of // forwarding (still logged); a log without `err` is never reported. diff --git a/packages/core/src/server/runtime/create-app.ts b/packages/core/src/server/runtime/create-app.ts index b891c750..d2ad55c3 100644 --- a/packages/core/src/server/runtime/create-app.ts +++ b/packages/core/src/server/runtime/create-app.ts @@ -24,7 +24,7 @@ import { createLogger, createRedisClient, withRequestContext, - setErrorSink, + setErrorReporter, EVENT_BUS, type OssContext, } from '../kernel/index.js'; @@ -279,12 +279,10 @@ export async function createApp(config: CreateAppConfig): Promise { const registry = await loadPlugins(config.plugins, container); await config.configure?.(container); - // Bridge the logger's error sink to whatever ERROR_TRACKING an overlay bound, - // after plugins/configure so the final binding wins. Unbound -> logs stay logs-only. if (container.has(ERROR_TRACKING)) { const tracker = container.get(ERROR_TRACKING); - setErrorSink((error, context) => tracker.captureException(error, context)); - container.onDispose(() => setErrorSink(undefined)); + setErrorReporter((error, context) => tracker.captureException(error, context)); + container.onDispose(() => setErrorReporter(undefined)); } const bus = container.get(EVENT_BUS); @@ -341,7 +339,7 @@ export async function createApp(config: CreateAppConfig): Promise { // Outermost catch for errors thrown in the Hono middleware chain (session // resolution, raw-body capture, etag/cache) - the one seam oRPC's onError // interceptor never sees, since handler.handle turns route/service errors into - // responses. logger.error reports via the sink; HTTPExceptions are deliberate + // responses. logger.error reports via the reporter; HTTPExceptions are deliberate // outcomes, returned without reporting. app.onError((err, c) => { if (err instanceof HTTPException) {