Datadog Implementation (#1990)

This commit is contained in:
adam
2025-08-25 21:11:49 +03:00
committed by GitHub
parent 1e35eba086
commit cd252bd980
16 changed files with 1312 additions and 120 deletions

27
apps/mail/lib/trpc.ts Normal file
View File

@@ -0,0 +1,27 @@
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@zero/server/trpc';
import superjson from 'superjson';
const getUrl = () => import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/trpc';
export const api = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
maxItems: 1,
url: getUrl(),
transformer: superjson,
fetch: (url, options) =>
fetch(url, { ...options, credentials: 'include' }).then((res) => {
if (typeof window !== 'undefined') {
const currentPath = new URL(window.location.href).pathname;
const redirectPath = res.headers.get('X-Zero-Redirect');
if (!!redirectPath && redirectPath !== currentPath) {
window.location.href = redirectPath;
res.headers.delete('X-Zero-Redirect');
}
}
return res;
}),
}),
],
});

View File

@@ -31,6 +31,7 @@
"@arcadeai/arcadejs": "1.8.1",
"@barkleapp/css-sanitizer": "1.0.0",
"@coinbase/cookie-manager": "1.1.8",
"@datadog/datadog-api-client": "1.40.0",
"@dub/better-auth": "0.0.3",
"@googleapis/gmail": "12.0.0",
"@googleapis/people": "3.0.9",

View File

@@ -8,6 +8,8 @@ export type HonoVariables = {
auth: Auth;
sessionUser?: SessionUser;
autumn?: Autumn;
traceId?: string;
requestId?: string;
};
export type HonoContext = { Variables: HonoVariables; Bindings: ZeroEnv };

View File

@@ -1,5 +1,6 @@
import type { ThinkingMCP, ThreadSyncWorker, WorkflowRunner, ZeroDB, ZeroMCP } from './main';
import type { ShardRegistry, ZeroAgent, ZeroDriver } from './routes/agent';
import { env as _env } from 'cloudflare:workers';
import type { QueryableHandler } from 'dormroom';
@@ -11,6 +12,7 @@ export type ZeroEnv = {
ZERO_MCP: DurableObjectNamespace<ZeroMCP & QueryableHandler>;
THINKING_MCP: DurableObjectNamespace<ThinkingMCP & QueryableHandler>;
WORKFLOW_RUNNER: DurableObjectNamespace<WorkflowRunner & QueryableHandler>;
THREAD_SYNC_WORKER: DurableObjectNamespace<ThreadSyncWorker>;
SYNC_THREADS_WORKFLOW: Workflow;
SYNC_THREADS_COORDINATOR_WORKFLOW: Workflow;
@@ -97,6 +99,9 @@ export type ZeroEnv = {
OTEL_EXPORTER_OTLP_ENDPOINT?: string;
OTEL_EXPORTER_OTLP_HEADERS?: string;
OTEL_SERVICE_NAME?: string;
DD_API_KEY: string;
DD_APP_KEY: string;
DD_SITE: string;
};
const env = _env as ZeroEnv;

View File

@@ -0,0 +1,206 @@
import { client, v2 } from '@datadog/datadog-api-client';
import type { TRPCCallLog } from '../types/logging';
import type { ZeroEnv } from '../env';
export class DatadogService {
private apiInstance: v2.LogsApi;
private apiKey: string;
private appKey: string;
private site: string;
constructor(env?: ZeroEnv) {
// Runtime validation for required Datadog credentials
if (!env?.DD_API_KEY || env.DD_API_KEY.trim() === '') {
throw new Error('DD_API_KEY environment variable is required and cannot be empty for Datadog service');
}
if (!env?.DD_APP_KEY || env.DD_APP_KEY.trim() === '') {
throw new Error('DD_APP_KEY environment variable is required and cannot be empty for Datadog service');
}
const configuration = client.createConfiguration({
authMethods: {
apiKeyAuth: env.DD_API_KEY,
appKeyAuth: env.DD_APP_KEY,
},
});
// Set the site for the configuration (defaults to datadoghq.com if not provided)
const ddSite = env?.DD_SITE || 'datadoghq.com';
configuration.setServerVariables({ site: ddSite });
this.apiInstance = new v2.LogsApi(configuration);
this.apiKey = env.DD_API_KEY;
this.appKey = env.DD_APP_KEY;
this.site = ddSite;
}
private generateId(): string {
return crypto.randomUUID().replace(/-/g, '');
}
// Check if a procedure is logging-related to avoid recursive logging
private isLoggingProcedure(procedure: string): boolean {
const loggingProcedures = [
'logging.getSessionStats',
'logging.clearSession',
'logging.getSessionState',
'logging.exportToDatadog',
];
return loggingProcedures.includes(procedure);
}
async logSingleCall(sessionId: string, userId: string, log: TRPCCallLog): Promise<void> {
// Skip logging-related procedures to avoid recursive logging
if (this.isLoggingProcedure(log.procedure)) {
return;
}
try {
const traceId = this.generateId();
const spanId = this.generateId();
const performanceCategory = log.duration < 100 ? 'fast' : log.duration < 500 ? 'normal' : 'slow';
const hasError = !!log.error;
const logLevel = hasError ? 'error' : performanceCategory === 'slow' ? 'warn' : 'info';
// Parse user agent for device/browser info
const parseUserAgent = (userAgent?: string) => {
if (!userAgent) return {};
const browsers = {
chrome: /Chrome\/([0-9.]+)/i,
firefox: /Firefox\/([0-9.]+)/i,
safari: /Safari\/([0-9.]+)/i,
edge: /Edg\/([0-9.]+)/i,
};
const os = {
windows: /Windows NT ([0-9.]+)/i,
macos: /Mac OS X ([0-9_.]+)/i,
linux: /Linux/i,
android: /Android ([0-9.]+)/i,
ios: /OS ([0-9_]+)/i,
};
const devices = {
mobile: /Mobile|Android|iPhone/i,
tablet: /iPad|Tablet/i,
desktop: /Windows|Mac|Linux/i,
};
let browser = 'unknown', browserVersion = '', operatingSystem = 'unknown', osVersion = '', deviceType = 'unknown';
// Detect browser
for (const [name, regex] of Object.entries(browsers)) {
const match = userAgent.match(regex);
if (match) {
browser = name;
browserVersion = match[1];
break;
}
}
// Detect OS
for (const [name, regex] of Object.entries(os)) {
const match = userAgent.match(regex);
if (match) {
operatingSystem = name;
osVersion = match[1]?.replace(/_/g, '.') || '';
break;
}
}
// Detect device type
for (const [type, regex] of Object.entries(devices)) {
if (regex.test(userAgent)) {
deviceType = type;
break;
}
}
return {
browser,
browser_version: browserVersion,
operating_system: operatingSystem,
os_version: osVersion,
device_type: deviceType,
user_agent: userAgent,
};
};
const deviceInfo = parseUserAgent(log.metadata?.userAgent);
const logEntry = {
message: `${logLevel.toUpperCase()}: TRPC call: [${log.procedure}] (${log.duration}ms)`,
status: logLevel,
service: 'zero-mail-app',
ddsource: 'trpc-logging',
ddtags: `session:${sessionId},user:${userId},procedure:${log.procedure},duration:${log.duration}ms,has_error:${hasError},performance:${performanceCategory},browser:${deviceInfo.browser},device:${deviceInfo.device_type}`,
hostname: 'cloudflare-worker',
timestamp: log.timestamp,
// Trace correlation fields
dd: {
trace_id: traceId,
span_id: spanId,
},
additionalProperties: {
// Core call data
call_id: log.id,
procedure: log.procedure,
duration: log.duration,
performance_category: performanceCategory,
trpc_method: log.metadata?.method || 'unknown',
// Session context
session_id: sessionId,
user_id: userId,
// HTTP context
http_method: 'POST',
http_url: `/api/trpc/${log.procedure}`,
client_ip: log.metadata?.ip,
referer: log.metadata?.referer,
origin: log.metadata?.origin,
accept_language: log.metadata?.acceptLanguage,
accept_encoding: log.metadata?.acceptEncoding,
request_id: log.metadata?.requestId,
// Device and browser information
...deviceInfo,
// Error handling
has_error: hasError,
...(log.error && {
error_message: log.error,
error_type: 'trpc_error',
}),
// Full request/response data
request_payload: log.input,
...(log.output && {
response_payload: log.output,
}),
// Performance metrics
timing: {
start_time: log.metadata?.startTime || log.timestamp,
end_time: log.metadata?.endTime || (log.timestamp + log.duration),
duration_ms: log.duration,
performance_category: performanceCategory,
},
// Complete request trace with all spans (from log.trace)
trace: log.trace,
}
};
await this.apiInstance.submitLog({ body: [logEntry] });
} catch (error) {
console.error('❌ Failed to log TRPC call to Datadog:', error);
}
}
}

View File

@@ -0,0 +1,117 @@
import type { TRPCCallLog, LoggingState, SessionStats } from '../types/logging';
import { DatadogService } from './datadog-service';
import type { ZeroEnv } from '../env';
// In-memory session storage for stats
// In a production environment, you might want to use a distributed cache like Redis
const sessionStats = new Map<string, LoggingState>();
export class LoggingService {
private datadogService: DatadogService;
constructor(env: ZeroEnv) {
this.datadogService = new DatadogService(env);
}
async logCall(callData: Omit<TRPCCallLog, 'id' | 'timestamp'>): Promise<void> {
const log: TRPCCallLog = {
...callData,
id: crypto.randomUUID(),
timestamp: Date.now(),
};
// Immediately export to Datadog
try {
await this.datadogService.logSingleCall(
callData.sessionId,
callData.userId,
log
);
} catch (error) {
console.error('❌ Failed to log TRPC call to Datadog:', error);
}
// Update in-memory session stats
this.updateSessionStats(callData.sessionId, callData.userId, log);
}
private updateSessionStats(sessionId: string, userId: string, log: TRPCCallLog): void {
let currentState = sessionStats.get(sessionId);
if (!currentState) {
currentState = {
sessionId,
userId,
startedAt: Date.now(),
lastActivity: Date.now(),
totalCalls: 0,
totalErrors: 0,
totalDuration: 0,
};
}
currentState.lastActivity = log.timestamp;
currentState.totalCalls++;
currentState.totalDuration += log.duration;
if (log.error) {
currentState.totalErrors++;
}
sessionStats.set(sessionId, currentState);
}
getState(sessionId: string): LoggingState {
let state = sessionStats.get(sessionId);
if (!state) {
// Initialize new state
state = {
sessionId,
userId: '',
startedAt: Date.now(),
lastActivity: Date.now(),
totalCalls: 0,
totalErrors: 0,
totalDuration: 0,
};
sessionStats.set(sessionId, state);
}
return state;
}
initializeSession(sessionId: string, userId: string): void {
const state = this.getState(sessionId);
state.userId = userId;
state.sessionId = sessionId;
state.startedAt = Date.now();
state.lastActivity = Date.now();
sessionStats.set(sessionId, state);
}
getSessionStats(sessionId: string): SessionStats {
const state = this.getState(sessionId);
const sessionDuration = Date.now() - state.startedAt;
return {
totalCalls: state.totalCalls,
totalErrors: state.totalErrors,
totalDuration: state.totalDuration,
averageDuration: state.totalCalls > 0 ? state.totalDuration / state.totalCalls : 0,
errorRate: state.totalCalls > 0 ? (state.totalErrors / state.totalCalls) * 100 : 0,
sessionDuration,
};
}
clearSession(sessionId: string): void {
const newState: LoggingState = {
sessionId,
userId: '',
startedAt: Date.now(),
lastActivity: Date.now(),
totalCalls: 0,
totalErrors: 0,
totalDuration: 0,
};
sessionStats.set(sessionId, newState);
}
}

View File

@@ -30,7 +30,7 @@ class MockExecutionContext implements ExecutionContext {
console.error('MockExecutionContext: Error in waitUntil', error);
}
}
passThroughOnException(): void {}
passThroughOnException(): void { }
props: any;
}
@@ -286,15 +286,15 @@ export const getThread: (
connectionId: string,
threadId: string,
) => {
const result = await Effect.runPromise(getThreadEffect(connectionId, threadId));
if (!result.result) {
throw new Error(`Thread ${threadId} not found`);
}
if (!result.shardId) {
throw new Error(`Thread ${threadId} not found in any shard`);
}
return { result: result.result, shardId: result.shardId };
};
const result = await Effect.runPromise(getThreadEffect(connectionId, threadId));
if (!result.result) {
throw new Error(`Thread ${threadId} not found`);
}
if (!result.shardId) {
throw new Error(`Thread ${threadId} not found in any shard`);
}
return { result: result.result, shardId: result.shardId };
};
export const modifyThreadLabelsInDB = async (
connectionId: string,
@@ -501,7 +501,7 @@ const getCounts = async (connectionId: string): Promise<CountResult[]> => {
export const sendDoState = async (connectionId: string) => {
try {
const agent = await getZeroSocketAgent(connectionId);
const cached = await agent.getCachedDoState();
if (cached) {
console.log(`[sendDoState] Using cached data for connection ${connectionId}`);
@@ -522,9 +522,9 @@ export const sendDoState = async (connectionId: string) => {
getCounts(connectionId),
]);
const shards = await listShards(registry);
await agent.setCachedDoState(size, counts, shards.length);
return agent.broadcastChatMessage({
type: OutgoingMessageType.Do_State,
isSyncing: false,
@@ -604,6 +604,8 @@ export const verifyToken = async (token: string) => {
return !!data;
};
export const resetConnection = async (connectionId: string) => {
const { db, conn } = createDb(env.HYPERDRIVE.connectionString);
await db

View File

@@ -0,0 +1,240 @@
export interface TraceSpan {
id: string;
name: string;
startTime: number;
endTime?: number;
duration?: number;
status: 'started' | 'completed' | 'error';
metadata?: Record<string, any>;
error?: string;
tags?: Record<string, string>;
}
export interface RequestTrace {
traceId: string;
startTime: number;
endTime?: number;
duration?: number;
spans: TraceSpan[];
metadata: {
procedure?: string;
userId?: string;
sessionId?: string;
ip?: string;
userAgent?: string;
requestId?: string;
};
}
class TraceContextClass {
private traces = new Map<string, RequestTrace>();
private readonly MAX_TRACES = 10000; // Maximum number of traces to keep in memory
private readonly TRACE_TTL_MS = 5 * 60 * 1000; // 5 minutes TTL for uncompleted traces
private cleanupInterval: NodeJS.Timeout;
constructor() {
// Start periodic cleanup every 2 minutes
this.cleanupInterval = setInterval(() => {
this.performCleanup();
}, 2 * 60 * 1000);
}
/**
* Performs cleanup of stale traces based on TTL and enforces max size
*/
private performCleanup(): void {
const now = Date.now();
const tracesToDelete: string[] = [];
// Find traces that have exceeded TTL
for (const [traceId, trace] of this.traces) {
const age = now - trace.startTime;
if (age > this.TRACE_TTL_MS) {
tracesToDelete.push(traceId);
}
}
// Remove stale traces
for (const traceId of tracesToDelete) {
this.traces.delete(traceId);
}
// If still over max size, remove oldest traces (LRU-style eviction)
if (this.traces.size > this.MAX_TRACES) {
const sortedTraces = Array.from(this.traces.entries())
.sort(([, a], [, b]) => a.startTime - b.startTime);
const excessCount = this.traces.size - this.MAX_TRACES;
for (let i = 0; i < excessCount; i++) {
this.traces.delete(sortedTraces[i][0]);
}
}
// Log cleanup statistics in development
if (tracesToDelete.length > 0 || this.traces.size > this.MAX_TRACES * 0.8) {
console.debug(`Trace cleanup: removed ${tracesToDelete.length} stale traces, ${this.traces.size} traces remaining`);
}
}
/**
* Get current trace statistics for monitoring
*/
getStats(): { totalTraces: number; oldestTraceAge: number } {
if (this.traces.size === 0) {
return { totalTraces: 0, oldestTraceAge: 0 };
}
const now = Date.now();
let oldestAge = 0;
for (const trace of this.traces.values()) {
const age = now - trace.startTime;
oldestAge = Math.max(oldestAge, age);
}
return {
totalTraces: this.traces.size,
oldestTraceAge: oldestAge,
};
}
createTrace(traceId: string, metadata: RequestTrace['metadata']): RequestTrace {
const existing = this.traces.get(traceId);
if (existing) return existing;
// Trigger cleanup if we're approaching max capacity
if (this.traces.size >= this.MAX_TRACES * 0.9) {
this.performCleanup();
}
const trace: RequestTrace = {
traceId,
startTime: Date.now(),
spans: [],
metadata,
};
this.traces.set(traceId, trace);
return trace;
}
getTrace(traceId: string): RequestTrace | undefined {
return this.traces.get(traceId);
}
addSpan(traceId: string, span: Omit<TraceSpan, 'id' | 'startTime' | 'status'>): TraceSpan {
const trace = this.traces.get(traceId);
if (!trace) {
throw new Error(`Trace not found: ${traceId}`);
}
const fullSpan: TraceSpan = {
id: crypto.randomUUID(),
startTime: Date.now(),
status: 'started',
...span,
};
trace.spans.push(fullSpan);
return fullSpan;
}
completeSpan(traceId: string, spanId: string, metadata?: Record<string, any>, error?: string): void {
const trace = this.traces.get(traceId);
if (!trace) return;
const span = trace.spans.find(s => s.id === spanId);
if (!span) return;
span.endTime = Date.now();
span.duration = span.endTime - span.startTime;
span.status = error ? 'error' : 'completed';
if (error) span.error = error;
if (metadata) {
span.metadata = span.metadata ? { ...span.metadata, ...metadata } : metadata;
}
}
completeTrace(traceId: string): RequestTrace | undefined {
const trace = this.traces.get(traceId);
if (!trace) return;
trace.endTime = Date.now();
trace.duration = trace.endTime - trace.startTime;
setTimeout(() => {
this.traces.delete(traceId);
}, 10000);
return trace;
}
destroy(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.traces.clear();
}
// Helper to create and immediately start a span
startSpan(traceId: string, name: string, metadata?: Record<string, any>, tags?: Record<string, string>): TraceSpan {
return this.addSpan(traceId, {
name,
metadata,
tags,
});
}
}
export const TraceContext = new TraceContextClass();
// Helper function to safely get trace from request context using context variables
export function getRequestTrace(c: any): RequestTrace | undefined {
// Try to get trace ID from context variables (set in main.ts)
const traceId = c?.var?.traceId || c?.get?.('traceId');
// Fallback to headers if context variables aren't available
if (!traceId) {
const headerTraceId = c.req?.header?.('X-Trace-ID') ||
c.req?.header?.('x-trace-id') ||
c.req?.headers?.get?.('X-Trace-ID') ||
c.req?.headers?.get?.('x-trace-id');
if (!headerTraceId) {
return undefined;
}
return TraceContext.getTrace(headerTraceId);
}
return TraceContext.getTrace(traceId);
}
// Helper function to get trace ID from context variables or headers
export function getTraceId(c: any): string | undefined {
return c?.var?.traceId || c?.get?.('traceId') || c.req?.header?.('X-Trace-ID') || c.req?.header?.('x-trace-id');
}
// Helper function to safely add span to current request
export function addRequestSpan(c: any, name: string, metadata?: Record<string, any>, tags?: Record<string, string>): TraceSpan | undefined {
const traceId = getTraceId(c);
if (!traceId) return undefined;
return TraceContext.startSpan(traceId, name, metadata, tags);
}
// Helper function to complete span in current request
export function completeRequestSpan(c: any, spanId: string, metadata?: Record<string, any>, error?: string): void {
const traceId = getTraceId(c);
if (!traceId) return;
TraceContext.completeSpan(traceId, spanId, metadata, error);
}
// Helper function to get trace context statistics for monitoring
export function getTraceStats(): { totalTraces: number; oldestTraceAge: number } {
return TraceContext.getStats();
}
// Helper function for graceful shutdown
export function destroyTraceContext(): void {
TraceContext.destroy();
}

View File

@@ -0,0 +1,245 @@
import type { TRPCCallLog } from '../types/logging';
import { LoggingService } from './logging-service';
import { getContext } from 'hono/context-storage';
import type { HonoContext } from '../ctx';
// Utility function to hash IP addresses for PII protection
function hashIpAddress(ip: string | undefined): string | undefined {
if (!ip) return undefined;
// Simple but effective hash for IP addresses
// This preserves uniqueness while protecting PII
const salt = 'zero-mail-ip-salt-2024'; // Consider using env variable for production
let hash = 0;
const str = ip + salt;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
// Return a prefixed hex representation
return `ip_${Math.abs(hash).toString(16).padStart(8, '0')}`;
}
export interface LoggingContext {
sessionId: string;
userId?: string;
}
export const createLoggingMiddleware = () => {
return async (opts: {
path: string;
type: 'query' | 'mutation' | 'subscription';
next: () => Promise<any>;
input: any;
ctx: any;
}) => {
const startTime = Date.now();
const c = getContext<HonoContext>();
const sessionId = c.var.sessionUser?.id || 'anonymous';
const userId = c.var.sessionUser?.id;
// Initialize logging service
let loggingService: LoggingService | undefined;
if (userId && c.env) {
try {
loggingService = new LoggingService(c.env);
loggingService.initializeSession(sessionId, userId);
} catch (error) {
console.error('Failed to initialize logging service:', error);
}
}
let output: any;
let error: string | undefined;
// Start TRPC procedure execution span
const { addRequestSpan, completeRequestSpan } = await import('./trace-context');
const procedureSpan = addRequestSpan(c, 'trpc_procedure_execution', {
procedure: opts.path,
type: opts.type,
hasInput: !!opts.input,
inputSize: opts.input ? JSON.stringify(opts.input).length : 0,
}, {
'trpc.procedure': opts.path,
'trpc.type': opts.type,
});
try {
// Execute the TRPC call
output = await opts.next();
// Complete procedure span
if (procedureSpan) {
completeRequestSpan(c, procedureSpan.id, {
success: true,
hasOutput: !!output,
outputSize: output ? JSON.stringify(output).length : 0,
});
}
// Sanitize output to remove non-serializable objects
const sanitizeOutput = (obj: any): any => {
if (obj === null || obj === undefined) return obj;
if (typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(sanitizeOutput);
const sanitized: any = {};
for (const [key, value] of Object.entries(obj)) {
// Skip known non-serializable fields
if (key === 'ctx' && value && typeof value === 'object') {
continue;
}
try {
structuredClone(value);
sanitized[key] = sanitizeOutput(value);
} catch (err) {
// If it can't be serialized, replace with a description
console.log('[TRACE DEBUG] Non-serializable value:', err);
sanitized[key] = `[Non-serializable: ${value?.constructor?.name || typeof value}]`;
}
}
return sanitized;
};
// Log successful call
const callData: TRPCCallLog = {
id: crypto.randomUUID(),
timestamp: startTime,
userId: userId || 'anonymous',
sessionId,
procedure: opts.path,
input: opts.input,
output: sanitizeOutput(output),
duration: Date.now() - startTime,
metadata: {
method: opts.type,
userAgent: c.req.header('User-Agent'),
ip: hashIpAddress(c.req.header('CF-Connecting-IP') || c.req.header('X-Forwarded-For')),
referer: c.req.header('Referer'),
origin: c.req.header('Origin'),
acceptLanguage: c.req.header('Accept-Language'),
acceptEncoding: c.req.header('Accept-Encoding'),
requestId: c.req.header('X-Request-Id') || crypto.randomUUID(),
timestamp: new Date().toISOString(),
startTime,
endTime: Date.now(),
},
};
// Log using the new logging service
if (loggingService) {
const { getRequestTrace } = await import('./trace-context');
// Get the complete trace for this request
const trace = getRequestTrace(c);
// Add trace to call data
if (trace) {
callData.trace = {
traceId: trace.traceId,
requestStartTime: trace.startTime,
requestEndTime: trace.endTime,
requestDuration: trace.duration,
spans: trace.spans,
totalSpans: trace.spans.length,
completedSpans: trace.spans.filter(s => s.status === 'completed').length,
errorSpans: trace.spans.filter(s => s.status === 'error').length,
};
callData.metadata.traceId = trace.traceId;
callData.metadata.requestDuration = trace.duration;
}
// Log using the new service which will immediately log to Datadog
loggingService.logCall(callData).catch((err) => {
console.error('Failed to log TRPC call:', err);
});
// Complete the trace after logging
if (trace) {
const { TraceContext } = await import('./trace-context');
TraceContext.completeTrace(trace.traceId);
}
}
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
// Complete procedure span with error
if (procedureSpan) {
completeRequestSpan(c, procedureSpan.id, {
success: false,
errorType: err instanceof Error ? err.constructor.name : 'UnknownError',
}, error);
}
// Log failed call
const callData: TRPCCallLog = {
id: crypto.randomUUID(),
timestamp: startTime,
userId: userId || 'anonymous',
sessionId,
procedure: opts.path,
input: opts.input,
error,
duration: Date.now() - startTime,
metadata: {
method: opts.type,
userAgent: c.req.header('User-Agent'),
ip: hashIpAddress(c.req.header('CF-Connecting-IP') || c.req.header('X-Forwarded-For')),
referer: c.req.header('Referer'),
origin: c.req.header('Origin'),
acceptLanguage: c.req.header('Accept-Language'),
acceptEncoding: c.req.header('Accept-Encoding'),
requestId: c.req.header('X-Request-Id') || crypto.randomUUID(),
timestamp: new Date().toISOString(),
startTime,
endTime: Date.now(),
},
};
// Log error using the new logging service
if (loggingService) {
const { getRequestTrace } = await import('./trace-context');
// Get the complete trace for this request
const trace = getRequestTrace(c);
// Add trace to call data
if (trace) {
callData.trace = {
traceId: trace.traceId,
requestStartTime: trace.startTime,
requestEndTime: trace.endTime,
requestDuration: trace.duration,
spans: trace.spans,
totalSpans: trace.spans.length,
completedSpans: trace.spans.filter(s => s.status === 'completed').length,
errorSpans: trace.spans.filter(s => s.status === 'error').length,
};
callData.metadata.traceId = trace.traceId;
callData.metadata.requestDuration = trace.duration;
}
// Log using the new service which will immediately log to Datadog
loggingService.logCall(callData).catch((logErr) => {
console.error('Failed to log TRPC error:', logErr);
});
// Complete the trace after logging error
if (trace) {
const { TraceContext } = await import('./trace-context');
TraceContext.completeTrace(trace.traceId);
}
}
throw err;
}
return output;
};
};

View File

@@ -31,6 +31,7 @@ import { oAuthDiscoveryMetadata } from 'better-auth/plugins';
import { EProviders, type IEmailSendBatch } from './types';
import { eq, and, desc, asc, inArray } from 'drizzle-orm';
import { ThinkingMCP } from './lib/sequential-thinking';
import { contextStorage } from 'hono/context-storage';
import { defaultUserSettings } from './lib/schemas';
import { createLocalJWKSet, jwtVerify } from 'jose';
@@ -563,32 +564,141 @@ class ZeroDB extends DurableObject<ZeroEnv> {
}
}
// Utility function to hash IP addresses for PII protection
function hashIpAddress(ip: string | undefined): string | undefined {
if (!ip) return undefined;
// Simple but effective hash for IP addresses
// This preserves uniqueness while protecting PII
const salt = 'zero-mail-ip-salt-2024'; // Consider using env variable for production
let hash = 0;
const str = ip + salt;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
// Return a prefixed hex representation
return `ip_${Math.abs(hash).toString(16).padStart(8, '0')}`;
}
const api = new Hono<HonoContext>()
.use(contextStorage())
.use('*', async (c, next) => {
// Initialize request tracing using headers (no context pollution)
const traceId = c.req.header('X-Trace-ID') || crypto.randomUUID();
const requestId = c.req.header('X-Request-Id') || crypto.randomUUID();
// Set trace ID in response headers for client correlation
c.header('X-Trace-ID', traceId);
c.header('X-Request-ID', requestId);
// Store trace ID in context variables for TRPC access
c.set('traceId', traceId);
c.set('requestId', requestId);
const { TraceContext } = await import('./lib/trace-context');
// Create trace for this request
const rawIp = c.req.header('CF-Connecting-IP') || c.req.header('X-Forwarded-For');
const trace = TraceContext.createTrace(traceId, {
requestId,
ip: hashIpAddress(rawIp), // Hash IP address to protect PII
userAgent: c.req.header('User-Agent'),
});
// Start authentication span
const authSpan = TraceContext.startSpan(traceId, 'authentication', {
method: c.req.method,
url: c.req.url,
hasAuthHeader: !!c.req.header('Authorization'),
}, {
'auth.method': c.req.header('Authorization') ? 'bearer_token' : 'session_cookie'
});
const auth = createAuth();
c.set('auth', auth);
const session = await auth.api.getSession({ headers: c.req.raw.headers });
c.set('sessionUser', session?.user);
if (c.req.header('Authorization') && !session?.user) {
// Start token verification span
const tokenSpan = TraceContext.startSpan(traceId, 'token_verification', {
tokenPresent: true,
}, {
'auth.token_type': 'jwt'
});
const token = c.req.header('Authorization')?.split(' ')[1];
if (token) {
const localJwks = await auth.api.getJwks();
const jwks = createLocalJWKSet(localJwks);
try {
const localJwks = await auth.api.getJwks();
const jwks = createLocalJWKSet(localJwks);
const { payload } = await jwtVerify(token, jwks);
const userId = payload.sub;
const { payload } = await jwtVerify(token, jwks);
const userId = payload.sub;
if (userId) {
const db = await getZeroDB(userId);
c.set('sessionUser', await db.findUser());
if (userId) {
const db = await getZeroDB(userId);
const user = await db.findUser();
c.set('sessionUser', user);
TraceContext.completeSpan(traceId, tokenSpan.id, {
success: true,
userId,
});
} else {
TraceContext.completeSpan(traceId, tokenSpan.id, {
success: false,
reason: 'no_user_id_in_token',
});
}
} catch (error) {
TraceContext.completeSpan(traceId, tokenSpan.id, {
success: false,
reason: 'token_verification_failed',
}, error instanceof Error ? error.message : 'Unknown token error');
}
} else {
TraceContext.completeSpan(traceId, tokenSpan.id, {
success: false,
reason: 'no_token_provided',
});
}
}
await next();
// Complete auth span
TraceContext.completeSpan(traceId, authSpan.id, {
authenticated: !!c.var.sessionUser,
userId: c.var.sessionUser?.id,
authMethod: session?.user ? 'session' : (c.req.header('Authorization') ? 'token' : 'none'),
});
// Update trace metadata with user info
trace.metadata.userId = c.var.sessionUser?.id;
trace.metadata.sessionId = c.var.sessionUser?.id || 'anonymous';
// Start request processing span
const requestSpan = TraceContext.startSpan(traceId, 'request_processing', {
authenticated: !!c.var.sessionUser,
path: new URL(c.req.url).pathname,
});
try {
await next();
// Don't complete the request span here - let TRPC middleware handle it
} catch (error) {
TraceContext.completeSpan(traceId, requestSpan.id, {
success: false,
statusCode: c.res.status,
}, error instanceof Error ? error.message : 'Unknown request error');
throw error;
}
// Note: Trace will be completed by TRPC middleware after logging
c.set('sessionUser', undefined);
c.set('auth', undefined as any);

View File

@@ -17,6 +17,7 @@ import { bimiRouter } from './routes/bimi';
import type { HonoContext } from '../ctx';
import { aiRouter } from './routes/ai';
import { router } from './trpc';
import { loggingRouter } from './routes/logging';
export const appRouter = router({
ai: aiRouter,
@@ -34,6 +35,7 @@ export const appRouter = router({
user: userRouter,
templates: templatesRouter,
meet: meetRouter,
logging: loggingRouter,
});
export type AppRouter = typeof appRouter;

View File

@@ -0,0 +1,45 @@
import { privateProcedure, router } from '../trpc';
import { LoggingService } from '../../lib/logging-service';
import { TRPCError } from '@trpc/server';
export const loggingRouter = router({
getSessionStats: privateProcedure
.query(async ({ ctx }) => {
if (!ctx.sessionUser) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Authentication required',
});
}
const sessionId = ctx.sessionUser.id;
const loggingService = new LoggingService(ctx.c.env);
return loggingService.getSessionStats(sessionId);
}),
clearSession: privateProcedure
.mutation(async ({ ctx }) => {
if (!ctx.sessionUser) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Authentication required',
});
}
const sessionId = ctx.sessionUser.id;
const loggingService = new LoggingService(ctx.c.env);
loggingService.clearSession(sessionId);
return { success: true };
}),
getSessionState: privateProcedure
.query(async ({ ctx }) => {
if (!ctx.sessionUser) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Authentication required',
});
}
const sessionId = ctx.sessionUser.id;
const loggingService = new LoggingService(ctx.c.env);
return loggingService.getState(sessionId);
}),
});

View File

@@ -3,6 +3,7 @@ import { Ratelimit, type RatelimitConfig } from '@upstash/ratelimit';
import type { HonoContext, HonoVariables } from '../ctx';
import { getConnInfo } from 'hono/cloudflare-workers';
import { initTRPC, TRPCError } from '@trpc/server';
import { createLoggingMiddleware } from '../lib/trpc-logging';
import { redis } from '../lib/services';
import type { Context } from 'hono';
@@ -14,24 +15,75 @@ type TrpcContext = {
const t = initTRPC.context<TrpcContext>().create({ transformer: superjson });
const loggingMiddleware = createLoggingMiddleware();
export const router = t.router;
export const publicProcedure = t.procedure;
export const publicProcedure = t.procedure.use(loggingMiddleware);
export const privateProcedure = publicProcedure.use(async ({ ctx, next }) => {
const { addRequestSpan, completeRequestSpan } = await import('../lib/trace-context');
// Start auth validation span
const authSpan = addRequestSpan(ctx.c, 'trpc_auth_validation', {
hasSessionUser: !!ctx.sessionUser,
procedure: 'private',
}, {
'trpc.auth_required': 'true'
});
if (!ctx.sessionUser) {
if (authSpan) {
completeRequestSpan(ctx.c, authSpan.id, {
success: false,
reason: 'no_session_user',
}, 'UNAUTHORIZED: No session user found');
}
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
if (authSpan) {
completeRequestSpan(ctx.c, authSpan.id, {
success: true,
userId: ctx.sessionUser.id,
});
}
return next({ ctx: { ...ctx, sessionUser: ctx.sessionUser } });
});
export const activeConnectionProcedure = privateProcedure.use(async ({ ctx, next }) => {
const { addRequestSpan, completeRequestSpan } = await import('../lib/trace-context');
// Start connection validation span
const connectionSpan = addRequestSpan(ctx.c, 'trpc_connection_validation', {
userId: ctx.sessionUser.id,
}, {
'trpc.connection_required': 'true'
});
try {
const activeConnection = await getActiveConnection();
if (connectionSpan) {
completeRequestSpan(ctx.c, connectionSpan.id, {
success: true,
connectionId: activeConnection.id,
connectionType: activeConnection.providerId,
});
}
return next({ ctx: { ...ctx, activeConnection } });
} catch (err) {
if (connectionSpan) {
completeRequestSpan(ctx.c, connectionSpan.id, {
success: false,
reason: 'connection_not_found',
}, err instanceof Error ? err.message : 'Failed to get active connection');
}
await ctx.c.var.auth.api.signOut({ headers: ctx.c.req.raw.headers });
throw new TRPCError({
code: 'BAD_REQUEST',
@@ -69,12 +121,12 @@ export const activeDriverProcedure = activeConnectionProcedure.use(async ({ ctx,
accessToken: null,
refreshToken: null,
});
if (activeConnection.accessToken) {
ctx.c.header(
'X-Zero-Redirect',
`/settings/connections?disconnectedConnectionId=${activeConnection.id}`,
);
}
ctx.c.header(
'X-Zero-Redirect',
`/settings/connections?disconnectedConnectionId=${activeConnection.id}`,
);
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Connection expired. Please reconnect.',

View File

@@ -0,0 +1,70 @@
export interface TraceSpan {
id: string;
name: string;
startTime: number;
endTime?: number;
duration?: number;
status: 'started' | 'completed' | 'error';
metadata?: Record<string, any>;
error?: string;
tags?: Record<string, string>;
}
export interface TRPCCallLog {
id: string;
timestamp: number;
userId: string;
sessionId: string;
procedure: string;
input: any;
output?: any;
error?: string;
duration: number;
metadata: {
userAgent?: string;
ip?: string;
method: 'query' | 'mutation' | 'subscription';
// Additional metadata
referer?: string;
origin?: string;
acceptLanguage?: string;
acceptEncoding?: string;
requestId?: string;
timestamp?: string;
startTime?: number;
endTime?: number;
// Trace information
traceId?: string;
requestDuration?: number;
};
// Complete trace spans for this request
trace?: {
traceId: string;
requestStartTime: number;
requestEndTime?: number;
requestDuration?: number;
spans: TraceSpan[];
totalSpans: number;
completedSpans: number;
errorSpans: number;
};
}
export interface LoggingState {
sessionId: string;
userId: string;
startedAt: number;
lastActivity: number;
totalCalls: number;
totalErrors: number;
totalDuration: number;
}
export interface SessionStats {
totalCalls: number;
totalErrors: number;
totalDuration: number;
averageDuration: number;
errorRate: number;
sessionDuration: number;
}

View File

@@ -58,6 +58,7 @@
"name": "WORKFLOW_RUNNER",
"class_name": "WorkflowRunner",
},
{
"name": "THREAD_SYNC_WORKER",
"class_name": "ThreadSyncWorker",
@@ -144,6 +145,7 @@
"tag": "v9",
"new_sqlite_classes": ["ShardRegistry"],
},
],
"observability": {
@@ -177,6 +179,9 @@
"MEET_AUTH_HEADER": "",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.axiom.co/v1/traces",
"OTEL_SERVICE_NAME": "zero-email-server-local",
"DD_API_KEY": "",
"DD_APP_KEY": "",
"DD_SITE": "datadoghq.com",
},
"kv_namespaces": [
{
@@ -282,6 +287,7 @@
"name": "SHARD_REGISTRY",
"class_name": "ShardRegistry",
},
],
},
"workflows": [
@@ -370,6 +376,7 @@
"tag": "v10",
"new_sqlite_classes": ["ShardRegistry"],
},
],
"observability": {
"enabled": true,
@@ -393,6 +400,9 @@
"DISABLE_WORKFLOWS": "false",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.axiom.co/v1/traces",
"OTEL_SERVICE_NAME": "zero-email-server-staging",
"DD_API_KEY": "",
"DD_APP_KEY": "",
"DD_SITE": "datadoghq.com",
},
"kv_namespaces": [
{
@@ -511,6 +521,7 @@
"name": "SHARD_REGISTRY",
"class_name": "ShardRegistry",
},
],
},
"workflows": [
@@ -593,6 +604,7 @@
"tag": "v10",
"new_sqlite_classes": ["ShardRegistry"],
},
],
"vars": {
"NODE_ENV": "production",
@@ -606,6 +618,9 @@
"DISABLE_WORKFLOWS": "true",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.axiom.co/v1/traces",
"OTEL_SERVICE_NAME": "zero-email-server-production",
"DD_API_KEY": "",
"DD_APP_KEY": "",
"DD_SITE": "datadoghq.com",
},
"kv_namespaces": [
{

237
pnpm-lock.yaml generated
View File

@@ -20,7 +20,7 @@ catalogs:
version: 0.0.48
better-auth:
specifier: ^1.3.4
version: 1.3.4
version: 1.3.7
drizzle-kit:
specifier: ^0.31.1
version: 0.31.4
@@ -41,7 +41,7 @@ catalogs:
version: 5.8.3
wrangler:
specifier: ^4.28.1
version: 4.28.1
version: 4.30.0
zod:
specifier: ^3.25.42
version: 3.25.67
@@ -142,7 +142,7 @@ importers:
version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@react-router/dev':
specifier: ^7.6.1
version: 7.6.3(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(wrangler@4.28.1(@cloudflare/workers-types@4.20250628.0))(yaml@2.8.0)
version: 7.6.3(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(wrangler@4.30.0(@cloudflare/workers-types@4.20250628.0))(yaml@2.8.0)
'@sentry/react':
specifier: 9.40.0
version: 9.40.0(react@19.1.0)
@@ -232,7 +232,7 @@ importers:
version: 19.1.0-rc.2
better-auth:
specifier: 'catalog:'
version: 1.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
version: 1.3.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.25.67)
canvas-confetti:
specifier: 1.9.3
version: 1.9.3
@@ -419,7 +419,7 @@ importers:
devDependencies:
'@cloudflare/vite-plugin':
specifier: ^1.3.1
version: 1.7.5(rollup@4.44.1)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(workerd@1.20250803.0)(wrangler@4.28.1(@cloudflare/workers-types@4.20250628.0))
version: 1.7.5(rollup@4.44.1)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(workerd@1.20250813.0)(wrangler@4.30.0(@cloudflare/workers-types@4.20250628.0))
'@inlang/cli':
specifier: ^3.0.0
version: 3.0.12
@@ -485,7 +485,7 @@ importers:
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
wrangler:
specifier: 'catalog:'
version: 4.28.1(@cloudflare/workers-types@4.20250628.0)
version: 4.30.0(@cloudflare/workers-types@4.20250628.0)
apps/server:
dependencies:
@@ -516,6 +516,9 @@ importers:
'@coinbase/cookie-manager':
specifier: 1.1.8
version: 1.1.8
'@datadog/datadog-api-client':
specifier: 1.40.0
version: 1.40.0
'@dub/better-auth':
specifier: 0.0.3
version: 0.0.3
@@ -581,7 +584,7 @@ importers:
version: 1.5.1
better-auth:
specifier: 'catalog:'
version: 1.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
version: 1.3.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.25.67)
cheerio:
specifier: 1.1.0
version: 1.1.0
@@ -599,7 +602,7 @@ importers:
version: 1.0.1
drizzle-orm:
specifier: 'catalog:'
version: 0.43.1(@cloudflare/workers-types@4.20250628.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(better-sqlite3@11.10.0)(kysely@0.28.2)(postgres@3.4.5)
version: 0.43.1(@cloudflare/workers-types@4.20250628.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(better-sqlite3@11.10.0)(kysely@0.28.5)(postgres@3.4.5)
dub:
specifier: 0.64.2
version: 0.64.2(@modelcontextprotocol/sdk@1.15.1)(zod@3.25.67)
@@ -677,7 +680,7 @@ importers:
version: 11.1.0
wrangler:
specifier: 'catalog:'
version: 4.28.1(@cloudflare/workers-types@4.20250628.0)
version: 4.30.0(@cloudflare/workers-types@4.20250628.0)
zod:
specifier: 'catalog:'
version: 3.25.67
@@ -1089,8 +1092,8 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
'@better-auth/utils@0.2.5':
resolution: {integrity: sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==}
'@better-auth/utils@0.2.6':
resolution: {integrity: sha512-3y/vaL5Ox33dBwgJ6ub3OPkVqr6B5xL2kgxNHG8eHZuryLyG/4JSPGqjbdRSgjuy9kALUZYDFl+ORIAxlWMSuA==}
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
@@ -1129,8 +1132,8 @@ packages:
workerd:
optional: true
'@cloudflare/unenv-preset@2.6.0':
resolution: {integrity: sha512-h7Txw0WbDuUbrvZwky6+x7ft+U/Gppfn/rWx6IdR+e9gjygozRJnV26Y2TOr3yrIFa6OsZqqR2lN+jWTrakHXg==}
'@cloudflare/unenv-preset@2.6.1':
resolution: {integrity: sha512-48rC6jo9CkSRkImfu5KU4zKyoPJx7b9GTUpZn0Emr6J+jkmrLhwCY3BI10QS+fhOt1NkJNlxIcYrBgvWeCpKOw==}
peerDependencies:
unenv: 2.0.0-rc.19
workerd: ^1.20250802.0
@@ -1150,8 +1153,8 @@ packages:
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-64@1.20250803.0':
resolution: {integrity: sha512-6QciMnJp1p3F1qUiN0LaLfmw7SuZA/gfUBOe8Ft81pw16JYZ3CyiqIKPJvc1SV8jgDx8r+gz/PRi1NwOMt329A==}
'@cloudflare/workerd-darwin-64@1.20250813.0':
resolution: {integrity: sha512-Pka37/jqLy7ZaQlwpBy79A/BLH+qpRPSEX2h/zWND+qRfoCVCCaZQPdknHZO0pcvHPzK8E2Z4j5QI1IafPA5UA==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
@@ -1162,8 +1165,8 @@ packages:
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20250803.0':
resolution: {integrity: sha512-DoIgghDowtqoNhL6OoN/F92SKtrk7mRQKc4YSs/Dst8IwFZq+pCShOlWfB0MXqHKPSoiz5xLSrUKR9H6gQMPvw==}
'@cloudflare/workerd-darwin-arm64@1.20250813.0':
resolution: {integrity: sha512-QnaJbmhcA32+4uZ+or1hXZjdxGqrFUuh6Ye+skEGu3iB/xzq9CmyVyoKoshiUOcWGKndQb7KRo56dq0bVvVLFw==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
@@ -1174,8 +1177,8 @@ packages:
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-64@1.20250803.0':
resolution: {integrity: sha512-mYdz4vNWX3+PoqRjssepVQqgh42IBiSrl+wb7vbh7VVWUVzBnQKtW3G+UFiBF62hohCLexGIEi7L0cFfRlcKSQ==}
'@cloudflare/workerd-linux-64@1.20250813.0':
resolution: {integrity: sha512-6pokgBQmujJsAuqOme2wBX5ol/1YW3d7kV7wp0Y1/tFi46TnmWcEy08B4FD5t2AARQJ68a7XMxIJKWChcaJ9Cg==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
@@ -1186,8 +1189,8 @@ packages:
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20250803.0':
resolution: {integrity: sha512-RmrtUYLRUg6djKU7Z6yebS6YGJVnaDVY6bbXca+2s26vw4ibJDOTPLuBHFQF62Grw3fAfsNbjQh5i14vG2mqUg==}
'@cloudflare/workerd-linux-arm64@1.20250813.0':
resolution: {integrity: sha512-lFwqohi8fkR98OwjHT69sbThx4BJem7vu6N8kqrge7wuKJWrMDNbzOTdyBA8adV9DmE07ELuN2vcbbu8ZjaL2Q==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
@@ -1198,8 +1201,8 @@ packages:
cpu: [x64]
os: [win32]
'@cloudflare/workerd-windows-64@1.20250803.0':
resolution: {integrity: sha512-uLV8gdudz36o9sUaAKbBxxTwZwLFz1KyW7QpBvOo4+r3Ib8yVKXGiySIMWGD7A0urSMrjf3e5LlLcJKgZUOjMA==}
'@cloudflare/workerd-windows-64@1.20250813.0':
resolution: {integrity: sha512-Fs62NvUajtoXb+4W8jaRXzw64Nbmb8X+PbRLZbxUFv68sGhxKPw1nB1YEmNNZ215ma47hTlSdF3UQh4FOmz7NA==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
@@ -1242,6 +1245,10 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
'@datadog/datadog-api-client@1.40.0':
resolution: {integrity: sha512-b8XxPEy0pnsmKed/4aejWMXkzV2IXolUreK7K7KYGJW/bYPpQLJpwBgv2W+GJTaHWX1YfH1veQnm3CW3Qvs04Q==}
engines: {node: '>=12.0.0'}
'@daybrush/utils@1.13.0':
resolution: {integrity: sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==}
@@ -4309,11 +4316,11 @@ packages:
engines: {node: '>= 8.0.0'}
hasBin: true
'@simplewebauthn/browser@13.1.0':
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
'@simplewebauthn/browser@13.1.2':
resolution: {integrity: sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==}
'@simplewebauthn/server@13.1.1':
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
'@simplewebauthn/server@13.1.2':
resolution: {integrity: sha512-VwoDfvLXSCaRiD+xCIuyslU0HLxVggeE5BL06+GbsP2l1fGf5op8e0c3ZtKoi+vSg1q4ikjtAghC23ze2Q3H9g==}
engines: {node: '>=20.0.0'}
'@sinclair/typebox@0.27.8':
@@ -4750,6 +4757,9 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/buffer-from@1.1.3':
resolution: {integrity: sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w==}
'@types/canvas-confetti@1.9.0':
resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
@@ -4864,6 +4874,9 @@ packages:
'@types/node@22.15.29':
resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
'@types/pako@1.0.7':
resolution: {integrity: sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==}
'@types/pg-pool@2.0.6':
resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==}
@@ -5305,11 +5318,12 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
better-auth@1.3.4:
resolution: {integrity: sha512-JbZYam6Cs3Eu5CSoMK120zSshfaKvrCftSo/+v7524H1RvhryQ7UtMbzagBcXj0Digjj8hZtVkkR4tTZD/wK2g==}
better-auth@1.3.7:
resolution: {integrity: sha512-/1fEyx2SGgJQM5ujozDCh9eJksnVkNU/J7Fk/tG5Y390l8nKbrPvqiFlCjlMM+scR+UABJbQzA6An7HT50LHyQ==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
react:
optional: true
@@ -5649,6 +5663,9 @@ packages:
resolution: {integrity: sha512-nH0a49E/kSVk6BeFgKZy4uUsy6D2A16p120h5bYD9ILBhQu7o2sJFH+WI4R731TSBQ0dB1Ik7inB/dRAB4C8QQ==}
engines: {node: '>=18'}
cross-fetch@3.2.0:
resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -6169,6 +6186,9 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
es6-promise@4.2.8:
resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
esbuild-register@3.6.0:
resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
peerDependencies:
@@ -7314,9 +7334,9 @@ packages:
resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==}
engines: {node: '>=14.0.0'}
kysely@0.28.2:
resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==}
engines: {node: '>=18.0.0'}
kysely@0.28.5:
resolution: {integrity: sha512-rlB0I/c6FBDWPcQoDtkxi9zIvpmnV5xoIalfCMSMCa7nuA6VGA3F54TW9mEgX4DVf10sXAWCF5fDbamI/5ZpKA==}
engines: {node: '>=20.0.0'}
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
@@ -7451,6 +7471,10 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
loglevel@1.9.2:
resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
engines: {node: '>= 0.6.0'}
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
@@ -7691,8 +7715,8 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
miniflare@4.20250803.0:
resolution: {integrity: sha512-1tmCLfmMw0SqRBF9PPII9CVLQRzOrO7uIBmSng8BMSmtgs2kos7OeoM0sg6KbR9FrvP/zAniLyZuCAMAjuu4fQ==}
miniflare@4.20250813.1:
resolution: {integrity: sha512-6PyXwR4pZmH9ukO0jR5LmhlFVMktsVVGVcUjD9Lpev5QwnqjTRPEv73cnXCe0+oTbIm5TYnvXsAklaWxQuxstA==}
engines: {node: '>=18.0.0'}
hasBin: true
@@ -8053,6 +8077,9 @@ packages:
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -8450,8 +8477,8 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
protobufjs@7.5.3:
resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==}
protobufjs@7.5.4:
resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
engines: {node: '>=12.0.0'}
proxy-addr@2.0.7:
@@ -9244,8 +9271,8 @@ packages:
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
engines: {node: '>=16'}
supports-color@10.1.0:
resolution: {integrity: sha512-GBuewsPrhJPftT+fqDa9oI/zc5HNsG9nREqwzoSFDOIqf0NggOZbHQj2TE1P1CDJK8ZogFnlZY9hWoUiur7I/A==}
supports-color@10.2.0:
resolution: {integrity: sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==}
engines: {node: '>=18'}
supports-color@7.2.0:
@@ -9991,20 +10018,20 @@ packages:
engines: {node: '>=16'}
hasBin: true
workerd@1.20250803.0:
resolution: {integrity: sha512-oYH29mE/wNolPc32NHHQbySaNorj6+KASUtOvQHySxB5mO1NWdGuNv49woxNCF5971UYceGQndY+OLT+24C3wQ==}
workerd@1.20250813.0:
resolution: {integrity: sha512-bDlPGSnb/KESpGFE57cDjgP8mEKDM4WBTd/uGJBsQYCB6Aokk1eK3ivtHoxFx3MfJNo3v6/hJy6KK1b6rw1gvg==}
engines: {node: '>=16'}
hasBin: true
workers-og@0.0.25:
resolution: {integrity: sha512-OkTyqCkUCUpGHwMwGmVCMtFPUASf9oBEiCYyOVMBDnUidTQt7AwvDx5EIuCMuQELUGm/tIyvvC8OU/hBsxlBUw==}
wrangler@4.28.1:
resolution: {integrity: sha512-B1w6XS3o1q1Icyx1CyirY5GNyYhucd63Jqml/EYSbB5dgv0VT8ir7L8IkCdbICEa4yYTETIgvTTZqffM6tBulA==}
wrangler@4.30.0:
resolution: {integrity: sha512-NXJUObuXxgG8/ChQ4yXkWLmDQ5ZcO98gyq1yFKYVntJ884C0IpDQrVnAv2RA0ZEz5eB8zal+4OKnr26P3N7ItA==}
engines: {node: '>=18.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20250803.0
'@cloudflare/workers-types': ^4.20250813.0
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
@@ -10123,9 +10150,6 @@ packages:
zod@3.25.67:
resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
zod@4.0.15:
resolution: {integrity: sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ==}
zustand@4.5.7:
resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
engines: {node: '>=12.7.0'}
@@ -10515,9 +10539,8 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
'@better-auth/utils@0.2.5':
'@better-auth/utils@0.2.6':
dependencies:
typescript: 5.8.3
uncrypto: 0.1.3
'@better-fetch/fetch@1.1.18': {}
@@ -10556,21 +10579,21 @@ snapshots:
'@cloudflare/playwright@0.0.11': {}
'@cloudflare/unenv-preset@2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250803.0)':
'@cloudflare/unenv-preset@2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250813.0)':
dependencies:
unenv: 2.0.0-rc.17
optionalDependencies:
workerd: 1.20250803.0
workerd: 1.20250813.0
'@cloudflare/unenv-preset@2.6.0(unenv@2.0.0-rc.19)(workerd@1.20250803.0)':
'@cloudflare/unenv-preset@2.6.1(unenv@2.0.0-rc.19)(workerd@1.20250813.0)':
dependencies:
unenv: 2.0.0-rc.19
optionalDependencies:
workerd: 1.20250803.0
workerd: 1.20250813.0
'@cloudflare/vite-plugin@1.7.5(rollup@4.44.1)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(workerd@1.20250803.0)(wrangler@4.28.1(@cloudflare/workers-types@4.20250628.0))':
'@cloudflare/vite-plugin@1.7.5(rollup@4.44.1)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(workerd@1.20250813.0)(wrangler@4.30.0(@cloudflare/workers-types@4.20250628.0))':
dependencies:
'@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250803.0)
'@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250813.0)
'@mjackson/node-fetch-server': 0.6.1
'@rollup/plugin-replace': 6.0.2(rollup@4.44.1)
get-port: 7.1.0
@@ -10579,7 +10602,7 @@ snapshots:
tinyglobby: 0.2.14
unenv: 2.0.0-rc.17
vite: 6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
wrangler: 4.28.1(@cloudflare/workers-types@4.20250628.0)
wrangler: 4.30.0(@cloudflare/workers-types@4.20250628.0)
ws: 8.18.0
transitivePeerDependencies:
- bufferutil
@@ -10590,31 +10613,31 @@ snapshots:
'@cloudflare/workerd-darwin-64@1.20250617.0':
optional: true
'@cloudflare/workerd-darwin-64@1.20250803.0':
'@cloudflare/workerd-darwin-64@1.20250813.0':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20250617.0':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20250803.0':
'@cloudflare/workerd-darwin-arm64@1.20250813.0':
optional: true
'@cloudflare/workerd-linux-64@1.20250617.0':
optional: true
'@cloudflare/workerd-linux-64@1.20250803.0':
'@cloudflare/workerd-linux-64@1.20250813.0':
optional: true
'@cloudflare/workerd-linux-arm64@1.20250617.0':
optional: true
'@cloudflare/workerd-linux-arm64@1.20250803.0':
'@cloudflare/workerd-linux-arm64@1.20250813.0':
optional: true
'@cloudflare/workerd-windows-64@1.20250617.0':
optional: true
'@cloudflare/workerd-windows-64@1.20250803.0':
'@cloudflare/workerd-windows-64@1.20250813.0':
optional: true
'@cloudflare/workers-types@4.20250628.0': {}
@@ -10647,6 +10670,20 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
'@datadog/datadog-api-client@1.40.0':
dependencies:
'@types/buffer-from': 1.1.3
'@types/node': 22.15.29
'@types/pako': 1.0.7
buffer-from: 1.1.2
cross-fetch: 3.2.0
es6-promise: 4.2.8
form-data: 4.0.3
loglevel: 1.9.2
pako: 2.1.0
transitivePeerDependencies:
- encoding
'@daybrush/utils@1.13.0': {}
'@dnd-kit/accessibility@3.1.1(react@19.1.0)':
@@ -11341,7 +11378,7 @@ snapshots:
dependencies:
'@jridgewell/set-array': 1.2.1
'@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.29
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/resolve-uri@3.1.2': {}
@@ -11758,7 +11795,7 @@ snapshots:
'@opentelemetry/sdk-logs': 0.200.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-metrics': 2.0.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 2.0.0(@opentelemetry/api@1.9.0)
protobufjs: 7.5.3
protobufjs: 7.5.4
'@opentelemetry/redis-common@0.36.2': {}
@@ -11899,7 +11936,7 @@ snapshots:
dependencies:
'@poppinss/colors': 4.1.5
'@sindresorhus/is': 7.0.2
supports-color: 10.1.0
supports-color: 10.2.0
'@poppinss/exception@1.2.2': {}
@@ -13313,7 +13350,7 @@ snapshots:
dependencies:
react: 19.1.0
'@react-router/dev@7.6.3(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(wrangler@4.28.1(@cloudflare/workers-types@4.20250628.0))(yaml@2.8.0)':
'@react-router/dev@7.6.3(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(tsx@4.19.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(wrangler@4.30.0(@cloudflare/workers-types@4.20250628.0))(yaml@2.8.0)':
dependencies:
'@babel/core': 7.27.7
'@babel/generator': 7.27.5
@@ -13346,7 +13383,7 @@ snapshots:
vite-node: 3.2.4(@types/node@22.13.8)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
optionalDependencies:
typescript: 5.8.3
wrangler: 4.28.1(@cloudflare/workers-types@4.20250628.0)
wrangler: 4.30.0(@cloudflare/workers-types@4.20250628.0)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -13672,9 +13709,9 @@ snapshots:
fflate: 0.7.4
string.prototype.codepointat: 0.2.1
'@simplewebauthn/browser@13.1.0': {}
'@simplewebauthn/browser@13.1.2': {}
'@simplewebauthn/server@13.1.1':
'@simplewebauthn/server@13.1.2':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
@@ -14126,6 +14163,10 @@ snapshots:
dependencies:
'@babel/types': 7.28.2
'@types/buffer-from@1.1.3':
dependencies:
'@types/node': 22.15.29
'@types/canvas-confetti@1.9.0': {}
'@types/chai@5.2.2':
@@ -14239,6 +14280,8 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/pako@1.0.7': {}
'@types/pg-pool@2.0.6':
dependencies:
'@types/pg': 8.6.1
@@ -14815,20 +14858,20 @@ snapshots:
base64-js@1.5.1: {}
better-auth@1.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
better-auth@1.3.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.25.67):
dependencies:
'@better-auth/utils': 0.2.5
'@better-auth/utils': 0.2.6
'@better-fetch/fetch': 1.1.18
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.8.0
'@simplewebauthn/browser': 13.1.0
'@simplewebauthn/server': 13.1.1
'@simplewebauthn/browser': 13.1.2
'@simplewebauthn/server': 13.1.2
better-call: 1.0.13
defu: 6.1.4
jose: 5.10.0
kysely: 0.28.2
kysely: 0.28.5
nanostores: 0.11.4
zod: 4.0.15
zod: 3.25.67
optionalDependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@@ -15216,6 +15259,12 @@ snapshots:
cron-schedule@5.0.4: {}
cross-fetch@3.2.0:
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -15502,13 +15551,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250628.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(better-sqlite3@11.10.0)(kysely@0.28.2)(postgres@3.4.5):
drizzle-orm@0.43.1(@cloudflare/workers-types@4.20250628.0)(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(better-sqlite3@11.10.0)(kysely@0.28.5)(postgres@3.4.5):
optionalDependencies:
'@cloudflare/workers-types': 4.20250628.0
'@opentelemetry/api': 1.9.0
'@types/pg': 8.6.1
better-sqlite3: 11.10.0
kysely: 0.28.2
kysely: 0.28.5
postgres: 3.4.5
dub@0.64.2(@modelcontextprotocol/sdk@1.15.1)(zod@3.25.67):
@@ -15741,6 +15790,8 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
es6-promise@4.2.8: {}
esbuild-register@3.6.0(esbuild@0.25.5):
dependencies:
debug: 4.4.1
@@ -17116,7 +17167,7 @@ snapshots:
kysely@0.27.6: {}
kysely@0.28.2: {}
kysely@0.28.5: {}
leac@0.6.0: {}
@@ -17231,6 +17282,8 @@ snapshots:
lodash@4.17.21: {}
loglevel@1.9.2: {}
long@5.3.2: {}
longest-streak@3.1.0: {}
@@ -17595,7 +17648,7 @@ snapshots:
- bufferutil
- utf-8-validate
miniflare@4.20250803.0:
miniflare@4.20250813.1:
dependencies:
'@cspotcode/source-map-support': 0.8.1
acorn: 8.14.0
@@ -17605,7 +17658,7 @@ snapshots:
sharp: 0.33.5
stoppable: 1.1.0
undici: 7.11.0
workerd: 1.20250803.0
workerd: 1.20250813.0
ws: 8.18.0
youch: 4.1.0-beta.10
zod: 3.22.3
@@ -18013,6 +18066,8 @@ snapshots:
pako@1.0.11: {}
pako@2.1.0: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -18382,7 +18437,7 @@ snapshots:
proto-list@1.2.4: {}
protobufjs@7.5.3:
protobufjs@7.5.4:
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/base64': 1.1.2
@@ -19429,7 +19484,7 @@ snapshots:
dependencies:
copy-anything: 3.0.5
supports-color@10.1.0: {}
supports-color@10.2.0: {}
supports-color@7.2.0:
dependencies:
@@ -20252,13 +20307,13 @@ snapshots:
'@cloudflare/workerd-linux-arm64': 1.20250617.0
'@cloudflare/workerd-windows-64': 1.20250617.0
workerd@1.20250803.0:
workerd@1.20250813.0:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20250803.0
'@cloudflare/workerd-darwin-arm64': 1.20250803.0
'@cloudflare/workerd-linux-64': 1.20250803.0
'@cloudflare/workerd-linux-arm64': 1.20250803.0
'@cloudflare/workerd-windows-64': 1.20250803.0
'@cloudflare/workerd-darwin-64': 1.20250813.0
'@cloudflare/workerd-darwin-arm64': 1.20250813.0
'@cloudflare/workerd-linux-64': 1.20250813.0
'@cloudflare/workerd-linux-arm64': 1.20250813.0
'@cloudflare/workerd-windows-64': 1.20250813.0
workers-og@0.0.25:
dependencies:
@@ -20267,16 +20322,16 @@ snapshots:
satori: 0.10.14
yoga-wasm-web: 0.3.3
wrangler@4.28.1(@cloudflare/workers-types@4.20250628.0):
wrangler@4.30.0(@cloudflare/workers-types@4.20250628.0):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
'@cloudflare/unenv-preset': 2.6.0(unenv@2.0.0-rc.19)(workerd@1.20250803.0)
'@cloudflare/unenv-preset': 2.6.1(unenv@2.0.0-rc.19)(workerd@1.20250813.0)
blake3-wasm: 2.1.5
esbuild: 0.25.4
miniflare: 4.20250803.0
miniflare: 4.20250813.1
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.19
workerd: 1.20250803.0
workerd: 1.20250813.0
optionalDependencies:
'@cloudflare/workers-types': 4.20250628.0
fsevents: 2.3.3
@@ -20385,8 +20440,6 @@ snapshots:
zod@3.25.67: {}
zod@4.0.15: {}
zustand@4.5.7(@types/react@19.0.10)(react@19.1.0):
dependencies:
use-sync-external-store: 1.5.0(react@19.1.0)