mirror of
https://github.com/Mail-0/Zero.git
synced 2026-06-28 06:46:15 +00:00
Datadog Implementation (#1990)
This commit is contained in:
27
apps/mail/lib/trpc.ts
Normal file
27
apps/mail/lib/trpc.ts
Normal 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;
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -8,6 +8,8 @@ export type HonoVariables = {
|
||||
auth: Auth;
|
||||
sessionUser?: SessionUser;
|
||||
autumn?: Autumn;
|
||||
traceId?: string;
|
||||
requestId?: string;
|
||||
};
|
||||
|
||||
export type HonoContext = { Variables: HonoVariables; Bindings: ZeroEnv };
|
||||
|
||||
@@ -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;
|
||||
|
||||
206
apps/server/src/lib/datadog-service.ts
Normal file
206
apps/server/src/lib/datadog-service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
apps/server/src/lib/logging-service.ts
Normal file
117
apps/server/src/lib/logging-service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
240
apps/server/src/lib/trace-context.ts
Normal file
240
apps/server/src/lib/trace-context.ts
Normal 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();
|
||||
}
|
||||
245
apps/server/src/lib/trpc-logging.ts
Normal file
245
apps/server/src/lib/trpc-logging.ts
Normal 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;
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
45
apps/server/src/trpc/routes/logging.ts
Normal file
45
apps/server/src/trpc/routes/logging.ts
Normal 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);
|
||||
}),
|
||||
});
|
||||
@@ -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.',
|
||||
|
||||
70
apps/server/src/types/logging.ts
Normal file
70
apps/server/src/types/logging.ts
Normal 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;
|
||||
}
|
||||
@@ -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
237
pnpm-lock.yaml
generated
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user