@forinda/kickjs 5.18.0 → 6.0.0

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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/{application-Dt7kC5tp.mjs → application-C4I2wDo4.mjs} +3 -3
  3. package/dist/{application-Dt7kC5tp.mjs.map → application-C4I2wDo4.mjs.map} +1 -1
  4. package/dist/{bootstrap-Bvz4uYd9.mjs → bootstrap-Bcjl1X48.mjs} +3 -3
  5. package/dist/{bootstrap-Bvz4uYd9.mjs.map → bootstrap-Bcjl1X48.mjs.map} +1 -1
  6. package/dist/config/index.mjs +2 -2
  7. package/dist/{config-EVk6fGNK.mjs → config-B7sX8Rfr.mjs} +3 -3
  8. package/dist/{config-EVk6fGNK.mjs.map → config-B7sX8Rfr.mjs.map} +1 -1
  9. package/dist/{container-Dx8WD6U8.mjs → container-DBuNmKXr.mjs} +3 -3
  10. package/dist/{container-Dx8WD6U8.mjs.map → container-DBuNmKXr.mjs.map} +1 -1
  11. package/dist/{context-BFqb1Ze3.mjs → context-CakY_skc.mjs} +3 -3
  12. package/dist/{context-BFqb1Ze3.mjs.map → context-CakY_skc.mjs.map} +1 -1
  13. package/dist/core/adapter.mjs +1 -1
  14. package/dist/core/app-module.mjs +1 -1
  15. package/dist/core/cache.mjs +1 -1
  16. package/dist/core/container.mjs +2 -2
  17. package/dist/core/cron.mjs +2 -2
  18. package/dist/core/decorators.mjs +2 -2
  19. package/dist/core/errors.mjs +1 -1
  20. package/dist/core/interfaces.mjs +1 -1
  21. package/dist/core/logger.mjs +1 -1
  22. package/dist/core/path.mjs +1 -1
  23. package/dist/core/plugin.mjs +1 -1
  24. package/dist/core/reactivity.mjs +1 -1
  25. package/dist/{core-hOkYAOcJ.mjs → core-sC6BTbfO.mjs} +3 -3
  26. package/dist/{core-hOkYAOcJ.mjs.map → core-sC6BTbfO.mjs.map} +1 -1
  27. package/dist/{env-mpP5dV-d.mjs → env-CPeaOC0t.mjs} +3 -3
  28. package/dist/{env-mpP5dV-d.mjs.map → env-CPeaOC0t.mjs.map} +1 -1
  29. package/dist/http/application.mjs +2 -2
  30. package/dist/http/bootstrap.mjs +2 -2
  31. package/dist/http/context.mjs +2 -2
  32. package/dist/http/middleware/cors.mjs +1 -1
  33. package/dist/http/middleware/csrf.mjs +1 -1
  34. package/dist/http/middleware/error-handler.mjs +2 -2
  35. package/dist/http/middleware/helmet.mjs +1 -1
  36. package/dist/http/middleware/rate-limit.mjs +1 -1
  37. package/dist/http/middleware/request-id.mjs +1 -1
  38. package/dist/http/middleware/request-logger.mjs +2 -2
  39. package/dist/http/middleware/session.mjs +1 -1
  40. package/dist/http/middleware/spa.mjs +2 -2
  41. package/dist/http/middleware/upload.mjs +2 -2
  42. package/dist/http/middleware/validate.mjs +2 -2
  43. package/dist/http/middleware/views.mjs +2 -2
  44. package/dist/http/query/index.mjs +2 -2
  45. package/dist/http/router-builder.mjs +2 -2
  46. package/dist/http/runtimes/fastify.d.mts.map +1 -1
  47. package/dist/http/runtimes/fastify.mjs +3 -3
  48. package/dist/http/runtimes/fastify.mjs.map +1 -1
  49. package/dist/http/runtimes/h3.d.mts.map +1 -1
  50. package/dist/http/runtimes/h3.mjs +2 -2
  51. package/dist/http/runtimes/h3.mjs.map +1 -1
  52. package/dist/index.mjs +2 -2
  53. package/dist/{metadata-X1R3UCs1.mjs → metadata-C22KwFSr.mjs} +2 -2
  54. package/dist/{metadata-X1R3UCs1.mjs.map → metadata-C22KwFSr.mjs.map} +1 -1
  55. package/dist/{query-BV2Wga0Z.mjs → query-Ca_k_URC.mjs} +2 -2
  56. package/dist/{query-BV2Wga0Z.mjs.map → query-Ca_k_URC.mjs.map} +1 -1
  57. package/dist/{request-scope-BdgR9Glw.mjs → request-scope-BTNd6dAe.mjs} +3 -3
  58. package/dist/{request-scope-BdgR9Glw.mjs.map → request-scope-BTNd6dAe.mjs.map} +1 -1
  59. package/package.json +5 -3
@@ -1 +1 @@
1
- {"version":3,"file":"h3.mjs","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"sourcesContent":["// The h3 runtime — `@forinda/kickjs/h3`.\n//\n// h3 (unjs) is the HTTP layer behind Nitro / Nuxt. This binds it to the same\n// HttpRuntime contract as Express and Fastify: routes become native h3 router\n// routes, and the node `ServerResponse` (h3 exposes it as `event.node.res`) is\n// wrapped in a RuntimeResponse so `ctx.json` / `ctx.html` / `ctx.sse` work\n// unchanged.\n//\n// Targets h3 **v1** (stable, node-based: `createApp` / `createRouter` /\n// `toNodeListener`, with `event.node.req` / `event.node.res`). `h3` is an\n// optional peer pinned to `^1`.\n//\n// h3 v2 (https://h3.dev/migration) moves to a web-standard `Request`/`Response`\n// core. Adopting it is the spec's §8 \"web-standard driver\" — `RuntimeResponse`\n// was shaped so a future `webRuntime()` can implement it over WHATWG streams,\n// at which point h3 v2 (and edge/Bun/Deno) become a driver swap rather than a\n// rewrite. Until that lands, this binding uses the node-compatible v1 surface.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\nconst peerRequire = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst h3 = (): any => peerRequire('h3')\n\ninterface H3EventLike {\n node: { req: IncomingMessage; res: ServerResponse }\n}\ninterface H3AppLike {\n use(arg1: unknown, arg2?: unknown): unknown\n handler: unknown\n}\n\nconst NEXT = Symbol('kickjs.h3.next')\nconst ERROR_MW = Symbol('kickjs.h3.errorMw')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse. */\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n res.statusCode = code\n return driver\n },\n json(data) {\n if (!res.headersSent) res.setHeader('content-type', 'application/json; charset=utf-8')\n res.end(JSON.stringify(data))\n return driver\n },\n send(data) {\n res.end(data as never)\n return driver\n },\n type(contentType) {\n res.setHeader('content-type', contentType)\n return driver\n },\n setHeader(name, value) {\n res.setHeader(name, value as never)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n res.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n res.flushHeaders()\n return driver\n },\n write(chunk) {\n return res.write(chunk)\n },\n end(data) {\n res.end(data as never)\n return driver\n },\n once(event, listener) {\n res.once(event, listener)\n return driver\n },\n get headersSent() {\n return res.headersSent\n },\n }\n return driver\n}\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(entry: RouteEntry): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n return async (event: H3EventLike) => {\n const req = event.node.req as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n const res = event.node.res\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n if (upload && upload.mode !== 'none') {\n // @FileUpload: read the multipart body once (readBody would consume the\n // same stream, so the two are mutually exclusive). File parts → the\n // multer-shaped ctx.file/ctx.files; non-file fields → req.body.\n const form = (await readMultipartFormData(event).catch(() => undefined)) as\n | Array<{ name?: string; filename?: string; type?: string; data: Buffer }>\n | undefined\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const part of form ?? []) {\n if (part.filename) {\n rawParts.push({\n fieldname: part.name ?? '',\n filename: part.filename,\n mimetype: part.type ?? 'application/octet-stream',\n buffer: part.data,\n })\n } else if (part.name) {\n fields[part.name] = part.data.toString('utf-8')\n }\n }\n const { file, files } = applyUploadConfig(rawParts, upload)\n req.file = file\n req.files = files\n req.body = fields\n } else if (BODY_METHODS.has(req.method ?? 'GET')) {\n req.body = await readBody(event).catch(() => undefined)\n }\n\n const headerId = req.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || req.requestId\n const store = createRequestStore(requestId)\n req.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(req as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || res.writableEnded) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (res.writableEnded) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The h3 HTTP runtime. Pass to `bootstrap({ runtime: h3Runtime() })`.\n * h3 parses bodies itself (`readBody`), so the Application skips its default\n * `express.json()` (see `nativeBodyParsing`).\n */\nexport function h3Runtime(): HttpRuntime<H3AppLike> {\n return {\n name: 'h3',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const { createApp } = h3()\n const app = createApp({\n onError: (error: unknown, event: H3EventLike) => {\n const mw = (app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW]\n if (mw && !event.node.res.writableEnded) {\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n error,\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }\n },\n })\n return app as H3AppLike\n },\n\n nodeHandler(app) {\n const { toNodeListener } = h3()\n const listener = toNodeListener(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n Promise.resolve(listener(req, res)).catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n const { createRouter, eventHandler } = h3()\n const router = createRouter()\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const method = entry.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n router[method](url, eventHandler(makeEventHandler(entry)))\n }\n }\n app.use(router)\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const { fromNodeMiddleware } = h3()\n const handler = fromNodeMiddleware(mw)\n if (opts?.path !== undefined) app.use(opts.path, handler)\n else app.use(handler)\n },\n\n serveStatic(app, prefix, dir) {\n // `serve-static` (the standalone connect middleware behind express.static)\n // so the h3 runtime carries no `express` dependency — bridged via\n // fromNodeMiddleware like any other connect middleware.\n const serveStatic = peerRequire('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n const { eventHandler } = h3()\n // Registered after the router, so it only runs when nothing matched.\n app.use(\n eventHandler((event: H3EventLike) => {\n const next = (event.node.req as IncomingMessage & { [NEXT]?: () => void })[NEXT]\n if (next) {\n next()\n return\n }\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }),\n )\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The h3 runtime's engine-native types — what the runtime-typed escape hatches\n * resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become the h3 `App`; `ctx.req` / `ctx.res` the per-request `H3Event` (h3 v1\n * routes everything through the event). Mirrors `ExpressRuntimeTypes`.\n */\nexport interface H3RuntimeTypes {\n request: import('h3').H3Event\n response: import('h3').H3Event\n app: import('h3').App\n}\n\n/** Join a mount prefix and a route path; a root path maps to the prefix itself. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":";;;;;;;;;;oSAoCA,MAAM,YAAc,cAAc,OAAO,KAAK,GAAG,EAE3C,OAAgB,YAAY,IAAI,EAUhC,KAAO,OAAO,gBAAgB,EAC9B,SAAW,OAAO,mBAAmB,EACrC,cAAwB,CAAC,EACzB,aAAe,IAAI,IAAI,CAAC,OAAQ,MAAO,QAAS,QAAQ,CAAC,EAG/D,SAAS,UAAU,IAAsC,CACvD,IAAM,OAA0B,CAC9B,OAAO,KAAM,CAEX,MADA,KAAI,WAAa,KACV,MACT,EACA,KAAK,KAAM,CAGT,OAFK,IAAI,aAAa,IAAI,UAAU,eAAgB,iCAAiC,EACrF,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,EACrB,MACT,EACA,KAAK,KAAM,CAET,OADA,IAAI,IAAI,IAAa,EACd,MACT,EACA,KAAK,YAAa,CAEhB,OADA,IAAI,UAAU,eAAgB,WAAW,EAClC,MACT,EACA,UAAU,KAAM,MAAO,CAErB,OADA,IAAI,UAAU,KAAM,KAAc,EAC3B,MACT,EACA,QAAS,CACP,MAAU,MAAM,mEAAmE,CACrF,EACA,UAAU,WAAY,QAAS,CAE7B,OADA,IAAI,UAAU,WAAY,OAAO,EAC1B,MACT,EACA,cAAe,CAEb,OADA,IAAI,aAAa,EACV,MACT,EACA,MAAM,MAAO,CACX,OAAO,IAAI,MAAM,KAAK,CACxB,EACA,IAAI,KAAM,CAER,OADA,IAAI,IAAI,IAAa,EACd,MACT,EACA,KAAK,MAAO,SAAU,CAEpB,OADA,IAAI,KAAK,MAAO,QAAQ,EACjB,MACT,EACA,IAAI,aAAc,CAChB,OAAO,IAAI,WACb,CACF,EACA,OAAO,MACT,CAGA,SAAS,iBAAiB,MAA0D,CAClF,GAAM,CAAE,gBAAiB,SAAU,SAAU,uBAA0B,GAAG,EACpE,OAAS,MAAM,KAAK,OAC1B,OAAO,KAAO,QAAuB,CACnC,IAAM,IAAM,MAAM,KAAK,IAQjB,IAAM,MAAM,KAAK,IAKvB,GAFA,IAAI,OAAS,gBAAgB,KAAK,GAAK,CAAC,EACxC,IAAI,MAAQ,SAAS,KAAK,GAAK,CAAC,EAC5B,QAAU,OAAO,OAAS,OAAQ,CAIpC,IAAM,KAAQ,MAAM,sBAAsB,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EAGhE,SAA4B,CAAC,EAC7B,OAAkC,CAAC,EACzC,IAAK,IAAM,QAAQ,MAAQ,CAAC,EACtB,KAAK,SACP,SAAS,KAAK,CACZ,UAAW,KAAK,MAAQ,GACxB,SAAU,KAAK,SACf,SAAU,KAAK,MAAQ,2BACvB,OAAQ,KAAK,IACf,CAAC,EACQ,KAAK,OACd,OAAO,KAAK,MAAQ,KAAK,KAAK,SAAS,OAAO,GAGlD,GAAM,CAAE,KAAM,OAAU,kBAAkB,SAAU,MAAM,EAC1D,IAAI,KAAO,KACX,IAAI,MAAQ,MACZ,IAAI,KAAO,MACb,MAAW,aAAa,IAAI,IAAI,QAAU,KAAK,IAC7C,IAAI,KAAO,MAAM,SAAS,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,GAGxD,IAAM,SAAW,IAAI,QAAQ,gBAEvB,MAAQ,oBADK,MAAM,QAAQ,QAAQ,EAAI,SAAS,GAAK,WAAa,IAAI,SAClC,EAC1C,IAAI,UAAY,MAAM,UAEtB,MAAM,aAAa,IAAI,MAAO,SAAY,CACxC,IAAM,IAAM,IAAI,eAAe,IAAc,IAAc,UAAW,UAAU,GAAG,CAAC,EAEpF,GAAI,MAAM,KAAK,WAAY,CACzB,IAAM,EAAI,SAAS,MAAM,KAAK,UAAU,EACxC,MAAM,IAAI,SAAe,QAAS,SAAW,CAC3C,EAAE,IAAc,IAAA,GAAqB,KAAmB,IAAM,OAAO,GAAG,EAAI,QAAQ,CAAE,CACxF,CAAC,CACH,CAEA,IAAK,IAAM,MAAM,MAAM,YAAa,CAClC,IAAI,SAAW,GASf,GARA,MAAM,IAAI,SAAe,QAAS,SAAW,CAM3C,QAAQ,QAAQ,GAAG,IALL,KAAwB,CACpC,SAAW,GACP,IAAK,OAAO,GAAG,EACd,QAAQ,CACf,CAC4B,CAAC,CAAC,CAAC,MAAM,MAAM,CAC7C,CAAC,EACG,CAAC,UAAY,IAAI,cAAe,MACtC,CAEI,MAAM,mBAAmB,MAAM,MAAM,kBAAkB,GAAG,EAC1D,KAAI,eACR,MAAM,MAAM,QAAQ,GAAG,CACzB,CAAC,CACH,CACF,CAOA,SAAgB,WAAoC,CAClD,MAAO,CACL,KAAM,KAEN,UAAU,SAA8B,CAAC,EAAc,CACrD,GAAM,CAAE,WAAc,GAAG,EACnB,IAAM,UAAU,CACpB,SAAU,MAAgB,QAAuB,CAC/C,IAAM,GAAM,IAA2C,UACnD,IAAM,CAAC,MAAM,KAAK,IAAI,eACvB,GACC,MACA,MAAM,KAAK,IACX,UAAU,MAAM,KAAK,GAAG,EACxB,SACF,CAEJ,CACF,CAAC,EACD,OAAO,GACT,EAEA,YAAY,IAAK,CACf,GAAM,CAAE,gBAAmB,GAAG,EACxB,SAAW,eAAe,GAAG,EACnC,OAAQ,IAAsB,IAAqB,OAAmC,CAChF,OAAM,IAAgE,MAAQ,MAClF,QAAQ,QAAQ,SAAS,IAAK,GAAG,CAAC,CAAC,CAAC,MAAO,KAAQ,CAC7C,MAAM,KAAK,GAAG,CACpB,CAAC,CACH,CACF,EAEA,YAAY,IAAK,MAAmB,CAClC,GAAM,CAAE,aAAc,cAAiB,GAAG,EACpC,OAAS,aAAa,EAC5B,IAAK,GAAM,CAAE,UAAW,UAAY,MAClC,IAAK,IAAM,SAAS,OAAQ,CAC1B,IAAM,IAAM,SAAS,UAAW,MAAM,IAAI,EAE1C,OADe,MAAM,OAAO,YAChB,EAAE,CAAC,IAAK,aAAa,iBAAiB,KAAK,CAAC,CAAC,CAC3D,CAEF,IAAI,IAAI,MAAM,CAChB,EAEA,WAAW,IAAK,GAAuB,KAA0B,CAC/D,GAAM,CAAE,oBAAuB,GAAG,EAC5B,QAAU,mBAAmB,EAAE,EACjC,MAAM,OAAS,IAAA,GACd,IAAI,IAAI,OAAO,EADU,IAAI,IAAI,KAAK,KAAM,OAAO,CAE1D,EAEA,YAAY,IAAK,OAAQ,IAAK,CAI5B,IAAM,YAAc,YAAY,cAAc,EAC9C,KAAK,WAAW,IAAK,YAAY,GAAG,EAAwB,CAAE,KAAM,MAAO,CAAC,CAC9E,EAEA,YAAY,IAAK,GAAuB,CACtC,GAAM,CAAE,cAAiB,GAAG,EAE5B,IAAI,IACF,aAAc,OAAuB,CACnC,IAAM,KAAQ,MAAM,KAAK,IAAkD,MAC3E,GAAI,KAAM,CACR,KAAK,EACL,MACF,CACC,GACC,MAAM,KAAK,IACX,UAAU,MAAM,KAAK,GAAG,EACxB,SACF,CACF,CAAC,CACH,CACF,EAEA,gBAAgB,IAAK,GAAuB,CACzC,IAA4C,UAAY,EAC3D,EAEA,aAAc,CACZ,OAAQ,GACR,QAAS,GACT,kBAAmB,GACnB,kBAAmB,EACrB,CACF,CACF,CAgBA,SAAS,SAAS,UAAmB,KAAsB,CACzD,IAAM,EAAI,UAAU,SAAS,GAAG,EAAI,UAAU,MAAM,EAAG,EAAE,EAAI,UAG7D,OAFI,OAAS,KAAO,OAAS,GAAW,IAAM,GAAK,IAAM,EAElD,GAAG,IADA,KAAK,WAAW,GAAG,EAAI,KAAO,IAAI,QAE9C"}
1
+ {"version":3,"file":"h3.mjs","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"sourcesContent":["// The h3 runtime — `@forinda/kickjs/h3`.\n//\n// h3 (unjs) is the HTTP layer behind Nitro / Nuxt. This binds it to the same\n// HttpRuntime contract as Express and Fastify: routes become native h3 router\n// routes, and the node `ServerResponse` (h3 exposes it as `event.node.res`) is\n// wrapped in a RuntimeResponse so `ctx.json` / `ctx.html` / `ctx.sse` work\n// unchanged.\n//\n// Targets h3 **v1** (stable, node-based: `createApp` / `createRouter` /\n// `toNodeListener`, with `event.node.req` / `event.node.res`). `h3` is an\n// optional peer pinned to `^1`.\n//\n// h3 v2 (https://h3.dev/migration) moves to a web-standard `Request`/`Response`\n// core. Adopting it is the spec's §8 \"web-standard driver\" — `RuntimeResponse`\n// was shaped so a future `webRuntime()` can implement it over WHATWG streams,\n// at which point h3 v2 (and edge/Bun/Deno) become a driver swap rather than a\n// rewrite. Until that lands, this binding uses the node-compatible v1 surface.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\nconst peerRequire = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst h3 = (): any => peerRequire('h3')\n\ninterface H3EventLike {\n node: { req: IncomingMessage; res: ServerResponse }\n}\ninterface H3AppLike {\n use(arg1: unknown, arg2?: unknown): unknown\n handler: unknown\n}\n\nconst NEXT = Symbol('kickjs.h3.next')\nconst ERROR_MW = Symbol('kickjs.h3.errorMw')\n// h3's `createRouter` is terminal — on no match it THROWS a 404, it does not\n// fall through to the next `app.use` layer like an Express Router. So every\n// route source (controllers, /health, devtools /_debug, ad-hoc adapter routes)\n// must live on ONE shared router, registered AFTER all connect middleware.\nconst ROUTER = Symbol('kickjs.h3.router')\nconst NOTFOUND_MW = Symbol('kickjs.h3.notFoundMw')\nconst ASSEMBLED = Symbol('kickjs.h3.assembled')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse. */\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n res.statusCode = code\n return driver\n },\n json(data) {\n if (!res.headersSent) res.setHeader('content-type', 'application/json; charset=utf-8')\n res.end(JSON.stringify(data))\n return driver\n },\n send(data) {\n res.end(data as never)\n return driver\n },\n type(contentType) {\n res.setHeader('content-type', contentType)\n return driver\n },\n setHeader(name, value) {\n res.setHeader(name, value as never)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n res.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n res.flushHeaders()\n return driver\n },\n write(chunk) {\n return res.write(chunk)\n },\n end(data) {\n res.end(data as never)\n return driver\n },\n once(event, listener) {\n res.once(event, listener)\n return driver\n },\n get headersSent() {\n return res.headersSent\n },\n }\n return driver\n}\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(entry: RouteEntry): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n return async (event: H3EventLike) => {\n const req = event.node.req as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n const res = event.node.res\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n if (upload && upload.mode !== 'none') {\n // @FileUpload: read the multipart body once (readBody would consume the\n // same stream, so the two are mutually exclusive). File parts → the\n // multer-shaped ctx.file/ctx.files; non-file fields → req.body.\n const form = (await readMultipartFormData(event).catch(() => undefined)) as\n | Array<{ name?: string; filename?: string; type?: string; data: Buffer }>\n | undefined\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const part of form ?? []) {\n if (part.filename) {\n rawParts.push({\n fieldname: part.name ?? '',\n filename: part.filename,\n mimetype: part.type ?? 'application/octet-stream',\n buffer: part.data,\n })\n } else if (part.name) {\n fields[part.name] = part.data.toString('utf-8')\n }\n }\n const { file, files } = applyUploadConfig(rawParts, upload)\n req.file = file\n req.files = files\n req.body = fields\n } else if (BODY_METHODS.has(req.method ?? 'GET')) {\n req.body = await readBody(event).catch(() => undefined)\n }\n\n const headerId = req.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || req.requestId\n const store = createRequestStore(requestId)\n req.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(req as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || res.writableEnded) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (res.writableEnded) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The h3 HTTP runtime. Pass to `bootstrap({ runtime: h3Runtime() })`.\n * h3 parses bodies itself (`readBody`), so the Application skips its default\n * `express.json()` (see `nativeBodyParsing`).\n */\nexport function h3Runtime(): HttpRuntime<H3AppLike> {\n return {\n name: 'h3',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const { createApp } = h3()\n const app = createApp({\n onError: (error: unknown, event: H3EventLike) => {\n const res = event.node.res\n if (res.writableEnded) return\n const state = app as {\n [ERROR_MW]?: ConnectMiddleware\n [NOTFOUND_MW]?: ConnectMiddleware\n }\n // The shared router throws a 404 on no match (it's terminal). Treat\n // that as \"not found\", not a server error: hand back to the outer\n // chain (Vite dev fall-through) when present, else run the framework's\n // notFound handler so it returns a proper 404 — only genuine handler\n // errors reach the error middleware (and the ErrorHandler log).\n const status = (error as { statusCode?: number } | undefined)?.statusCode\n if (status === 404) {\n const next = (event.node.req as IncomingMessage & { [NEXT]?: () => void })[NEXT]\n if (next) {\n next()\n return\n }\n const nf = state[NOTFOUND_MW]\n if (nf) {\n ;(nf as (req: unknown, res: unknown, next: () => void) => void)(\n event.node.req,\n resDriver(res),\n NOOP_NEXT,\n )\n return\n }\n }\n const mw = state[ERROR_MW]\n if (mw) {\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n error,\n event.node.req,\n resDriver(res),\n NOOP_NEXT,\n )\n }\n },\n })\n return app as H3AppLike\n },\n\n nodeHandler(app) {\n const { toNodeListener } = h3()\n // Register the shared router exactly once, AFTER all connect middleware\n // (serveStatic / useConnect) the setup phase added via `app.use` — the\n // router is terminal, so anything that must run for non-route paths has to\n // sit before it in the stack.\n const state = app as { [ROUTER]?: unknown; [ASSEMBLED]?: boolean }\n if (!state[ASSEMBLED]) {\n state[ASSEMBLED] = true\n if (state[ROUTER]) app.use(state[ROUTER])\n }\n const listener = toNodeListener(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n Promise.resolve(listener(req, res)).catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n const { createRouter, eventHandler } = h3()\n // One shared router per app — every mountRoutes call (controllers, health,\n // devtools, ad-hoc adapter routes) adds to it. `app.use(router)` is\n // deferred to nodeHandler so the router lands after connect middleware.\n const state = app as { [ROUTER]?: ReturnType<typeof createRouter> }\n if (!state[ROUTER]) state[ROUTER] = createRouter()\n const router = state[ROUTER]\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const method = entry.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n router[method](url, eventHandler(makeEventHandler(entry)))\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const { fromNodeMiddleware } = h3()\n const handler = fromNodeMiddleware(mw)\n if (opts?.path !== undefined) app.use(opts.path, handler)\n else app.use(handler)\n },\n\n serveStatic(app, prefix, dir) {\n // `serve-static` (the standalone connect middleware behind express.static)\n // so the h3 runtime carries no `express` dependency — bridged via\n // fromNodeMiddleware like any other connect middleware.\n const serveStatic = peerRequire('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n // Stash it — the shared router throws a 404 on no match, which `onError`\n // (createApp) dispatches to this handler (or to the Vite fall-through when\n // a `next` is present). A post-router `app.use` could never run, since the\n // terminal router throws before the stack advances.\n ;(app as { [NOTFOUND_MW]?: ConnectMiddleware })[NOTFOUND_MW] = mw\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The h3 runtime's engine-native types — what the runtime-typed escape hatches\n * resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become the h3 `App`; `ctx.req` / `ctx.res` the per-request `H3Event` (h3 v1\n * routes everything through the event). Mirrors `ExpressRuntimeTypes`.\n */\nexport interface H3RuntimeTypes {\n request: import('h3').H3Event\n response: import('h3').H3Event\n app: import('h3').App\n}\n\n/** Join a mount prefix and a route path; a root path maps to the prefix itself. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.18.0
2
+ * @forinda/kickjs v6.0.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{_ as isInjectionToken,a as formatKickError,c as UnknownAssetError,d as resolveAsset,f as useAssets,g as createToken,h as INJECTION_TOKEN,i as KickError,l as assets,m as stripExt,n as tokenName,o as ASSETS,p as groupAssetKeys,s as ASSET_MANIFEST_VERSION,t as Container,u as clearAssetCache}from"./container-Dx8WD6U8.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{ConsoleLoggerProvider,Logger,createLogger}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-X1R3UCs1.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{_ as ModuleList,a as MissingMountDepError,c as runContributors,d as DuplicateContributorError,f as MissingContributorError,g as MutableModuleRegistry,h as CircuitOpenError,i as DuplicateMountNameError,l as buildPipeline,m as CircuitBreaker,n as defineAdapter,o as MountCycleError,p as defineContextDecorator,r as definePlugin,s as mountSort,t as defineAugmentation,u as ContributorCycleError,v as defineModules,y as defineModule}from"./core-hOkYAOcJ.mjs";import{CRON_META,Cron,getCronJobs}from"./core/cron.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{i as requestStore,n as getRequestStore,r as getRequestValue,t as RequestContext}from"./context-BFqb1Ze3.mjs";import{r as requestScopeMiddleware}from"./request-scope-BdgR9Glw.mjs";import{buildRouteTable}from"./http/router-builder.mjs";import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"./query-BV2Wga0Z.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload}from"./http/middleware/upload.mjs";import{i as materializeRouter,n as buildRoutes,r as expressRuntime,t as Application}from"./application-Dt7kC5tp.mjs";import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-mpP5dV-d.mjs";import{n as isClusterPrimary,t as bootstrap}from"./bootstrap-Bvz4uYd9.mjs";import{csrf}from"./http/middleware/csrf.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-EVk6fGNK.mjs";import crypto from"node:crypto";function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return crypto.randomBytes(16).toString(`hex`)}function generateSpanId(){return crypto.randomBytes(8).toString(`hex`)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,FILTER_OPERATORS,FileUpload,Get,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,KickError,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,Patch,Post,PostConstruct,ProblemException,Problems,Put,REQUEST_ID_HEADER,Repository,RequestContext,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,assets,baseEnvSchema,bootstrap,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createLogger,createToken,csrf,defaultProblemTitle,defineAdapter,defineAugmentation,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,groupAssetKeys,hasClassMeta,helmet,isClusterPrimary,isInjectionToken,isRef,joinPaths,loadEnv,loadEnvFromSchema,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,reactive,ref,reloadEnv,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,runContributors,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,watch,withBuilder};
11
+ import{_ as isInjectionToken,a as formatKickError,c as UnknownAssetError,d as resolveAsset,f as useAssets,g as createToken,h as INJECTION_TOKEN,i as KickError,l as assets,m as stripExt,n as tokenName,o as ASSETS,p as groupAssetKeys,s as ASSET_MANIFEST_VERSION,t as Container,u as clearAssetCache}from"./container-DBuNmKXr.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{ConsoleLoggerProvider,Logger,createLogger}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-C22KwFSr.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{_ as ModuleList,a as MissingMountDepError,c as runContributors,d as DuplicateContributorError,f as MissingContributorError,g as MutableModuleRegistry,h as CircuitOpenError,i as DuplicateMountNameError,l as buildPipeline,m as CircuitBreaker,n as defineAdapter,o as MountCycleError,p as defineContextDecorator,r as definePlugin,s as mountSort,t as defineAugmentation,u as ContributorCycleError,v as defineModules,y as defineModule}from"./core-sC6BTbfO.mjs";import{CRON_META,Cron,getCronJobs}from"./core/cron.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{i as requestStore,n as getRequestStore,r as getRequestValue,t as RequestContext}from"./context-CakY_skc.mjs";import{r as requestScopeMiddleware}from"./request-scope-BTNd6dAe.mjs";import{buildRouteTable}from"./http/router-builder.mjs";import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"./query-Ca_k_URC.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload}from"./http/middleware/upload.mjs";import{i as materializeRouter,n as buildRoutes,r as expressRuntime,t as Application}from"./application-C4I2wDo4.mjs";import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-CPeaOC0t.mjs";import{n as isClusterPrimary,t as bootstrap}from"./bootstrap-Bcjl1X48.mjs";import{csrf}from"./http/middleware/csrf.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-B7sX8Rfr.mjs";import crypto from"node:crypto";function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return crypto.randomBytes(16).toString(`hex`)}function generateSpanId(){return crypto.randomBytes(8).toString(`hex`)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,FILTER_OPERATORS,FileUpload,Get,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,KickError,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,Patch,Post,PostConstruct,ProblemException,Problems,Put,REQUEST_ID_HEADER,Repository,RequestContext,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,assets,baseEnvSchema,bootstrap,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createLogger,createToken,csrf,defaultProblemTitle,defineAdapter,defineAugmentation,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,groupAssetKeys,hasClassMeta,helmet,isClusterPrimary,isInjectionToken,isRef,joinPaths,loadEnv,loadEnvFromSchema,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,reactive,ref,reloadEnv,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,runContributors,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,watch,withBuilder};
12
12
  //# sourceMappingURL=index.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.18.0
2
+ * @forinda/kickjs v6.0.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  import"reflect-metadata";function setClassMeta(key,value,target){Reflect.defineMetadata(key,value,target)}function setMethodMeta(key,value,target,method){Reflect.defineMetadata(key,value,target,method)}function getClassMeta(key,target,fallback){return Reflect.getMetadata(key,target)??fallback}function getMethodMeta(key,target,method,fallback){return Reflect.getMetadata(key,target,method)??fallback}function hasClassMeta(key,target){return Reflect.hasMetadata(key,target)}function getClassMetaOrUndefined(key,target){return Reflect.getMetadata(key,target)}function getMethodMetaOrUndefined(key,target,method){return Reflect.getMetadata(key,target,method)}function pushClassMeta(key,target,...items){let existing=Reflect.getOwnMetadata(key,target)??[];Reflect.defineMetadata(key,[...existing,...items],target)}function pushMethodMeta(key,target,method,...items){let existing=Reflect.getOwnMetadata(key,target,method)??[];Reflect.defineMetadata(key,[...existing,...items],target,method)}function setInMetaMap(key,target,mapKey,mapValue){let inherited=Reflect.getOwnMetadata(key,target),map=inherited?new Map(inherited):new Map;map.set(mapKey,mapValue),Reflect.defineMetadata(key,map,target)}function getMetaMap(key,target){return Reflect.getMetadata(key,target)??new Map}function setInMetaRecord(key,target,recKey,recValue){let inherited=Reflect.getOwnMetadata(key,target),rec=inherited?{...inherited}:{};rec[recKey]=recValue,Reflect.defineMetadata(key,rec,target)}function getMetaRecord(key,target){return Reflect.getMetadata(key,target)??{}}export{getMethodMeta as a,pushClassMeta as c,setInMetaMap as d,setInMetaRecord as f,getMetaRecord as i,pushMethodMeta as l,getClassMetaOrUndefined as n,getMethodMetaOrUndefined as o,setMethodMeta as p,getMetaMap as r,hasClassMeta as s,getClassMeta as t,setClassMeta as u};
12
- //# sourceMappingURL=metadata-X1R3UCs1.mjs.map
12
+ //# sourceMappingURL=metadata-C22KwFSr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-X1R3UCs1.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
1
+ {"version":3,"file":"metadata-C22KwFSr.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.18.0
2
+ * @forinda/kickjs v6.0.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  const FILTER_OPERATORS=new Set([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`between`,`in`,`contains`,`starts`,`ends`]),BUILTIN_DEFAULTS={maxLimit:100,defaultLimit:20,maxSearchLength:200,defaultPage:1},defaults={...BUILTIN_DEFAULTS};function setQueryParsingDefaults(partial){partial.maxLimit!==void 0&&(defaults.maxLimit=partial.maxLimit),partial.defaultLimit!==void 0&&(defaults.defaultLimit=partial.defaultLimit),partial.maxSearchLength!==void 0&&(defaults.maxSearchLength=partial.maxSearchLength)}function getQueryParsingDefaults(){return{...defaults}}function resetQueryParsingDefaults(){Object.assign(defaults,BUILTIN_DEFAULTS)}function parseFilters(filterParam,allowedFields,options){if(!filterParam)return[];let items=Array.isArray(filterParam)?filterParam:[filterParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let firstColon=item.indexOf(`:`);if(firstColon===-1)continue;let secondColon=item.indexOf(`:`,firstColon+1);if(secondColon===-1)continue;let field=item.slice(0,firstColon),operator=item.slice(firstColon+1,secondColon),value=item.slice(secondColon+1);if(!(!field||!value)){if(!FILTER_OPERATORS.has(operator)){onReject?.({kind:`filter`,field,reason:`unknown-operator`});continue}if(allowed&&!allowed.has(field)){onReject?.({kind:`filter`,field,reason:`field-not-allowed`});continue}results.push({field,operator,value})}}return results}function parseSort(sortParam,allowedFields,options){if(!sortParam)return[];let items=Array.isArray(sortParam)?sortParam:[sortParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let lastColon=item.lastIndexOf(`:`);if(lastColon===-1)continue;let field=item.slice(0,lastColon),dir=item.slice(lastColon+1).toLowerCase();if(field&&!(dir!==`asc`&&dir!==`desc`)){if(allowed&&!allowed.has(field)){onReject?.({kind:`sort`,field,reason:`field-not-allowed`});continue}results.push({field,direction:dir})}}return results}function parsePagination(params,options){let defaultLimit=options?.defaultLimit??defaults.defaultLimit,maxLimit=options?.maxLimit??defaults.maxLimit,page=typeof params.page==`string`?Number.parseInt(params.page,10):params.page??defaults.defaultPage,limit=typeof params.limit==`string`?Number.parseInt(params.limit,10):params.limit??defaultLimit;return(Number.isNaN(page)||page<1)&&(page=defaults.defaultPage),(Number.isNaN(limit)||limit<1)&&(limit=defaultLimit),limit>maxLimit&&(limit=maxLimit),{page,limit,offset:(page-1)*limit}}function parseSearchQuery(q,options){if(!q)return``;let maxLen=options?.maxSearchLength??defaults.maxSearchLength,trimmed=q.trim();return trimmed.length>maxLen?(options?.onReject?.({kind:`search`,field:`q`,reason:`truncated`}),trimmed.slice(0,maxLen)):trimmed}function normalizeFieldConfig(config){if(config)return`columns`in config&&config.columns&&typeof config.columns==`object`?{filterable:Object.keys(config.columns),sortable:config.sortable?Object.keys(config.sortable):void 0,searchable:void 0}:config}function parseQuery(query,fieldConfig,options){let normalized=normalizeFieldConfig(fieldConfig),rejectOpts=options?.onReject?{onReject:options.onReject}:void 0;return{filters:parseFilters(query.filter,normalized?.filterable,rejectOpts),sort:parseSort(query.sort,normalized?.sortable,rejectOpts),pagination:parsePagination({page:query.page,limit:query.limit},options),search:parseSearchQuery(query.q,options)}}function buildQueryParams(parsed){let params={};return parsed.filters?.length&&(params.filter=parsed.filters.map(f=>`${f.field}:${f.operator}:${f.value}`)),parsed.sort?.length&&(params.sort=parsed.sort.map(s=>`${s.field}:${s.direction}`)),parsed.pagination&&(params.page=parsed.pagination.page,params.limit=parsed.pagination.limit),parsed.search&&(params.q=parsed.search),params}export{parseQuery as a,resetQueryParsingDefaults as c,parsePagination as i,setQueryParsingDefaults as l,getQueryParsingDefaults as n,parseSearchQuery as o,parseFilters as r,parseSort as s,buildQueryParams as t,FILTER_OPERATORS as u};
12
- //# sourceMappingURL=query-BV2Wga0Z.mjs.map
12
+ //# sourceMappingURL=query-Ca_k_URC.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-BV2Wga0Z.mjs","names":[],"sources":["../src/http/query/types.ts","../src/http/query/parse-query.ts"],"sourcesContent":["/** Supported filter operators for query string parsing */\nexport type FilterOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'between'\n | 'in'\n | 'contains'\n | 'starts'\n | 'ends'\n\nexport const FILTER_OPERATORS = new Set<string>([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'in',\n 'contains',\n 'starts',\n 'ends',\n])\n\nexport interface FilterItem {\n field: string\n operator: FilterOperator\n value: string\n}\n\nexport interface SortItem {\n field: string\n direction: 'asc' | 'desc'\n}\n\nexport interface PaginationParams {\n page: number\n limit: number\n offset: number\n}\n\n/**\n * The result of parsing a query string. ORM-agnostic — pass this to\n * a query builder adapter (Drizzle, Prisma, Sequelize, etc.) to produce\n * database-specific query objects.\n */\nexport interface ParsedQuery {\n filters: FilterItem[]\n sort: SortItem[]\n pagination: PaginationParams\n search: string\n}\n\n/**\n * Helper that extracts the literal field union from a `QueryFieldConfig`\n * key. When the user passes `{ filterable: ['status', 'category'] } as const`,\n * `FieldsOf<typeof config, 'filterable'>` resolves to `'status' | 'category'`.\n *\n * Without `as const`, the arrays widen to `string[]` and the union falls\n * back to `string`. This is the documented escape hatch.\n */\nexport type FieldsOf<\n TConfig extends QueryFieldConfig | undefined,\n K extends 'filterable' | 'sortable' | 'searchable',\n> = TConfig extends { readonly [P in K]: readonly (infer F)[] }\n ? F extends string\n ? F\n : string\n : TConfig extends { columns: infer C }\n ? K extends 'filterable'\n ? keyof C & string\n : string\n : string\n\n/**\n * Narrowed `ParsedQuery` whose `filters[].field` and `sort[].field`\n * unions are constrained to the literal whitelist passed to\n * `ctx.qs()` / `parseQuery()`. Extends `ParsedQuery`, so any function\n * accepting `ParsedQuery` accepts a `TypedParsedQuery<X>` automatically\n * (subtype assignment, no codegen, backward compatible).\n *\n * @example\n * ```ts\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt'],\n * } as const)\n * parsed.filters[0]?.field // 'status' | 'priority'\n * parsed.sort[0]?.field // 'createdAt'\n * ```\n */\nexport interface TypedParsedQuery<\n TConfig extends QueryFieldConfig | undefined,\n> extends ParsedQuery {\n filters: Array<FilterItem & { field: FieldsOf<TConfig, 'filterable'> }>\n sort: Array<SortItem & { field: FieldsOf<TConfig, 'sortable'> }>\n}\n\n/**\n * Restrict which fields can be filtered, sorted, or searched.\n * Fields not in the allow-list are silently ignored.\n *\n * Accepts two forms:\n * 1. **String arrays** (standard): `{ filterable: ['name', 'status'], sortable: ['createdAt'] }`\n * 2. **Column-object maps** (Drizzle-style): `{ columns: { name: col, status: col }, sortable: { createdAt: col } }`\n *\n * When column-object maps are provided, `Object.keys()` is used to derive the field names.\n */\nexport type QueryFieldConfig = StringQueryFieldConfig | ColumnQueryFieldConfig\n\nexport interface StringQueryFieldConfig {\n filterable?: string[]\n sortable?: string[]\n searchable?: string[]\n}\n\n/**\n * Column-object-based field config (e.g., from DrizzleQueryParamsConfig).\n * Keys are the field names used in query strings; values are ORM column references.\n */\nexport interface ColumnQueryFieldConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/** Standardized paginated response shape */\nexport interface PaginatedResponse<T = any> {\n data: T[]\n meta: {\n page: number\n limit: number\n total: number\n totalPages: number\n hasNext: boolean\n hasPrev: boolean\n }\n}\n\n/**\n * Interface for ORM-specific query builder adapters.\n * Implement this to translate a `ParsedQuery` into your ORM's query format.\n *\n * @example\n * ```ts\n * // Drizzle adapter\n * class DrizzleQueryAdapter implements QueryBuilderAdapter<DrizzleQueryResult> {\n * build(parsed: ParsedQuery, config: DrizzleConfig): DrizzleQueryResult {\n * // Convert filters → Drizzle SQL conditions\n * // Convert sort → Drizzle orderBy\n * return { where, orderBy, limit, offset }\n * }\n * }\n *\n * // Prisma adapter\n * class PrismaQueryAdapter implements QueryBuilderAdapter<PrismaQueryResult> {\n * build(parsed: ParsedQuery, config: PrismaConfig): PrismaQueryResult {\n * return { where, orderBy, skip, take }\n * }\n * }\n * ```\n */\nexport interface QueryBuilderAdapter<TResult = any, TConfig = any> {\n /** Human-readable name for debugging */\n readonly name: string\n\n /**\n * Convert a ParsedQuery into an ORM-specific query object.\n * @param parsed - The ORM-agnostic parsed query\n * @param config - ORM-specific configuration (column maps, search columns, etc.)\n */\n build(parsed: ParsedQuery, config: TConfig): TResult\n}\n","import {\n FILTER_OPERATORS,\n type FilterItem,\n type FilterOperator,\n type SortItem,\n type PaginationParams,\n type ParsedQuery,\n type QueryFieldConfig,\n type StringQueryFieldConfig,\n type TypedParsedQuery,\n} from './types'\n\n// ── Configurable defaults ───────────────────────────────────────────────\n\n/**\n * Why a rejected field/value was dropped. Surfaced through\n * {@link ParseQueryOptions.onReject} so a caller can warn, count, or\n * return a 400 instead of the historical silent drop.\n */\nexport type QueryRejectReason = 'field-not-allowed' | 'unknown-operator' | 'truncated'\n\nexport interface QueryRejection {\n kind: 'filter' | 'sort' | 'search'\n field: string\n reason: QueryRejectReason\n}\n\nexport interface ParseQueryOptions {\n /** Hard cap on `limit`. Default: global (100, override via {@link setQueryParsingDefaults}). */\n maxLimit?: number\n /** `limit` when the query omits one. Default: global (20). */\n defaultLimit?: number\n /** Max `q` length before truncation. Default: global (200). */\n maxSearchLength?: number\n /**\n * Called once per dropped filter/sort field or truncated search.\n * The historical behaviour is a silent drop; wire this to a logger or\n * a 400 to make over-broad / typo'd queries visible.\n */\n onReject?: (rejection: QueryRejection) => void\n}\n\ninterface QueryParsingDefaults {\n maxLimit: number\n defaultLimit: number\n maxSearchLength: number\n defaultPage: number\n}\n\nconst BUILTIN_DEFAULTS: QueryParsingDefaults = {\n maxLimit: 100,\n defaultLimit: 20,\n maxSearchLength: 200,\n defaultPage: 1,\n}\n\nconst defaults: QueryParsingDefaults = { ...BUILTIN_DEFAULTS }\n\n/**\n * Globally override the query-parsing limits — call once at bootstrap.\n * Per-call {@link ParseQueryOptions} still take precedence over these.\n */\nexport function setQueryParsingDefaults(\n partial: Partial<Omit<QueryParsingDefaults, 'defaultPage'>>,\n): void {\n if (partial.maxLimit !== undefined) defaults.maxLimit = partial.maxLimit\n if (partial.defaultLimit !== undefined) defaults.defaultLimit = partial.defaultLimit\n if (partial.maxSearchLength !== undefined) defaults.maxSearchLength = partial.maxSearchLength\n}\n\n/** Read the current global query-parsing defaults (a copy). */\nexport function getQueryParsingDefaults(): Readonly<QueryParsingDefaults> {\n return { ...defaults }\n}\n\n/**\n * Restore the built-in query-parsing defaults (maxLimit 100, defaultLimit\n * 20, maxSearchLength 200). Useful in test teardown after a\n * {@link setQueryParsingDefaults} override so global state can't leak\n * between tests.\n */\nexport function resetQueryParsingDefaults(): void {\n Object.assign(defaults, BUILTIN_DEFAULTS)\n}\n\n// ── Individual parsers ──────────────────────────────────────────────────\n\n/** Parse filter strings like \"status:eq:active\" into structured objects */\nexport function parseFilters(\n filterParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): FilterItem[] {\n if (!filterParam) return []\n\n const items = Array.isArray(filterParam) ? filterParam : [filterParam]\n const results: FilterItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on first two colons only — value may contain colons (e.g. timestamps)\n const firstColon = item.indexOf(':')\n if (firstColon === -1) continue\n\n const secondColon = item.indexOf(':', firstColon + 1)\n if (secondColon === -1) continue\n\n const field = item.slice(0, firstColon)\n const operator = item.slice(firstColon + 1, secondColon)\n const value = item.slice(secondColon + 1)\n\n if (!field || !value) continue\n if (!FILTER_OPERATORS.has(operator)) {\n onReject?.({ kind: 'filter', field, reason: 'unknown-operator' })\n continue\n }\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'filter', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, operator: operator as FilterOperator, value })\n }\n\n return results\n}\n\n/** Parse sort strings like \"firstName:asc\" into structured objects */\nexport function parseSort(\n sortParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): SortItem[] {\n if (!sortParam) return []\n\n const items = Array.isArray(sortParam) ? sortParam : [sortParam]\n const results: SortItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on last colon so field names with colons work\n const lastColon = item.lastIndexOf(':')\n if (lastColon === -1) continue\n\n const field = item.slice(0, lastColon)\n const dir = item.slice(lastColon + 1).toLowerCase()\n\n if (!field) continue\n if (dir !== 'asc' && dir !== 'desc') continue\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'sort', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, direction: dir })\n }\n\n return results\n}\n\n/** Parse page/limit into pagination with computed offset */\nexport function parsePagination(\n params: {\n page?: string | number\n limit?: string | number\n },\n options?: Pick<ParseQueryOptions, 'maxLimit' | 'defaultLimit'>,\n): PaginationParams {\n const defaultLimit = options?.defaultLimit ?? defaults.defaultLimit\n const maxLimit = options?.maxLimit ?? defaults.maxLimit\n\n let page =\n typeof params.page === 'string'\n ? Number.parseInt(params.page, 10)\n : (params.page ?? defaults.defaultPage)\n let limit =\n typeof params.limit === 'string'\n ? Number.parseInt(params.limit, 10)\n : (params.limit ?? defaultLimit)\n\n if (Number.isNaN(page) || page < 1) page = defaults.defaultPage\n if (Number.isNaN(limit) || limit < 1) limit = defaultLimit\n if (limit > maxLimit) limit = maxLimit\n\n return { page, limit, offset: (page - 1) * limit }\n}\n\n/** Sanitize and truncate search query */\nexport function parseSearchQuery(\n q: string | undefined,\n options?: Pick<ParseQueryOptions, 'maxSearchLength' | 'onReject'>,\n): string {\n if (!q) return ''\n const maxLen = options?.maxSearchLength ?? defaults.maxSearchLength\n const trimmed = q.trim()\n if (trimmed.length > maxLen) {\n options?.onReject?.({ kind: 'search', field: 'q', reason: 'truncated' })\n return trimmed.slice(0, maxLen)\n }\n return trimmed\n}\n\n// ── Config normalizer ───────────────────────────────────────────────────\n\n/**\n * Normalize a QueryFieldConfig into the string-based form.\n * Detects column-object-based configs (DrizzleQueryParamsConfig) by checking\n * for a `columns` property and extracts `Object.keys()`.\n */\nfunction normalizeFieldConfig(config?: QueryFieldConfig): StringQueryFieldConfig | undefined {\n if (!config) return undefined\n\n // Column-object-based config (e.g., DrizzleQueryParamsConfig)\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: undefined, // searchColumns are Column objects, not used for string filtering\n }\n }\n\n // Already string-based\n return config as StringQueryFieldConfig\n}\n\n// ── Combined parser ─────────────────────────────────────────────────────\n\n/**\n * Parse a raw Express query object into a structured, ORM-agnostic ParsedQuery.\n *\n * @param query - Raw query string object from `req.query` or Zod-validated object\n * @param fieldConfig - Optional field restrictions (whitelist filterable/sortable/searchable).\n * Accepts both string-based configs (`{ filterable, sortable, searchable }`) and\n * column-object configs (`{ columns, sortable, searchColumns }`).\n * @param options - Limits + an `onReject` hook for dropped/truncated input.\n *\n * @example\n * ```ts\n * // String-based config\n * const parsed = parseQuery(ctx.query, {\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n *\n * // Column-object config (DrizzleQueryParamsConfig)\n * const parsed = parseQuery(ctx.query, TASK_QUERY_CONFIG)\n * ```\n *\n * Query string format:\n * - Filters: `?filter=field:operator:value` (repeatable)\n * - Sort: `?sort=field:asc|desc` (repeatable)\n * - Pagination: `?page=1&limit=20`\n * - Search: `?q=search+term`\n *\n * Filter operators: eq, neq, gt, gte, lt, lte, between, in, contains, starts, ends\n */\nexport function parseQuery<TConfig extends QueryFieldConfig | undefined = undefined>(\n query: Record<string, any>,\n fieldConfig?: TConfig,\n options?: ParseQueryOptions,\n): TypedParsedQuery<TConfig> {\n const normalized = normalizeFieldConfig(fieldConfig)\n const rejectOpts = options?.onReject ? { onReject: options.onReject } : undefined\n return {\n filters: parseFilters(query.filter, normalized?.filterable, rejectOpts),\n sort: parseSort(query.sort, normalized?.sortable, rejectOpts),\n pagination: parsePagination({ page: query.page, limit: query.limit }, options),\n search: parseSearchQuery(query.q, options),\n } as TypedParsedQuery<TConfig>\n}\n\n// ── Query URL builder (for client-side or testing) ──────────────────────\n\n/** Convert ParsedQuery back into query string parameters */\nexport function buildQueryParams(\n parsed: Partial<ParsedQuery>,\n): Record<string, string | string[] | number> {\n const params: Record<string, string | string[] | number> = {}\n\n if (parsed.filters?.length) {\n params.filter = parsed.filters.map((f) => `${f.field}:${f.operator}:${f.value}`)\n }\n\n if (parsed.sort?.length) {\n params.sort = parsed.sort.map((s) => `${s.field}:${s.direction}`)\n }\n\n if (parsed.pagination) {\n params.page = parsed.pagination.page\n params.limit = parsed.pagination.limit\n }\n\n if (parsed.search) {\n params.q = parsed.search\n }\n\n return params\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-Ca_k_URC.mjs","names":[],"sources":["../src/http/query/types.ts","../src/http/query/parse-query.ts"],"sourcesContent":["/** Supported filter operators for query string parsing */\nexport type FilterOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'between'\n | 'in'\n | 'contains'\n | 'starts'\n | 'ends'\n\nexport const FILTER_OPERATORS = new Set<string>([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'in',\n 'contains',\n 'starts',\n 'ends',\n])\n\nexport interface FilterItem {\n field: string\n operator: FilterOperator\n value: string\n}\n\nexport interface SortItem {\n field: string\n direction: 'asc' | 'desc'\n}\n\nexport interface PaginationParams {\n page: number\n limit: number\n offset: number\n}\n\n/**\n * The result of parsing a query string. ORM-agnostic — pass this to\n * a query builder adapter (Drizzle, Prisma, Sequelize, etc.) to produce\n * database-specific query objects.\n */\nexport interface ParsedQuery {\n filters: FilterItem[]\n sort: SortItem[]\n pagination: PaginationParams\n search: string\n}\n\n/**\n * Helper that extracts the literal field union from a `QueryFieldConfig`\n * key. When the user passes `{ filterable: ['status', 'category'] } as const`,\n * `FieldsOf<typeof config, 'filterable'>` resolves to `'status' | 'category'`.\n *\n * Without `as const`, the arrays widen to `string[]` and the union falls\n * back to `string`. This is the documented escape hatch.\n */\nexport type FieldsOf<\n TConfig extends QueryFieldConfig | undefined,\n K extends 'filterable' | 'sortable' | 'searchable',\n> = TConfig extends { readonly [P in K]: readonly (infer F)[] }\n ? F extends string\n ? F\n : string\n : TConfig extends { columns: infer C }\n ? K extends 'filterable'\n ? keyof C & string\n : string\n : string\n\n/**\n * Narrowed `ParsedQuery` whose `filters[].field` and `sort[].field`\n * unions are constrained to the literal whitelist passed to\n * `ctx.qs()` / `parseQuery()`. Extends `ParsedQuery`, so any function\n * accepting `ParsedQuery` accepts a `TypedParsedQuery<X>` automatically\n * (subtype assignment, no codegen, backward compatible).\n *\n * @example\n * ```ts\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt'],\n * } as const)\n * parsed.filters[0]?.field // 'status' | 'priority'\n * parsed.sort[0]?.field // 'createdAt'\n * ```\n */\nexport interface TypedParsedQuery<\n TConfig extends QueryFieldConfig | undefined,\n> extends ParsedQuery {\n filters: Array<FilterItem & { field: FieldsOf<TConfig, 'filterable'> }>\n sort: Array<SortItem & { field: FieldsOf<TConfig, 'sortable'> }>\n}\n\n/**\n * Restrict which fields can be filtered, sorted, or searched.\n * Fields not in the allow-list are silently ignored.\n *\n * Accepts two forms:\n * 1. **String arrays** (standard): `{ filterable: ['name', 'status'], sortable: ['createdAt'] }`\n * 2. **Column-object maps** (Drizzle-style): `{ columns: { name: col, status: col }, sortable: { createdAt: col } }`\n *\n * When column-object maps are provided, `Object.keys()` is used to derive the field names.\n */\nexport type QueryFieldConfig = StringQueryFieldConfig | ColumnQueryFieldConfig\n\nexport interface StringQueryFieldConfig {\n filterable?: string[]\n sortable?: string[]\n searchable?: string[]\n}\n\n/**\n * Column-object-based field config (e.g., from DrizzleQueryParamsConfig).\n * Keys are the field names used in query strings; values are ORM column references.\n */\nexport interface ColumnQueryFieldConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/** Standardized paginated response shape */\nexport interface PaginatedResponse<T = any> {\n data: T[]\n meta: {\n page: number\n limit: number\n total: number\n totalPages: number\n hasNext: boolean\n hasPrev: boolean\n }\n}\n\n/**\n * Interface for ORM-specific query builder adapters.\n * Implement this to translate a `ParsedQuery` into your ORM's query format.\n *\n * @example\n * ```ts\n * // Drizzle adapter\n * class DrizzleQueryAdapter implements QueryBuilderAdapter<DrizzleQueryResult> {\n * build(parsed: ParsedQuery, config: DrizzleConfig): DrizzleQueryResult {\n * // Convert filters → Drizzle SQL conditions\n * // Convert sort → Drizzle orderBy\n * return { where, orderBy, limit, offset }\n * }\n * }\n *\n * // Prisma adapter\n * class PrismaQueryAdapter implements QueryBuilderAdapter<PrismaQueryResult> {\n * build(parsed: ParsedQuery, config: PrismaConfig): PrismaQueryResult {\n * return { where, orderBy, skip, take }\n * }\n * }\n * ```\n */\nexport interface QueryBuilderAdapter<TResult = any, TConfig = any> {\n /** Human-readable name for debugging */\n readonly name: string\n\n /**\n * Convert a ParsedQuery into an ORM-specific query object.\n * @param parsed - The ORM-agnostic parsed query\n * @param config - ORM-specific configuration (column maps, search columns, etc.)\n */\n build(parsed: ParsedQuery, config: TConfig): TResult\n}\n","import {\n FILTER_OPERATORS,\n type FilterItem,\n type FilterOperator,\n type SortItem,\n type PaginationParams,\n type ParsedQuery,\n type QueryFieldConfig,\n type StringQueryFieldConfig,\n type TypedParsedQuery,\n} from './types'\n\n// ── Configurable defaults ───────────────────────────────────────────────\n\n/**\n * Why a rejected field/value was dropped. Surfaced through\n * {@link ParseQueryOptions.onReject} so a caller can warn, count, or\n * return a 400 instead of the historical silent drop.\n */\nexport type QueryRejectReason = 'field-not-allowed' | 'unknown-operator' | 'truncated'\n\nexport interface QueryRejection {\n kind: 'filter' | 'sort' | 'search'\n field: string\n reason: QueryRejectReason\n}\n\nexport interface ParseQueryOptions {\n /** Hard cap on `limit`. Default: global (100, override via {@link setQueryParsingDefaults}). */\n maxLimit?: number\n /** `limit` when the query omits one. Default: global (20). */\n defaultLimit?: number\n /** Max `q` length before truncation. Default: global (200). */\n maxSearchLength?: number\n /**\n * Called once per dropped filter/sort field or truncated search.\n * The historical behaviour is a silent drop; wire this to a logger or\n * a 400 to make over-broad / typo'd queries visible.\n */\n onReject?: (rejection: QueryRejection) => void\n}\n\ninterface QueryParsingDefaults {\n maxLimit: number\n defaultLimit: number\n maxSearchLength: number\n defaultPage: number\n}\n\nconst BUILTIN_DEFAULTS: QueryParsingDefaults = {\n maxLimit: 100,\n defaultLimit: 20,\n maxSearchLength: 200,\n defaultPage: 1,\n}\n\nconst defaults: QueryParsingDefaults = { ...BUILTIN_DEFAULTS }\n\n/**\n * Globally override the query-parsing limits — call once at bootstrap.\n * Per-call {@link ParseQueryOptions} still take precedence over these.\n */\nexport function setQueryParsingDefaults(\n partial: Partial<Omit<QueryParsingDefaults, 'defaultPage'>>,\n): void {\n if (partial.maxLimit !== undefined) defaults.maxLimit = partial.maxLimit\n if (partial.defaultLimit !== undefined) defaults.defaultLimit = partial.defaultLimit\n if (partial.maxSearchLength !== undefined) defaults.maxSearchLength = partial.maxSearchLength\n}\n\n/** Read the current global query-parsing defaults (a copy). */\nexport function getQueryParsingDefaults(): Readonly<QueryParsingDefaults> {\n return { ...defaults }\n}\n\n/**\n * Restore the built-in query-parsing defaults (maxLimit 100, defaultLimit\n * 20, maxSearchLength 200). Useful in test teardown after a\n * {@link setQueryParsingDefaults} override so global state can't leak\n * between tests.\n */\nexport function resetQueryParsingDefaults(): void {\n Object.assign(defaults, BUILTIN_DEFAULTS)\n}\n\n// ── Individual parsers ──────────────────────────────────────────────────\n\n/** Parse filter strings like \"status:eq:active\" into structured objects */\nexport function parseFilters(\n filterParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): FilterItem[] {\n if (!filterParam) return []\n\n const items = Array.isArray(filterParam) ? filterParam : [filterParam]\n const results: FilterItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on first two colons only — value may contain colons (e.g. timestamps)\n const firstColon = item.indexOf(':')\n if (firstColon === -1) continue\n\n const secondColon = item.indexOf(':', firstColon + 1)\n if (secondColon === -1) continue\n\n const field = item.slice(0, firstColon)\n const operator = item.slice(firstColon + 1, secondColon)\n const value = item.slice(secondColon + 1)\n\n if (!field || !value) continue\n if (!FILTER_OPERATORS.has(operator)) {\n onReject?.({ kind: 'filter', field, reason: 'unknown-operator' })\n continue\n }\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'filter', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, operator: operator as FilterOperator, value })\n }\n\n return results\n}\n\n/** Parse sort strings like \"firstName:asc\" into structured objects */\nexport function parseSort(\n sortParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): SortItem[] {\n if (!sortParam) return []\n\n const items = Array.isArray(sortParam) ? sortParam : [sortParam]\n const results: SortItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on last colon so field names with colons work\n const lastColon = item.lastIndexOf(':')\n if (lastColon === -1) continue\n\n const field = item.slice(0, lastColon)\n const dir = item.slice(lastColon + 1).toLowerCase()\n\n if (!field) continue\n if (dir !== 'asc' && dir !== 'desc') continue\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'sort', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, direction: dir })\n }\n\n return results\n}\n\n/** Parse page/limit into pagination with computed offset */\nexport function parsePagination(\n params: {\n page?: string | number\n limit?: string | number\n },\n options?: Pick<ParseQueryOptions, 'maxLimit' | 'defaultLimit'>,\n): PaginationParams {\n const defaultLimit = options?.defaultLimit ?? defaults.defaultLimit\n const maxLimit = options?.maxLimit ?? defaults.maxLimit\n\n let page =\n typeof params.page === 'string'\n ? Number.parseInt(params.page, 10)\n : (params.page ?? defaults.defaultPage)\n let limit =\n typeof params.limit === 'string'\n ? Number.parseInt(params.limit, 10)\n : (params.limit ?? defaultLimit)\n\n if (Number.isNaN(page) || page < 1) page = defaults.defaultPage\n if (Number.isNaN(limit) || limit < 1) limit = defaultLimit\n if (limit > maxLimit) limit = maxLimit\n\n return { page, limit, offset: (page - 1) * limit }\n}\n\n/** Sanitize and truncate search query */\nexport function parseSearchQuery(\n q: string | undefined,\n options?: Pick<ParseQueryOptions, 'maxSearchLength' | 'onReject'>,\n): string {\n if (!q) return ''\n const maxLen = options?.maxSearchLength ?? defaults.maxSearchLength\n const trimmed = q.trim()\n if (trimmed.length > maxLen) {\n options?.onReject?.({ kind: 'search', field: 'q', reason: 'truncated' })\n return trimmed.slice(0, maxLen)\n }\n return trimmed\n}\n\n// ── Config normalizer ───────────────────────────────────────────────────\n\n/**\n * Normalize a QueryFieldConfig into the string-based form.\n * Detects column-object-based configs (DrizzleQueryParamsConfig) by checking\n * for a `columns` property and extracts `Object.keys()`.\n */\nfunction normalizeFieldConfig(config?: QueryFieldConfig): StringQueryFieldConfig | undefined {\n if (!config) return undefined\n\n // Column-object-based config (e.g., DrizzleQueryParamsConfig)\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: undefined, // searchColumns are Column objects, not used for string filtering\n }\n }\n\n // Already string-based\n return config as StringQueryFieldConfig\n}\n\n// ── Combined parser ─────────────────────────────────────────────────────\n\n/**\n * Parse a raw Express query object into a structured, ORM-agnostic ParsedQuery.\n *\n * @param query - Raw query string object from `req.query` or Zod-validated object\n * @param fieldConfig - Optional field restrictions (whitelist filterable/sortable/searchable).\n * Accepts both string-based configs (`{ filterable, sortable, searchable }`) and\n * column-object configs (`{ columns, sortable, searchColumns }`).\n * @param options - Limits + an `onReject` hook for dropped/truncated input.\n *\n * @example\n * ```ts\n * // String-based config\n * const parsed = parseQuery(ctx.query, {\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n *\n * // Column-object config (DrizzleQueryParamsConfig)\n * const parsed = parseQuery(ctx.query, TASK_QUERY_CONFIG)\n * ```\n *\n * Query string format:\n * - Filters: `?filter=field:operator:value` (repeatable)\n * - Sort: `?sort=field:asc|desc` (repeatable)\n * - Pagination: `?page=1&limit=20`\n * - Search: `?q=search+term`\n *\n * Filter operators: eq, neq, gt, gte, lt, lte, between, in, contains, starts, ends\n */\nexport function parseQuery<TConfig extends QueryFieldConfig | undefined = undefined>(\n query: Record<string, any>,\n fieldConfig?: TConfig,\n options?: ParseQueryOptions,\n): TypedParsedQuery<TConfig> {\n const normalized = normalizeFieldConfig(fieldConfig)\n const rejectOpts = options?.onReject ? { onReject: options.onReject } : undefined\n return {\n filters: parseFilters(query.filter, normalized?.filterable, rejectOpts),\n sort: parseSort(query.sort, normalized?.sortable, rejectOpts),\n pagination: parsePagination({ page: query.page, limit: query.limit }, options),\n search: parseSearchQuery(query.q, options),\n } as TypedParsedQuery<TConfig>\n}\n\n// ── Query URL builder (for client-side or testing) ──────────────────────\n\n/** Convert ParsedQuery back into query string parameters */\nexport function buildQueryParams(\n parsed: Partial<ParsedQuery>,\n): Record<string, string | string[] | number> {\n const params: Record<string, string | string[] | number> = {}\n\n if (parsed.filters?.length) {\n params.filter = parsed.filters.map((f) => `${f.field}:${f.operator}:${f.value}`)\n }\n\n if (parsed.sort?.length) {\n params.sort = parsed.sort.map((s) => `${s.field}:${s.direction}`)\n }\n\n if (parsed.pagination) {\n params.page = parsed.pagination.page\n params.limit = parsed.pagination.limit\n }\n\n if (parsed.search) {\n params.q = parsed.search\n }\n\n return params\n}\n"],"mappings":";;;;;;;;;;AAcA,MAAa,iBAAmB,IAAI,IAAY,CAC9C,KACA,MACA,KACA,MACA,KACA,MACA,UACA,KACA,WACA,SACA,MACF,CAAC,ECuBK,iBAAyC,CAC7C,SAAU,IACV,aAAc,GACd,gBAAiB,IACjB,YAAa,CACf,EAEM,SAAiC,CAAE,GAAG,gBAAiB,EAM7D,SAAgB,wBACd,QACM,CACF,QAAQ,WAAa,IAAA,KAAW,SAAS,SAAW,QAAQ,UAC5D,QAAQ,eAAiB,IAAA,KAAW,SAAS,aAAe,QAAQ,cACpE,QAAQ,kBAAoB,IAAA,KAAW,SAAS,gBAAkB,QAAQ,gBAChF,CAGA,SAAgB,yBAA0D,CACxE,MAAO,CAAE,GAAG,QAAS,CACvB,CAQA,SAAgB,2BAAkC,CAChD,OAAO,OAAO,SAAU,gBAAgB,CAC1C,CAKA,SAAgB,aACd,YACA,cACA,QACc,CACd,GAAI,CAAC,YAAa,MAAO,CAAC,EAE1B,IAAM,MAAQ,MAAM,QAAQ,WAAW,EAAI,YAAc,CAAC,WAAW,EAC/D,QAAwB,CAAC,EACzB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KACnD,SAAW,SAAS,SAE1B,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,WAAa,KAAK,QAAQ,GAAG,EACnC,GAAI,aAAe,GAAI,SAEvB,IAAM,YAAc,KAAK,QAAQ,IAAK,WAAa,CAAC,EACpD,GAAI,cAAgB,GAAI,SAExB,IAAM,MAAQ,KAAK,MAAM,EAAG,UAAU,EAChC,SAAW,KAAK,MAAM,WAAa,EAAG,WAAW,EACjD,MAAQ,KAAK,MAAM,YAAc,CAAC,EAEpC,MAAC,OAAS,CAAC,OACf,IAAI,CAAC,iBAAiB,IAAI,QAAQ,EAAG,CACnC,WAAW,CAAE,KAAM,SAAU,MAAO,OAAQ,kBAAmB,CAAC,EAChE,QACF,CACA,GAAI,SAAW,CAAC,QAAQ,IAAI,KAAK,EAAG,CAClC,WAAW,CAAE,KAAM,SAAU,MAAO,OAAQ,mBAAoB,CAAC,EACjE,QACF,CAEA,QAAQ,KAAK,CAAE,MAAiB,SAA4B,KAAM,CAAC,CANnE,CAOF,CAEA,OAAO,OACT,CAGA,SAAgB,UACd,UACA,cACA,QACY,CACZ,GAAI,CAAC,UAAW,MAAO,CAAC,EAExB,IAAM,MAAQ,MAAM,QAAQ,SAAS,EAAI,UAAY,CAAC,SAAS,EACzD,QAAsB,CAAC,EACvB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KACnD,SAAW,SAAS,SAE1B,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,UAAY,KAAK,YAAY,GAAG,EACtC,GAAI,YAAc,GAAI,SAEtB,IAAM,MAAQ,KAAK,MAAM,EAAG,SAAS,EAC/B,IAAM,KAAK,MAAM,UAAY,CAAC,CAAC,CAAC,YAAY,EAE7C,UACD,QAAQ,OAAS,MAAQ,QAC7B,IAAI,SAAW,CAAC,QAAQ,IAAI,KAAK,EAAG,CAClC,WAAW,CAAE,KAAM,OAAQ,MAAO,OAAQ,mBAAoB,CAAC,EAC/D,QACF,CAEA,QAAQ,KAAK,CAAE,MAAO,UAAW,GAAI,CAAC,CAFtC,CAGF,CAEA,OAAO,OACT,CAGA,SAAgB,gBACd,OAIA,QACkB,CAClB,IAAM,aAAe,SAAS,cAAgB,SAAS,aACjD,SAAW,SAAS,UAAY,SAAS,SAE3C,KACF,OAAO,OAAO,MAAS,SACnB,OAAO,SAAS,OAAO,KAAM,EAAE,EAC9B,OAAO,MAAQ,SAAS,YAC3B,MACF,OAAO,OAAO,OAAU,SACpB,OAAO,SAAS,OAAO,MAAO,EAAE,EAC/B,OAAO,OAAS,aAMvB,OAJI,OAAO,MAAM,IAAI,GAAK,KAAO,KAAG,KAAO,SAAS,cAChD,OAAO,MAAM,KAAK,GAAK,MAAQ,KAAG,MAAQ,cAC1C,MAAQ,WAAU,MAAQ,UAEvB,CAAE,KAAM,MAAO,QAAS,KAAO,GAAK,KAAM,CACnD,CAGA,SAAgB,iBACd,EACA,QACQ,CACR,GAAI,CAAC,EAAG,MAAO,GACf,IAAM,OAAS,SAAS,iBAAmB,SAAS,gBAC9C,QAAU,EAAE,KAAK,EAKvB,OAJI,QAAQ,OAAS,QACnB,SAAS,WAAW,CAAE,KAAM,SAAU,MAAO,IAAK,OAAQ,WAAY,CAAC,EAChE,QAAQ,MAAM,EAAG,MAAM,GAEzB,OACT,CASA,SAAS,qBAAqB,OAA+D,CACtF,UAYL,MATI,YAAa,QAAU,OAAO,SAAW,OAAO,OAAO,SAAY,SAC9D,CACL,WAAY,OAAO,KAAK,OAAO,OAAO,EACtC,SAAU,OAAO,SAAW,OAAO,KAAK,OAAO,QAAQ,EAAI,IAAA,GAC3D,WAAY,IAAA,EACd,EAIK,MACT,CAiCA,SAAgB,WACd,MACA,YACA,QAC2B,CAC3B,IAAM,WAAa,qBAAqB,WAAW,EAC7C,WAAa,SAAS,SAAW,CAAE,SAAU,QAAQ,QAAS,EAAI,IAAA,GACxE,MAAO,CACL,QAAS,aAAa,MAAM,OAAQ,YAAY,WAAY,UAAU,EACtE,KAAM,UAAU,MAAM,KAAM,YAAY,SAAU,UAAU,EAC5D,WAAY,gBAAgB,CAAE,KAAM,MAAM,KAAM,MAAO,MAAM,KAAM,EAAG,OAAO,EAC7E,OAAQ,iBAAiB,MAAM,EAAG,OAAO,CAC3C,CACF,CAKA,SAAgB,iBACd,OAC4C,CAC5C,IAAM,OAAqD,CAAC,EAmB5D,OAjBI,OAAO,SAAS,SAClB,OAAO,OAAS,OAAO,QAAQ,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,OAAO,GAG7E,OAAO,MAAM,SACf,OAAO,KAAO,OAAO,KAAK,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,WAAW,GAG9D,OAAO,aACT,OAAO,KAAO,OAAO,WAAW,KAChC,OAAO,MAAQ,OAAO,WAAW,OAG/B,OAAO,SACT,OAAO,EAAI,OAAO,QAGb,MACT"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.18.0
2
+ * @forinda/kickjs v6.0.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{i as requestStore}from"./context-BFqb1Ze3.mjs";import crypto from"node:crypto";const REQUEST_SCOPE_MIDDLEWARE_MARKER=Symbol.for(`@kickjs/requestScopeMiddleware`);function createRequestStore(requestId){return{requestId:requestId||crypto.randomUUID(),instances:new Map,values:new Map}}function requestScopeMiddleware(){let mw=(req,_res,next)=>{let requestIdHeader=req.headers[`x-request-id`],requestId=(Array.isArray(requestIdHeader)?requestIdHeader[0]:requestIdHeader)||crypto.randomUUID(),store=createRequestStore(requestId);req.requestId=requestId,requestStore.run(store,()=>next())};return mw[REQUEST_SCOPE_MIDDLEWARE_MARKER]=!0,mw}function isRequestScopeMiddleware(value){return typeof value==`function`?value[REQUEST_SCOPE_MIDDLEWARE_MARKER]===!0:!1}export{isRequestScopeMiddleware as n,requestScopeMiddleware as r,createRequestStore as t};
12
- //# sourceMappingURL=request-scope-BdgR9Glw.mjs.map
11
+ import{i as requestStore}from"./context-CakY_skc.mjs";import crypto from"node:crypto";const REQUEST_SCOPE_MIDDLEWARE_MARKER=Symbol.for(`@kickjs/requestScopeMiddleware`);function createRequestStore(requestId){return{requestId:requestId||crypto.randomUUID(),instances:new Map,values:new Map}}function requestScopeMiddleware(){let mw=(req,_res,next)=>{let requestIdHeader=req.headers[`x-request-id`],requestId=(Array.isArray(requestIdHeader)?requestIdHeader[0]:requestIdHeader)||crypto.randomUUID(),store=createRequestStore(requestId);req.requestId=requestId,requestStore.run(store,()=>next())};return mw[REQUEST_SCOPE_MIDDLEWARE_MARKER]=!0,mw}function isRequestScopeMiddleware(value){return typeof value==`function`?value[REQUEST_SCOPE_MIDDLEWARE_MARKER]===!0:!1}export{isRequestScopeMiddleware as n,requestScopeMiddleware as r,createRequestStore as t};
12
+ //# sourceMappingURL=request-scope-BTNd6dAe.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-scope-BdgR9Glw.mjs","names":[],"sources":["../src/http/middleware/request-scope.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\nimport { requestStore, type RequestStore } from '../request-store'\n\n/**\n * Marker symbol stamped on the function returned by {@link requestScopeMiddleware}.\n *\n * `Application.setup()` uses {@link isRequestScopeMiddleware} to detect whether\n * the user-supplied middleware list already contains the request-scope wrapper,\n * and skips its automatic auto-mount when one is found. Lets adopters control\n * the exact position of the ALS frame in their pipeline (e.g., after a custom\n * tracing wrapper) without ending up with two nested frames.\n */\n// `Symbol.for(...)` returns a plain `symbol` (not `unique symbol`), but the\n// runtime detection marker doesn't need the unique nominal property — it only\n// needs the interned-slot reference equality that `Symbol.for` already provides.\nconst REQUEST_SCOPE_MIDDLEWARE_MARKER = Symbol.for('@kickjs/requestScopeMiddleware')\n\n/**\n * Build a fresh per-request store. Shared by {@link requestScopeMiddleware}\n * (Express) and by runtimes whose handler runs outside the connect-middleware\n * chain (e.g. Fastify), so both establish an identical ALS frame.\n */\nexport function createRequestStore(requestId?: string): RequestStore {\n return {\n requestId: requestId || crypto.randomUUID(),\n instances: new Map(),\n // Explicit generic locks the \"string keys only\" decision.\n values: new Map<string, unknown>(),\n }\n}\n\n/**\n * Wraps each request in an AsyncLocalStorage context.\n * Enables REQUEST-scoped DI and automatic requestId propagation to logs.\n *\n * Should be mounted early in the middleware pipeline (before route handlers).\n * `Application` mounts this automatically unless `contextStore: 'manual'` is set\n * or the user already includes one in their middleware list.\n */\nexport function requestScopeMiddleware() {\n const mw = (req: Request, _res: Response, next: NextFunction) => {\n const requestIdHeader = req.headers['x-request-id']\n const requestId =\n (Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader) || crypto.randomUUID()\n const store = createRequestStore(requestId)\n // Surface the requestId on req so the standalone requestId() middleware\n // (when also mounted) reuses this value instead of generating a divergent\n // second one. Without this, log lines that read from the ALS store would\n // disagree with the X-Request-Id response header for any request that\n // didn't carry an inbound x-request-id header.\n ;(req as Request & { requestId?: string }).requestId = requestId\n requestStore.run(store, () => next())\n }\n ;(mw as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] = true\n return mw\n}\n\n/**\n * Returns `true` when `value` is the function produced by {@link requestScopeMiddleware}.\n *\n * Detection uses a `Symbol.for(...)` marker so the check survives different\n * copies of `@forinda/kickjs` loaded under different module identities (e.g.,\n * a published package alongside a workspace-linked one during local dev).\n */\nexport function isRequestScopeMiddleware(value: unknown): boolean {\n if (typeof value !== 'function') return false\n return (value as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] === true\n}\n"],"mappings":";;;;;;;;;;sFAgBA,MAAM,gCAAkC,OAAO,IAAI,gCAAgC,EAOnF,SAAgB,mBAAmB,UAAkC,CACnE,MAAO,CACL,UAAW,WAAa,OAAO,WAAW,EAC1C,UAAW,IAAI,IAEf,OAAQ,IAAI,GACd,CACF,CAUA,SAAgB,wBAAyB,CACvC,IAAM,IAAM,IAAc,KAAgB,OAAuB,CAC/D,IAAM,gBAAkB,IAAI,QAAQ,gBAC9B,WACH,MAAM,QAAQ,eAAe,EAAI,gBAAgB,GAAK,kBAAoB,OAAO,WAAW,EACzF,MAAQ,mBAAmB,SAAS,EAMzC,IAA0C,UAAY,UACvD,aAAa,IAAI,UAAa,KAAK,CAAC,CACtC,EAEA,MADC,IAA2C,iCAAmC,GACxE,EACT,CASA,SAAgB,yBAAyB,MAAyB,CAEhE,OADI,OAAO,OAAU,WACb,MAA6C,mCAAqC,GADlD,EAE1C"}
1
+ {"version":3,"file":"request-scope-BTNd6dAe.mjs","names":[],"sources":["../src/http/middleware/request-scope.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\nimport { requestStore, type RequestStore } from '../request-store'\n\n/**\n * Marker symbol stamped on the function returned by {@link requestScopeMiddleware}.\n *\n * `Application.setup()` uses {@link isRequestScopeMiddleware} to detect whether\n * the user-supplied middleware list already contains the request-scope wrapper,\n * and skips its automatic auto-mount when one is found. Lets adopters control\n * the exact position of the ALS frame in their pipeline (e.g., after a custom\n * tracing wrapper) without ending up with two nested frames.\n */\n// `Symbol.for(...)` returns a plain `symbol` (not `unique symbol`), but the\n// runtime detection marker doesn't need the unique nominal property — it only\n// needs the interned-slot reference equality that `Symbol.for` already provides.\nconst REQUEST_SCOPE_MIDDLEWARE_MARKER = Symbol.for('@kickjs/requestScopeMiddleware')\n\n/**\n * Build a fresh per-request store. Shared by {@link requestScopeMiddleware}\n * (Express) and by runtimes whose handler runs outside the connect-middleware\n * chain (e.g. Fastify), so both establish an identical ALS frame.\n */\nexport function createRequestStore(requestId?: string): RequestStore {\n return {\n requestId: requestId || crypto.randomUUID(),\n instances: new Map(),\n // Explicit generic locks the \"string keys only\" decision.\n values: new Map<string, unknown>(),\n }\n}\n\n/**\n * Wraps each request in an AsyncLocalStorage context.\n * Enables REQUEST-scoped DI and automatic requestId propagation to logs.\n *\n * Should be mounted early in the middleware pipeline (before route handlers).\n * `Application` mounts this automatically unless `contextStore: 'manual'` is set\n * or the user already includes one in their middleware list.\n */\nexport function requestScopeMiddleware() {\n const mw = (req: Request, _res: Response, next: NextFunction) => {\n const requestIdHeader = req.headers['x-request-id']\n const requestId =\n (Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader) || crypto.randomUUID()\n const store = createRequestStore(requestId)\n // Surface the requestId on req so the standalone requestId() middleware\n // (when also mounted) reuses this value instead of generating a divergent\n // second one. Without this, log lines that read from the ALS store would\n // disagree with the X-Request-Id response header for any request that\n // didn't carry an inbound x-request-id header.\n ;(req as Request & { requestId?: string }).requestId = requestId\n requestStore.run(store, () => next())\n }\n ;(mw as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] = true\n return mw\n}\n\n/**\n * Returns `true` when `value` is the function produced by {@link requestScopeMiddleware}.\n *\n * Detection uses a `Symbol.for(...)` marker so the check survives different\n * copies of `@forinda/kickjs` loaded under different module identities (e.g.,\n * a published package alongside a workspace-linked one during local dev).\n */\nexport function isRequestScopeMiddleware(value: unknown): boolean {\n if (typeof value !== 'function') return false\n return (value as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] === true\n}\n"],"mappings":";;;;;;;;;;sFAgBA,MAAM,gCAAkC,OAAO,IAAI,gCAAgC,EAOnF,SAAgB,mBAAmB,UAAkC,CACnE,MAAO,CACL,UAAW,WAAa,OAAO,WAAW,EAC1C,UAAW,IAAI,IAEf,OAAQ,IAAI,GACd,CACF,CAUA,SAAgB,wBAAyB,CACvC,IAAM,IAAM,IAAc,KAAgB,OAAuB,CAC/D,IAAM,gBAAkB,IAAI,QAAQ,gBAC9B,WACH,MAAM,QAAQ,eAAe,EAAI,gBAAgB,GAAK,kBAAoB,OAAO,WAAW,EACzF,MAAQ,mBAAmB,SAAS,EAMzC,IAA0C,UAAY,UACvD,aAAa,IAAI,UAAa,KAAK,CAAC,CACtC,EAEA,MADC,IAA2C,iCAAmC,GACxE,EACT,CASA,SAAgB,yBAAyB,MAAyB,CAEhE,OADI,OAAO,OAAU,WACb,MAA6C,mCAAqC,GADlD,EAE1C"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forinda/kickjs",
3
- "version": "5.18.0",
4
- "description": "A production-grade, decorator-driven Node.js framework built on Express 5 and TypeScript.",
3
+ "version": "6.0.0",
4
+ "description": "A production-grade, decorator-driven Node.js framework for TypeScript — runs on Express, Fastify, or h3 (swap the engine in one line).",
5
5
  "keywords": [
6
6
  "kickjs",
7
7
  "nodejs",
@@ -9,7 +9,9 @@
9
9
  "decorator-driven",
10
10
  "framework",
11
11
  "express",
12
- "express-5",
12
+ "fastify",
13
+ "h3",
14
+ "pluggable-runtime",
13
15
  "dependency-injection",
14
16
  "decorators",
15
17
  "di-container",