@validors/sdk 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { BatchQueue } from \"./core\";\nimport type { ValidorsOptions, SdkEvent } from \"./types\";\n\nexport type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\nexport { BatchQueue } from \"./core\";\n\n// ---------------------------------------------------------------------------\n// Module-level default queue instance (created lazily)\n// ---------------------------------------------------------------------------\n\nlet _defaultQueue: BatchQueue | null = null;\n\nfunction getOrCreateQueue(options: ValidorsOptions): BatchQueue {\n if (!_defaultQueue) {\n _defaultQueue = new BatchQueue(options);\n\n if (typeof process !== \"undefined\" && typeof process.on === \"function\") {\n process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\n });\n }\n }\n return _defaultQueue;\n}\n\n/** Flush all pending events immediately. Call before process exit if needed. */\nexport async function shutdown(): Promise<void> {\n if (_defaultQueue) {\n await _defaultQueue.shutdown();\n _defaultQueue = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Express middleware\n// ---------------------------------------------------------------------------\n\ntype ExpressRequest = {\n method: string;\n path: string;\n route?: { path?: string };\n};\n\ntype ExpressResponse = {\n statusCode: number;\n locals: Record<string, unknown>;\n on(event: string, listener: () => void): void;\n};\n\ntype NextFunction = () => void;\n\nexport type ExpressRequestHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void;\n\n/**\n * Express middleware that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * app.use(validors.express({ apiKey: 'vl_...' }));\n */\nexport function express(options: ValidorsOptions): ExpressRequestHandler {\n const queue = getOrCreateQueue(options);\n\n return function validorsMiddleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n ) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const durationMs = Date.now() - startTime;\n const route = req.route?.path ?? req.path ?? \"unknown\";\n const error =\n typeof res.locals.error === \"string\" ? res.locals.error : undefined;\n\n const event: SdkEvent = {\n route,\n method: req.method,\n status_code: res.statusCode,\n duration_ms: durationMs,\n ...(error ? { error } : {}),\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n });\n\n next();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Fastify plugin\n// ---------------------------------------------------------------------------\n\ntype FastifyInstance = {\n addHook(\n name: \"onResponse\",\n fn: (request: FastifyRequest, reply: FastifyReply) => Promise<void>\n ): void;\n};\n\ntype FastifyRequest = {\n method: string;\n routerPath?: string;\n routeOptions?: { url?: string };\n};\n\ntype FastifyReply = {\n statusCode: number;\n elapsedTime?: number;\n};\n\ntype FastifyPlugin = (\n fastify: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n) => void;\n\n/**\n * Fastify plugin that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * await fastify.register(validors.fastify, { apiKey: 'vl_...' });\n */\nexport function fastify(options: ValidorsOptions): FastifyPlugin {\n const queue = getOrCreateQueue(options);\n\n return function validorsFastifyPlugin(\n fastifyInstance: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n ) {\n fastifyInstance.addHook(\n \"onResponse\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n const route =\n request.routerPath ??\n request.routeOptions?.url ??\n \"unknown\";\n\n const durationMs =\n typeof reply.elapsedTime === \"number\"\n ? Math.round(reply.elapsedTime)\n : 0;\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: reply.statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n );\n\n done();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Generic / Hono / Edge middleware\n// ---------------------------------------------------------------------------\n\ntype GenericHandler = (\n request: Request,\n env?: unknown\n) => Promise<Response>;\n\n/**\n * Generic fetch-based middleware wrapper for Hono, Cloudflare Workers,\n * and other edge runtimes.\n *\n * Returns a handler that you can use as a Hono middleware or wrap your\n * existing fetch handler with.\n *\n * @example\n * // Hono\n * import { Hono } from 'hono';\n * import { createMiddleware } from '@validors/sdk';\n *\n * const app = new Hono();\n * app.use('*', createMiddleware({ apiKey: 'vl_...' }));\n */\nexport function createMiddleware(options: ValidorsOptions) {\n const queue = new BatchQueue(options);\n\n return async function validorsHonoMiddleware(\n c: {\n req: { method: string; url: string; routePath?: string };\n res?: { status?: number };\n },\n next: () => Promise<void>\n ) {\n const startTime = Date.now();\n\n try {\n await next();\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = c.req.routePath ?? new URL(c.req.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const statusCode = c.res?.status ?? 200;\n\n const event: SdkEvent = {\n route,\n method: c.req.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\n/**\n * Wrap a generic fetch handler (for edge runtimes like Cloudflare Workers).\n *\n * @example\n * import { wrapFetch } from '@validors/sdk';\n *\n * export default {\n * fetch: wrapFetch({ apiKey: 'vl_...' }, async (request, env) => {\n * return new Response('Hello!');\n * })\n * };\n */\nexport function wrapFetch(\n options: ValidorsOptions,\n handler: GenericHandler\n): GenericHandler {\n const queue = new BatchQueue(options);\n\n return async function wrappedFetch(request: Request, env?: unknown) {\n const startTime = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(request, env);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = new URL(request.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\nexport default BatchQueue;\n\n// ---------------------------------------------------------------------------\n// Next.js App Router\n// ---------------------------------------------------------------------------\n\ntype NextJsRequest = Request & {\n nextUrl?: { pathname: string };\n};\n\ntype NextJsResponse = Response;\n\ntype NextJsHandler<C = unknown> = {\n bivarianceHack(req: NextJsRequest, context?: C): Promise<NextJsResponse> | NextJsResponse;\n}[\"bivarianceHack\"];\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\";\ntype RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;\n\n/**\n * Next.js App Router integration.\n *\n * Returns two helpers:\n * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)\n * - `monitor(path, handler)` — wraps a single handler\n *\n * @example\n * // lib/validors.ts\n * import * as validors from '@validors/sdk';\n * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { NextResponse } from 'next/server';\n * import { route } from '@/lib/validors';\n *\n * export const { GET, POST } = route('/api/users', {\n * GET: async (req) => NextResponse.json({ users: [] }),\n * POST: async (req) => NextResponse.json({}, { status: 201 }),\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function wrap<C>(\n routePath: string | undefined,\n handler: NextJsHandler<C>\n ): NextJsHandler<C> {\n return async (req: NextJsRequest, context?: C) => {\n const start = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(req, context);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n let resolvedRoute: string;\n if (routePath) {\n resolvedRoute = routePath;\n } else {\n try {\n resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n resolvedRoute = \"unknown\";\n }\n }\n\n queue.push({\n route: resolvedRoute,\n method: req.method,\n status_code: statusCode,\n duration_ms: Date.now() - start,\n timestamp: new Date().toISOString(),\n });\n }\n };\n }\n\n /** Wrap all handlers in a route file at once. One call per file. */\n function route<H extends RouteHandlers>(routePath: string, handlers: H): H {\n return Object.fromEntries(\n Object.entries(handlers).map(([method, handler]) => [\n method,\n wrap(routePath, handler as NextJsHandler),\n ])\n ) as H;\n }\n\n /** Wrap a single handler. */\n function monitor<C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(\n routeOrHandler: string | NextJsHandler<C>,\n maybeHandler?: NextJsHandler<C>\n ): NextJsHandler<C> {\n const routePath = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n return wrap(routePath, handler);\n }\n\n return { route, monitor };\n}\n","import type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\n\nconst DEFAULT_INGEST_URL = \"https://validors.app/api/ingest\";\nconst DOCS_URL = \"https://validors.com\";\n\nfunction getErrorMessage(status: number, apiKey: string): string {\n const keyHint = `(key: ${apiKey.slice(0, 8)}...)`;\n\n switch (status) {\n case 401:\n return (\n `[validors] Invalid or missing API key ${keyHint}. ` +\n `Check your key in the dashboard → ${DOCS_URL}`\n );\n case 403:\n return (\n `[validors] Access denied ${keyHint}. Your plan may not support this. ` +\n `Upgrade at ${DOCS_URL}`\n );\n case 400:\n return (\n `[validors] Bad request — the event payload was rejected. ` +\n `This is likely a bug. Please report it at ${DOCS_URL}`\n );\n case 413:\n return (\n `[validors] Payload too large. ` +\n `Reduce your batch size or flush more frequently. See ${DOCS_URL}`\n );\n case 429:\n return (\n `[validors] Rate limit exceeded ${keyHint}. ` +\n `Events are being dropped. Reduce flush frequency or upgrade at ${DOCS_URL}`\n );\n case 500:\n case 502:\n case 503:\n case 504:\n return (\n `[validors] Server error (HTTP ${status}). ` +\n `Events may have been lost. Check status at ${DOCS_URL}`\n );\n default:\n return (\n `[validors] Unexpected response (HTTP ${status}). ` +\n `See ${DOCS_URL} for help.`\n );\n }\n}\nconst DEFAULT_FLUSH_INTERVAL = 5000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class BatchQueue {\n private queue: SdkEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private options: Required<ValidorsOptions>;\n\n constructor(options: ValidorsOptions) {\n this.options = {\n apiKey: options.apiKey,\n ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,\n flushInterval: options.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: options.debug ?? false,\n ignoreRoutes: options.ignoreRoutes ?? [],\n environment: options.environment ?? process.env.NODE_ENV ?? \"production\",\n };\n\n this.timer = setInterval(() => {\n this.flush().catch(() => undefined);\n }, this.options.flushInterval);\n\n // Allow process to exit even if timer is active\n if (this.timer.unref) {\n this.timer.unref();\n }\n }\n\n push(event: SdkEvent): void {\n // Check ignored routes\n for (const ignored of this.options.ignoreRoutes) {\n if (event.route === ignored || event.route.startsWith(ignored)) return;\n }\n\n this.queue.push(event);\n\n if (this.options.debug) {\n console.log(\n `[validors] queued event: ${event.method} ${event.route} ${event.status_code} ${event.duration_ms}ms (queue=${this.queue.length})`\n );\n }\n\n if (this.queue.length >= this.options.batchSize) {\n this.flush().catch(() => undefined);\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n if (this.options.debug) {\n console.log(`[validors] flushing ${batch.length} events to ${this.options.ingestUrl}`);\n }\n\n const payload: IngestPayload = { events: batch };\n\n try {\n const response = await fetch(this.options.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.options.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n if (this.options.debug) {\n const data = await response.json() as { received?: number };\n console.log(`[validors] flushed successfully: received=${data.received}`);\n }\n } else {\n const message = getErrorMessage(response.status, this.options.apiKey);\n console.warn(message);\n }\n } catch (err) {\n // Never throw — silently swallow network errors\n if (this.options.debug) {\n console.error(\"[validors] network error during flush:\", err);\n }\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { BatchQueue } from \"./core\";\nimport type { ValidorsOptions, SdkEvent } from \"./types\";\n\nexport type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\nexport { BatchQueue } from \"./core\";\n\n// ---------------------------------------------------------------------------\n// Module-level default queue instance (created lazily)\n// ---------------------------------------------------------------------------\n\nlet _defaultQueue: BatchQueue | null = null;\n\nfunction getOrCreateQueue(options: ValidorsOptions): BatchQueue {\n if (!_defaultQueue) {\n _defaultQueue = new BatchQueue(options);\n\n if (typeof process !== \"undefined\" && typeof process.on === \"function\") {\n process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\n });\n }\n }\n return _defaultQueue;\n}\n\n/** Flush all pending events immediately. Call before process exit if needed. */\nexport async function shutdown(): Promise<void> {\n if (_defaultQueue) {\n await _defaultQueue.shutdown();\n _defaultQueue = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Express middleware\n// ---------------------------------------------------------------------------\n\ntype ExpressRequest = {\n method: string;\n path: string;\n route?: { path?: string };\n};\n\ntype ExpressResponse = {\n statusCode: number;\n locals: Record<string, unknown>;\n on(event: string, listener: () => void): void;\n};\n\ntype NextFunction = () => void;\n\nexport type ExpressRequestHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void;\n\n/**\n * Express middleware that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * app.use(validors.express({ apiKey: 'vl_...' }));\n */\nexport function express(options: ValidorsOptions): ExpressRequestHandler {\n const queue = getOrCreateQueue(options);\n\n return function validorsMiddleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n ) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const durationMs = Date.now() - startTime;\n const route = req.route?.path ?? req.path ?? \"unknown\";\n const error =\n typeof res.locals.error === \"string\" ? res.locals.error : undefined;\n\n const event: SdkEvent = {\n route,\n method: req.method,\n status_code: res.statusCode,\n duration_ms: durationMs,\n ...(error ? { error } : {}),\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n });\n\n next();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Fastify plugin\n// ---------------------------------------------------------------------------\n\ntype FastifyInstance = {\n addHook(\n name: \"onResponse\",\n fn: (request: FastifyRequest, reply: FastifyReply) => Promise<void>\n ): void;\n};\n\ntype FastifyRequest = {\n method: string;\n routerPath?: string;\n routeOptions?: { url?: string };\n};\n\ntype FastifyReply = {\n statusCode: number;\n elapsedTime?: number;\n};\n\ntype FastifyPlugin = (\n fastify: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n) => void;\n\n/**\n * Fastify plugin that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * await fastify.register(validors.fastify, { apiKey: 'vl_...' });\n */\nexport function fastify(options: ValidorsOptions): FastifyPlugin {\n const queue = getOrCreateQueue(options);\n\n return function validorsFastifyPlugin(\n fastifyInstance: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n ) {\n fastifyInstance.addHook(\n \"onResponse\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n const route =\n request.routerPath ??\n request.routeOptions?.url ??\n \"unknown\";\n\n const durationMs =\n typeof reply.elapsedTime === \"number\"\n ? Math.round(reply.elapsedTime)\n : 0;\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: reply.statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n );\n\n done();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Generic / Hono / Edge middleware\n// ---------------------------------------------------------------------------\n\ntype GenericHandler = (\n request: Request,\n env?: unknown\n) => Promise<Response>;\n\n/**\n * Generic fetch-based middleware wrapper for Hono, Cloudflare Workers,\n * and other edge runtimes.\n *\n * Returns a handler that you can use as a Hono middleware or wrap your\n * existing fetch handler with.\n *\n * @example\n * // Hono\n * import { Hono } from 'hono';\n * import { createMiddleware } from '@validors/sdk';\n *\n * const app = new Hono();\n * app.use('*', createMiddleware({ apiKey: 'vl_...' }));\n */\nexport function createMiddleware(options: ValidorsOptions) {\n const queue = new BatchQueue(options);\n\n return async function validorsHonoMiddleware(\n c: {\n req: { method: string; url: string; routePath?: string };\n res?: { status?: number };\n },\n next: () => Promise<void>\n ) {\n const startTime = Date.now();\n\n try {\n await next();\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = c.req.routePath ?? new URL(c.req.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const statusCode = c.res?.status ?? 200;\n\n const event: SdkEvent = {\n route,\n method: c.req.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\n/**\n * Wrap a generic fetch handler (for edge runtimes like Cloudflare Workers).\n *\n * @example\n * import { wrapFetch } from '@validors/sdk';\n *\n * export default {\n * fetch: wrapFetch({ apiKey: 'vl_...' }, async (request, env) => {\n * return new Response('Hello!');\n * })\n * };\n */\nexport function wrapFetch(\n options: ValidorsOptions,\n handler: GenericHandler\n): GenericHandler {\n const queue = new BatchQueue(options);\n\n return async function wrappedFetch(request: Request, env?: unknown) {\n const startTime = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(request, env);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = new URL(request.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\nexport default BatchQueue;\n\n// ---------------------------------------------------------------------------\n// Next.js App Router\n// ---------------------------------------------------------------------------\n\ntype NextJsRequest = Request & {\n nextUrl?: { pathname: string };\n};\n\ntype NextJsResponse = Response;\n\ntype NextJsHandler<C = unknown> = {\n bivarianceHack(req: NextJsRequest, context?: C): Promise<NextJsResponse> | NextJsResponse;\n}[\"bivarianceHack\"];\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\";\ntype RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;\n\n/**\n * Next.js App Router integration.\n *\n * Returns two helpers:\n * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)\n * - `monitor(path, handler)` — wraps a single handler\n *\n * @example\n * // lib/validors.ts\n * import * as validors from '@validors/sdk';\n * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { NextResponse } from 'next/server';\n * import { route } from '@/lib/validors';\n *\n * export const { GET, POST } = route('/api/users', {\n * GET: async (req) => NextResponse.json({ users: [] }),\n * POST: async (req) => NextResponse.json({}, { status: 201 }),\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function wrap<C>(\n routePath: string | undefined,\n handler: NextJsHandler<C>\n ): NextJsHandler<C> {\n return async (req: NextJsRequest, context?: C) => {\n const start = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(req, context);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n let resolvedRoute: string;\n if (routePath) {\n resolvedRoute = routePath;\n } else {\n try {\n resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n resolvedRoute = \"unknown\";\n }\n }\n\n queue.push({\n route: resolvedRoute,\n method: req.method,\n status_code: statusCode,\n duration_ms: Date.now() - start,\n timestamp: new Date().toISOString(),\n });\n\n // Await the flush so the event is guaranteed to be delivered before\n // the function returns — works on Node.js, serverless, and edge runtimes.\n await queue.flush().catch(() => undefined);\n }\n };\n }\n\n /** Wrap all handlers in a route file at once. One call per file. */\n function route<H extends RouteHandlers>(routePath: string, handlers: H): H {\n return Object.fromEntries(\n Object.entries(handlers).map(([method, handler]) => [\n method,\n wrap(routePath, handler as NextJsHandler),\n ])\n ) as H;\n }\n\n /** Wrap a single handler. */\n function monitor<C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(\n routeOrHandler: string | NextJsHandler<C>,\n maybeHandler?: NextJsHandler<C>\n ): NextJsHandler<C> {\n const routePath = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n return wrap(routePath, handler);\n }\n\n return { route, monitor };\n}\n","import type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\n\nconst DEFAULT_INGEST_URL = \"https://validors.app/api/ingest\";\nconst DOCS_URL = \"https://validors.com\";\n\nfunction getErrorMessage(status: number, apiKey: string): string {\n const keyHint = `(key: ${apiKey.slice(0, 8)}...)`;\n\n switch (status) {\n case 401:\n return (\n `[validors] Invalid or missing API key ${keyHint}. ` +\n `Check your key in the dashboard → ${DOCS_URL}`\n );\n case 403:\n return (\n `[validors] Access denied ${keyHint}. Your plan may not support this. ` +\n `Upgrade at ${DOCS_URL}`\n );\n case 400:\n return (\n `[validors] Bad request — the event payload was rejected. ` +\n `This is likely a bug. Please report it at ${DOCS_URL}`\n );\n case 413:\n return (\n `[validors] Payload too large. ` +\n `Reduce your batch size or flush more frequently. See ${DOCS_URL}`\n );\n case 429:\n return (\n `[validors] Rate limit exceeded ${keyHint}. ` +\n `Events are being dropped. Reduce flush frequency or upgrade at ${DOCS_URL}`\n );\n case 500:\n case 502:\n case 503:\n case 504:\n return (\n `[validors] Server error (HTTP ${status}). ` +\n `Events may have been lost. Check status at ${DOCS_URL}`\n );\n default:\n return (\n `[validors] Unexpected response (HTTP ${status}). ` +\n `See ${DOCS_URL} for help.`\n );\n }\n}\nconst DEFAULT_FLUSH_INTERVAL = 5000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class BatchQueue {\n private queue: SdkEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private options: Required<ValidorsOptions>;\n\n constructor(options: ValidorsOptions) {\n this.options = {\n apiKey: options.apiKey,\n ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,\n flushInterval: options.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: options.debug ?? false,\n ignoreRoutes: options.ignoreRoutes ?? [],\n environment: options.environment ?? process.env.NODE_ENV ?? \"production\",\n };\n\n this.timer = setInterval(() => {\n this.flush().catch(() => undefined);\n }, this.options.flushInterval);\n\n // Allow process to exit even if timer is active\n if (this.timer.unref) {\n this.timer.unref();\n }\n }\n\n push(event: SdkEvent): void {\n // Check ignored routes\n for (const ignored of this.options.ignoreRoutes) {\n if (event.route === ignored || event.route.startsWith(ignored)) return;\n }\n\n this.queue.push(event);\n\n if (this.options.debug) {\n console.log(\n `[validors] queued event: ${event.method} ${event.route} ${event.status_code} ${event.duration_ms}ms (queue=${this.queue.length})`\n );\n }\n\n if (this.queue.length >= this.options.batchSize) {\n this.flush().catch(() => undefined);\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n if (this.options.debug) {\n console.log(`[validors] flushing ${batch.length} events to ${this.options.ingestUrl}`);\n }\n\n const payload: IngestPayload = { events: batch };\n\n try {\n const response = await fetch(this.options.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.options.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n if (this.options.debug) {\n const data = await response.json() as { received?: number };\n console.log(`[validors] flushed successfully: received=${data.received}`);\n }\n } else {\n const message = getErrorMessage(response.status, this.options.apiKey);\n console.warn(message);\n }\n } catch (err) {\n // Never throw — silently swallow network errors\n if (this.options.debug) {\n console.error(\"[validors] network error during flush:\", err);\n }\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["import type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\n\nconst DEFAULT_INGEST_URL = \"https://validors.app/api/ingest\";\nconst DOCS_URL = \"https://validors.com\";\n\nfunction getErrorMessage(status: number, apiKey: string): string {\n const keyHint = `(key: ${apiKey.slice(0, 8)}...)`;\n\n switch (status) {\n case 401:\n return (\n `[validors] Invalid or missing API key ${keyHint}. ` +\n `Check your key in the dashboard → ${DOCS_URL}`\n );\n case 403:\n return (\n `[validors] Access denied ${keyHint}. Your plan may not support this. ` +\n `Upgrade at ${DOCS_URL}`\n );\n case 400:\n return (\n `[validors] Bad request — the event payload was rejected. ` +\n `This is likely a bug. Please report it at ${DOCS_URL}`\n );\n case 413:\n return (\n `[validors] Payload too large. ` +\n `Reduce your batch size or flush more frequently. See ${DOCS_URL}`\n );\n case 429:\n return (\n `[validors] Rate limit exceeded ${keyHint}. ` +\n `Events are being dropped. Reduce flush frequency or upgrade at ${DOCS_URL}`\n );\n case 500:\n case 502:\n case 503:\n case 504:\n return (\n `[validors] Server error (HTTP ${status}). ` +\n `Events may have been lost. Check status at ${DOCS_URL}`\n );\n default:\n return (\n `[validors] Unexpected response (HTTP ${status}). ` +\n `See ${DOCS_URL} for help.`\n );\n }\n}\nconst DEFAULT_FLUSH_INTERVAL = 5000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class BatchQueue {\n private queue: SdkEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private options: Required<ValidorsOptions>;\n\n constructor(options: ValidorsOptions) {\n this.options = {\n apiKey: options.apiKey,\n ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,\n flushInterval: options.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: options.debug ?? false,\n ignoreRoutes: options.ignoreRoutes ?? [],\n environment: options.environment ?? process.env.NODE_ENV ?? \"production\",\n };\n\n this.timer = setInterval(() => {\n this.flush().catch(() => undefined);\n }, this.options.flushInterval);\n\n // Allow process to exit even if timer is active\n if (this.timer.unref) {\n this.timer.unref();\n }\n }\n\n push(event: SdkEvent): void {\n // Check ignored routes\n for (const ignored of this.options.ignoreRoutes) {\n if (event.route === ignored || event.route.startsWith(ignored)) return;\n }\n\n this.queue.push(event);\n\n if (this.options.debug) {\n console.log(\n `[validors] queued event: ${event.method} ${event.route} ${event.status_code} ${event.duration_ms}ms (queue=${this.queue.length})`\n );\n }\n\n if (this.queue.length >= this.options.batchSize) {\n this.flush().catch(() => undefined);\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n if (this.options.debug) {\n console.log(`[validors] flushing ${batch.length} events to ${this.options.ingestUrl}`);\n }\n\n const payload: IngestPayload = { events: batch };\n\n try {\n const response = await fetch(this.options.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.options.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n if (this.options.debug) {\n const data = await response.json() as { received?: number };\n console.log(`[validors] flushed successfully: received=${data.received}`);\n }\n } else {\n const message = getErrorMessage(response.status, this.options.apiKey);\n console.warn(message);\n }\n } catch (err) {\n // Never throw — silently swallow network errors\n if (this.options.debug) {\n console.error(\"[validors] network error during flush:\", err);\n }\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n","import { BatchQueue } from \"./core\";\nimport type { ValidorsOptions, SdkEvent } from \"./types\";\n\nexport type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\nexport { BatchQueue } from \"./core\";\n\n// ---------------------------------------------------------------------------\n// Module-level default queue instance (created lazily)\n// ---------------------------------------------------------------------------\n\nlet _defaultQueue: BatchQueue | null = null;\n\nfunction getOrCreateQueue(options: ValidorsOptions): BatchQueue {\n if (!_defaultQueue) {\n _defaultQueue = new BatchQueue(options);\n\n if (typeof process !== \"undefined\" && typeof process.on === \"function\") {\n process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\n });\n }\n }\n return _defaultQueue;\n}\n\n/** Flush all pending events immediately. Call before process exit if needed. */\nexport async function shutdown(): Promise<void> {\n if (_defaultQueue) {\n await _defaultQueue.shutdown();\n _defaultQueue = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Express middleware\n// ---------------------------------------------------------------------------\n\ntype ExpressRequest = {\n method: string;\n path: string;\n route?: { path?: string };\n};\n\ntype ExpressResponse = {\n statusCode: number;\n locals: Record<string, unknown>;\n on(event: string, listener: () => void): void;\n};\n\ntype NextFunction = () => void;\n\nexport type ExpressRequestHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void;\n\n/**\n * Express middleware that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * app.use(validors.express({ apiKey: 'vl_...' }));\n */\nexport function express(options: ValidorsOptions): ExpressRequestHandler {\n const queue = getOrCreateQueue(options);\n\n return function validorsMiddleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n ) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const durationMs = Date.now() - startTime;\n const route = req.route?.path ?? req.path ?? \"unknown\";\n const error =\n typeof res.locals.error === \"string\" ? res.locals.error : undefined;\n\n const event: SdkEvent = {\n route,\n method: req.method,\n status_code: res.statusCode,\n duration_ms: durationMs,\n ...(error ? { error } : {}),\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n });\n\n next();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Fastify plugin\n// ---------------------------------------------------------------------------\n\ntype FastifyInstance = {\n addHook(\n name: \"onResponse\",\n fn: (request: FastifyRequest, reply: FastifyReply) => Promise<void>\n ): void;\n};\n\ntype FastifyRequest = {\n method: string;\n routerPath?: string;\n routeOptions?: { url?: string };\n};\n\ntype FastifyReply = {\n statusCode: number;\n elapsedTime?: number;\n};\n\ntype FastifyPlugin = (\n fastify: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n) => void;\n\n/**\n * Fastify plugin that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * await fastify.register(validors.fastify, { apiKey: 'vl_...' });\n */\nexport function fastify(options: ValidorsOptions): FastifyPlugin {\n const queue = getOrCreateQueue(options);\n\n return function validorsFastifyPlugin(\n fastifyInstance: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n ) {\n fastifyInstance.addHook(\n \"onResponse\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n const route =\n request.routerPath ??\n request.routeOptions?.url ??\n \"unknown\";\n\n const durationMs =\n typeof reply.elapsedTime === \"number\"\n ? Math.round(reply.elapsedTime)\n : 0;\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: reply.statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n );\n\n done();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Generic / Hono / Edge middleware\n// ---------------------------------------------------------------------------\n\ntype GenericHandler = (\n request: Request,\n env?: unknown\n) => Promise<Response>;\n\n/**\n * Generic fetch-based middleware wrapper for Hono, Cloudflare Workers,\n * and other edge runtimes.\n *\n * Returns a handler that you can use as a Hono middleware or wrap your\n * existing fetch handler with.\n *\n * @example\n * // Hono\n * import { Hono } from 'hono';\n * import { createMiddleware } from '@validors/sdk';\n *\n * const app = new Hono();\n * app.use('*', createMiddleware({ apiKey: 'vl_...' }));\n */\nexport function createMiddleware(options: ValidorsOptions) {\n const queue = new BatchQueue(options);\n\n return async function validorsHonoMiddleware(\n c: {\n req: { method: string; url: string; routePath?: string };\n res?: { status?: number };\n },\n next: () => Promise<void>\n ) {\n const startTime = Date.now();\n\n try {\n await next();\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = c.req.routePath ?? new URL(c.req.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const statusCode = c.res?.status ?? 200;\n\n const event: SdkEvent = {\n route,\n method: c.req.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\n/**\n * Wrap a generic fetch handler (for edge runtimes like Cloudflare Workers).\n *\n * @example\n * import { wrapFetch } from '@validors/sdk';\n *\n * export default {\n * fetch: wrapFetch({ apiKey: 'vl_...' }, async (request, env) => {\n * return new Response('Hello!');\n * })\n * };\n */\nexport function wrapFetch(\n options: ValidorsOptions,\n handler: GenericHandler\n): GenericHandler {\n const queue = new BatchQueue(options);\n\n return async function wrappedFetch(request: Request, env?: unknown) {\n const startTime = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(request, env);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = new URL(request.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\nexport default BatchQueue;\n\n// ---------------------------------------------------------------------------\n// Next.js App Router\n// ---------------------------------------------------------------------------\n\ntype NextJsRequest = Request & {\n nextUrl?: { pathname: string };\n};\n\ntype NextJsResponse = Response;\n\ntype NextJsHandler<C = unknown> = {\n bivarianceHack(req: NextJsRequest, context?: C): Promise<NextJsResponse> | NextJsResponse;\n}[\"bivarianceHack\"];\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\";\ntype RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;\n\n/**\n * Next.js App Router integration.\n *\n * Returns two helpers:\n * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)\n * - `monitor(path, handler)` — wraps a single handler\n *\n * @example\n * // lib/validors.ts\n * import * as validors from '@validors/sdk';\n * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { NextResponse } from 'next/server';\n * import { route } from '@/lib/validors';\n *\n * export const { GET, POST } = route('/api/users', {\n * GET: async (req) => NextResponse.json({ users: [] }),\n * POST: async (req) => NextResponse.json({}, { status: 201 }),\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function wrap<C>(\n routePath: string | undefined,\n handler: NextJsHandler<C>\n ): NextJsHandler<C> {\n return async (req: NextJsRequest, context?: C) => {\n const start = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(req, context);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n let resolvedRoute: string;\n if (routePath) {\n resolvedRoute = routePath;\n } else {\n try {\n resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n resolvedRoute = \"unknown\";\n }\n }\n\n queue.push({\n route: resolvedRoute,\n method: req.method,\n status_code: statusCode,\n duration_ms: Date.now() - start,\n timestamp: new Date().toISOString(),\n });\n }\n };\n }\n\n /** Wrap all handlers in a route file at once. One call per file. */\n function route<H extends RouteHandlers>(routePath: string, handlers: H): H {\n return Object.fromEntries(\n Object.entries(handlers).map(([method, handler]) => [\n method,\n wrap(routePath, handler as NextJsHandler),\n ])\n ) as H;\n }\n\n /** Wrap a single handler. */\n function monitor<C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(\n routeOrHandler: string | NextJsHandler<C>,\n maybeHandler?: NextJsHandler<C>\n ): NextJsHandler<C> {\n const routePath = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n return wrap(routePath, handler);\n }\n\n return { route, monitor };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/index.ts"],"sourcesContent":["import type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\n\nconst DEFAULT_INGEST_URL = \"https://validors.app/api/ingest\";\nconst DOCS_URL = \"https://validors.com\";\n\nfunction getErrorMessage(status: number, apiKey: string): string {\n const keyHint = `(key: ${apiKey.slice(0, 8)}...)`;\n\n switch (status) {\n case 401:\n return (\n `[validors] Invalid or missing API key ${keyHint}. ` +\n `Check your key in the dashboard → ${DOCS_URL}`\n );\n case 403:\n return (\n `[validors] Access denied ${keyHint}. Your plan may not support this. ` +\n `Upgrade at ${DOCS_URL}`\n );\n case 400:\n return (\n `[validors] Bad request — the event payload was rejected. ` +\n `This is likely a bug. Please report it at ${DOCS_URL}`\n );\n case 413:\n return (\n `[validors] Payload too large. ` +\n `Reduce your batch size or flush more frequently. See ${DOCS_URL}`\n );\n case 429:\n return (\n `[validors] Rate limit exceeded ${keyHint}. ` +\n `Events are being dropped. Reduce flush frequency or upgrade at ${DOCS_URL}`\n );\n case 500:\n case 502:\n case 503:\n case 504:\n return (\n `[validors] Server error (HTTP ${status}). ` +\n `Events may have been lost. Check status at ${DOCS_URL}`\n );\n default:\n return (\n `[validors] Unexpected response (HTTP ${status}). ` +\n `See ${DOCS_URL} for help.`\n );\n }\n}\nconst DEFAULT_FLUSH_INTERVAL = 5000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class BatchQueue {\n private queue: SdkEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private options: Required<ValidorsOptions>;\n\n constructor(options: ValidorsOptions) {\n this.options = {\n apiKey: options.apiKey,\n ingestUrl: options.ingestUrl ?? DEFAULT_INGEST_URL,\n flushInterval: options.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: options.debug ?? false,\n ignoreRoutes: options.ignoreRoutes ?? [],\n environment: options.environment ?? process.env.NODE_ENV ?? \"production\",\n };\n\n this.timer = setInterval(() => {\n this.flush().catch(() => undefined);\n }, this.options.flushInterval);\n\n // Allow process to exit even if timer is active\n if (this.timer.unref) {\n this.timer.unref();\n }\n }\n\n push(event: SdkEvent): void {\n // Check ignored routes\n for (const ignored of this.options.ignoreRoutes) {\n if (event.route === ignored || event.route.startsWith(ignored)) return;\n }\n\n this.queue.push(event);\n\n if (this.options.debug) {\n console.log(\n `[validors] queued event: ${event.method} ${event.route} ${event.status_code} ${event.duration_ms}ms (queue=${this.queue.length})`\n );\n }\n\n if (this.queue.length >= this.options.batchSize) {\n this.flush().catch(() => undefined);\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n if (this.options.debug) {\n console.log(`[validors] flushing ${batch.length} events to ${this.options.ingestUrl}`);\n }\n\n const payload: IngestPayload = { events: batch };\n\n try {\n const response = await fetch(this.options.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.options.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (response.ok) {\n if (this.options.debug) {\n const data = await response.json() as { received?: number };\n console.log(`[validors] flushed successfully: received=${data.received}`);\n }\n } else {\n const message = getErrorMessage(response.status, this.options.apiKey);\n console.warn(message);\n }\n } catch (err) {\n // Never throw — silently swallow network errors\n if (this.options.debug) {\n console.error(\"[validors] network error during flush:\", err);\n }\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n","import { BatchQueue } from \"./core\";\nimport type { ValidorsOptions, SdkEvent } from \"./types\";\n\nexport type { ValidorsOptions, SdkEvent, IngestPayload } from \"./types\";\nexport { BatchQueue } from \"./core\";\n\n// ---------------------------------------------------------------------------\n// Module-level default queue instance (created lazily)\n// ---------------------------------------------------------------------------\n\nlet _defaultQueue: BatchQueue | null = null;\n\nfunction getOrCreateQueue(options: ValidorsOptions): BatchQueue {\n if (!_defaultQueue) {\n _defaultQueue = new BatchQueue(options);\n\n if (typeof process !== \"undefined\" && typeof process.on === \"function\") {\n process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\n });\n }\n }\n return _defaultQueue;\n}\n\n/** Flush all pending events immediately. Call before process exit if needed. */\nexport async function shutdown(): Promise<void> {\n if (_defaultQueue) {\n await _defaultQueue.shutdown();\n _defaultQueue = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Express middleware\n// ---------------------------------------------------------------------------\n\ntype ExpressRequest = {\n method: string;\n path: string;\n route?: { path?: string };\n};\n\ntype ExpressResponse = {\n statusCode: number;\n locals: Record<string, unknown>;\n on(event: string, listener: () => void): void;\n};\n\ntype NextFunction = () => void;\n\nexport type ExpressRequestHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void;\n\n/**\n * Express middleware that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * app.use(validors.express({ apiKey: 'vl_...' }));\n */\nexport function express(options: ValidorsOptions): ExpressRequestHandler {\n const queue = getOrCreateQueue(options);\n\n return function validorsMiddleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n ) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const durationMs = Date.now() - startTime;\n const route = req.route?.path ?? req.path ?? \"unknown\";\n const error =\n typeof res.locals.error === \"string\" ? res.locals.error : undefined;\n\n const event: SdkEvent = {\n route,\n method: req.method,\n status_code: res.statusCode,\n duration_ms: durationMs,\n ...(error ? { error } : {}),\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n });\n\n next();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Fastify plugin\n// ---------------------------------------------------------------------------\n\ntype FastifyInstance = {\n addHook(\n name: \"onResponse\",\n fn: (request: FastifyRequest, reply: FastifyReply) => Promise<void>\n ): void;\n};\n\ntype FastifyRequest = {\n method: string;\n routerPath?: string;\n routeOptions?: { url?: string };\n};\n\ntype FastifyReply = {\n statusCode: number;\n elapsedTime?: number;\n};\n\ntype FastifyPlugin = (\n fastify: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n) => void;\n\n/**\n * Fastify plugin that tracks request latency and status codes.\n *\n * @example\n * import * as validors from '@validors/sdk';\n * await fastify.register(validors.fastify, { apiKey: 'vl_...' });\n */\nexport function fastify(options: ValidorsOptions): FastifyPlugin {\n const queue = getOrCreateQueue(options);\n\n return function validorsFastifyPlugin(\n fastifyInstance: FastifyInstance,\n _opts: Record<string, unknown>,\n done: () => void\n ) {\n fastifyInstance.addHook(\n \"onResponse\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n const route =\n request.routerPath ??\n request.routeOptions?.url ??\n \"unknown\";\n\n const durationMs =\n typeof reply.elapsedTime === \"number\"\n ? Math.round(reply.elapsedTime)\n : 0;\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: reply.statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n );\n\n done();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Generic / Hono / Edge middleware\n// ---------------------------------------------------------------------------\n\ntype GenericHandler = (\n request: Request,\n env?: unknown\n) => Promise<Response>;\n\n/**\n * Generic fetch-based middleware wrapper for Hono, Cloudflare Workers,\n * and other edge runtimes.\n *\n * Returns a handler that you can use as a Hono middleware or wrap your\n * existing fetch handler with.\n *\n * @example\n * // Hono\n * import { Hono } from 'hono';\n * import { createMiddleware } from '@validors/sdk';\n *\n * const app = new Hono();\n * app.use('*', createMiddleware({ apiKey: 'vl_...' }));\n */\nexport function createMiddleware(options: ValidorsOptions) {\n const queue = new BatchQueue(options);\n\n return async function validorsHonoMiddleware(\n c: {\n req: { method: string; url: string; routePath?: string };\n res?: { status?: number };\n },\n next: () => Promise<void>\n ) {\n const startTime = Date.now();\n\n try {\n await next();\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = c.req.routePath ?? new URL(c.req.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const statusCode = c.res?.status ?? 200;\n\n const event: SdkEvent = {\n route,\n method: c.req.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\n/**\n * Wrap a generic fetch handler (for edge runtimes like Cloudflare Workers).\n *\n * @example\n * import { wrapFetch } from '@validors/sdk';\n *\n * export default {\n * fetch: wrapFetch({ apiKey: 'vl_...' }, async (request, env) => {\n * return new Response('Hello!');\n * })\n * };\n */\nexport function wrapFetch(\n options: ValidorsOptions,\n handler: GenericHandler\n): GenericHandler {\n const queue = new BatchQueue(options);\n\n return async function wrappedFetch(request: Request, env?: unknown) {\n const startTime = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(request, env);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n const durationMs = Date.now() - startTime;\n\n let route: string;\n try {\n route = new URL(request.url).pathname ?? \"unknown\";\n } catch {\n route = \"unknown\";\n }\n\n const event: SdkEvent = {\n route,\n method: request.method,\n status_code: statusCode,\n duration_ms: durationMs,\n timestamp: new Date().toISOString(),\n };\n\n queue.push(event);\n }\n };\n}\n\nexport default BatchQueue;\n\n// ---------------------------------------------------------------------------\n// Next.js App Router\n// ---------------------------------------------------------------------------\n\ntype NextJsRequest = Request & {\n nextUrl?: { pathname: string };\n};\n\ntype NextJsResponse = Response;\n\ntype NextJsHandler<C = unknown> = {\n bivarianceHack(req: NextJsRequest, context?: C): Promise<NextJsResponse> | NextJsResponse;\n}[\"bivarianceHack\"];\n\ntype HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\";\ntype RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;\n\n/**\n * Next.js App Router integration.\n *\n * Returns two helpers:\n * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)\n * - `monitor(path, handler)` — wraps a single handler\n *\n * @example\n * // lib/validors.ts\n * import * as validors from '@validors/sdk';\n * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { NextResponse } from 'next/server';\n * import { route } from '@/lib/validors';\n *\n * export const { GET, POST } = route('/api/users', {\n * GET: async (req) => NextResponse.json({ users: [] }),\n * POST: async (req) => NextResponse.json({}, { status: 201 }),\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function wrap<C>(\n routePath: string | undefined,\n handler: NextJsHandler<C>\n ): NextJsHandler<C> {\n return async (req: NextJsRequest, context?: C) => {\n const start = Date.now();\n let statusCode = 200;\n\n try {\n const response = await handler(req, context);\n statusCode = response.status;\n return response;\n } catch (err) {\n statusCode = 500;\n throw err;\n } finally {\n let resolvedRoute: string;\n if (routePath) {\n resolvedRoute = routePath;\n } else {\n try {\n resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n resolvedRoute = \"unknown\";\n }\n }\n\n queue.push({\n route: resolvedRoute,\n method: req.method,\n status_code: statusCode,\n duration_ms: Date.now() - start,\n timestamp: new Date().toISOString(),\n });\n\n // Await the flush so the event is guaranteed to be delivered before\n // the function returns — works on Node.js, serverless, and edge runtimes.\n await queue.flush().catch(() => undefined);\n }\n };\n }\n\n /** Wrap all handlers in a route file at once. One call per file. */\n function route<H extends RouteHandlers>(routePath: string, handlers: H): H {\n return Object.fromEntries(\n Object.entries(handlers).map(([method, handler]) => [\n method,\n wrap(routePath, handler as NextJsHandler),\n ])\n ) as H;\n }\n\n /** Wrap a single handler. */\n function monitor<C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(handler: NextJsHandler<C>): NextJsHandler<C>;\n function monitor<C>(\n routeOrHandler: string | NextJsHandler<C>,\n maybeHandler?: NextJsHandler<C>\n ): NextJsHandler<C> {\n const routePath = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n return wrap(routePath, handler);\n }\n\n return { route, monitor };\n}\n"],"mappings":";AAEA,IAAM,qBAAqB;AAC3B,IAAM,WAAW;AAEjB,SAAS,gBAAgB,QAAgB,QAAwB;AAC/D,QAAM,UAAU,SAAS,OAAO,MAAM,GAAG,CAAC,CAAC;AAE3C,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aACE,yCAAyC,OAAO,4CACX,QAAQ;AAAA,IAEjD,KAAK;AACH,aACE,4BAA4B,OAAO,gDACrB,QAAQ;AAAA,IAE1B,KAAK;AACH,aACE,2GAC6C,QAAQ;AAAA,IAEzD,KAAK;AACH,aACE,sFACwD,QAAQ;AAAA,IAEpE,KAAK;AACH,aACE,kCAAkC,OAAO,oEACyB,QAAQ;AAAA,IAE9E,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aACE,iCAAiC,MAAM,iDACO,QAAQ;AAAA,IAE1D;AACE,aACE,wCAAwC,MAAM,UACvC,QAAQ;AAAA,EAErB;AACF;AACA,IAAM,yBAAyB;AAC/B,IAAM,qBAAqB;AAEpB,IAAM,aAAN,MAAiB;AAAA,EAKtB,YAAY,SAA0B;AAJtC,SAAQ,QAAoB,CAAC;AAC7B,SAAQ,QAA+C;AAIrD,SAAK,UAAU;AAAA,MACb,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ,aAAa;AAAA,MAChC,eAAe,QAAQ,iBAAiB;AAAA,MACxC,WAAW,QAAQ,aAAa;AAAA,MAChC,OAAO,QAAQ,SAAS;AAAA,MACxB,cAAc,QAAQ,gBAAgB,CAAC;AAAA,MACvC,aAAa,QAAQ,eAAe,QAAQ,IAAI,YAAY;AAAA,IAC9D;AAEA,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IACpC,GAAG,KAAK,QAAQ,aAAa;AAG7B,QAAI,KAAK,MAAM,OAAO;AACpB,WAAK,MAAM,MAAM;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,KAAK,OAAuB;AAE1B,eAAW,WAAW,KAAK,QAAQ,cAAc;AAC/C,UAAI,MAAM,UAAU,WAAW,MAAM,MAAM,WAAW,OAAO,EAAG;AAAA,IAClE;AAEA,SAAK,MAAM,KAAK,KAAK;AAErB,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ;AAAA,QACN,4BAA4B,MAAM,MAAM,IAAI,MAAM,KAAK,IAAI,MAAM,WAAW,IAAI,MAAM,WAAW,aAAa,KAAK,MAAM,MAAM;AAAA,MACjI;AAAA,IACF;AAEA,QAAI,KAAK,MAAM,UAAU,KAAK,QAAQ,WAAW;AAC/C,WAAK,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IACpC;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,MAAM,WAAW,EAAG;AAE7B,UAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,MAAM,MAAM;AAEpD,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ,IAAI,uBAAuB,MAAM,MAAM,cAAc,KAAK,QAAQ,SAAS,EAAE;AAAA,IACvF;AAEA,UAAM,UAAyB,EAAE,QAAQ,MAAM;AAE/C,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,QAAQ,WAAW;AAAA,QACnD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK,QAAQ;AAAA,QAC5B;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC9B,CAAC;AAED,UAAI,SAAS,IAAI;AACf,YAAI,KAAK,QAAQ,OAAO;AACtB,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,kBAAQ,IAAI,6CAA6C,KAAK,QAAQ,EAAE;AAAA,QAC1E;AAAA,MACF,OAAO;AACL,cAAM,UAAU,gBAAgB,SAAS,QAAQ,KAAK,QAAQ,MAAM;AACpE,gBAAQ,KAAK,OAAO;AAAA,MACtB;AAAA,IACF,SAAS,KAAK;AAEZ,UAAI,KAAK,QAAQ,OAAO;AACtB,gBAAQ,MAAM,0CAA0C,GAAG;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AACA,UAAM,KAAK,MAAM;AAAA,EACnB;AACF;;;ACpIA,IAAI,gBAAmC;AAEvC,SAAS,iBAAiB,SAAsC;AAC9D,MAAI,CAAC,eAAe;AAClB,oBAAgB,IAAI,WAAW,OAAO;AAEtC,QAAI,OAAO,YAAY,eAAe,OAAO,QAAQ,OAAO,YAAY;AACtE,cAAQ,GAAG,cAAc,MAAM;AAC7B,uBAAe,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,MAC9C,CAAC;AAED,cAAQ,GAAG,WAAW,MAAM;AAC1B,uBAAe,SAAS,EAAE,MAAM,MAAM,MAAS;AAAA,MACjD,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAsB,WAA0B;AAC9C,MAAI,eAAe;AACjB,UAAM,cAAc,SAAS;AAC7B,oBAAgB;AAAA,EAClB;AACF;AAiCO,SAAS,QAAQ,SAAiD;AACvE,QAAM,QAAQ,iBAAiB,OAAO;AAEtC,SAAO,SAAS,mBACd,KACA,KACA,MACA;AACA,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI,GAAG,UAAU,MAAM;AACrB,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAM,QAAQ,IAAI,OAAO,QAAQ,IAAI,QAAQ;AAC7C,YAAM,QACJ,OAAO,IAAI,OAAO,UAAU,WAAW,IAAI,OAAO,QAAQ;AAE5D,YAAM,QAAkB;AAAA,QACtB;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,aAAa,IAAI;AAAA,QACjB,aAAa;AAAA,QACb,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QACzB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAEA,YAAM,KAAK,KAAK;AAAA,IAClB,CAAC;AAED,SAAK;AAAA,EACP;AACF;AAqCO,SAAS,QAAQ,SAAyC;AAC/D,QAAM,QAAQ,iBAAiB,OAAO;AAEtC,SAAO,SAAS,sBACd,iBACA,OACA,MACA;AACA,oBAAgB;AAAA,MACd;AAAA,MACA,OAAO,SAAyB,UAAwB;AACtD,cAAM,QACJ,QAAQ,cACR,QAAQ,cAAc,OACtB;AAEF,cAAM,aACJ,OAAO,MAAM,gBAAgB,WACzB,KAAK,MAAM,MAAM,WAAW,IAC5B;AAEN,cAAM,QAAkB;AAAA,UACtB;AAAA,UACA,QAAQ,QAAQ;AAAA,UAChB,aAAa,MAAM;AAAA,UACnB,aAAa;AAAA,UACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAEA,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF;AAEA,SAAK;AAAA,EACP;AACF;AA0BO,SAAS,iBAAiB,SAA0B;AACzD,QAAM,QAAQ,IAAI,WAAW,OAAO;AAEpC,SAAO,eAAe,uBACpB,GAIA,MACA;AACA,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AACF,YAAM,KAAK;AAAA,IACb,UAAE;AACA,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,UAAI;AACJ,UAAI;AACF,gBAAQ,EAAE,IAAI,aAAa,IAAI,IAAI,EAAE,IAAI,GAAG,EAAE,YAAY;AAAA,MAC5D,QAAQ;AACN,gBAAQ;AAAA,MACV;AAEA,YAAM,aAAa,EAAE,KAAK,UAAU;AAEpC,YAAM,QAAkB;AAAA,QACtB;AAAA,QACA,QAAQ,EAAE,IAAI;AAAA,QACd,aAAa;AAAA,QACb,aAAa;AAAA,QACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAEA,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AACF;AAcO,SAAS,UACd,SACA,SACgB;AAChB,QAAM,QAAQ,IAAI,WAAW,OAAO;AAEpC,SAAO,eAAe,aAAa,SAAkB,KAAe;AAClE,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,aAAa;AAEjB,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,SAAS,GAAG;AAC3C,mBAAa,SAAS;AACtB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,mBAAa;AACb,YAAM;AAAA,IACR,UAAE;AACA,YAAM,aAAa,KAAK,IAAI,IAAI;AAEhC,UAAI;AACJ,UAAI;AACF,gBAAQ,IAAI,IAAI,QAAQ,GAAG,EAAE,YAAY;AAAA,MAC3C,QAAQ;AACN,gBAAQ;AAAA,MACV;AAEA,YAAM,QAAkB;AAAA,QACtB;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,aAAa;AAAA,QACb,aAAa;AAAA,QACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAEA,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;AAwCR,SAAS,OAAO,SAA0B;AAC/C,QAAM,QAAQ,iBAAiB,OAAO;AAEtC,WAAS,KACP,WACA,SACkB;AAClB,WAAO,OAAO,KAAoB,YAAgB;AAChD,YAAM,QAAQ,KAAK,IAAI;AACvB,UAAI,aAAa;AAEjB,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ,KAAK,OAAO;AAC3C,qBAAa,SAAS;AACtB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,qBAAa;AACb,cAAM;AAAA,MACR,UAAE;AACA,YAAI;AACJ,YAAI,WAAW;AACb,0BAAgB;AAAA,QAClB,OAAO;AACL,cAAI;AACF,4BAAgB,IAAI,SAAS,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE;AAAA,UAC5D,QAAQ;AACN,4BAAgB;AAAA,UAClB;AAAA,QACF;AAEA,cAAM,KAAK;AAAA,UACT,OAAO;AAAA,UACP,QAAQ,IAAI;AAAA,UACZ,aAAa;AAAA,UACb,aAAa,KAAK,IAAI,IAAI;AAAA,UAC1B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAID,cAAM,MAAM,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAGA,WAAS,MAA+B,WAAmB,UAAgB;AACzE,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC,CAAC,QAAQ,OAAO,MAAM;AAAA,QAClD;AAAA,QACA,KAAK,WAAW,OAAwB;AAAA,MAC1C,CAAC;AAAA,IACH;AAAA,EACF;AAKA,WAAS,QACP,gBACA,cACkB;AAClB,UAAM,YAAY,OAAO,mBAAmB,WAAW,iBAAiB;AACxE,UAAM,UAAU,OAAO,mBAAmB,aAAa,iBAAiB;AACxE,WAAO,KAAK,WAAW,OAAO;AAAA,EAChC;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;","names":[]}
|