@validors/sdk 0.1.6 → 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 +31 -25
- package/dist/index.d.mts +19 -11
- package/dist/index.d.ts +19 -11
- package/dist/index.js +21 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,34 +118,33 @@ All options are passed as a `ValidorsOptions` object to any of the middleware fa
|
|
|
118
118
|
|
|
119
119
|
### Next.js (App Router)
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
**Step 1** — create `lib/monitor.ts`:
|
|
121
|
+
**Step 1** — create `lib/validors.ts` once in your project:
|
|
124
122
|
|
|
125
123
|
```ts
|
|
126
124
|
import * as validors from '@validors/sdk';
|
|
127
125
|
|
|
128
|
-
export const
|
|
126
|
+
export const { route } = validors.nextjs({
|
|
129
127
|
apiKey: process.env.VALIDORS_API_KEY!,
|
|
130
128
|
});
|
|
131
129
|
```
|
|
132
130
|
|
|
133
|
-
**Step 2** —
|
|
131
|
+
**Step 2** — add one line per route file:
|
|
134
132
|
|
|
135
133
|
```ts
|
|
136
134
|
// app/api/users/route.ts
|
|
137
135
|
import { NextResponse } from 'next/server';
|
|
138
|
-
import {
|
|
139
|
-
|
|
140
|
-
export const GET =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
},
|
|
149
148
|
});
|
|
150
149
|
```
|
|
151
150
|
|
|
@@ -153,18 +152,25 @@ export const POST = monitor('/api/users', async (req) => {
|
|
|
153
152
|
|
|
154
153
|
```ts
|
|
155
154
|
// app/api/users/[id]/route.ts
|
|
156
|
-
import {
|
|
157
|
-
import {
|
|
158
|
-
|
|
159
|
-
export const GET =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
},
|
|
164
170
|
});
|
|
165
171
|
```
|
|
166
172
|
|
|
167
|
-
>
|
|
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.
|
|
168
174
|
|
|
169
175
|
---
|
|
170
176
|
|
package/dist/index.d.mts
CHANGED
|
@@ -134,27 +134,35 @@ type NextJsResponse = {
|
|
|
134
134
|
status: number;
|
|
135
135
|
};
|
|
136
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>>;
|
|
137
139
|
/**
|
|
138
|
-
* Next.js App Router
|
|
140
|
+
* Next.js App Router integration.
|
|
139
141
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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
|
|
143
145
|
*
|
|
144
146
|
* @example
|
|
145
|
-
* // lib/
|
|
147
|
+
* // lib/validors.ts
|
|
146
148
|
* import * as validors from '@validors/sdk';
|
|
147
|
-
* export const
|
|
149
|
+
* export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });
|
|
148
150
|
*
|
|
149
151
|
* // app/api/users/route.ts
|
|
150
|
-
* import {
|
|
151
|
-
*
|
|
152
|
-
*
|
|
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 }),
|
|
153
158
|
* });
|
|
154
159
|
*/
|
|
155
160
|
declare function nextjs(options: ValidorsOptions): {
|
|
156
|
-
<
|
|
157
|
-
|
|
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
|
+
};
|
|
158
166
|
};
|
|
159
167
|
|
|
160
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
|
@@ -134,27 +134,35 @@ type NextJsResponse = {
|
|
|
134
134
|
status: number;
|
|
135
135
|
};
|
|
136
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>>;
|
|
137
139
|
/**
|
|
138
|
-
* Next.js App Router
|
|
140
|
+
* Next.js App Router integration.
|
|
139
141
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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
|
|
143
145
|
*
|
|
144
146
|
* @example
|
|
145
|
-
* // lib/
|
|
147
|
+
* // lib/validors.ts
|
|
146
148
|
* import * as validors from '@validors/sdk';
|
|
147
|
-
* export const
|
|
149
|
+
* export const { route } = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });
|
|
148
150
|
*
|
|
149
151
|
* // app/api/users/route.ts
|
|
150
|
-
* import {
|
|
151
|
-
*
|
|
152
|
-
*
|
|
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 }),
|
|
153
158
|
* });
|
|
154
159
|
*/
|
|
155
160
|
declare function nextjs(options: ValidorsOptions): {
|
|
156
|
-
<
|
|
157
|
-
|
|
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
|
+
};
|
|
158
166
|
};
|
|
159
167
|
|
|
160
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
|
@@ -254,9 +254,7 @@ function wrapFetch(options, handler) {
|
|
|
254
254
|
var index_default = BatchQueue;
|
|
255
255
|
function nextjs(options) {
|
|
256
256
|
const queue = getOrCreateQueue(options);
|
|
257
|
-
function
|
|
258
|
-
const explicitRoute = typeof routeOrHandler === "string" ? routeOrHandler : void 0;
|
|
259
|
-
const handler = typeof routeOrHandler === "function" ? routeOrHandler : maybeHandler;
|
|
257
|
+
function wrap(routePath, handler) {
|
|
260
258
|
return async (req, context) => {
|
|
261
259
|
const start = Date.now();
|
|
262
260
|
let statusCode = 200;
|
|
@@ -268,18 +266,18 @@ function nextjs(options) {
|
|
|
268
266
|
statusCode = 500;
|
|
269
267
|
throw err;
|
|
270
268
|
} finally {
|
|
271
|
-
let
|
|
272
|
-
if (
|
|
273
|
-
|
|
269
|
+
let resolvedRoute;
|
|
270
|
+
if (routePath) {
|
|
271
|
+
resolvedRoute = routePath;
|
|
274
272
|
} else {
|
|
275
273
|
try {
|
|
276
|
-
|
|
274
|
+
resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;
|
|
277
275
|
} catch {
|
|
278
|
-
|
|
276
|
+
resolvedRoute = "unknown";
|
|
279
277
|
}
|
|
280
278
|
}
|
|
281
279
|
queue.push({
|
|
282
|
-
route,
|
|
280
|
+
route: resolvedRoute,
|
|
283
281
|
method: req.method,
|
|
284
282
|
status_code: statusCode,
|
|
285
283
|
duration_ms: Date.now() - start,
|
|
@@ -288,7 +286,20 @@ function nextjs(options) {
|
|
|
288
286
|
}
|
|
289
287
|
};
|
|
290
288
|
}
|
|
291
|
-
|
|
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 };
|
|
292
303
|
}
|
|
293
304
|
// Annotate the CommonJS export names for ESM import in node:
|
|
294
305
|
0 && (module.exports = {
|
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\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\n/**\n * Next.js App Router route handler wrapper.\n *\n * Create a single `monitor` instance in a shared file and use it to wrap\n * your exported route handlers. Tracks latency, status codes, and errors\n * automatically — including uncaught exceptions (reported as 500).\n *\n * @example\n * // lib/monitor.ts\n * import * as validors from '@validors/sdk';\n * export const monitor = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { monitor } from '@/lib/monitor';\n * export const GET = monitor('/api/users', async (req) => {\n * return NextResponse.json({ users: [] });\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function monitor<C>(route: 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 explicitRoute = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n\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 route: string;\n if (explicitRoute) {\n route = explicitRoute;\n } else {\n try {\n route = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n route = \"unknown\";\n }\n }\n\n queue.push({\n route,\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 return 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 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
|
@@ -221,9 +221,7 @@ function wrapFetch(options, handler) {
|
|
|
221
221
|
var index_default = BatchQueue;
|
|
222
222
|
function nextjs(options) {
|
|
223
223
|
const queue = getOrCreateQueue(options);
|
|
224
|
-
function
|
|
225
|
-
const explicitRoute = typeof routeOrHandler === "string" ? routeOrHandler : void 0;
|
|
226
|
-
const handler = typeof routeOrHandler === "function" ? routeOrHandler : maybeHandler;
|
|
224
|
+
function wrap(routePath, handler) {
|
|
227
225
|
return async (req, context) => {
|
|
228
226
|
const start = Date.now();
|
|
229
227
|
let statusCode = 200;
|
|
@@ -235,18 +233,18 @@ function nextjs(options) {
|
|
|
235
233
|
statusCode = 500;
|
|
236
234
|
throw err;
|
|
237
235
|
} finally {
|
|
238
|
-
let
|
|
239
|
-
if (
|
|
240
|
-
|
|
236
|
+
let resolvedRoute;
|
|
237
|
+
if (routePath) {
|
|
238
|
+
resolvedRoute = routePath;
|
|
241
239
|
} else {
|
|
242
240
|
try {
|
|
243
|
-
|
|
241
|
+
resolvedRoute = req.nextUrl?.pathname ?? new URL(req.url).pathname;
|
|
244
242
|
} catch {
|
|
245
|
-
|
|
243
|
+
resolvedRoute = "unknown";
|
|
246
244
|
}
|
|
247
245
|
}
|
|
248
246
|
queue.push({
|
|
249
|
-
route,
|
|
247
|
+
route: resolvedRoute,
|
|
250
248
|
method: req.method,
|
|
251
249
|
status_code: statusCode,
|
|
252
250
|
duration_ms: Date.now() - start,
|
|
@@ -255,7 +253,20 @@ function nextjs(options) {
|
|
|
255
253
|
}
|
|
256
254
|
};
|
|
257
255
|
}
|
|
258
|
-
|
|
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 };
|
|
259
270
|
}
|
|
260
271
|
export {
|
|
261
272
|
BatchQueue,
|
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 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\n/**\n * Next.js App Router route handler wrapper.\n *\n * Create a single `monitor` instance in a shared file and use it to wrap\n * your exported route handlers. Tracks latency, status codes, and errors\n * automatically — including uncaught exceptions (reported as 500).\n *\n * @example\n * // lib/monitor.ts\n * import * as validors from '@validors/sdk';\n * export const monitor = validors.nextjs({ apiKey: process.env.VALIDORS_API_KEY! });\n *\n * // app/api/users/route.ts\n * import { monitor } from '@/lib/monitor';\n * export const GET = monitor('/api/users', async (req) => {\n * return NextResponse.json({ users: [] });\n * });\n */\nexport function nextjs(options: ValidorsOptions) {\n const queue = getOrCreateQueue(options);\n\n function monitor<C>(route: 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 explicitRoute = typeof routeOrHandler === \"string\" ? routeOrHandler : undefined;\n const handler = typeof routeOrHandler === \"function\" ? routeOrHandler : maybeHandler!;\n\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 route: string;\n if (explicitRoute) {\n route = explicitRoute;\n } else {\n try {\n route = req.nextUrl?.pathname ?? new URL(req.url).pathname;\n } catch {\n route = \"unknown\";\n }\n }\n\n queue.push({\n route,\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 return 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 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":[]}
|