@parsrun/server 0.1.27 → 0.1.29

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/pagination.ts","../../src/context.ts","../../src/utils/response.ts"],"sourcesContent":["/**\n * @parsrun/server - Pagination Utilities\n * Helpers for paginated API responses\n */\n\nimport type { HonoContext } from \"../context.js\";\n\n/**\n * Pagination parameters\n */\nexport interface PaginationParams {\n page: number;\n limit: number;\n offset: number;\n}\n\n/**\n * Pagination metadata\n */\nexport interface PaginationMeta {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/**\n * Paginated response\n */\nexport interface PaginatedResponse<T> {\n data: T[];\n pagination: PaginationMeta;\n}\n\n/**\n * Pagination options\n */\nexport interface PaginationOptions {\n /** Default page size */\n defaultLimit?: number;\n /** Maximum page size */\n maxLimit?: number;\n /** Page query parameter name */\n pageParam?: string;\n /** Limit query parameter name */\n limitParam?: string;\n}\n\nconst defaultOptions: Required<PaginationOptions> = {\n defaultLimit: 20,\n maxLimit: 100,\n pageParam: \"page\",\n limitParam: \"limit\",\n};\n\n/**\n * Parse pagination from request query\n *\n * @example\n * ```typescript\n * app.get('/users', async (c) => {\n * const { page, limit, offset } = parsePagination(c);\n *\n * const users = await db.query.users.findMany({\n * limit,\n * offset,\n * });\n *\n * const total = await db.query.users.count();\n *\n * return c.json(paginate(users, { page, limit, total }));\n * });\n * ```\n */\nexport function parsePagination(\n c: HonoContext,\n options: PaginationOptions = {}\n): PaginationParams {\n const opts = { ...defaultOptions, ...options };\n\n const pageStr = c.req.query(opts.pageParam);\n const limitStr = c.req.query(opts.limitParam);\n\n let page = pageStr ? parseInt(pageStr, 10) : 1;\n let limit = limitStr ? parseInt(limitStr, 10) : opts.defaultLimit;\n\n // Validate and clamp values\n if (isNaN(page) || page < 1) page = 1;\n if (isNaN(limit) || limit < 1) limit = opts.defaultLimit;\n if (limit > opts.maxLimit) limit = opts.maxLimit;\n\n const offset = (page - 1) * limit;\n\n return { page, limit, offset };\n}\n\n/**\n * Create pagination metadata\n */\nexport function createPaginationMeta(params: {\n page: number;\n limit: number;\n total: number;\n}): PaginationMeta {\n const { page, limit, total } = params;\n const totalPages = Math.ceil(total / limit);\n\n return {\n page,\n limit,\n total,\n totalPages,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n };\n}\n\n/**\n * Create paginated response\n *\n * @example\n * ```typescript\n * const users = await getUsers({ limit, offset });\n * const total = await countUsers();\n *\n * return c.json(paginate(users, { page, limit, total }));\n * ```\n */\nexport function paginate<T>(\n data: T[],\n params: { page: number; limit: number; total: number }\n): PaginatedResponse<T> {\n return {\n data,\n pagination: createPaginationMeta(params),\n };\n}\n\n/**\n * Cursor-based pagination params\n */\nexport interface CursorPaginationParams {\n cursor?: string | undefined;\n limit: number;\n direction: \"forward\" | \"backward\";\n}\n\n/**\n * Cursor pagination metadata\n */\nexport interface CursorPaginationMeta {\n cursor?: string | undefined;\n nextCursor?: string | undefined;\n prevCursor?: string | undefined;\n hasMore: boolean;\n limit: number;\n}\n\n/**\n * Cursor paginated response\n */\nexport interface CursorPaginatedResponse<T> {\n data: T[];\n pagination: CursorPaginationMeta;\n}\n\n/**\n * Parse cursor pagination from request\n *\n * @example\n * ```typescript\n * app.get('/feed', async (c) => {\n * const { cursor, limit, direction } = parseCursorPagination(c);\n *\n * const items = await db.query.posts.findMany({\n * where: cursor ? { id: { gt: cursor } } : undefined,\n * limit: limit + 1, // Fetch one extra to check hasMore\n * orderBy: { createdAt: 'desc' },\n * });\n *\n * return c.json(cursorPaginate(items, { cursor, limit }));\n * });\n * ```\n */\nexport function parseCursorPagination(\n c: HonoContext,\n options: PaginationOptions = {}\n): CursorPaginationParams {\n const opts = { ...defaultOptions, ...options };\n\n const cursor = c.req.query(\"cursor\") ?? undefined;\n const limitStr = c.req.query(opts.limitParam);\n const direction = c.req.query(\"direction\") === \"backward\" ? \"backward\" : \"forward\";\n\n let limit = limitStr ? parseInt(limitStr, 10) : opts.defaultLimit;\n if (isNaN(limit) || limit < 1) limit = opts.defaultLimit;\n if (limit > opts.maxLimit) limit = opts.maxLimit;\n\n return { cursor, limit, direction };\n}\n\n/**\n * Create cursor paginated response\n *\n * @example\n * ```typescript\n * // Fetch limit + 1 items\n * const items = await fetchItems(limit + 1);\n * return c.json(cursorPaginate(items, { cursor, limit }));\n * ```\n */\nexport function cursorPaginate<T extends { id: string }>(\n data: T[],\n params: { cursor?: string; limit: number }\n): CursorPaginatedResponse<T> {\n const { cursor, limit } = params;\n const hasMore = data.length > limit;\n\n // Remove the extra item if we have more\n const items = hasMore ? data.slice(0, limit) : data;\n\n const lastItem = items[items.length - 1];\n const firstItem = items[0];\n\n return {\n data: items,\n pagination: {\n cursor,\n nextCursor: hasMore && lastItem ? lastItem.id : undefined,\n prevCursor: cursor && firstItem ? firstItem.id : undefined,\n hasMore,\n limit,\n },\n };\n}\n\n/**\n * Add pagination headers to response\n */\nexport function setPaginationHeaders(\n c: HonoContext,\n meta: PaginationMeta\n): void {\n c.header(\"X-Total-Count\", String(meta.total));\n c.header(\"X-Total-Pages\", String(meta.totalPages));\n c.header(\"X-Page\", String(meta.page));\n c.header(\"X-Per-Page\", String(meta.limit));\n c.header(\"X-Has-Next\", String(meta.hasNext));\n c.header(\"X-Has-Prev\", String(meta.hasPrev));\n}\n","/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n","/**\n * @parsrun/server - Response Utilities\n * Helpers for API responses\n */\n\nimport type { HonoContext, ApiResponse } from \"../context.js\";\nimport { success, error } from \"../context.js\";\n\n/**\n * Send JSON success response\n *\n * @example\n * ```typescript\n * app.get('/users/:id', async (c) => {\n * const user = await getUser(c.req.param('id'));\n * return json(c, user);\n * });\n * ```\n */\nexport function json<T>(c: HonoContext, data: T, status = 200): Response {\n return c.json(success(data), status as 200);\n}\n\n/**\n * Send JSON success response with metadata\n *\n * @example\n * ```typescript\n * app.get('/users', async (c) => {\n * const { users, total } = await getUsers();\n * return jsonWithMeta(c, users, { total, page: 1, limit: 20 });\n * });\n * ```\n */\nexport function jsonWithMeta<T>(\n c: HonoContext,\n data: T,\n meta: ApiResponse[\"meta\"],\n status = 200\n): Response {\n return c.json(success(data, meta), status as 200);\n}\n\n/**\n * Send JSON error response\n *\n * @example\n * ```typescript\n * app.get('/users/:id', async (c) => {\n * const user = await getUser(c.req.param('id'));\n * if (!user) {\n * return jsonError(c, 'USER_NOT_FOUND', 'User not found', 404);\n * }\n * return json(c, user);\n * });\n * ```\n */\nexport function jsonError(\n c: HonoContext,\n code: string,\n message: string,\n status = 400,\n details?: Record<string, unknown>\n): Response {\n return c.json(error(code, message, details), status as 400);\n}\n\n/**\n * Send created response (201)\n */\nexport function created<T>(c: HonoContext, data: T, location?: string): Response {\n if (location) {\n c.header(\"Location\", location);\n }\n return c.json(success(data), 201);\n}\n\n/**\n * Send no content response (204)\n */\nexport function noContent(_c: HonoContext): Response {\n return new Response(null, { status: 204 });\n}\n\n/**\n * Send accepted response (202) - for async operations\n */\nexport function accepted<T>(c: HonoContext, data?: T): Response {\n if (data) {\n return c.json(success(data), 202);\n }\n return new Response(null, { status: 202 });\n}\n\n/**\n * Redirect response\n */\nexport function redirect(c: HonoContext, url: string, status: 301 | 302 | 303 | 307 | 308 = 302): Response {\n return c.redirect(url, status);\n}\n\n/**\n * Stream response helper\n *\n * @example\n * ```typescript\n * app.get('/stream', (c) => {\n * return stream(c, async (write) => {\n * for (let i = 0; i < 10; i++) {\n * await write(`data: ${i}\\n\\n`);\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * });\n * });\n * ```\n */\nexport function stream(\n _c: HonoContext,\n callback: (write: (chunk: string) => Promise<void>) => Promise<void>,\n options: {\n contentType?: string;\n headers?: Record<string, string>;\n } = {}\n): Response {\n const encoder = new TextEncoder();\n\n const readableStream = new ReadableStream({\n async start(controller) {\n const write = async (chunk: string) => {\n controller.enqueue(encoder.encode(chunk));\n };\n\n try {\n await callback(write);\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(readableStream, {\n headers: {\n \"Content-Type\": options.contentType ?? \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n ...options.headers,\n },\n });\n}\n\n/**\n * Server-Sent Events helper\n *\n * @example\n * ```typescript\n * app.get('/events', (c) => {\n * return sse(c, async (send) => {\n * for await (const event of eventStream) {\n * await send({ event: 'update', data: event });\n * }\n * });\n * });\n * ```\n */\nexport function sse(\n c: HonoContext,\n callback: (\n send: (event: { event?: string; data: unknown; id?: string; retry?: number }) => Promise<void>\n ) => Promise<void>\n): Response {\n return stream(c, async (write) => {\n const send = async (event: { event?: string; data: unknown; id?: string; retry?: number }) => {\n let message = \"\";\n\n if (event.id) {\n message += `id: ${event.id}\\n`;\n }\n\n if (event.event) {\n message += `event: ${event.event}\\n`;\n }\n\n if (event.retry) {\n message += `retry: ${event.retry}\\n`;\n }\n\n const data = typeof event.data === \"string\" ? event.data : JSON.stringify(event.data);\n message += `data: ${data}\\n\\n`;\n\n await write(message);\n };\n\n await callback(send);\n });\n}\n\n/**\n * File download response\n */\nexport function download(\n c: HonoContext,\n data: Uint8Array | ArrayBuffer | string,\n filename: string,\n contentType = \"application/octet-stream\"\n): Response {\n c.header(\"Content-Disposition\", `attachment; filename=\"${filename}\"`);\n c.header(\"Content-Type\", contentType);\n\n if (typeof data === \"string\") {\n return c.body(data);\n }\n\n return new Response(data, {\n headers: c.res.headers,\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/utils/pagination.ts","../../src/context.ts","../../src/utils/response.ts"],"sourcesContent":["/**\n * @parsrun/server - Pagination Utilities\n * Helpers for paginated API responses\n */\n\nimport type { HonoContext } from \"../context.js\";\n\n/**\n * Pagination parameters\n */\nexport interface PaginationParams {\n page: number;\n limit: number;\n offset: number;\n}\n\n/**\n * Pagination metadata\n */\nexport interface PaginationMeta {\n page: number;\n limit: number;\n total: number;\n totalPages: number;\n hasNext: boolean;\n hasPrev: boolean;\n}\n\n/**\n * Paginated response\n */\nexport interface PaginatedResponse<T> {\n data: T[];\n pagination: PaginationMeta;\n}\n\n/**\n * Pagination options\n */\nexport interface PaginationOptions {\n /** Default page size */\n defaultLimit?: number;\n /** Maximum page size */\n maxLimit?: number;\n /** Page query parameter name */\n pageParam?: string;\n /** Limit query parameter name */\n limitParam?: string;\n}\n\nconst defaultOptions: Required<PaginationOptions> = {\n defaultLimit: 20,\n maxLimit: 100,\n pageParam: \"page\",\n limitParam: \"limit\",\n};\n\n/**\n * Parse pagination from request query\n *\n * @example\n * ```typescript\n * app.get('/users', async (c) => {\n * const { page, limit, offset } = parsePagination(c);\n *\n * const users = await db.query.users.findMany({\n * limit,\n * offset,\n * });\n *\n * const total = await db.query.users.count();\n *\n * return c.json(paginate(users, { page, limit, total }));\n * });\n * ```\n */\nexport function parsePagination(\n c: HonoContext,\n options: PaginationOptions = {}\n): PaginationParams {\n const opts = { ...defaultOptions, ...options };\n\n const pageStr = c.req.query(opts.pageParam);\n const limitStr = c.req.query(opts.limitParam);\n\n let page = pageStr ? parseInt(pageStr, 10) : 1;\n let limit = limitStr ? parseInt(limitStr, 10) : opts.defaultLimit;\n\n // Validate and clamp values\n if (isNaN(page) || page < 1) page = 1;\n if (isNaN(limit) || limit < 1) limit = opts.defaultLimit;\n if (limit > opts.maxLimit) limit = opts.maxLimit;\n\n const offset = (page - 1) * limit;\n\n return { page, limit, offset };\n}\n\n/**\n * Create pagination metadata\n */\nexport function createPaginationMeta(params: {\n page: number;\n limit: number;\n total: number;\n}): PaginationMeta {\n const { page, limit, total } = params;\n const totalPages = Math.ceil(total / limit);\n\n return {\n page,\n limit,\n total,\n totalPages,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n };\n}\n\n/**\n * Create paginated response\n *\n * @example\n * ```typescript\n * const users = await getUsers({ limit, offset });\n * const total = await countUsers();\n *\n * return c.json(paginate(users, { page, limit, total }));\n * ```\n */\nexport function paginate<T>(\n data: T[],\n params: { page: number; limit: number; total: number }\n): PaginatedResponse<T> {\n return {\n data,\n pagination: createPaginationMeta(params),\n };\n}\n\n/**\n * Cursor-based pagination params\n */\nexport interface CursorPaginationParams {\n cursor?: string | undefined;\n limit: number;\n direction: \"forward\" | \"backward\";\n}\n\n/**\n * Cursor pagination metadata\n */\nexport interface CursorPaginationMeta {\n cursor?: string | undefined;\n nextCursor?: string | undefined;\n prevCursor?: string | undefined;\n hasMore: boolean;\n limit: number;\n}\n\n/**\n * Cursor paginated response\n */\nexport interface CursorPaginatedResponse<T> {\n data: T[];\n pagination: CursorPaginationMeta;\n}\n\n/**\n * Parse cursor pagination from request\n *\n * @example\n * ```typescript\n * app.get('/feed', async (c) => {\n * const { cursor, limit, direction } = parseCursorPagination(c);\n *\n * const items = await db.query.posts.findMany({\n * where: cursor ? { id: { gt: cursor } } : undefined,\n * limit: limit + 1, // Fetch one extra to check hasMore\n * orderBy: { createdAt: 'desc' },\n * });\n *\n * return c.json(cursorPaginate(items, { cursor, limit }));\n * });\n * ```\n */\nexport function parseCursorPagination(\n c: HonoContext,\n options: PaginationOptions = {}\n): CursorPaginationParams {\n const opts = { ...defaultOptions, ...options };\n\n const cursor = c.req.query(\"cursor\") ?? undefined;\n const limitStr = c.req.query(opts.limitParam);\n const direction = c.req.query(\"direction\") === \"backward\" ? \"backward\" : \"forward\";\n\n let limit = limitStr ? parseInt(limitStr, 10) : opts.defaultLimit;\n if (isNaN(limit) || limit < 1) limit = opts.defaultLimit;\n if (limit > opts.maxLimit) limit = opts.maxLimit;\n\n return { cursor, limit, direction };\n}\n\n/**\n * Create cursor paginated response\n *\n * @example\n * ```typescript\n * // Fetch limit + 1 items\n * const items = await fetchItems(limit + 1);\n * return c.json(cursorPaginate(items, { cursor, limit }));\n * ```\n */\nexport function cursorPaginate<T extends { id: string }>(\n data: T[],\n params: { cursor?: string; limit: number }\n): CursorPaginatedResponse<T> {\n const { cursor, limit } = params;\n const hasMore = data.length > limit;\n\n // Remove the extra item if we have more\n const items = hasMore ? data.slice(0, limit) : data;\n\n const lastItem = items[items.length - 1];\n const firstItem = items[0];\n\n return {\n data: items,\n pagination: {\n cursor,\n nextCursor: hasMore && lastItem ? lastItem.id : undefined,\n prevCursor: cursor && firstItem ? firstItem.id : undefined,\n hasMore,\n limit,\n },\n };\n}\n\n/**\n * Add pagination headers to response\n */\nexport function setPaginationHeaders(\n c: HonoContext,\n meta: PaginationMeta\n): void {\n c.header(\"X-Total-Count\", String(meta.total));\n c.header(\"X-Total-Pages\", String(meta.totalPages));\n c.header(\"X-Page\", String(meta.page));\n c.header(\"X-Per-Page\", String(meta.limit));\n c.header(\"X-Has-Next\", String(meta.hasNext));\n c.header(\"X-Has-Prev\", String(meta.hasPrev));\n}\n","/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\n/**\n * W3C Trace Context - Parsed traceparent header\n * Format: {version}-{trace-id}-{parent-id}-{trace-flags}\n */\nexport interface TraceContext {\n /** Trace version (currently \"00\") */\n version: string;\n /** 32 hex character trace ID */\n traceId: string;\n /** 16 hex character parent span ID */\n parentId: string;\n /** Trace flags (sampled, etc.) */\n traceFlags: number;\n}\n\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n /** W3C trace context (if propagation is enabled) */\n traceContext?: TraceContext;\n /** Current span ID for this request */\n spanId?: string;\n /** Tracestate header value (for forwarding) */\n traceState?: string;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n","/**\n * @parsrun/server - Response Utilities\n * Helpers for API responses\n */\n\nimport type { HonoContext, ApiResponse } from \"../context.js\";\nimport { success, error } from \"../context.js\";\n\n/**\n * Send JSON success response\n *\n * @example\n * ```typescript\n * app.get('/users/:id', async (c) => {\n * const user = await getUser(c.req.param('id'));\n * return json(c, user);\n * });\n * ```\n */\nexport function json<T>(c: HonoContext, data: T, status = 200): Response {\n return c.json(success(data), status as 200);\n}\n\n/**\n * Send JSON success response with metadata\n *\n * @example\n * ```typescript\n * app.get('/users', async (c) => {\n * const { users, total } = await getUsers();\n * return jsonWithMeta(c, users, { total, page: 1, limit: 20 });\n * });\n * ```\n */\nexport function jsonWithMeta<T>(\n c: HonoContext,\n data: T,\n meta: ApiResponse[\"meta\"],\n status = 200\n): Response {\n return c.json(success(data, meta), status as 200);\n}\n\n/**\n * Send JSON error response\n *\n * @example\n * ```typescript\n * app.get('/users/:id', async (c) => {\n * const user = await getUser(c.req.param('id'));\n * if (!user) {\n * return jsonError(c, 'USER_NOT_FOUND', 'User not found', 404);\n * }\n * return json(c, user);\n * });\n * ```\n */\nexport function jsonError(\n c: HonoContext,\n code: string,\n message: string,\n status = 400,\n details?: Record<string, unknown>\n): Response {\n return c.json(error(code, message, details), status as 400);\n}\n\n/**\n * Send created response (201)\n */\nexport function created<T>(c: HonoContext, data: T, location?: string): Response {\n if (location) {\n c.header(\"Location\", location);\n }\n return c.json(success(data), 201);\n}\n\n/**\n * Send no content response (204)\n */\nexport function noContent(_c: HonoContext): Response {\n return new Response(null, { status: 204 });\n}\n\n/**\n * Send accepted response (202) - for async operations\n */\nexport function accepted<T>(c: HonoContext, data?: T): Response {\n if (data) {\n return c.json(success(data), 202);\n }\n return new Response(null, { status: 202 });\n}\n\n/**\n * Redirect response\n */\nexport function redirect(c: HonoContext, url: string, status: 301 | 302 | 303 | 307 | 308 = 302): Response {\n return c.redirect(url, status);\n}\n\n/**\n * Stream response helper\n *\n * @example\n * ```typescript\n * app.get('/stream', (c) => {\n * return stream(c, async (write) => {\n * for (let i = 0; i < 10; i++) {\n * await write(`data: ${i}\\n\\n`);\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * });\n * });\n * ```\n */\nexport function stream(\n _c: HonoContext,\n callback: (write: (chunk: string) => Promise<void>) => Promise<void>,\n options: {\n contentType?: string;\n headers?: Record<string, string>;\n } = {}\n): Response {\n const encoder = new TextEncoder();\n\n const readableStream = new ReadableStream({\n async start(controller) {\n const write = async (chunk: string) => {\n controller.enqueue(encoder.encode(chunk));\n };\n\n try {\n await callback(write);\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(readableStream, {\n headers: {\n \"Content-Type\": options.contentType ?? \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n ...options.headers,\n },\n });\n}\n\n/**\n * Server-Sent Events helper\n *\n * @example\n * ```typescript\n * app.get('/events', (c) => {\n * return sse(c, async (send) => {\n * for await (const event of eventStream) {\n * await send({ event: 'update', data: event });\n * }\n * });\n * });\n * ```\n */\nexport function sse(\n c: HonoContext,\n callback: (\n send: (event: { event?: string; data: unknown; id?: string; retry?: number }) => Promise<void>\n ) => Promise<void>\n): Response {\n return stream(c, async (write) => {\n const send = async (event: { event?: string; data: unknown; id?: string; retry?: number }) => {\n let message = \"\";\n\n if (event.id) {\n message += `id: ${event.id}\\n`;\n }\n\n if (event.event) {\n message += `event: ${event.event}\\n`;\n }\n\n if (event.retry) {\n message += `retry: ${event.retry}\\n`;\n }\n\n const data = typeof event.data === \"string\" ? event.data : JSON.stringify(event.data);\n message += `data: ${data}\\n\\n`;\n\n await write(message);\n };\n\n await callback(send);\n });\n}\n\n/**\n * File download response\n */\nexport function download(\n c: HonoContext,\n data: Uint8Array | ArrayBuffer | string,\n filename: string,\n contentType = \"application/octet-stream\"\n): Response {\n c.header(\"Content-Disposition\", `attachment; filename=\"${filename}\"`);\n c.header(\"Content-Type\", contentType);\n\n if (typeof data === \"string\") {\n return c.body(data);\n }\n\n return new Response(data, {\n headers: c.res.headers,\n });\n}\n"],"mappings":";AAkDA,IAAM,iBAA8C;AAAA,EAClD,cAAc;AAAA,EACd,UAAU;AAAA,EACV,WAAW;AAAA,EACX,YAAY;AACd;AAqBO,SAAS,gBACd,GACA,UAA6B,CAAC,GACZ;AAClB,QAAM,OAAO,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAE7C,QAAM,UAAU,EAAE,IAAI,MAAM,KAAK,SAAS;AAC1C,QAAM,WAAW,EAAE,IAAI,MAAM,KAAK,UAAU;AAE5C,MAAI,OAAO,UAAU,SAAS,SAAS,EAAE,IAAI;AAC7C,MAAI,QAAQ,WAAW,SAAS,UAAU,EAAE,IAAI,KAAK;AAGrD,MAAI,MAAM,IAAI,KAAK,OAAO,EAAG,QAAO;AACpC,MAAI,MAAM,KAAK,KAAK,QAAQ,EAAG,SAAQ,KAAK;AAC5C,MAAI,QAAQ,KAAK,SAAU,SAAQ,KAAK;AAExC,QAAM,UAAU,OAAO,KAAK;AAE5B,SAAO,EAAE,MAAM,OAAO,OAAO;AAC/B;AAKO,SAAS,qBAAqB,QAIlB;AACjB,QAAM,EAAE,MAAM,OAAO,MAAM,IAAI;AAC/B,QAAM,aAAa,KAAK,KAAK,QAAQ,KAAK;AAE1C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,SAAS,OAAO;AAAA,EAClB;AACF;AAaO,SAAS,SACd,MACA,QACsB;AACtB,SAAO;AAAA,IACL;AAAA,IACA,YAAY,qBAAqB,MAAM;AAAA,EACzC;AACF;AAgDO,SAAS,sBACd,GACA,UAA6B,CAAC,GACN;AACxB,QAAM,OAAO,EAAE,GAAG,gBAAgB,GAAG,QAAQ;AAE7C,QAAM,SAAS,EAAE,IAAI,MAAM,QAAQ,KAAK;AACxC,QAAM,WAAW,EAAE,IAAI,MAAM,KAAK,UAAU;AAC5C,QAAM,YAAY,EAAE,IAAI,MAAM,WAAW,MAAM,aAAa,aAAa;AAEzE,MAAI,QAAQ,WAAW,SAAS,UAAU,EAAE,IAAI,KAAK;AACrD,MAAI,MAAM,KAAK,KAAK,QAAQ,EAAG,SAAQ,KAAK;AAC5C,MAAI,QAAQ,KAAK,SAAU,SAAQ,KAAK;AAExC,SAAO,EAAE,QAAQ,OAAO,UAAU;AACpC;AAYO,SAAS,eACd,MACA,QAC4B;AAC5B,QAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,QAAM,UAAU,KAAK,SAAS;AAG9B,QAAM,QAAQ,UAAU,KAAK,MAAM,GAAG,KAAK,IAAI;AAE/C,QAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,QAAM,YAAY,MAAM,CAAC;AAEzB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY;AAAA,MACV;AAAA,MACA,YAAY,WAAW,WAAW,SAAS,KAAK;AAAA,MAChD,YAAY,UAAU,YAAY,UAAU,KAAK;AAAA,MACjD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,qBACd,GACA,MACM;AACN,IAAE,OAAO,iBAAiB,OAAO,KAAK,KAAK,CAAC;AAC5C,IAAE,OAAO,iBAAiB,OAAO,KAAK,UAAU,CAAC;AACjD,IAAE,OAAO,UAAU,OAAO,KAAK,IAAI,CAAC;AACpC,IAAE,OAAO,cAAc,OAAO,KAAK,KAAK,CAAC;AACzC,IAAE,OAAO,cAAc,OAAO,KAAK,OAAO,CAAC;AAC3C,IAAE,OAAO,cAAc,OAAO,KAAK,OAAO,CAAC;AAC7C;;;ACjBO,SAAS,QAAW,MAAS,MAA4C;AAC9E,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,MAAM,QAAQ;AAAA,EAChB;AACF;AAKO,SAAS,MACd,MACA,SACA,SACoB;AACpB,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,EAAE,MAAM,SAAS,SAAS,WAAW,OAAU;AAAA,EACxD;AACF;;;AC3OO,SAAS,KAAQ,GAAgB,MAAS,SAAS,KAAe;AACvE,SAAO,EAAE,KAAK,QAAQ,IAAI,GAAG,MAAa;AAC5C;AAaO,SAAS,aACd,GACA,MACA,MACA,SAAS,KACC;AACV,SAAO,EAAE,KAAK,QAAQ,MAAM,IAAI,GAAG,MAAa;AAClD;AAgBO,SAAS,UACd,GACA,MACA,SACA,SAAS,KACT,SACU;AACV,SAAO,EAAE,KAAK,MAAM,MAAM,SAAS,OAAO,GAAG,MAAa;AAC5D;AAKO,SAAS,QAAW,GAAgB,MAAS,UAA6B;AAC/E,MAAI,UAAU;AACZ,MAAE,OAAO,YAAY,QAAQ;AAAA,EAC/B;AACA,SAAO,EAAE,KAAK,QAAQ,IAAI,GAAG,GAAG;AAClC;AAKO,SAAS,UAAU,IAA2B;AACnD,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAC3C;AAKO,SAAS,SAAY,GAAgB,MAAoB;AAC9D,MAAI,MAAM;AACR,WAAO,EAAE,KAAK,QAAQ,IAAI,GAAG,GAAG;AAAA,EAClC;AACA,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAC3C;AAKO,SAAS,SAAS,GAAgB,KAAa,SAAsC,KAAe;AACzG,SAAO,EAAE,SAAS,KAAK,MAAM;AAC/B;AAiBO,SAAS,OACd,IACA,UACA,UAGI,CAAC,GACK;AACV,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,iBAAiB,IAAI,eAAe;AAAA,IACxC,MAAM,MAAM,YAAY;AACtB,YAAM,QAAQ,OAAO,UAAkB;AACrC,mBAAW,QAAQ,QAAQ,OAAO,KAAK,CAAC;AAAA,MAC1C;AAEA,UAAI;AACF,cAAM,SAAS,KAAK;AAAA,MACtB,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,IAAI,SAAS,gBAAgB;AAAA,IAClC,SAAS;AAAA,MACP,gBAAgB,QAAQ,eAAe;AAAA,MACvC,iBAAiB;AAAA,MACjB,YAAY;AAAA,MACZ,GAAG,QAAQ;AAAA,IACb;AAAA,EACF,CAAC;AACH;AAgBO,SAAS,IACd,GACA,UAGU;AACV,SAAO,OAAO,GAAG,OAAO,UAAU;AAChC,UAAM,OAAO,OAAO,UAA0E;AAC5F,UAAI,UAAU;AAEd,UAAI,MAAM,IAAI;AACZ,mBAAW,OAAO,MAAM,EAAE;AAAA;AAAA,MAC5B;AAEA,UAAI,MAAM,OAAO;AACf,mBAAW,UAAU,MAAM,KAAK;AAAA;AAAA,MAClC;AAEA,UAAI,MAAM,OAAO;AACf,mBAAW,UAAU,MAAM,KAAK;AAAA;AAAA,MAClC;AAEA,YAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,KAAK,UAAU,MAAM,IAAI;AACpF,iBAAW,SAAS,IAAI;AAAA;AAAA;AAExB,YAAM,MAAM,OAAO;AAAA,IACrB;AAEA,UAAM,SAAS,IAAI;AAAA,EACrB,CAAC;AACH;AAKO,SAAS,SACd,GACA,MACA,UACA,cAAc,4BACJ;AACV,IAAE,OAAO,uBAAuB,yBAAyB,QAAQ,GAAG;AACpE,IAAE,OAAO,gBAAgB,WAAW;AAEpC,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,EAAE,KAAK,IAAI;AAAA,EACpB;AAEA,SAAO,IAAI,SAAS,MAAM;AAAA,IACxB,SAAS,EAAE,IAAI;AAAA,EACjB,CAAC;AACH;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/context.ts","../../src/middleware/error-handler.ts","../../src/validation/index.ts"],"sourcesContent":["/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n","/**\n * @parsrun/server - Error Handler Middleware\n * Global error handling and response formatting\n */\n\nimport type { HonoContext, HonoNext } from \"../context.js\";\nimport { error as errorResponse } from \"../context.js\";\nimport type { ErrorTransport, ErrorContext } from \"@parsrun/core/transports\";\n\n/**\n * Base API error class\n */\nexport class ApiError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly code: string,\n message: string,\n public readonly details?: Record<string, unknown>\n ) {\n super(message);\n this.name = \"ApiError\";\n }\n\n toResponse() {\n return errorResponse(this.code, this.message, this.details);\n }\n}\n\n/**\n * 400 Bad Request\n */\nexport class BadRequestError extends ApiError {\n constructor(message = \"Bad request\", details?: Record<string, unknown>) {\n super(400, \"BAD_REQUEST\", message, details);\n this.name = \"BadRequestError\";\n }\n}\n\n/**\n * 401 Unauthorized\n */\nexport class UnauthorizedError extends ApiError {\n constructor(message = \"Unauthorized\", details?: Record<string, unknown>) {\n super(401, \"UNAUTHORIZED\", message, details);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/**\n * 403 Forbidden\n */\nexport class ForbiddenError extends ApiError {\n constructor(message = \"Forbidden\", details?: Record<string, unknown>) {\n super(403, \"FORBIDDEN\", message, details);\n this.name = \"ForbiddenError\";\n }\n}\n\n/**\n * 404 Not Found\n */\nexport class NotFoundError extends ApiError {\n constructor(message = \"Not found\", details?: Record<string, unknown>) {\n super(404, \"NOT_FOUND\", message, details);\n this.name = \"NotFoundError\";\n }\n}\n\n/**\n * 409 Conflict\n */\nexport class ConflictError extends ApiError {\n constructor(message = \"Conflict\", details?: Record<string, unknown>) {\n super(409, \"CONFLICT\", message, details);\n this.name = \"ConflictError\";\n }\n}\n\n/**\n * 422 Unprocessable Entity (Validation Error)\n */\nexport class ValidationError extends ApiError {\n constructor(message = \"Validation failed\", details?: Record<string, unknown>) {\n super(422, \"VALIDATION_ERROR\", message, details);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * 429 Too Many Requests\n */\nexport class RateLimitError extends ApiError {\n constructor(\n message = \"Too many requests\",\n public readonly retryAfter?: number\n ) {\n super(429, \"RATE_LIMIT_EXCEEDED\", message, { retryAfter });\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * 500 Internal Server Error\n */\nexport class InternalError extends ApiError {\n constructor(message = \"Internal server error\", details?: Record<string, unknown>) {\n super(500, \"INTERNAL_ERROR\", message, details);\n this.name = \"InternalError\";\n }\n}\n\n/**\n * 503 Service Unavailable\n */\nexport class ServiceUnavailableError extends ApiError {\n constructor(message = \"Service unavailable\", details?: Record<string, unknown>) {\n super(503, \"SERVICE_UNAVAILABLE\", message, details);\n this.name = \"ServiceUnavailableError\";\n }\n}\n\n/**\n * Error handler options\n */\nexport interface ErrorHandlerOptions {\n /** Include stack trace in development */\n includeStack?: boolean;\n /** Custom error logger */\n onError?: (error: Error, c: HonoContext) => void;\n /**\n * Error transport for external error tracking (e.g., Sentry)\n * Automatically captures exceptions with request context\n */\n errorTransport?: ErrorTransport;\n /**\n * Capture all errors including 4xx client errors\n * By default, only 5xx server errors are captured\n * @default false\n */\n captureAllErrors?: boolean;\n /**\n * Custom function to determine if an error should be captured\n * Overrides the default captureAllErrors behavior\n */\n shouldCapture?: (error: Error, statusCode: number) => boolean;\n}\n\n/**\n * Global error handler middleware\n *\n * @example Basic usage\n * ```typescript\n * app.use('*', errorHandler({\n * includeStack: process.env.NODE_ENV === 'development',\n * onError: (error, c) => {\n * console.error(`[${c.get('requestId')}]`, error);\n * },\n * }));\n * ```\n *\n * @example With Sentry error tracking\n * ```typescript\n * import { SentryTransport } from '@parsrun/core/transports';\n *\n * const sentry = new SentryTransport({\n * dsn: process.env.SENTRY_DSN!,\n * environment: process.env.NODE_ENV,\n * });\n *\n * app.use('*', errorHandler({\n * errorTransport: sentry,\n * captureAllErrors: false, // Only capture 5xx errors\n * }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}) {\n const {\n includeStack = false,\n onError,\n errorTransport,\n captureAllErrors = false,\n shouldCapture,\n } = options;\n\n return async (c: HonoContext, next: HonoNext) => {\n try {\n await next();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n\n // Determine status code\n const statusCode = error instanceof ApiError ? error.statusCode : 500;\n\n // Log error\n if (onError) {\n onError(error, c);\n } else {\n const logger = c.get(\"logger\");\n if (logger) {\n logger.error(\"Request error\", {\n requestId: c.get(\"requestId\"),\n error: error.message,\n stack: error.stack,\n });\n }\n }\n\n // Capture to error transport if configured\n if (errorTransport) {\n const shouldCaptureError = shouldCapture\n ? shouldCapture(error, statusCode)\n : captureAllErrors || statusCode >= 500;\n\n if (shouldCaptureError) {\n const user = c.get(\"user\");\n const tenant = c.get(\"tenant\");\n\n // Build error context with only defined values\n const errorContext: ErrorContext = {\n requestId: c.get(\"requestId\"),\n tags: {\n path: c.req.path,\n method: c.req.method,\n statusCode: String(statusCode),\n },\n };\n\n if (user?.id) {\n errorContext.userId = user.id;\n }\n if (tenant?.id) {\n errorContext.tenantId = tenant.id;\n }\n\n // Add extra context\n const extra: Record<string, unknown> = {\n query: c.req.query(),\n };\n if (error instanceof ApiError) {\n extra[\"errorCode\"] = error.code;\n }\n errorContext.extra = extra;\n\n // Capture asynchronously to not block response\n Promise.resolve(\n errorTransport.captureException(error, errorContext)\n ).catch(() => {\n // Silent fail - don't let transport errors affect response\n });\n }\n }\n\n // Handle known API errors\n if (error instanceof ApiError) {\n return c.json(error.toResponse(), error.statusCode as 400);\n }\n\n // Handle unknown errors\n const details: Record<string, unknown> = {};\n\n if (includeStack && error.stack) {\n details[\"stack\"] = error.stack;\n }\n\n return c.json(\n errorResponse(\"INTERNAL_ERROR\", \"An unexpected error occurred\", details),\n 500\n );\n }\n };\n}\n\n/**\n * Not found handler\n *\n * @example\n * ```typescript\n * app.notFound(notFoundHandler);\n * ```\n */\nexport function notFoundHandler(c: HonoContext) {\n return c.json(\n errorResponse(\"NOT_FOUND\", `Route ${c.req.method} ${c.req.path} not found`),\n 404\n );\n}\n","/**\n * @parsrun/server - ArkType Validation\n * Request validation with ArkType (powered by @parsrun/types)\n */\n\nimport type { HonoContext, HonoNext } from \"../context.js\";\nimport { ValidationError } from \"../middleware/error-handler.js\";\nimport type { Type } from \"@parsrun/types\";\n\n// Re-export from @parsrun/types\nexport {\n type,\n // Common schemas\n uuid,\n timestamp,\n email,\n url,\n nonEmptyString,\n positiveInt,\n nonNegativeInt,\n status,\n pagination,\n paginationMeta,\n cursorPagination,\n cursorPaginationMeta,\n // Server schemas\n uuidParam,\n paginationQuery,\n cursorPaginationQuery,\n searchQuery,\n dateRangeQuery,\n healthResponse,\n apiInfoResponse,\n corsConfig,\n serverRateLimitConfig,\n loggerConfig,\n serverConfig,\n authContext,\n requestContext,\n // Response helpers\n successResponse,\n errorResponse,\n paginatedResponse,\n cursorPaginatedResponse,\n parsError,\n // Validation helpers\n validateWithSchema,\n safeValidate,\n isValid,\n formatErrors,\n // Types\n type UUID,\n type Timestamp,\n type Email,\n type Url,\n type NonEmptyString,\n type PositiveInt,\n type NonNegativeInt,\n type Status,\n type Pagination,\n type PaginationMeta,\n type CursorPagination,\n type CursorPaginationMeta,\n type UuidParam,\n type PaginationQuery,\n type CursorPaginationQuery,\n type SearchQuery,\n type DateRangeQuery,\n type HealthResponse,\n type ApiInfoResponse,\n type CorsConfig,\n type ServerRateLimitConfig,\n type LoggerConfig,\n type ServerConfig,\n type AuthContext,\n type RequestContext,\n type ErrorResponse,\n type ParsError,\n type ApiResponse,\n type ApiErrorResponse,\n type ApiPaginatedResponse,\n type ApiCursorPaginatedResponse,\n} from \"@parsrun/types\";\n\nimport { type, formatErrors as formatArkErrors } from \"@parsrun/types\";\n\nexport type { Type } from \"@parsrun/types\";\n\n/**\n * Infer type from ArkType schema\n */\nexport type Infer<T extends Type> = T[\"infer\"];\n\n/**\n * Validation target\n */\nexport type ValidationTarget = \"json\" | \"query\" | \"param\" | \"header\" | \"form\";\n\n/**\n * Validation options\n */\nexport interface ValidateOptions {\n /** Error message prefix */\n messagePrefix?: string;\n}\n\n/**\n * Format ArkType errors to a simple object with arrays\n */\nfunction formatValidationErrors(errors: type.errors): Record<string, string[]> {\n const formatted = formatArkErrors(errors);\n const result: Record<string, string[]> = {};\n\n for (const [key, value] of Object.entries(formatted)) {\n result[key] = [value];\n }\n\n return result;\n}\n\n/**\n * Validate request body with ArkType schema\n *\n * @example\n * ```typescript\n * import { type } from '@parsrun/types';\n *\n * const CreateUserSchema = type({\n * email: 'string.email',\n * name: 'string',\n * age: 'number >= 0',\n * });\n *\n * app.post('/users', validateBody(CreateUserSchema), async (c) => {\n * const data = c.get('validatedBody');\n * // data is typed as { email: string; name: string; age: number }\n * });\n * ```\n */\nexport function validateBody<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n let body: unknown;\n\n try {\n body = await c.req.json();\n } catch {\n throw new ValidationError(\"Invalid JSON body\");\n }\n\n const result = schema(body);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Validation failed\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n // Store validated data in context\n (c as HonoContext & { validatedBody: T[\"infer\"] }).set(\"validatedBody\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate query parameters\n *\n * @example\n * ```typescript\n * const PaginationSchema = type({\n * 'page?': 'string',\n * 'limit?': 'string',\n * 'search?': 'string',\n * });\n *\n * app.get('/users', validateQuery(PaginationSchema), async (c) => {\n * const { page, limit, search } = c.get('validatedQuery');\n * });\n * ```\n */\nexport function validateQuery<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n const query = c.req.query();\n const result = schema(query);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid query parameters\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedQuery: T[\"infer\"] }).set(\"validatedQuery\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate route parameters\n *\n * @example\n * ```typescript\n * const IdParamSchema = type({\n * id: 'string',\n * });\n *\n * app.get('/users/:id', validateParams(IdParamSchema), async (c) => {\n * const { id } = c.get('validatedParams');\n * });\n * ```\n */\nexport function validateParams<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n const params = c.req.param();\n const result = schema(params);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid route parameters\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedParams: T[\"infer\"] }).set(\"validatedParams\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate headers\n *\n * @example\n * ```typescript\n * const ApiKeySchema = type({\n * 'x-api-key': 'string',\n * });\n *\n * app.use('/api/*', validateHeaders(ApiKeySchema));\n * ```\n */\nexport function validateHeaders<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n // Convert headers to plain object\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n\n const result = schema(headers);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid headers\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedHeaders: T[\"infer\"] }).set(\"validatedHeaders\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Combined validation middleware\n *\n * @example\n * ```typescript\n * app.post('/users/:id',\n * validate({\n * params: type({ id: 'string' }),\n * body: type({ name: 'string', email: 'string.email' }),\n * query: type({ 'include?': 'string' }),\n * }),\n * async (c) => {\n * const params = c.get('validatedParams');\n * const body = c.get('validatedBody');\n * const query = c.get('validatedQuery');\n * }\n * );\n * ```\n */\nexport function validate<\n TParams extends Type | undefined = undefined,\n TBody extends Type | undefined = undefined,\n TQuery extends Type | undefined = undefined,\n THeaders extends Type | undefined = undefined\n>(schemas: {\n params?: TParams;\n body?: TBody;\n query?: TQuery;\n headers?: THeaders;\n}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n // Validate params\n if (schemas.params) {\n const params = c.req.param();\n const result = schemas.params(params);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid route parameters\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedParams: unknown }).set(\"validatedParams\" as never, result as never);\n }\n\n // Validate query\n if (schemas.query) {\n const query = c.req.query();\n const result = schemas.query(query);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid query parameters\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedQuery: unknown }).set(\"validatedQuery\" as never, result as never);\n }\n\n // Validate headers\n if (schemas.headers) {\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n const result = schemas.headers(headers);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid headers\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedHeaders: unknown }).set(\"validatedHeaders\" as never, result as never);\n }\n\n // Validate body\n if (schemas.body) {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw new ValidationError(\"Invalid JSON body\");\n }\n const result = schemas.body(body);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Validation failed\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedBody: unknown }).set(\"validatedBody\" as never, result as never);\n }\n\n await next();\n };\n}\n\n// ============================================================================\n// Legacy Aliases (for backward compatibility)\n// ============================================================================\n\n// Schema aliases with PascalCase for backward compatibility\n// Use the camelCase versions from @parsrun/types for new code\nexport {\n uuidParam as UuidParamSchema,\n paginationQuery as PaginationQuerySchema,\n searchQuery as SearchQuerySchema,\n dateRangeQuery as DateRangeQuerySchema,\n} from \"@parsrun/types\";\n"],"mappings":";AAgOO,SAAS,MACd,MACA,SACA,SACoB;AACpB,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,EAAE,MAAM,SAAS,SAAS,WAAW,OAAU;AAAA,EACxD;AACF;;;AC7NO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClC,YACkB,YACA,MAChB,SACgB,SAChB;AACA,UAAM,OAAO;AALG;AACA;AAEA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,aAAa;AACX,WAAO,MAAc,KAAK,MAAM,KAAK,SAAS,KAAK,OAAO;AAAA,EAC5D;AACF;AAuDO,IAAM,kBAAN,cAA8B,SAAS;AAAA,EAC5C,YAAY,UAAU,qBAAqB,SAAmC;AAC5E,UAAM,KAAK,oBAAoB,SAAS,OAAO;AAC/C,SAAK,OAAO;AAAA,EACd;AACF;;;AC5EA;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAiCK;AAEP,SAAS,QAAAA,OAAM,gBAAgB,uBAAuB;AAuRtD;AAAA,EACe,aAAbC;AAAA,EACmB,mBAAnBC;AAAA,EACe,eAAfC;AAAA,EACkB,kBAAlBC;AAAA,OACK;AAnQP,SAAS,uBAAuB,QAA+C;AAC7E,QAAM,YAAY,gBAAgB,MAAM;AACxC,QAAM,SAAmC,CAAC;AAE1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,WAAO,GAAG,IAAI,CAAC,KAAK;AAAA,EACtB;AAEA,SAAO;AACT;AAqBO,SAAS,aAA6B,QAAW,UAA2B,CAAC,GAAG;AACrF,SAAO,OAAO,GAAgB,SAAkC;AAC9D,QAAI;AAEJ,QAAI;AACF,aAAO,MAAM,EAAE,IAAI,KAAK;AAAA,IAC1B,QAAQ;AACN,YAAM,IAAI,gBAAgB,mBAAmB;AAAA,IAC/C;AAEA,UAAM,SAAS,OAAO,IAAI;AAE1B,QAAI,kBAAkBJ,MAAK,QAAQ;AACjC,YAAM,IAAI;AAAA,QACR,QAAQ,iBAAiB;AAAA,QACzB,EAAE,QAAQ,uBAAuB,MAAM,EAAE;AAAA,MAC3C;AAAA,IACF;AAGA,IAAC,EAAkD,IAAI,iBAA0B,MAAe;AAEhG,UAAM,KAAK;AAAA,EACb;AACF;AAkBO,SAAS,cAA8B,QAAW,UAA2B,CAAC,GAAG;AACtF,SAAO,OAAO,GAAgB,SAAkC;AAC9D,UAAM,QAAQ,EAAE,IAAI,MAAM;AAC1B,UAAM,SAAS,OAAO,KAAK;AAE3B,QAAI,kBAAkBA,MAAK,QAAQ;AACjC,YAAM,IAAI;AAAA,QACR,QAAQ,iBAAiB;AAAA,QACzB,EAAE,QAAQ,uBAAuB,MAAM,EAAE;AAAA,MAC3C;AAAA,IACF;AAEA,IAAC,EAAmD,IAAI,kBAA2B,MAAe;AAElG,UAAM,KAAK;AAAA,EACb;AACF;AAgBO,SAAS,eAA+B,QAAW,UAA2B,CAAC,GAAG;AACvF,SAAO,OAAO,GAAgB,SAAkC;AAC9D,UAAM,SAAS,EAAE,IAAI,MAAM;AAC3B,UAAM,SAAS,OAAO,MAAM;AAE5B,QAAI,kBAAkBA,MAAK,QAAQ;AACjC,YAAM,IAAI;AAAA,QACR,QAAQ,iBAAiB;AAAA,QACzB,EAAE,QAAQ,uBAAuB,MAAM,EAAE;AAAA,MAC3C;AAAA,IACF;AAEA,IAAC,EAAoD,IAAI,mBAA4B,MAAe;AAEpG,UAAM,KAAK;AAAA,EACb;AACF;AAcO,SAAS,gBAAgC,QAAW,UAA2B,CAAC,GAAG;AACxF,SAAO,OAAO,GAAgB,SAAkC;AAE9D,UAAM,UAAkC,CAAC;AACzC,MAAE,IAAI,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACxC,cAAQ,IAAI,YAAY,CAAC,IAAI;AAAA,IAC/B,CAAC;AAED,UAAM,SAAS,OAAO,OAAO;AAE7B,QAAI,kBAAkBA,MAAK,QAAQ;AACjC,YAAM,IAAI;AAAA,QACR,QAAQ,iBAAiB;AAAA,QACzB,EAAE,QAAQ,uBAAuB,MAAM,EAAE;AAAA,MAC3C;AAAA,IACF;AAEA,IAAC,EAAqD,IAAI,oBAA6B,MAAe;AAEtG,UAAM,KAAK;AAAA,EACb;AACF;AAqBO,SAAS,SAKd,SAKC;AACD,SAAO,OAAO,GAAgB,SAAkC;AAE9D,QAAI,QAAQ,QAAQ;AAClB,YAAM,SAAS,EAAE,IAAI,MAAM;AAC3B,YAAM,SAAS,QAAQ,OAAO,MAAM;AACpC,UAAI,kBAAkBA,MAAK,QAAQ;AACjC,cAAM,IAAI,gBAAgB,4BAA4B;AAAA,UACpD,QAAQ,uBAAuB,MAAM;AAAA,QACvC,CAAC;AAAA,MACH;AACA,MAAC,EAAiD,IAAI,mBAA4B,MAAe;AAAA,IACnG;AAGA,QAAI,QAAQ,OAAO;AACjB,YAAM,QAAQ,EAAE,IAAI,MAAM;AAC1B,YAAM,SAAS,QAAQ,MAAM,KAAK;AAClC,UAAI,kBAAkBA,MAAK,QAAQ;AACjC,cAAM,IAAI,gBAAgB,4BAA4B;AAAA,UACpD,QAAQ,uBAAuB,MAAM;AAAA,QACvC,CAAC;AAAA,MACH;AACA,MAAC,EAAgD,IAAI,kBAA2B,MAAe;AAAA,IACjG;AAGA,QAAI,QAAQ,SAAS;AACnB,YAAM,UAAkC,CAAC;AACzC,QAAE,IAAI,IAAI,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACxC,gBAAQ,IAAI,YAAY,CAAC,IAAI;AAAA,MAC/B,CAAC;AACD,YAAM,SAAS,QAAQ,QAAQ,OAAO;AACtC,UAAI,kBAAkBA,MAAK,QAAQ;AACjC,cAAM,IAAI,gBAAgB,mBAAmB;AAAA,UAC3C,QAAQ,uBAAuB,MAAM;AAAA,QACvC,CAAC;AAAA,MACH;AACA,MAAC,EAAkD,IAAI,oBAA6B,MAAe;AAAA,IACrG;AAGA,QAAI,QAAQ,MAAM;AAChB,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,EAAE,IAAI,KAAK;AAAA,MAC1B,QAAQ;AACN,cAAM,IAAI,gBAAgB,mBAAmB;AAAA,MAC/C;AACA,YAAM,SAAS,QAAQ,KAAK,IAAI;AAChC,UAAI,kBAAkBA,MAAK,QAAQ;AACjC,cAAM,IAAI,gBAAgB,qBAAqB;AAAA,UAC7C,QAAQ,uBAAuB,MAAM;AAAA,QACvC,CAAC;AAAA,MACH;AACA,MAAC,EAA+C,IAAI,iBAA0B,MAAe;AAAA,IAC/F;AAEA,UAAM,KAAK;AAAA,EACb;AACF;","names":["type","uuidParam","paginationQuery","searchQuery","dateRangeQuery"]}
1
+ {"version":3,"sources":["../../src/context.ts","../../src/middleware/error-handler.ts","../../src/validation/index.ts"],"sourcesContent":["/**\n * @parsrun/server - Server Context\n * Type definitions for server context and configuration\n */\n\nimport type { Logger } from \"@parsrun/core\";\n\n/**\n * Database adapter interface\n * Implement this for your database (Drizzle, Prisma, etc.)\n */\nexport interface DatabaseAdapter {\n /** Execute raw SQL query */\n execute(sql: string): Promise<unknown>;\n /** Check connection */\n ping?(): Promise<boolean>;\n}\n\n/**\n * Module manifest for registering modules\n */\nexport interface ModuleManifest {\n /** Unique module name */\n name: string;\n /** Module version */\n version: string;\n /** Module description */\n description: string;\n /** Required permissions for this module */\n permissions: Record<string, string[]>;\n /** Module dependencies (other module names) */\n dependencies?: string[];\n /** Register routes for this module */\n registerRoutes: (app: HonoApp) => void;\n /** Called when module is enabled */\n onEnable?: () => Promise<void>;\n /** Called when module is disabled */\n onDisable?: () => Promise<void>;\n}\n\n/**\n * Server configuration\n */\nexport interface ServerConfig {\n /** Database adapter */\n database: DatabaseAdapter;\n /** CORS configuration */\n cors?: CorsConfig;\n /** Base path for API */\n basePath?: string;\n /** Cookie prefix for all cookies */\n cookiePrefix?: string;\n /** Logger instance */\n logger?: Logger;\n /** Custom context data */\n custom?: Record<string, unknown>;\n}\n\n/**\n * CORS configuration\n */\nexport interface CorsConfig {\n /** Allowed origins */\n origin: string | string[] | ((origin: string) => boolean);\n /** Allow credentials */\n credentials?: boolean;\n /** Allowed methods */\n methods?: string[];\n /** Allowed headers */\n allowedHeaders?: string[];\n /** Exposed headers */\n exposedHeaders?: string[];\n /** Max age in seconds */\n maxAge?: number;\n}\n\n/**\n * User information in context\n */\nexport interface ContextUser {\n id: string;\n email: string | undefined;\n tenantId: string | undefined;\n role: string | undefined;\n permissions: string[];\n}\n\n/**\n * Tenant information in context\n */\nexport interface ContextTenant {\n id: string;\n slug: string | undefined;\n name: string | undefined;\n status: string;\n}\n\n/**\n * Server context variables\n * Available in Hono context via c.get()\n */\n/**\n * W3C Trace Context - Parsed traceparent header\n * Format: {version}-{trace-id}-{parent-id}-{trace-flags}\n */\nexport interface TraceContext {\n /** Trace version (currently \"00\") */\n version: string;\n /** 32 hex character trace ID */\n traceId: string;\n /** 16 hex character parent span ID */\n parentId: string;\n /** Trace flags (sampled, etc.) */\n traceFlags: number;\n}\n\nexport interface ServerContextVariables {\n /** Database adapter */\n db: DatabaseAdapter;\n /** Server configuration */\n config: ServerConfig;\n /** Enabled modules set */\n enabledModules: Set<string>;\n /** Current user (if authenticated) */\n user: ContextUser | undefined;\n /** Current tenant (if resolved) */\n tenant: ContextTenant | undefined;\n /** Request logger */\n logger: Logger;\n /** Request ID */\n requestId: string;\n /** Cookie prefix */\n cookiePrefix: string | undefined;\n /** Custom context data */\n custom: Record<string, unknown>;\n /** W3C trace context (if propagation is enabled) */\n traceContext?: TraceContext;\n /** Current span ID for this request */\n spanId?: string;\n /** Tracestate header value (for forwarding) */\n traceState?: string;\n}\n\n/**\n * Hono app type with server context\n */\nexport type HonoApp = import(\"hono\").Hono<{ Variables: ServerContextVariables }>;\n\n/**\n * Hono context type with server context\n */\nexport type HonoContext = import(\"hono\").Context<{ Variables: ServerContextVariables }>;\n\n/**\n * Middleware next function\n */\nexport type HonoNext = () => Promise<void>;\n\n/**\n * Middleware function type\n */\nexport type Middleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Route handler function type\n */\nexport type RouteHandler = (c: HonoContext) => Promise<Response> | Response;\n\n/**\n * Permission check input\n */\nexport interface PermissionCheck {\n /** Resource name (e.g., \"users\", \"items\") */\n resource: string;\n /** Action name (e.g., \"read\", \"create\", \"update\", \"delete\") */\n action: string;\n /** Permission scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Permission definition\n */\nexport interface PermissionDefinition {\n /** Permission name (e.g., \"users:read\") */\n name: string;\n /** Resource part */\n resource: string;\n /** Action part */\n action: string;\n /** Description */\n description?: string;\n /** Scope */\n scope?: \"tenant\" | \"global\" | \"own\";\n}\n\n/**\n * Role definition\n */\nexport interface RoleDefinition {\n /** Role name */\n name: string;\n /** Display name */\n displayName?: string;\n /** Description */\n description?: string;\n /** Permissions assigned to this role */\n permissions: string[];\n /** Is this a system role */\n isSystem?: boolean;\n}\n\n/**\n * Standard API response structure\n */\nexport interface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown> | undefined;\n };\n meta?: {\n page?: number | undefined;\n limit?: number | undefined;\n total?: number | undefined;\n requestId?: string | undefined;\n } | undefined;\n}\n\n/**\n * Create a success response\n */\nexport function success<T>(data: T, meta?: ApiResponse[\"meta\"]): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: meta ?? undefined,\n };\n}\n\n/**\n * Create an error response\n */\nexport function error(\n code: string,\n message: string,\n details?: Record<string, unknown>\n): ApiResponse<never> {\n return {\n success: false,\n error: { code, message, details: details ?? undefined },\n };\n}\n\n/**\n * Generate a request ID\n */\nexport function generateRequestId(): string {\n return crypto.randomUUID();\n}\n","/**\n * @parsrun/server - Error Handler Middleware\n * Global error handling and response formatting\n */\n\nimport type { HonoContext, HonoNext } from \"../context.js\";\nimport { error as errorResponse } from \"../context.js\";\nimport type { ErrorTransport, ErrorContext } from \"@parsrun/core/transports\";\n\n/**\n * Base API error class\n */\nexport class ApiError extends Error {\n constructor(\n public readonly statusCode: number,\n public readonly code: string,\n message: string,\n public readonly details?: Record<string, unknown>\n ) {\n super(message);\n this.name = \"ApiError\";\n }\n\n toResponse() {\n return errorResponse(this.code, this.message, this.details);\n }\n}\n\n/**\n * 400 Bad Request\n */\nexport class BadRequestError extends ApiError {\n constructor(message = \"Bad request\", details?: Record<string, unknown>) {\n super(400, \"BAD_REQUEST\", message, details);\n this.name = \"BadRequestError\";\n }\n}\n\n/**\n * 401 Unauthorized\n */\nexport class UnauthorizedError extends ApiError {\n constructor(message = \"Unauthorized\", details?: Record<string, unknown>) {\n super(401, \"UNAUTHORIZED\", message, details);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/**\n * 403 Forbidden\n */\nexport class ForbiddenError extends ApiError {\n constructor(message = \"Forbidden\", details?: Record<string, unknown>) {\n super(403, \"FORBIDDEN\", message, details);\n this.name = \"ForbiddenError\";\n }\n}\n\n/**\n * 404 Not Found\n */\nexport class NotFoundError extends ApiError {\n constructor(message = \"Not found\", details?: Record<string, unknown>) {\n super(404, \"NOT_FOUND\", message, details);\n this.name = \"NotFoundError\";\n }\n}\n\n/**\n * 409 Conflict\n */\nexport class ConflictError extends ApiError {\n constructor(message = \"Conflict\", details?: Record<string, unknown>) {\n super(409, \"CONFLICT\", message, details);\n this.name = \"ConflictError\";\n }\n}\n\n/**\n * 422 Unprocessable Entity (Validation Error)\n */\nexport class ValidationError extends ApiError {\n constructor(message = \"Validation failed\", details?: Record<string, unknown>) {\n super(422, \"VALIDATION_ERROR\", message, details);\n this.name = \"ValidationError\";\n }\n}\n\n/**\n * 429 Too Many Requests\n */\nexport class RateLimitError extends ApiError {\n constructor(\n message = \"Too many requests\",\n public readonly retryAfter?: number\n ) {\n super(429, \"RATE_LIMIT_EXCEEDED\", message, { retryAfter });\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * 500 Internal Server Error\n */\nexport class InternalError extends ApiError {\n constructor(message = \"Internal server error\", details?: Record<string, unknown>) {\n super(500, \"INTERNAL_ERROR\", message, details);\n this.name = \"InternalError\";\n }\n}\n\n/**\n * 503 Service Unavailable\n */\nexport class ServiceUnavailableError extends ApiError {\n constructor(message = \"Service unavailable\", details?: Record<string, unknown>) {\n super(503, \"SERVICE_UNAVAILABLE\", message, details);\n this.name = \"ServiceUnavailableError\";\n }\n}\n\n/**\n * Error handler options\n */\nexport interface ErrorHandlerOptions {\n /** Include stack trace in development */\n includeStack?: boolean;\n /** Custom error logger */\n onError?: (error: Error, c: HonoContext) => void;\n /**\n * Error transport for external error tracking (e.g., Sentry)\n * Automatically captures exceptions with request context\n */\n errorTransport?: ErrorTransport;\n /**\n * Capture all errors including 4xx client errors\n * By default, only 5xx server errors are captured\n * @default false\n */\n captureAllErrors?: boolean;\n /**\n * Custom function to determine if an error should be captured\n * Overrides the default captureAllErrors behavior\n */\n shouldCapture?: (error: Error, statusCode: number) => boolean;\n}\n\n/**\n * Global error handler middleware\n *\n * @example Basic usage\n * ```typescript\n * app.use('*', errorHandler({\n * includeStack: process.env.NODE_ENV === 'development',\n * onError: (error, c) => {\n * console.error(`[${c.get('requestId')}]`, error);\n * },\n * }));\n * ```\n *\n * @example With Sentry error tracking\n * ```typescript\n * import { SentryTransport } from '@parsrun/core/transports';\n *\n * const sentry = new SentryTransport({\n * dsn: process.env.SENTRY_DSN!,\n * environment: process.env.NODE_ENV,\n * });\n *\n * app.use('*', errorHandler({\n * errorTransport: sentry,\n * captureAllErrors: false, // Only capture 5xx errors\n * }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}) {\n const {\n includeStack = false,\n onError,\n errorTransport,\n captureAllErrors = false,\n shouldCapture,\n } = options;\n\n return async (c: HonoContext, next: HonoNext) => {\n try {\n await next();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n\n // Determine status code\n const statusCode = error instanceof ApiError ? error.statusCode : 500;\n\n // Log error\n if (onError) {\n onError(error, c);\n } else {\n const logger = c.get(\"logger\");\n if (logger) {\n logger.error(\"Request error\", {\n requestId: c.get(\"requestId\"),\n error: error.message,\n stack: error.stack,\n });\n }\n }\n\n // Capture to error transport if configured\n if (errorTransport) {\n const shouldCaptureError = shouldCapture\n ? shouldCapture(error, statusCode)\n : captureAllErrors || statusCode >= 500;\n\n if (shouldCaptureError) {\n const user = c.get(\"user\");\n const tenant = c.get(\"tenant\");\n\n // Build error context with only defined values\n const errorContext: ErrorContext = {\n requestId: c.get(\"requestId\"),\n tags: {\n path: c.req.path,\n method: c.req.method,\n statusCode: String(statusCode),\n },\n };\n\n if (user?.id) {\n errorContext.userId = user.id;\n }\n if (tenant?.id) {\n errorContext.tenantId = tenant.id;\n }\n\n // Add extra context\n const extra: Record<string, unknown> = {\n query: c.req.query(),\n };\n if (error instanceof ApiError) {\n extra[\"errorCode\"] = error.code;\n }\n errorContext.extra = extra;\n\n // Capture asynchronously to not block response\n Promise.resolve(\n errorTransport.captureException(error, errorContext)\n ).catch(() => {\n // Silent fail - don't let transport errors affect response\n });\n }\n }\n\n // Handle known API errors\n if (error instanceof ApiError) {\n return c.json(error.toResponse(), error.statusCode as 400);\n }\n\n // Handle unknown errors\n const details: Record<string, unknown> = {};\n\n if (includeStack && error.stack) {\n details[\"stack\"] = error.stack;\n }\n\n return c.json(\n errorResponse(\"INTERNAL_ERROR\", \"An unexpected error occurred\", details),\n 500\n );\n }\n };\n}\n\n/**\n * Not found handler\n *\n * @example\n * ```typescript\n * app.notFound(notFoundHandler);\n * ```\n */\nexport function notFoundHandler(c: HonoContext) {\n return c.json(\n errorResponse(\"NOT_FOUND\", `Route ${c.req.method} ${c.req.path} not found`),\n 404\n );\n}\n","/**\n * @parsrun/server - ArkType Validation\n * Request validation with ArkType (powered by @parsrun/types)\n */\n\nimport type { HonoContext, HonoNext } from \"../context.js\";\nimport { ValidationError } from \"../middleware/error-handler.js\";\nimport type { Type } from \"@parsrun/types\";\n\n// Re-export from @parsrun/types\nexport {\n type,\n // Common schemas\n uuid,\n timestamp,\n email,\n url,\n nonEmptyString,\n positiveInt,\n nonNegativeInt,\n status,\n pagination,\n paginationMeta,\n cursorPagination,\n cursorPaginationMeta,\n // Server schemas\n uuidParam,\n paginationQuery,\n cursorPaginationQuery,\n searchQuery,\n dateRangeQuery,\n healthResponse,\n apiInfoResponse,\n corsConfig,\n serverRateLimitConfig,\n loggerConfig,\n serverConfig,\n authContext,\n requestContext,\n // Response helpers\n successResponse,\n errorResponse,\n paginatedResponse,\n cursorPaginatedResponse,\n parsError,\n // Validation helpers\n validateWithSchema,\n safeValidate,\n isValid,\n formatErrors,\n // Types\n type UUID,\n type Timestamp,\n type Email,\n type Url,\n type NonEmptyString,\n type PositiveInt,\n type NonNegativeInt,\n type Status,\n type Pagination,\n type PaginationMeta,\n type CursorPagination,\n type CursorPaginationMeta,\n type UuidParam,\n type PaginationQuery,\n type CursorPaginationQuery,\n type SearchQuery,\n type DateRangeQuery,\n type HealthResponse,\n type ApiInfoResponse,\n type CorsConfig,\n type ServerRateLimitConfig,\n type LoggerConfig,\n type ServerConfig,\n type AuthContext,\n type RequestContext,\n type ErrorResponse,\n type ParsError,\n type ApiResponse,\n type ApiErrorResponse,\n type ApiPaginatedResponse,\n type ApiCursorPaginatedResponse,\n} from \"@parsrun/types\";\n\nimport { type, formatErrors as formatArkErrors } from \"@parsrun/types\";\n\nexport type { Type } from \"@parsrun/types\";\n\n/**\n * Infer type from ArkType schema\n */\nexport type Infer<T extends Type> = T[\"infer\"];\n\n/**\n * Validation target\n */\nexport type ValidationTarget = \"json\" | \"query\" | \"param\" | \"header\" | \"form\";\n\n/**\n * Validation options\n */\nexport interface ValidateOptions {\n /** Error message prefix */\n messagePrefix?: string;\n}\n\n/**\n * Format ArkType errors to a simple object with arrays\n */\nfunction formatValidationErrors(errors: type.errors): Record<string, string[]> {\n const formatted = formatArkErrors(errors);\n const result: Record<string, string[]> = {};\n\n for (const [key, value] of Object.entries(formatted)) {\n result[key] = [value];\n }\n\n return result;\n}\n\n/**\n * Validate request body with ArkType schema\n *\n * @example\n * ```typescript\n * import { type } from '@parsrun/types';\n *\n * const CreateUserSchema = type({\n * email: 'string.email',\n * name: 'string',\n * age: 'number >= 0',\n * });\n *\n * app.post('/users', validateBody(CreateUserSchema), async (c) => {\n * const data = c.get('validatedBody');\n * // data is typed as { email: string; name: string; age: number }\n * });\n * ```\n */\nexport function validateBody<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n let body: unknown;\n\n try {\n body = await c.req.json();\n } catch {\n throw new ValidationError(\"Invalid JSON body\");\n }\n\n const result = schema(body);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Validation failed\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n // Store validated data in context\n (c as HonoContext & { validatedBody: T[\"infer\"] }).set(\"validatedBody\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate query parameters\n *\n * @example\n * ```typescript\n * const PaginationSchema = type({\n * 'page?': 'string',\n * 'limit?': 'string',\n * 'search?': 'string',\n * });\n *\n * app.get('/users', validateQuery(PaginationSchema), async (c) => {\n * const { page, limit, search } = c.get('validatedQuery');\n * });\n * ```\n */\nexport function validateQuery<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n const query = c.req.query();\n const result = schema(query);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid query parameters\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedQuery: T[\"infer\"] }).set(\"validatedQuery\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate route parameters\n *\n * @example\n * ```typescript\n * const IdParamSchema = type({\n * id: 'string',\n * });\n *\n * app.get('/users/:id', validateParams(IdParamSchema), async (c) => {\n * const { id } = c.get('validatedParams');\n * });\n * ```\n */\nexport function validateParams<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n const params = c.req.param();\n const result = schema(params);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid route parameters\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedParams: T[\"infer\"] }).set(\"validatedParams\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Validate headers\n *\n * @example\n * ```typescript\n * const ApiKeySchema = type({\n * 'x-api-key': 'string',\n * });\n *\n * app.use('/api/*', validateHeaders(ApiKeySchema));\n * ```\n */\nexport function validateHeaders<T extends Type>(schema: T, options: ValidateOptions = {}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n // Convert headers to plain object\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n\n const result = schema(headers);\n\n if (result instanceof type.errors) {\n throw new ValidationError(\n options.messagePrefix ?? \"Invalid headers\",\n { errors: formatValidationErrors(result) }\n );\n }\n\n (c as HonoContext & { validatedHeaders: T[\"infer\"] }).set(\"validatedHeaders\" as never, result as never);\n\n await next();\n };\n}\n\n/**\n * Combined validation middleware\n *\n * @example\n * ```typescript\n * app.post('/users/:id',\n * validate({\n * params: type({ id: 'string' }),\n * body: type({ name: 'string', email: 'string.email' }),\n * query: type({ 'include?': 'string' }),\n * }),\n * async (c) => {\n * const params = c.get('validatedParams');\n * const body = c.get('validatedBody');\n * const query = c.get('validatedQuery');\n * }\n * );\n * ```\n */\nexport function validate<\n TParams extends Type | undefined = undefined,\n TBody extends Type | undefined = undefined,\n TQuery extends Type | undefined = undefined,\n THeaders extends Type | undefined = undefined\n>(schemas: {\n params?: TParams;\n body?: TBody;\n query?: TQuery;\n headers?: THeaders;\n}) {\n return async (c: HonoContext, next: HonoNext): Promise<void> => {\n // Validate params\n if (schemas.params) {\n const params = c.req.param();\n const result = schemas.params(params);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid route parameters\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedParams: unknown }).set(\"validatedParams\" as never, result as never);\n }\n\n // Validate query\n if (schemas.query) {\n const query = c.req.query();\n const result = schemas.query(query);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid query parameters\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedQuery: unknown }).set(\"validatedQuery\" as never, result as never);\n }\n\n // Validate headers\n if (schemas.headers) {\n const headers: Record<string, string> = {};\n c.req.raw.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n const result = schemas.headers(headers);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Invalid headers\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedHeaders: unknown }).set(\"validatedHeaders\" as never, result as never);\n }\n\n // Validate body\n if (schemas.body) {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw new ValidationError(\"Invalid JSON body\");\n }\n const result = schemas.body(body);\n if (result instanceof type.errors) {\n throw new ValidationError(\"Validation failed\", {\n errors: formatValidationErrors(result),\n });\n }\n (c as HonoContext & { validatedBody: unknown }).set(\"validatedBody\" as never, result as never);\n }\n\n await next();\n };\n}\n\n// ============================================================================\n// Legacy Aliases (for backward compatibility)\n// ============================================================================\n\n// Schema aliases with PascalCase for backward compatibility\n// Use the camelCase versions from @parsrun/types for new code\nexport {\n uuidParam as UuidParamSchema,\n paginationQuery as PaginationQuerySchema,\n searchQuery as SearchQuerySchema,\n dateRangeQuery as DateRangeQuerySchema,\n} from \"@parsrun/types\";\n"],"mappings":"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","names":["type","uuidParam","paginationQuery","searchQuery","dateRangeQuery"]}
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@parsrun/server",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Server framework for Pars - Edge-compatible, multi-tenant, modular",
5
- "keywords": ["pars", "server", "hono", "edge", "cloudflare", "deno", "bun"],
5
+ "keywords": [
6
+ "pars",
7
+ "server",
8
+ "hono",
9
+ "edge",
10
+ "cloudflare",
11
+ "deno",
12
+ "bun"
13
+ ],
6
14
  "homepage": "https://pars.run",
7
15
  "repository": {
8
16
  "type": "git",
@@ -55,19 +63,13 @@
55
63
  },
56
64
  "main": "./dist/index.js",
57
65
  "types": "./dist/index.d.ts",
58
- "files": ["dist"],
59
- "scripts": {
60
- "build": "tsup",
61
- "dev": "tsup --watch",
62
- "test": "vitest run",
63
- "test:watch": "vitest",
64
- "typecheck": "tsc --noEmit",
65
- "clean": "rm -rf dist node_modules"
66
- },
66
+ "files": [
67
+ "dist"
68
+ ],
67
69
  "dependencies": {
68
- "@parsrun/core": "workspace:*",
69
- "@parsrun/types": "workspace:*",
70
- "hono": "^4.6.0"
70
+ "hono": "^4.6.0",
71
+ "@parsrun/core": "0.1.29",
72
+ "@parsrun/types": "0.1.29"
71
73
  },
72
74
  "devDependencies": {
73
75
  "tsup": "^8.3.5",
@@ -76,5 +78,13 @@
76
78
  },
77
79
  "peerDependencies": {
78
80
  "hono": ">=4.0.0"
81
+ },
82
+ "scripts": {
83
+ "build": "tsup",
84
+ "dev": "tsup --watch",
85
+ "test": "vitest run",
86
+ "test:watch": "vitest",
87
+ "typecheck": "tsc --noEmit",
88
+ "clean": "rm -rf dist node_modules"
79
89
  }
80
- }
90
+ }