@validors/sdk 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -116,6 +116,62 @@ All options are passed as a `ValidorsOptions` object to any of the middleware fa
116
116
  | `ignoreRoutes` | `string[]` | `[]` | Routes to exclude from tracking (exact match or prefix). E.g. `['/health']`. |
117
117
  | `environment` | `string` | `process.env.NODE_ENV` | Tag events with an environment label (`production`, `staging`, etc.). |
118
118
 
119
+ ### Next.js (App Router)
120
+
121
+ **Step 1** — create `lib/validors.ts` once in your project:
122
+
123
+ ```ts
124
+ import * as validors from '@validors/sdk';
125
+
126
+ export const { route } = validors.nextjs({
127
+ apiKey: process.env.VALIDORS_API_KEY!,
128
+ });
129
+ ```
130
+
131
+ **Step 2** — add one line per route file:
132
+
133
+ ```ts
134
+ // app/api/users/route.ts
135
+ import { NextResponse } from 'next/server';
136
+ import { route } from '@/lib/validors';
137
+
138
+ export const { GET, POST } = route('/api/users', {
139
+ GET: async (req) => {
140
+ const users = await getUsers();
141
+ return NextResponse.json({ users });
142
+ },
143
+ POST: async (req) => {
144
+ const body = await req.json();
145
+ const user = await createUser(body);
146
+ return NextResponse.json({ user }, { status: 201 });
147
+ },
148
+ });
149
+ ```
150
+
151
+ **With dynamic route params:**
152
+
153
+ ```ts
154
+ // app/api/users/[id]/route.ts
155
+ import { NextResponse } from 'next/server';
156
+ import { route } from '@/lib/validors';
157
+
158
+ export const { GET, DELETE } = route('/api/users/[id]', {
159
+ GET: async (req, context) => {
160
+ const { id } = await (context as { params: Promise<{ id: string }> }).params;
161
+ const user = await getUserById(id);
162
+ if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
163
+ return NextResponse.json({ user });
164
+ },
165
+ DELETE: async (req, context) => {
166
+ const { id } = await (context as { params: Promise<{ id: string }> }).params;
167
+ await deleteUser(id);
168
+ return NextResponse.json({ success: true });
169
+ },
170
+ });
171
+ ```
172
+
173
+ > Passing the route name (e.g. `/api/users/[id]`) keeps the dashboard clean — without it, each unique ID shows up as a separate route.
174
+
119
175
  ---
120
176
 
121
177
  ## Verifying your integration
package/dist/index.d.mts CHANGED
@@ -123,4 +123,46 @@ declare function createMiddleware(options: ValidorsOptions): (c: {
123
123
  */
124
124
  declare function wrapFetch(options: ValidorsOptions, handler: GenericHandler): GenericHandler;
125
125
 
126
- export { BatchQueue, type ExpressRequestHandler, type IngestPayload, type SdkEvent, type ValidorsOptions, createMiddleware, BatchQueue as default, express, fastify, shutdown, wrapFetch };
126
+ type NextJsRequest = {
127
+ method: string;
128
+ nextUrl?: {
129
+ pathname: string;
130
+ };
131
+ url: string;
132
+ };
133
+ type NextJsResponse = {
134
+ status: number;
135
+ };
136
+ type NextJsHandler<C = unknown> = (req: NextJsRequest, context?: C) => Promise<NextJsResponse> | NextJsResponse;
137
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
138
+ type RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;
139
+ /**
140
+ * Next.js App Router integration.
141
+ *
142
+ * Returns two helpers:
143
+ * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)
144
+ * - `monitor(path, handler)` — wraps a single handler
145
+ *
146
+ * @example
147
+ * // lib/validors.ts
148
+ * import * as validors from '@validors/sdk';
149
+ * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });
150
+ *
151
+ * // app/api/users/route.ts
152
+ * import { NextResponse } from 'next/server';
153
+ * import { route } from '@/lib/validors';
154
+ *
155
+ * export const { GET, POST } = route('/api/users', {
156
+ * GET: async (req) => NextResponse.json({ users: [] }),
157
+ * POST: async (req) => NextResponse.json({}, { status: 201 }),
158
+ * });
159
+ */
160
+ declare function nextjs(options: ValidorsOptions): {
161
+ route: <H extends RouteHandlers>(routePath: string, handlers: H) => H;
162
+ monitor: {
163
+ <C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;
164
+ <C>(handler: NextJsHandler<C>): NextJsHandler<C>;
165
+ };
166
+ };
167
+
168
+ export { BatchQueue, type ExpressRequestHandler, type IngestPayload, type SdkEvent, type ValidorsOptions, createMiddleware, BatchQueue as default, express, fastify, nextjs, shutdown, wrapFetch };
package/dist/index.d.ts CHANGED
@@ -123,4 +123,46 @@ declare function createMiddleware(options: ValidorsOptions): (c: {
123
123
  */
124
124
  declare function wrapFetch(options: ValidorsOptions, handler: GenericHandler): GenericHandler;
125
125
 
126
- export { BatchQueue, type ExpressRequestHandler, type IngestPayload, type SdkEvent, type ValidorsOptions, createMiddleware, BatchQueue as default, express, fastify, shutdown, wrapFetch };
126
+ type NextJsRequest = {
127
+ method: string;
128
+ nextUrl?: {
129
+ pathname: string;
130
+ };
131
+ url: string;
132
+ };
133
+ type NextJsResponse = {
134
+ status: number;
135
+ };
136
+ type NextJsHandler<C = unknown> = (req: NextJsRequest, context?: C) => Promise<NextJsResponse> | NextJsResponse;
137
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
138
+ type RouteHandlers = Partial<Record<HttpMethod, NextJsHandler>>;
139
+ /**
140
+ * Next.js App Router integration.
141
+ *
142
+ * Returns two helpers:
143
+ * - `route(path, { GET, POST, ... })` — wraps all handlers in a file at once (recommended)
144
+ * - `monitor(path, handler)` — wraps a single handler
145
+ *
146
+ * @example
147
+ * // lib/validors.ts
148
+ * import * as validors from '@validors/sdk';
149
+ * export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });
150
+ *
151
+ * // app/api/users/route.ts
152
+ * import { NextResponse } from 'next/server';
153
+ * import { route } from '@/lib/validors';
154
+ *
155
+ * export const { GET, POST } = route('/api/users', {
156
+ * GET: async (req) => NextResponse.json({ users: [] }),
157
+ * POST: async (req) => NextResponse.json({}, { status: 201 }),
158
+ * });
159
+ */
160
+ declare function nextjs(options: ValidorsOptions): {
161
+ route: <H extends RouteHandlers>(routePath: string, handlers: H) => H;
162
+ monitor: {
163
+ <C>(routePath: string, handler: NextJsHandler<C>): NextJsHandler<C>;
164
+ <C>(handler: NextJsHandler<C>): NextJsHandler<C>;
165
+ };
166
+ };
167
+
168
+ export { BatchQueue, type ExpressRequestHandler, type IngestPayload, type SdkEvent, type ValidorsOptions, createMiddleware, BatchQueue as default, express, fastify, nextjs, shutdown, wrapFetch };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  default: () => index_default,
26
26
  express: () => express,
27
27
  fastify: () => fastify,
28
+ nextjs: () => nextjs,
28
29
  shutdown: () => shutdown,
29
30
  wrapFetch: () => wrapFetch
30
31
  });
@@ -251,12 +252,62 @@ function wrapFetch(options, handler) {
251
252
  };
252
253
  }
253
254
  var index_default = BatchQueue;
255
+ function nextjs(options) {
256
+ const queue = getOrCreateQueue(options);
257
+ function wrap(routePath, handler) {
258
+ return async (req, context) => {
259
+ const start = Date.now();
260
+ let statusCode = 200;
261
+ try {
262
+ const response = await handler(req, context);
263
+ statusCode = response.status;
264
+ return response;
265
+ } catch (err) {
266
+ statusCode = 500;
267
+ throw err;
268
+ } finally {
269
+ let resolvedRoute;
270
+ if (routePath) {
271
+ resolvedRoute = routePath;
272
+ } else {
273
+ try {
274
+ resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;
275
+ } catch {
276
+ resolvedRoute = "unknown";
277
+ }
278
+ }
279
+ queue.push({
280
+ route: resolvedRoute,
281
+ method: req.method,
282
+ status_code: statusCode,
283
+ duration_ms: Date.now() - start,
284
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
285
+ });
286
+ }
287
+ };
288
+ }
289
+ function route(routePath, handlers) {
290
+ return Object.fromEntries(
291
+ Object.entries(handlers).map(([method, handler]) => [
292
+ method,
293
+ wrap(routePath, handler)
294
+ ])
295
+ );
296
+ }
297
+ function monitor(routeOrHandler, maybeHandler) {
298
+ const routePath = typeof routeOrHandler === "string" ? routeOrHandler : void 0;
299
+ const handler = typeof routeOrHandler === "function" ? routeOrHandler : maybeHandler;
300
+ return wrap(routePath, handler);
301
+ }
302
+ return { route, monitor };
303
+ }
254
304
  // Annotate the CommonJS export names for ESM import in node:
255
305
  0 && (module.exports = {
256
306
  BatchQueue,
257
307
  createMiddleware,
258
308
  express,
259
309
  fastify,
310
+ nextjs,
260
311
  shutdown,
261
312
  wrapFetch
262
313
  });
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 process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\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","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 process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\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 = {\n method: string;\n nextUrl?: { pathname: string };\n url: string;\n};\n\ntype NextJsResponse = { status: number };\n\ntype NextJsHandler<C = unknown> = (\n req: NextJsRequest,\n context?: C\n) => Promise<NextJsResponse> | NextJsResponse;\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":[]}
package/dist/index.mjs CHANGED
@@ -219,12 +219,62 @@ function wrapFetch(options, handler) {
219
219
  };
220
220
  }
221
221
  var index_default = BatchQueue;
222
+ function nextjs(options) {
223
+ const queue = getOrCreateQueue(options);
224
+ function wrap(routePath, handler) {
225
+ return async (req, context) => {
226
+ const start = Date.now();
227
+ let statusCode = 200;
228
+ try {
229
+ const response = await handler(req, context);
230
+ statusCode = response.status;
231
+ return response;
232
+ } catch (err) {
233
+ statusCode = 500;
234
+ throw err;
235
+ } finally {
236
+ let resolvedRoute;
237
+ if (routePath) {
238
+ resolvedRoute = routePath;
239
+ } else {
240
+ try {
241
+ resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;
242
+ } catch {
243
+ resolvedRoute = "unknown";
244
+ }
245
+ }
246
+ queue.push({
247
+ route: resolvedRoute,
248
+ method: req.method,
249
+ status_code: statusCode,
250
+ duration_ms: Date.now() - start,
251
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
252
+ });
253
+ }
254
+ };
255
+ }
256
+ function route(routePath, handlers) {
257
+ return Object.fromEntries(
258
+ Object.entries(handlers).map(([method, handler]) => [
259
+ method,
260
+ wrap(routePath, handler)
261
+ ])
262
+ );
263
+ }
264
+ function monitor(routeOrHandler, maybeHandler) {
265
+ const routePath = typeof routeOrHandler === "string" ? routeOrHandler : void 0;
266
+ const handler = typeof routeOrHandler === "function" ? routeOrHandler : maybeHandler;
267
+ return wrap(routePath, handler);
268
+ }
269
+ return { route, monitor };
270
+ }
222
271
  export {
223
272
  BatchQueue,
224
273
  createMiddleware,
225
274
  index_default as default,
226
275
  express,
227
276
  fastify,
277
+ nextjs,
228
278
  shutdown,
229
279
  wrapFetch
230
280
  };
@@ -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 process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\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"],"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 process.on(\"beforeExit\", () => {\n _defaultQueue?.flush().catch(() => undefined);\n });\n\n process.on(\"SIGTERM\", () => {\n _defaultQueue?.shutdown().catch(() => undefined);\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 = {\n method: string;\n nextUrl?: { pathname: string };\n url: string;\n};\n\ntype NextJsResponse = { status: number };\n\ntype NextJsHandler<C = unknown> = (\n req: NextJsRequest,\n context?: C\n) => Promise<NextJsResponse> | NextJsResponse;\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validors/sdk",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Backend API monitoring SDK for Validors",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",