@forinda/kickjs 9.0.3 → 9.2.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.
- package/dist/{adapter-DhjoIYVI.d.mts → adapter-DjfRMob8.d.mts} +3 -13
- package/dist/{adapter-DhjoIYVI.d.mts.map → adapter-DjfRMob8.d.mts.map} +1 -1
- package/dist/application-BSJpaiSY.mjs +12 -0
- package/dist/{application-DPLzamTb.mjs.map → application-BSJpaiSY.mjs.map} +1 -1
- package/dist/{application-DqD58rhF.d.mts → application-CGxZCYl1.d.mts} +8 -8
- package/dist/{application-DqD58rhF.d.mts.map → application-CGxZCYl1.d.mts.map} +1 -1
- package/dist/{bootstrap-DUNvDJ1a.mjs → bootstrap-CbTF6xvv.mjs} +3 -3
- package/dist/{bootstrap-DUNvDJ1a.mjs.map → bootstrap-CbTF6xvv.mjs.map} +1 -1
- package/dist/{client-ip-CTr33Gtu.mjs → client-ip-pcINkkPr.mjs} +2 -2
- package/dist/{client-ip-CTr33Gtu.mjs.map → client-ip-pcINkkPr.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-C-sAHakM.mjs → config-DmGiAIuH.mjs} +3 -3
- package/dist/{config-C-sAHakM.mjs.map → config-DmGiAIuH.mjs.map} +1 -1
- package/dist/{container-ByH0uCpG.mjs → container-CAPN7pBG.mjs} +3 -3
- package/dist/{container-ByH0uCpG.mjs.map → container-CAPN7pBG.mjs.map} +1 -1
- package/dist/{context-errors-VjQBtxEc.mjs → context-errors-DYJjhalQ.mjs} +2 -2
- package/dist/{context-errors-VjQBtxEc.mjs.map → context-errors-DYJjhalQ.mjs.map} +1 -1
- package/dist/context-gpvcMlG5.mjs +12 -0
- package/dist/context-gpvcMlG5.mjs.map +1 -0
- package/dist/{contributor-runner-BEjgGaoG.mjs → contributor-runner-vk9XlOED.mjs} +3 -3
- package/dist/{contributor-runner-BEjgGaoG.mjs.map → contributor-runner-vk9XlOED.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.d.mts +1 -1
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/decorators.mjs.map +1 -1
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.d.mts +2 -2
- package/dist/core/logger.mjs +2 -2
- package/dist/core/logger.mjs.map +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-DrjVUTxG.mjs → core-DKrKVLfj.mjs} +3 -3
- package/dist/{core-DrjVUTxG.mjs.map → core-DKrKVLfj.mjs.map} +1 -1
- package/dist/{csrf-COFjRrUz.d.mts → csrf-C73UkRgc.d.mts} +2 -2
- package/dist/{csrf-COFjRrUz.d.mts.map → csrf-C73UkRgc.d.mts.map} +1 -1
- package/dist/{csrf-CbFJiyKW.mjs → csrf-CxJItZVa.mjs} +3 -3
- package/dist/{csrf-CbFJiyKW.mjs.map → csrf-CxJItZVa.mjs.map} +1 -1
- package/dist/{decorate-hW_esogS.mjs → decorate-DfH1NIqV.mjs} +1 -1
- package/dist/{decorators-DOcWsXci.d.mts → decorators-BxNSz9Qt.d.mts} +4 -4
- package/dist/{decorators-DOcWsXci.d.mts.map → decorators-BxNSz9Qt.d.mts.map} +1 -1
- package/dist/{define-adapter-B4Iu_TcU.mjs → define-adapter-C6lFhCL-.mjs} +2 -2
- package/dist/{define-adapter-B4Iu_TcU.mjs.map → define-adapter-C6lFhCL-.mjs.map} +1 -1
- package/dist/{disposables-CgCLyCoP.mjs → disposables-NlW6Mibk.mjs} +2 -2
- package/dist/{disposables-CgCLyCoP.mjs.map → disposables-NlW6Mibk.mjs.map} +1 -1
- package/dist/{env-w27RDkCQ.mjs → env-__fzPsHD.mjs} +3 -3
- package/dist/{env-w27RDkCQ.mjs.map → env-__fzPsHD.mjs.map} +1 -1
- package/dist/{error-handler-DiueQHes.d.mts → error-handler-itG65lj0.d.mts} +2 -2
- package/dist/{error-handler-DiueQHes.d.mts.map → error-handler-itG65lj0.d.mts.map} +1 -1
- package/dist/handler-DnZbcleS.mjs +13 -0
- package/dist/handler-DnZbcleS.mjs.map +1 -0
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +1 -1
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.d.mts +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/request-logger.mjs.map +1 -1
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +3 -1
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +2 -2
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/{index-CDn-p3tq.d.mts → index-CNHSbZTE.d.mts} +4 -4
- package/dist/{index-CDn-p3tq.d.mts.map → index-CNHSbZTE.d.mts.map} +1 -1
- package/dist/index.d.mts +10 -10
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{jobs-CeP4aLYR.mjs → jobs-BmpeFvi0.mjs} +3 -3
- package/dist/{jobs-CeP4aLYR.mjs.map → jobs-BmpeFvi0.mjs.map} +1 -1
- package/dist/{kv-stores-CetFF9N_.d.mts → kv-stores-BZTe23Tw.d.mts} +3 -3
- package/dist/{kv-stores-CetFF9N_.d.mts.map → kv-stores-BZTe23Tw.d.mts.map} +1 -1
- package/dist/{kv-stores-5ocdxfdh.mjs → kv-stores-DJwxntyw.mjs} +3 -3
- package/dist/{kv-stores-5ocdxfdh.mjs.map → kv-stores-DJwxntyw.mjs.map} +1 -1
- package/dist/{logger-DA43DvNY.d.mts → logger-xE9qI_FS.d.mts} +45 -5
- package/dist/logger-xE9qI_FS.d.mts.map +1 -0
- package/dist/{metadata-CIXErBTj.mjs → metadata-CzzRUnAx.mjs} +2 -2
- package/dist/{metadata-CIXErBTj.mjs.map → metadata-CzzRUnAx.mjs.map} +1 -1
- package/dist/{module-registry-IO8RfVjX.mjs → module-registry-BnmRNy-3.mjs} +2 -2
- package/dist/{module-registry-IO8RfVjX.mjs.map → module-registry-BnmRNy-3.mjs.map} +1 -1
- package/dist/{observers-BtXkP5km.mjs → observers-COlQfinD.mjs} +2 -2
- package/dist/{observers-BtXkP5km.mjs.map → observers-COlQfinD.mjs.map} +1 -1
- package/dist/{query-CNa0mxSK.mjs → query-B5DTq_4W.mjs} +2 -2
- package/dist/{query-CNa0mxSK.mjs.map → query-B5DTq_4W.mjs.map} +1 -1
- package/dist/reply-CbTYIj_e.mjs +12 -0
- package/dist/reply-CbTYIj_e.mjs.map +1 -0
- package/dist/{respond-C4Vt5r46.mjs → respond-wbMSqHYo.mjs} +2 -2
- package/dist/{respond-C4Vt5r46.mjs.map → respond-wbMSqHYo.mjs.map} +1 -1
- package/dist/{route-flag-CpTa8y1n.mjs → route-flag-Bb7n9Mnp.mjs} +3 -3
- package/dist/{route-flag-CpTa8y1n.mjs.map → route-flag-Bb7n9Mnp.mjs.map} +1 -1
- package/dist/{route-policy-DPTLfer-.mjs → route-policy-C_Wsc-b1.mjs} +2 -2
- package/dist/{route-policy-DPTLfer-.mjs.map → route-policy-C_Wsc-b1.mjs.map} +1 -1
- package/dist/{router-builder-BSBSYgYW.d.mts → router-builder-CDkW88KQ.d.mts} +2 -2
- package/dist/{router-builder-BSBSYgYW.d.mts.map → router-builder-CDkW88KQ.d.mts.map} +1 -1
- package/dist/{router-builder-BxXKP8Wy.mjs → router-builder-DawnRzlp.mjs} +3 -3
- package/dist/{router-builder-BxXKP8Wy.mjs.map → router-builder-DawnRzlp.mjs.map} +1 -1
- package/dist/{upload-DjhnyHHm.d.mts → upload-Cy7qQy7M.d.mts} +3 -3
- package/dist/{upload-DjhnyHHm.d.mts.map → upload-Cy7qQy7M.d.mts.map} +1 -1
- package/dist/{upload-config-BmpmZZVK.mjs → upload-config-D01Ty40f.mjs} +2 -2
- package/dist/{upload-config-BmpmZZVK.mjs.map → upload-config-D01Ty40f.mjs.map} +1 -1
- package/dist/web.d.mts +3 -3
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/application-DPLzamTb.mjs +0 -12
- package/dist/context-fth87C3G.mjs +0 -12
- package/dist/context-fth87C3G.mjs.map +0 -1
- package/dist/handler-eguNTfCv.mjs +0 -13
- package/dist/handler-eguNTfCv.mjs.map +0 -1
- package/dist/logger-DA43DvNY.d.mts.map +0 -1
- package/dist/runtime-B4NQGN66.mjs +0 -12
- package/dist/runtime-B4NQGN66.mjs.map +0 -1
|
@@ -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 { classifyMediaType, unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { HttpException } from '../../core/errors'\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/**\n * Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse.\n * A class so the ~12 driver methods live on a shared prototype — the previous\n * object-literal version re-allocated every method closure on each request.\n */\nclass NodeResDriver implements RuntimeResponse {\n constructor(private readonly res: ServerResponse) {}\n status(code: number): this {\n this.res.statusCode = code\n return this\n }\n json(data: unknown): this {\n // Do not clobber a content-type the caller already chose. This used to set\n // `application/json` unconditionally, so every `application/problem+json`\n // response — RFC 9457 problem details, which the error handler sets the\n // header for before serialising — went out as plain JSON on this runtime\n // alone. Express and Fastify both leave an existing value alone.\n if (!this.res.headersSent && !this.res.getHeader('content-type')) {\n this.res.setHeader('content-type', 'application/json; charset=utf-8')\n }\n this.res.end(JSON.stringify(data))\n return this\n }\n send(data: unknown): this {\n this.res.end(data as never)\n return this\n }\n type(contentType: string): this {\n this.res.setHeader('content-type', contentType)\n return this\n }\n setHeader(name: string, value: unknown): this {\n this.res.setHeader(name, value as never)\n return this\n }\n render(): never {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n }\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n this.res.writeHead(statusCode, headers)\n return this\n }\n flushHeaders(): this {\n this.res.flushHeaders()\n return this\n }\n write(chunk: unknown): boolean {\n return this.res.write(chunk as never)\n }\n end(data?: unknown): this {\n this.res.end(data as never)\n return this\n }\n once(event: string, listener: (...args: unknown[]) => void): this {\n this.res.once(event, listener)\n return this\n }\n get headersSent(): boolean {\n return this.res.headersSent\n }\n}\n\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n return new NodeResDriver(res) as unknown as RuntimeResponse\n}\n\n/** Carries `trustProxy` from createApp() to the per-route handlers. */\nconst TRUST_PROXY = Symbol('kickjs.h3.trustProxy')\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(\n entry: RouteEntry,\n trustProxy: boolean,\n): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readRawBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n // Validation middleware built ONCE per route (parity with Express) — calling\n // validate() per request re-allocated the closure every time.\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\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 // h3 computes no client address of its own, so `resolveClientIp` fell\n // straight through to the socket — which behind a proxy is the proxy, and\n // put every caller in one rate-limit bucket with no way to configure out\n // of it (#614). Express and Fastify derive `req.ip` from their own\n // trust-proxy settings; this is h3's equivalent, and like theirs it is\n // OFF unless the app opts in, because an unverified `x-forwarded-for` is\n // client-controllable.\n if (trustProxy && !(req as { ip?: string }).ip) {\n const forwarded = req.headers['x-forwarded-for']\n const first = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(',')[0]?.trim()\n if (first) (req as { ip?: string }).ip = first\n }\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n\n // HTTP/2 frames a body in DATA frames: no `transfer-encoding`, and\n // `content-length` is optional. h3's `readRawBody` — behind both the\n // JSON/text path and `readMultipartFormData` — returns `undefined` when\n // neither header is present, dropping the body. Read it once as bytes and\n // hand it over on `req.rawBody`, which `readRawBody` checks first.\n if (\n req.httpVersionMajor === 2 &&\n req.headers['content-length'] === undefined &&\n BODY_METHODS.has(req.method ?? 'GET')\n ) {\n ;(req as { rawBody?: Buffer }).rawBody = await readStreamBuffer(req)\n }\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 // Swallow ONLY the absent-body case. `.catch(() => undefined)` also\n // swallowed a malformed one, so h3 answered 200 with the handler running\n // on `undefined` where Express and Fastify both answer 400 — a client\n // sending broken JSON got a success response, and the handler executed\n // against data that was never valid.\n // HTTP/2 frames the body in DATA frames: no `transfer-encoding`, and\n // `content-length` is optional, so there a body may be present with\n // neither header — only an explicit `content-length: 0` rules it out.\n const { 'content-length': length, 'transfer-encoding': encoding } = req.headers\n const sentBody =\n encoding !== undefined ||\n (length !== undefined && length !== '0') ||\n (req.httpVersionMajor === 2 && length !== '0')\n if (sentBody) {\n // Read raw and apply the shared policy rather than calling `readBody`,\n // whose own content-type dispatch is the source of the divergence:\n // it matches `application/json` with `===` (so `; charset=utf-8` misses)\n // and routes everything unrecognised through `destr`, which returns the\n // RAW STRING for malformed input instead of throwing (#590).\n const kind = classifyMediaType(req.headers['content-type'])\n if (kind !== 'multipart') {\n const raw = await readRawBody(event, 'utf8')\n // Reject only once the body is known to be non-empty. `sentBody` is\n // true for an empty CHUNKED post — `transfer-encoding` is present\n // with no payload — and rejecting on that alone would 415 a request\n // that sent nothing, contradicting the rule the other runtimes apply.\n if (raw) {\n if (kind === 'unsupported') {\n // A body was sent in a format the framework does not parse. Say\n // so rather than handing the handler `undefined` and letting it\n // fail somewhere less obvious.\n throw unsupportedMediaTypeError(req.headers['content-type'])\n }\n if (kind === 'json') {\n try {\n req.body = JSON.parse(raw)\n } catch (err) {\n throw HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n )\n }\n } else if (kind === 'urlencoded') {\n req.body = Object.fromEntries(new URLSearchParams(raw))\n } else {\n req.body = raw\n }\n }\n }\n }\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 // @PreDestroy teardown for REQUEST-scoped instances once the response\n // closes (finished or aborted) — parity with requestScopeMiddleware.\n res.once('close', () => disposeRequestStore(store))\n\n await requestStore.run(store, async () => {\n publishMatchedRoute(req, entry)\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (res.writableEnded) return\n }\n\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(req as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\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 const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n if (!res.writableEnded && !res.headersSent) applyHandlerResult(ctx, result)\n })\n }\n}\n\n/**\n * The original error h3 wrapped.\n *\n * h3 routes every thrown value through `createError()`, which returns its own\n * error with the original on `cause`. Handing that wrapper to the shared error\n * middleware means it sees something Express and Fastify never give it, and the\n * responses diverge: `instanceof HttpException` fails, so a thrown\n * `HttpException(418)` falls through to the generic branch and picks up a\n * `requestId` the other two omit, and `describeError` walks the cause chain to\n * report `kaboom ← caused by kaboom`.\n *\n * Only an `Error` cause is unwrapped. h3's own 404 carries no cause, so the\n * not-found path above is unaffected.\n */\nfunction unwrapH3Error(error: unknown): unknown {\n if (!(error instanceof Error)) return error\n const { cause } = error as { cause?: unknown }\n return cause instanceof Error ? cause : error\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 */\n/** Collect a request stream as bytes — never decoded, so file parts stay intact. */\nasync function readStreamBuffer(req: AsyncIterable<Buffer | string>): Promise<Buffer> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)\n return Buffer.concat(chunks)\n}\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 //\n // Only the ROUTER's no-match counts: h3 builds it from a plain object,\n // so its `cause` is not an Error. A handler that throws\n // `HttpException(404, '…')` arrives wrapped with the exception as\n // `cause` — that is a handler error and belongs to the error\n // middleware, which keeps its detail (and reports it to observers).\n const status = (error as { statusCode?: number } | undefined)?.statusCode\n const thrownByHandler = (error as { cause?: unknown } | undefined)?.cause instanceof Error\n if (status === 404 && !thrownByHandler) {\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 unwrapH3Error(error),\n event.node.req,\n resDriver(res),\n NOOP_NEXT,\n )\n }\n },\n })\n // Stash the setting for mountRoutes — h3 has no app-level config object\n // of its own to hang it on.\n ;(app as { [TRUST_PROXY]?: unknown })[TRUST_PROXY] = options.trustProxy\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 const trustProxy = Boolean((app as { [TRUST_PROXY]?: unknown })[TRUST_PROXY])\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, trustProxy)))\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 http2: 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 */\n// Resolved conditionally so this file typechecks against EITHER installed h3\n// major: v1 exports `App`; v2 (used by the sibling h3-web runtime) does not.\n// Runtime behavior is untouched — this block is type-only.\ntype H3AppType = typeof import('h3') extends { App: infer A } ? A : unknown\ntype H3EventType = typeof import('h3') extends { H3Event: new (...args: never[]) => infer E }\n ? E\n : unknown\n\nexport interface H3RuntimeTypes {\n request: H3EventType\n response: H3EventType\n app: H3AppType\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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"}
|
|
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 { classifyMediaType, unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute, stringifyJson } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { HttpException } from '../../core/errors'\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/**\n * Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse.\n * A class so the ~12 driver methods live on a shared prototype — the previous\n * object-literal version re-allocated every method closure on each request.\n */\nclass NodeResDriver implements RuntimeResponse {\n constructor(private readonly res: ServerResponse) {}\n status(code: number): this {\n this.res.statusCode = code\n return this\n }\n json(data: unknown): this {\n // Do not clobber a content-type the caller already chose. This used to set\n // `application/json` unconditionally, so every `application/problem+json`\n // response — RFC 9457 problem details, which the error handler sets the\n // header for before serialising — went out as plain JSON on this runtime\n // alone. Express and Fastify both leave an existing value alone.\n if (!this.res.headersSent && !this.res.getHeader('content-type')) {\n this.res.setHeader('content-type', 'application/json; charset=utf-8')\n }\n this.res.end(stringifyJson(data))\n return this\n }\n send(data: unknown): this {\n this.res.end(data as never)\n return this\n }\n type(contentType: string): this {\n this.res.setHeader('content-type', contentType)\n return this\n }\n setHeader(name: string, value: unknown): this {\n this.res.setHeader(name, value as never)\n return this\n }\n render(): never {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n }\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n this.res.writeHead(statusCode, headers)\n return this\n }\n flushHeaders(): this {\n this.res.flushHeaders()\n return this\n }\n write(chunk: unknown): boolean {\n return this.res.write(chunk as never)\n }\n end(data?: unknown): this {\n this.res.end(data as never)\n return this\n }\n once(event: string, listener: (...args: unknown[]) => void): this {\n this.res.once(event, listener)\n return this\n }\n get headersSent(): boolean {\n return this.res.headersSent\n }\n}\n\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n return new NodeResDriver(res) as unknown as RuntimeResponse\n}\n\n/** Carries `trustProxy` from createApp() to the per-route handlers. */\nconst TRUST_PROXY = Symbol('kickjs.h3.trustProxy')\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(\n entry: RouteEntry,\n trustProxy: boolean,\n): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readRawBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n // Validation middleware built ONCE per route (parity with Express) — calling\n // validate() per request re-allocated the closure every time.\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\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 // h3 computes no client address of its own, so `resolveClientIp` fell\n // straight through to the socket — which behind a proxy is the proxy, and\n // put every caller in one rate-limit bucket with no way to configure out\n // of it (#614). Express and Fastify derive `req.ip` from their own\n // trust-proxy settings; this is h3's equivalent, and like theirs it is\n // OFF unless the app opts in, because an unverified `x-forwarded-for` is\n // client-controllable.\n if (trustProxy && !(req as { ip?: string }).ip) {\n const forwarded = req.headers['x-forwarded-for']\n const first = (Array.isArray(forwarded) ? forwarded[0] : forwarded)?.split(',')[0]?.trim()\n if (first) (req as { ip?: string }).ip = first\n }\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n\n // HTTP/2 frames a body in DATA frames: no `transfer-encoding`, and\n // `content-length` is optional. h3's `readRawBody` — behind both the\n // JSON/text path and `readMultipartFormData` — returns `undefined` when\n // neither header is present, dropping the body. Read it once as bytes and\n // hand it over on `req.rawBody`, which `readRawBody` checks first.\n if (\n req.httpVersionMajor === 2 &&\n req.headers['content-length'] === undefined &&\n BODY_METHODS.has(req.method ?? 'GET')\n ) {\n ;(req as { rawBody?: Buffer }).rawBody = await readStreamBuffer(req)\n }\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 // Swallow ONLY the absent-body case. `.catch(() => undefined)` also\n // swallowed a malformed one, so h3 answered 200 with the handler running\n // on `undefined` where Express and Fastify both answer 400 — a client\n // sending broken JSON got a success response, and the handler executed\n // against data that was never valid.\n // HTTP/2 frames the body in DATA frames: no `transfer-encoding`, and\n // `content-length` is optional, so there a body may be present with\n // neither header — only an explicit `content-length: 0` rules it out.\n const { 'content-length': length, 'transfer-encoding': encoding } = req.headers\n const sentBody =\n encoding !== undefined ||\n (length !== undefined && length !== '0') ||\n (req.httpVersionMajor === 2 && length !== '0')\n if (sentBody) {\n // Read raw and apply the shared policy rather than calling `readBody`,\n // whose own content-type dispatch is the source of the divergence:\n // it matches `application/json` with `===` (so `; charset=utf-8` misses)\n // and routes everything unrecognised through `destr`, which returns the\n // RAW STRING for malformed input instead of throwing (#590).\n const kind = classifyMediaType(req.headers['content-type'])\n if (kind !== 'multipart') {\n const raw = await readRawBody(event, 'utf8')\n // Reject only once the body is known to be non-empty. `sentBody` is\n // true for an empty CHUNKED post — `transfer-encoding` is present\n // with no payload — and rejecting on that alone would 415 a request\n // that sent nothing, contradicting the rule the other runtimes apply.\n if (raw) {\n if (kind === 'unsupported') {\n // A body was sent in a format the framework does not parse. Say\n // so rather than handing the handler `undefined` and letting it\n // fail somewhere less obvious.\n throw unsupportedMediaTypeError(req.headers['content-type'])\n }\n if (kind === 'json') {\n try {\n req.body = JSON.parse(raw)\n } catch (err) {\n throw HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n )\n }\n } else if (kind === 'urlencoded') {\n req.body = Object.fromEntries(new URLSearchParams(raw))\n } else {\n req.body = raw\n }\n }\n }\n }\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 // @PreDestroy teardown for REQUEST-scoped instances once the response\n // closes (finished or aborted) — parity with requestScopeMiddleware.\n res.once('close', () => disposeRequestStore(store))\n\n await requestStore.run(store, async () => {\n publishMatchedRoute(req, entry)\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (res.writableEnded) return\n }\n\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(req as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\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 const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n if (!res.writableEnded && !res.headersSent) applyHandlerResult(ctx, result)\n })\n }\n}\n\n/**\n * The original error h3 wrapped.\n *\n * h3 routes every thrown value through `createError()`, which returns its own\n * error with the original on `cause`. Handing that wrapper to the shared error\n * middleware means it sees something Express and Fastify never give it, and the\n * responses diverge: `instanceof HttpException` fails, so a thrown\n * `HttpException(418)` falls through to the generic branch and picks up a\n * `requestId` the other two omit, and `describeError` walks the cause chain to\n * report `kaboom ← caused by kaboom`.\n *\n * Only an `Error` cause is unwrapped. h3's own 404 carries no cause, so the\n * not-found path above is unaffected.\n */\nfunction unwrapH3Error(error: unknown): unknown {\n if (!(error instanceof Error)) return error\n const { cause } = error as { cause?: unknown }\n return cause instanceof Error ? cause : error\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 */\n/** Collect a request stream as bytes — never decoded, so file parts stay intact. */\nasync function readStreamBuffer(req: AsyncIterable<Buffer | string>): Promise<Buffer> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)\n return Buffer.concat(chunks)\n}\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 //\n // Only the ROUTER's no-match counts: h3 builds it from a plain object,\n // so its `cause` is not an Error. A handler that throws\n // `HttpException(404, '…')` arrives wrapped with the exception as\n // `cause` — that is a handler error and belongs to the error\n // middleware, which keeps its detail (and reports it to observers).\n const status = (error as { statusCode?: number } | undefined)?.statusCode\n const thrownByHandler = (error as { cause?: unknown } | undefined)?.cause instanceof Error\n if (status === 404 && !thrownByHandler) {\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 unwrapH3Error(error),\n event.node.req,\n resDriver(res),\n NOOP_NEXT,\n )\n }\n },\n })\n // Stash the setting for mountRoutes — h3 has no app-level config object\n // of its own to hang it on.\n ;(app as { [TRUST_PROXY]?: unknown })[TRUST_PROXY] = options.trustProxy\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 const trustProxy = Boolean((app as { [TRUST_PROXY]?: unknown })[TRUST_PROXY])\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, trustProxy)))\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 http2: 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 */\n// Resolved conditionally so this file typechecks against EITHER installed h3\n// major: v1 exports `App`; v2 (used by the sibling h3-web runtime) does not.\n// Runtime behavior is untouched — this block is type-only.\ntype H3AppType = typeof import('h3') extends { App: infer A } ? A : unknown\ntype H3EventType = typeof import('h3') extends { H3Event: new (...args: never[]) => infer E }\n ? E\n : unknown\n\nexport interface H3RuntimeTypes {\n request: H3EventType\n response: H3EventType\n app: H3AppType\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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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { p as EnvKey } from "./decorators-
|
|
2
|
+
import { p as EnvKey } from "./decorators-BxNSz9Qt.mjs";
|
|
3
3
|
import { InferSchemaOutput } from "@forinda/kickjs-schema";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/config/env.d.ts
|
|
@@ -140,8 +140,8 @@ declare function reloadEnv(): void;
|
|
|
140
140
|
declare function resetEnvCache(): void;
|
|
141
141
|
/**
|
|
142
142
|
* Schema-agnostic env loader. Accepts any schema supported by
|
|
143
|
-
* `@forinda/kickjs-schema` (Zod, Valibot, Yup,
|
|
144
|
-
* KickSchema
|
|
143
|
+
* `@forinda/kickjs-schema` (Zod, Valibot, Yup, any Standard Schema, a
|
|
144
|
+
* KickSchema or one from a registered adapter, or a plain validator function).
|
|
145
145
|
*
|
|
146
146
|
* Unlike `loadEnv()` which uses Zod's `.parse()` and throws a
|
|
147
147
|
* `ZodError`, this function uses `detectSchema().safeParse()` and
|
|
@@ -310,4 +310,4 @@ declare function createConfigService<T extends z.ZodObject<any>>(schema: T): {
|
|
|
310
310
|
};
|
|
311
311
|
//#endregion
|
|
312
312
|
export { baseEnvSchema as a, loadEnv as c, resetEnvCache as d, withEnv as f, Env as i, loadEnvFromSchema as l, TypedConfigService as n, defineEnv as o, createConfigService as r, getEnv as s, ConfigService as t, reloadEnv as u };
|
|
313
|
-
//# sourceMappingURL=index-
|
|
313
|
+
//# sourceMappingURL=index-CNHSbZTE.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-
|
|
1
|
+
{"version":3,"file":"index-CNHSbZTE.d.mts","names":[],"sources":["../src/config/env.ts","../src/config/config-service.ts"],"mappings":";;;;;;;;;;iBAsOS,sBAAkB,EAAA;;;;;;;;GAQ1B,EAAA,KAAA;;KAGW,gBAAgB,kBAAkB;;;;;;;;;;;cAsBjC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6DZ,UAAU,UAAU,EAAE,aACpC,SAAS,aAAa,kBAAkB,EAAE,UAAU,KACnD,EAAE,iBAAiB,cAAc,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;iBAgC5B,QAAQ,UAAU,EAAE,gBAAgB,QAAQ,IAAI,EAAE,MAAM;iBACxD,YAAY,0BAA0B,MAAM;;;;;;;;;;;;;;;;;;;;iBAmC5C,OAAO,UAAU,QAAQ,KAAK,IAAI,QAAQ;iBAC1C,OAAO,UAAU,QAC/B,KAAK,GACL,UAAU,YAAY,QAAQ,MAC7B,YAAY,QAAQ;;;;;;;;iBAqCD,QAAQ,GAC5B,WAAW,yBACX,UAAU,IAAI,QAAQ,KACrB,QAAQ;;;;;;;;;;;;;;;;iBA2BK;;;;;;;iBA8BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+CA,kBAAkB,SAChC,QAAQ,0BACS,UAAU,0BAA0B,kBAAkB;;;;;;;;KAuB7D;EACV;EACA;EACA;;;;;;;;;;;KC9iBG,UAAU,qBAAqB,0BAA0B,IAAI,IAAI;;;;;;;KAQjE,YAAY,kBAAkB,KAAK,UAAU,SAAS,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;cA2B3D;;;;;;;;;;;;;;cAcC;;;;;;;;;;EAaZ,IAAI,kBAAkB,SAAS,KAAK,UAAU,KAAK,YAAY,GAAG;;EAElE,IAAI,kBAAkB,SACpB,KAAK,UAAU,IACf,UAAU,YAAY,YAAY,GAAG,MACpC,YAAY,YAAY,GAAG;;;;;;EAU9B,WAAW,0BAA0B,SAAS,uBAAuB,SAAS;;EAK9E;EAMA;EAIA;EAIA;;;;;;UASe,mBAAmB,aAAa;EAC/C,IAAI,yBAAyB,MAAM,KAAK,IAAI,KAAK;EACjD,IAAI,yBAAyB,MAAM,KAAK,GAAG,UAAU,YAAY,KAAK,MAAM,YAAY,KAAK;EAC7F,UAAU,SAAS;EACnB;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+Bc,oBAAoB,UAAU,EAAE,gBAAgB,QAAQ;UAuC5D,mBAAkB,EAAA,MAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
|
|
2
2
|
import { a as METADATA, c as Scope, i as Constructor, l as ServiceOptions, n as BuilderOf, o as MaybePromise, r as ClassKind, s as PostConstructStatus, t as Buildable } from "./interfaces-C1hCSoLD.mjs";
|
|
3
3
|
import { a as tokenName, c as createToken, i as KickJsRegistry, l as isInjectionToken, n as ContainerChangeEvent, o as INJECTION_TOKEN, r as ContainerChangeListener, s as InjectionToken, t as Container } from "./container-Dpvd66Yt.mjs";
|
|
4
|
-
import { A as Service, B as resolveAsset, C as Post, D as Put, F as ASSET_MANIFEST_VERSION, I as KickAssets, L as UnknownAssetError, M as normalizeApiQueryParamsConfig, N as withBuilder, O as Repository, P as ASSETS, R as assets, S as Patch, T as PreDestroy, V as useAssets, _ as Get, a as Autowired, b as Middleware, c as ColumnApiQueryParamsConfig, d as Delete, f as Env, g as FileUploadConfig, h as FileUpload, i as AssetKey, j as Value, k as RouteDefinition, l as Component, m as FileTypeFilter, n as ApiQueryParamsConfig, o as BaseUploadOptions, p as EnvKey, r as Asset, s as Builder, t as ApiQueryParams, u as Controller, v as Inject, w as PostConstruct, x as MiddlewareHandler, y as Injectable, z as clearAssetCache } from "./decorators-
|
|
5
|
-
import { a as baseEnvSchema, c as loadEnv, d as resetEnvCache, f as withEnv, l as loadEnvFromSchema, n as TypedConfigService, o as defineEnv, r as createConfigService, s as getEnv, t as ConfigService, u as reloadEnv } from "./index-
|
|
6
|
-
import { $ as setInMetaRecord, B as SourcedRegistration, C as RuntimeTypeMap, Ct as reportResponse, D as RequestContext, E as DeepReadonly, F as RunContributorsOptions, G as getMetaRecord, H as getClassMeta, I as runContributors, J as hasClassMeta, K as getMethodMeta, L as BuildPipelineOptions, M as AdapterFactory, N as DefineAdapterOptions, O as RouteShape, P as defineAdapter, Q as setInMetaMap, R as ContributorPipeline, S as RuntimeResponse, St as reportError, T as Ctx, U as getClassMetaOrUndefined, V as buildPipeline, W as getMetaMap, X as pushMethodMeta, Y as pushClassMeta, Z as setClassMeta, _ as RouteMeta, _t as IntrospectionKind, a as KickServer, at as defineModule, b as RuntimeAppOptions, bt as ErrorSource, c as ActiveRuntime, ct as PluginAsyncOptions, d as CtxHandler, dt as KickPlugin, et as setMethodMeta, f as ExpressRuntimeTypes, ft as ModuleRegistry, g as RouteEntry, gt as IntrospectFn, h as MatchedRoute, ht as KickJsPluginRegistry, i as AppAdapterClass, it as ModuleFactory, j as AdapterAsyncOptions, k as SseHandler, l as AdapterHttp, lt as PluginFactory, m as KickRuntimeRegister, mt as KickJsPluginName, n as AdapterMiddleware, nt as defineModules, o as MiddlewarePath, ot as BuildContext, p as HttpRuntime, pt as MutableModuleRegistry, q as getMethodMetaOrUndefined, r as AppAdapter, rt as DefineModuleOptions, s as MiddlewarePhase, st as DefinePluginOptions, t as AdapterContext, tt as ModuleList, u as ConnectMiddleware, ut as definePlugin, v as RouteMethod, vt as IntrospectionSnapshot, w as UseConnectOptions, x as RuntimeCapabilities, xt as ResponseInfo, y as RouteTable, yt as ErrorInfo, z as ContributorSource } from "./adapter-
|
|
4
|
+
import { A as Service, B as resolveAsset, C as Post, D as Put, F as ASSET_MANIFEST_VERSION, I as KickAssets, L as UnknownAssetError, M as normalizeApiQueryParamsConfig, N as withBuilder, O as Repository, P as ASSETS, R as assets, S as Patch, T as PreDestroy, V as useAssets, _ as Get, a as Autowired, b as Middleware, c as ColumnApiQueryParamsConfig, d as Delete, f as Env, g as FileUploadConfig, h as FileUpload, i as AssetKey, j as Value, k as RouteDefinition, l as Component, m as FileTypeFilter, n as ApiQueryParamsConfig, o as BaseUploadOptions, p as EnvKey, r as Asset, s as Builder, t as ApiQueryParams, u as Controller, v as Inject, w as PostConstruct, x as MiddlewareHandler, y as Injectable, z as clearAssetCache } from "./decorators-BxNSz9Qt.mjs";
|
|
5
|
+
import { a as baseEnvSchema, c as loadEnv, d as resetEnvCache, f as withEnv, l as loadEnvFromSchema, n as TypedConfigService, o as defineEnv, r as createConfigService, s as getEnv, t as ConfigService, u as reloadEnv } from "./index-CNHSbZTE.mjs";
|
|
6
|
+
import { $ as setInMetaRecord, B as SourcedRegistration, C as RuntimeTypeMap, Ct as reportResponse, D as RequestContext, E as DeepReadonly, F as RunContributorsOptions, G as getMetaRecord, H as getClassMeta, I as runContributors, J as hasClassMeta, K as getMethodMeta, L as BuildPipelineOptions, M as AdapterFactory, N as DefineAdapterOptions, O as RouteShape, P as defineAdapter, Q as setInMetaMap, R as ContributorPipeline, S as RuntimeResponse, St as reportError, T as Ctx, U as getClassMetaOrUndefined, V as buildPipeline, W as getMetaMap, X as pushMethodMeta, Y as pushClassMeta, Z as setClassMeta, _ as RouteMeta, _t as IntrospectionKind, a as KickServer, at as defineModule, b as RuntimeAppOptions, bt as ErrorSource, c as ActiveRuntime, ct as PluginAsyncOptions, d as CtxHandler, dt as KickPlugin, et as setMethodMeta, f as ExpressRuntimeTypes, ft as ModuleRegistry, g as RouteEntry, gt as IntrospectFn, h as MatchedRoute, ht as KickJsPluginRegistry, i as AppAdapterClass, it as ModuleFactory, j as AdapterAsyncOptions, k as SseHandler, l as AdapterHttp, lt as PluginFactory, m as KickRuntimeRegister, mt as KickJsPluginName, n as AdapterMiddleware, nt as defineModules, o as MiddlewarePath, ot as BuildContext, p as HttpRuntime, pt as MutableModuleRegistry, q as getMethodMetaOrUndefined, r as AppAdapter, rt as DefineModuleOptions, s as MiddlewarePhase, st as DefinePluginOptions, t as AdapterContext, tt as ModuleList, u as ConnectMiddleware, ut as definePlugin, v as RouteMethod, vt as IntrospectionSnapshot, w as UseConnectOptions, x as RuntimeCapabilities, xt as ResponseInfo, y as RouteTable, yt as ErrorInfo, z as ContributorSource } from "./adapter-DjfRMob8.mjs";
|
|
7
7
|
import { _ as getRouteFlags, a as RouteFlagContext, c as RouteFlagName, d as RouteFlagTest, f as RouteFlagValue, g as defineRouteFlag, h as assertFlagTest, i as RouteFlag, l as RouteFlagPredicate, m as ValuedRouteFlag, n as KickRouteFlags, o as RouteFlagDeclaration, p as RouteFlags, r as NegatedRouteFlagName, s as RouteFlagDeclarations, t as BareRouteFlag, u as RouteFlagRecord, v as matchesFlagTest, y as resolveRouteFlags } from "./route-flag-B1lQht6v.mjs";
|
|
8
8
|
import { C as MetaValue, S as ExecutionContext, _ as ResolvedDeps, a as ModuleRoutes, b as ContextMeta, c as ContextDecorator, d as ContextDecoratorTarget, f as ContextDecoratorWithDefaults, g as MissingParamKeys, h as DepValue, l as ContextDecoratorRequiringParams, m as ContributorRegistrations, n as AppModuleClass, o as AnyContributorRegistration, p as ContributorRegistration, r as AppModuleEntry, s as CallSiteParams, t as AppModule, u as ContextDecoratorSpec, v as defineContextDecorator, x as ContextMetaKey, y as ContextKeys } from "./app-module-DCA65GZZ.mjs";
|
|
9
9
|
import { a as CronOptions, c as getCronJobs, d as runCronJob, f as runCronJobs, i as CronJobMeta, l as isCronJobEnabled, n as Cron, o as CronRun, r as CronJob, s as cronScheduleId, t as CRON_META, u as listCronJobs } from "./cron-BTGytNg-.mjs";
|
|
10
|
-
import { A as runJob, C as JobLike, D as listJobHandlers, E as ProcessDefinition, O as listJobQueues, S as JobHandler, T as Process, _ as JOB_DISPATCHER, a as KvSessionStoreOptions, b as JobContextCarrier, c as InferHandlerResponse, d as isReply, f as reply, g as JOB_CONTEXT_FIELD, h as waitUntil, i as KvSessionStore, j as stampJobContext, k as registerJobContext, l as Reply, m as settleBackgroundWork, n as KvRateLimitStore, o as RateLimitGuardOptions, p as PlatformContext, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike, u as applyHandlerResult, v as JOB_META, w as NoJobHandlerError, x as JobDispatcher, y as Job } from "./kv-stores-
|
|
10
|
+
import { A as runJob, C as JobLike, D as listJobHandlers, E as ProcessDefinition, O as listJobQueues, S as JobHandler, T as Process, _ as JOB_DISPATCHER, a as KvSessionStoreOptions, b as JobContextCarrier, c as InferHandlerResponse, d as isReply, f as reply, g as JOB_CONTEXT_FIELD, h as waitUntil, i as KvSessionStore, j as stampJobContext, k as registerJobContext, l as Reply, m as settleBackgroundWork, n as KvRateLimitStore, o as RateLimitGuardOptions, p as PlatformContext, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike, u as applyHandlerResult, v as JOB_META, w as NoJobHandlerError, x as JobDispatcher, y as Job } from "./kv-stores-BZTe23Tw.mjs";
|
|
11
11
|
import { a as Cacheable, c as setCacheProvider, i as CacheProvider, n as CacheEvict, o as MemoryCacheProvider, r as CacheOptions, s as getCacheProvider } from "./cache-C0qL0NSD.mjs";
|
|
12
|
-
import { a as describeError, i as createLogger, n as Logger, r as LoggerProvider, t as ConsoleLoggerProvider } from "./logger-
|
|
13
|
-
import { $ as MissingMountDepError, A as defineHttpContextDecorator, B as CRON_WORKER_ENV, C as RequestStore, D as buildRoutes, E as requestStore, F as bootstrap, G as offerRoutePolicy, H as KickCronAdapter, I as ERROR_CHANNEL, J as GroupAssetKeysOptions, K as AssetKeyPair, L as HANDLER_CHANNEL, M as isClusterPrimary, N as KickHandler, O as expressRuntime, P as createHandler, Q as DuplicateMountNameError, R as HandlerTraceContext, S as requestScopeMiddleware, T as getRequestValue, U as RoutePolicyTable, V as CronAdapterOptions, W as bindRoutePolicy, X as groupAssetKeys, Y as GroupAssetKeysResult, Z as stripExt, _ as TRACEPARENT_HEADER, _t as Disposable, at as DuplicateContributorError, b as parseTraceparent, c as HealthController, ct as CircuitBreaker, d as healthModule, dt as CircuitBreakerStats, et as MountCycleError, f as SpaAdapter, ft as CircuitOpenError, g as ViewAdapterOptions, gt as formatKickError, h as ViewAdapter, ht as KickErrorInit, i as ServerOptions, it as ContributorCycleError, j as ClusterOptions, k as materializeRouter, l as HealthModuleConfig, lt as CircuitBreakerOptions, mt as KickError, n as ApplicationOptions, nt as MountSortItem, o as HEALTH_PROBE, ot as MissingContextValueError, p as SpaAdapterOptions, pt as FormatKickErrorOptions, q as AssetKeyStrategy, r as MiddlewareEntry, rt as mountSort, s as HealthCheckResult, st as MissingContributorError, t as Application, tt as MountKind, u as HealthProbe, ut as CircuitBreakerState, v as TraceContext, vt as disposeAll, w as getRequestStore, x as traceContext, y as TraceContextOptions, yt as registerDisposable, z as RESPONSE_CHANNEL } from "./application-
|
|
12
|
+
import { a as describeError, i as createLogger, n as Logger, r as LoggerProvider, t as ConsoleLoggerProvider } from "./logger-xE9qI_FS.mjs";
|
|
13
|
+
import { $ as MissingMountDepError, A as defineHttpContextDecorator, B as CRON_WORKER_ENV, C as RequestStore, D as buildRoutes, E as requestStore, F as bootstrap, G as offerRoutePolicy, H as KickCronAdapter, I as ERROR_CHANNEL, J as GroupAssetKeysOptions, K as AssetKeyPair, L as HANDLER_CHANNEL, M as isClusterPrimary, N as KickHandler, O as expressRuntime, P as createHandler, Q as DuplicateMountNameError, R as HandlerTraceContext, S as requestScopeMiddleware, T as getRequestValue, U as RoutePolicyTable, V as CronAdapterOptions, W as bindRoutePolicy, X as groupAssetKeys, Y as GroupAssetKeysResult, Z as stripExt, _ as TRACEPARENT_HEADER, _t as Disposable, at as DuplicateContributorError, b as parseTraceparent, c as HealthController, ct as CircuitBreaker, d as healthModule, dt as CircuitBreakerStats, et as MountCycleError, f as SpaAdapter, ft as CircuitOpenError, g as ViewAdapterOptions, gt as formatKickError, h as ViewAdapter, ht as KickErrorInit, i as ServerOptions, it as ContributorCycleError, j as ClusterOptions, k as materializeRouter, l as HealthModuleConfig, lt as CircuitBreakerOptions, mt as KickError, n as ApplicationOptions, nt as MountSortItem, o as HEALTH_PROBE, ot as MissingContextValueError, p as SpaAdapterOptions, pt as FormatKickErrorOptions, q as AssetKeyStrategy, r as MiddlewareEntry, rt as mountSort, s as HealthCheckResult, st as MissingContributorError, t as Application, tt as MountKind, u as HealthProbe, ut as CircuitBreakerState, v as TraceContext, vt as disposeAll, w as getRequestStore, x as traceContext, y as TraceContextOptions, yt as registerDisposable, z as RESPONSE_CHANNEL } from "./application-CGxZCYl1.mjs";
|
|
14
14
|
import { a as ProblemException, c as defaultProblemTitle, i as ProblemDetails, l as normalizeProblem, n as HttpStatus, o as Problems, r as HttpStatusCode, s as ValidationError, t as HttpException } from "./errors-By6UueCm.mjs";
|
|
15
15
|
import { n as joinPaths, r as normalizePath, t as buildMountPath } from "./path-CMwIHsdv.mjs";
|
|
16
16
|
import { a as isRef, c as toRefs, i as computed, l as unref, n as Ref, o as reactive, r as WatchOptions, s as ref, t as ComputedRef, u as watch } from "./reactivity-BkMNA0kp.mjs";
|
|
17
17
|
import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "./session-4Tb6q0Ny.mjs";
|
|
18
18
|
import { C as SortItem, S as QueryFieldConfig, T as TypedParsedQuery, _ as FilterOperator, a as getQueryParsingDefaults, b as ParsedQuery, c as parseQuery, d as resetQueryParsingDefaults, f as setQueryParsingDefaults, g as FilterItem, h as FieldsOf, i as buildQueryParams, l as parseSearchQuery, m as FILTER_OPERATORS, n as QueryRejectReason, o as parseFilters, r as QueryRejection, s as parsePagination, t as ParseQueryOptions, u as parseSort, v as PaginatedResponse, x as QueryBuilderAdapter, y as PaginationParams } from "./index-BfT5KZ-M.mjs";
|
|
19
|
-
import { i as buildRouteTable, t as BuildRoutesOptions } from "./router-builder-
|
|
19
|
+
import { i as buildRouteTable, t as BuildRoutesOptions } from "./router-builder-CDkW88KQ.mjs";
|
|
20
20
|
import { n as requestId, t as REQUEST_ID_HEADER } from "./request-id-CVzJ1uHD.mjs";
|
|
21
21
|
import { n as validate } from "./validate-DMYpUnrf.mjs";
|
|
22
|
-
import { n as errorHandler, r as notFoundHandler } from "./error-handler-
|
|
23
|
-
import { i as csrfGuard, n as CsrfOptions, r as csrf, t as CsrfGuardOptions } from "./csrf-
|
|
22
|
+
import { n as errorHandler, r as notFoundHandler } from "./error-handler-itG65lj0.mjs";
|
|
23
|
+
import { i as csrfGuard, n as CsrfOptions, r as csrf, t as CsrfGuardOptions } from "./csrf-C73UkRgc.mjs";
|
|
24
24
|
import { i as requestLogger, n as LoggedResponse, r as RequestLoggerOptions, t as LoggedRequest } from "./request-logger-TgcKRNDD.mjs";
|
|
25
25
|
import { n as HelmetOptions, r as helmet } from "./helmet-C2QEU6S-.mjs";
|
|
26
26
|
import { n as cors, t as CorsOptions } from "./cors-CsibNTQ6.mjs";
|
|
27
27
|
import { n as RateLimitStore, r as rateLimit, t as RateLimitOptions } from "./rate-limit-Bwed3jOV.mjs";
|
|
28
|
-
import { i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, r as cleanupFiles, t as UploadOptions } from "./upload-
|
|
28
|
+
import { i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, r as cleanupFiles, t as UploadOptions } from "./upload-Cy7qQy7M.mjs";
|
|
29
29
|
export { ASSETS, ASSET_MANIFEST_VERSION, type ActiveRuntime, type AdapterAsyncOptions, type AdapterContext, type AdapterFactory, type AdapterHttp, type AdapterMiddleware, type AnyContributorRegistration, ApiQueryParams, type ApiQueryParamsConfig, type ApiQueryParamsConfig as QueryParamsConfig, type AppAdapter, type AppAdapterClass, type AppModule, type AppModuleClass, type AppModuleEntry, Application, type ApplicationOptions, Asset, type AssetKey, type AssetKeyPair, type AssetKeyStrategy, Autowired, type BareRouteFlag, type BaseUploadOptions, type BuildContext, type BuildPipelineOptions, type BuildRoutesOptions, type Buildable, Builder, type BuilderOf, CRON_META, CRON_WORKER_ENV, CacheEvict, type CacheOptions, type CacheProvider, Cacheable, type CallSiteParams, CircuitBreaker, type CircuitBreakerOptions, type CircuitBreakerState, type CircuitBreakerStats, CircuitOpenError, type ClassKind, type ClusterOptions, type ColumnApiQueryParamsConfig, Component, type ComputedRef, ConfigService, type ConnectMiddleware, ConsoleLoggerProvider, type Constructor, Container, type ContainerChangeEvent, type ContainerChangeListener, type ContextDecorator, type ContextDecoratorRequiringParams, type ContextDecoratorSpec, type ContextDecoratorTarget, type ContextDecoratorWithDefaults, type ContextKeys, type ContextMeta, type ContextMetaKey, ContributorCycleError, type ContributorPipeline, type ContributorRegistration, type ContributorRegistrations, type ContributorSource, Controller, type CorsOptions, Cron, type CronAdapterOptions, type CronJob, type CronJobMeta, type CronOptions, type CronRun, type CsrfGuardOptions, type CsrfOptions, type Ctx, type CtxHandler, type DeepReadonly, type DefineAdapterOptions, type DefineModuleOptions, type DefinePluginOptions, Delete, type DepValue, type Disposable, DuplicateContributorError, DuplicateMountNameError, ERROR_CHANNEL, type Env, type EnvKey, type ErrorInfo, type ErrorSource, type ExecutionContext, type ExpressRuntimeTypes, FILTER_OPERATORS, type FieldsOf, type FileTypeFilter, FileUpload, type FileUploadConfig, type FilterItem, type FilterOperator, type FormatKickErrorOptions, Get, type GroupAssetKeysOptions, type GroupAssetKeysResult, HANDLER_CHANNEL, HEALTH_PROBE, type HandlerTraceContext, type HealthCheckResult, HealthController, type HealthModuleConfig, type HealthProbe, type HelmetOptions, HttpException, type HttpRuntime, HttpStatus, type HttpStatusCode, INJECTION_TOKEN, type InferHandlerResponse, Inject, Injectable, type InjectionToken, type IntrospectFn, type IntrospectionKind, type IntrospectionSnapshot, JOB_CONTEXT_FIELD, JOB_DISPATCHER, JOB_META, Job, type JobContextCarrier, type JobDispatcher, type JobHandler, type JobLike, type KickAssets, KickCronAdapter, KickError, type KickErrorInit, type KickHandler, type KickJsPluginName, type KickJsPluginRegistry, type KickJsRegistry, type KickPlugin, type KickRouteFlags, type KickRuntimeRegister, type KickServer, type KvLike, KvRateLimitStore, type KvRateLimitStoreOptions, KvSessionStore, type KvSessionStoreOptions, type LoggedRequest, type LoggedResponse, Logger, type LoggerProvider, METADATA, type MatchedRoute, type MaybePromise, MemoryCacheProvider, type MetaValue, Middleware, type MiddlewareEntry, type MiddlewareHandler, type MiddlewarePath, type MiddlewarePhase, MissingContextValueError, MissingContributorError, MissingMountDepError, type MissingParamKeys, type ModuleFactory, ModuleList, type ModuleRegistry, type ModuleRoutes, MountCycleError, type MountKind, type MountSortItem, MutableModuleRegistry, type NegatedRouteFlagName, NoJobHandlerError, type PaginatedResponse, type PaginationParams, type ParseQueryOptions, type ParsedQuery, Patch, type PlatformContext, type PluginAsyncOptions, type PluginFactory, Post, PostConstruct, type PostConstructStatus, PreDestroy, type ProblemDetails, ProblemException, Problems, Process, type ProcessDefinition, Put, type QueryBuilderAdapter, type QueryFieldConfig, type QueryRejectReason, type QueryRejection, REQUEST_ID_HEADER, RESPONSE_CHANNEL, type RateLimitGuardOptions, type RateLimitOptions, type RateLimitStore, type Ref, type Reply, Repository, RequestContext, type RequestLoggerOptions, type RequestStore, type ResolvedDeps, type ResponseInfo, type RouteDefinition, type RouteEntry, type RouteFlag, type RouteFlagContext, type RouteFlagDeclaration, type RouteFlagDeclarations, type RouteFlagName, type RouteFlagPredicate, type RouteFlagRecord, type RouteFlagTest, type RouteFlagValue, type RouteFlags, type RouteMeta, type RouteMethod, RoutePolicyTable, type RouteShape, type RouteTable, type RunContributorsOptions, type RuntimeAppOptions, type RuntimeCapabilities, type RuntimeResponse, type RuntimeTypeMap, Scope, type ServerOptions, Service, type ServiceOptions, type Session, type SessionData, type SessionOptions, type SessionStore, type SortItem, type SourcedRegistration, SpaAdapter, type SpaAdapterOptions, type SseHandler, TRACEPARENT_HEADER, type TraceContext, type TraceContextOptions, type TypedConfigService, type TypedParsedQuery, UnknownAssetError, type UploadOptions, type UseConnectOptions, type ValidationError, Value, type ValuedRouteFlag, ViewAdapter, type ViewAdapterOptions, type WatchOptions, applyHandlerResult, assertFlagTest, assets, baseEnvSchema, bindRoutePolicy, bootstrap, buildMountPath, buildPipeline, buildQueryParams, buildRouteTable, buildRoutes, buildUploadMiddleware, cleanupFiles, clearAssetCache, computed, cors, createConfigService, createHandler, createLogger, createToken, cronScheduleId, csrf, csrfGuard, defaultProblemTitle, defineAdapter, defineContextDecorator, defineEnv, defineHttpContextDecorator, defineModule, defineModules, definePlugin, defineRouteFlag, describeError, disposeAll, errorHandler, expressRuntime, formatKickError, getCacheProvider, getClassMeta, getClassMetaOrUndefined, getCronJobs, getEnv, getMetaMap, getMetaRecord, getMethodMeta, getMethodMetaOrUndefined, getQueryParsingDefaults, getRequestStore, getRequestValue, getRouteFlags, groupAssetKeys, hasClassMeta, healthModule, helmet, isClusterPrimary, isCronJobEnabled, isInjectionToken, isRef, isReply, joinPaths, listCronJobs, listJobHandlers, listJobQueues, loadEnv, loadEnvFromSchema, matchesFlagTest, materializeRouter, mountSort, normalizeApiQueryParamsConfig, normalizePath, normalizeProblem, notFoundHandler, offerRoutePolicy, parseFilters, parsePagination, parseQuery, parseSearchQuery, parseSort, parseTraceparent, pushClassMeta, pushMethodMeta, rateLimit, rateLimitGuard, reactive, ref, registerDisposable, registerJobContext, reloadEnv, reply, reportError, reportResponse, requestId, requestLogger, requestScopeMiddleware, requestStore, resetEnvCache, resetQueryParsingDefaults, resolveAsset, resolveMimeTypes, resolveRouteFlags, runContributors, runCronJob, runCronJobs, runJob, session, setCacheProvider, setClassMeta, setInMetaMap, setInMetaRecord, setMethodMeta, setQueryParsingDefaults, settleBackgroundWork, stampJobContext, stripExt, toRefs, tokenName, traceContext, unref, upload, useAssets, validate, waitUntil, watch, withBuilder, withEnv };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v9.0
|
|
2
|
+
* @forinda/kickjs v9.2.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{a as loadEnvFromSchema,c as withEnv,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-w27RDkCQ.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{a as isInjectionToken,d as KickError,f as formatKickError,i as createToken,n as tokenName,r as INJECTION_TOKEN,t as Container}from"./container-ByH0uCpG.mjs";import{ConsoleLoggerProvider,Logger,createLogger,describeError}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-CIXErBTj.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,PreDestroy,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{a as HEALTH_PROBE,c as ERROR_CHANNEL,d as DuplicateMountNameError,f as MissingMountDepError,h as defineModule,i as materializeRouter,l as HANDLER_CHANNEL,m as mountSort,n as buildRoutes,o as HealthController,p as MountCycleError,r as expressRuntime,s as healthModule,t as Application,u as RESPONSE_CHANNEL}from"./application-DPLzamTb.mjs";import{t as MutableModuleRegistry}from"./module-registry-IO8RfVjX.mjs";import{i as reportResponse,r as reportError}from"./observers-BtXkP5km.mjs";import{CRON_META,Cron,cronScheduleId,getCronJobs,isCronJobEnabled,listCronJobs,runCronJob,runCronJobs}from"./core/cron.mjs";import{a as NoJobHandlerError,c as listJobQueues,d as stampJobContext,i as Job,l as registerJobContext,n as JOB_DISPATCHER,o as Process,r as JOB_META,s as listJobHandlers,t as JOB_CONTEXT_FIELD,u as runJob}from"./jobs-CeP4aLYR.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{r as registerDisposable,t as disposeAll}from"./disposables-CgCLyCoP.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{buildMountPath,joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{l as resolveRouteFlags,n as assertFlagTest,o as getRouteFlags,r as defineRouteFlag,s as matchesFlagTest}from"./route-flag-CpTa8y1n.mjs";import{a as clearAssetCache,c as groupAssetKeys,i as assets,l as stripExt,n as ASSET_MANIFEST_VERSION,o as resolveAsset,r as UnknownAssetError,s as useAssets,t as ASSETS,u as defineContextDecorator}from"./core-DrjVUTxG.mjs";import{i as MissingContributorError,n as DuplicateContributorError,r as MissingContextValueError,t as ContributorCycleError}from"./context-errors-VjQBtxEc.mjs";import{i as buildPipeline,r as buildRouteTable}from"./router-builder-BxXKP8Wy.mjs";import{t as runContributors}from"./contributor-runner-BEjgGaoG.mjs";import{t as defineAdapter}from"./define-adapter-B4Iu_TcU.mjs";import{n as bindRoutePolicy,r as offerRoutePolicy,t as RoutePolicyTable}from"./route-policy-DPTLfer-.mjs";import{c as requestScopeMiddleware,i as reply,n as applyHandlerResult,r as isReply}from"./runtime-B4NQGN66.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{a as settleBackgroundWork,c as getRequestValue,l as requestStore,o as waitUntil,r as forwardBackgroundWork,s as getRequestStore,t as RequestContext}from"./context-fth87C3G.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.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-CNa0mxSK.mjs";import{validate}from"./http/middleware/validate.mjs";import{r as resolveMimeTypes}from"./upload-config-BmpmZZVK.mjs";import{buildUploadMiddleware,cleanupFiles,upload}from"./http/middleware/upload.mjs";import{i as KickCronAdapter,n as isClusterPrimary,r as CRON_WORKER_ENV,t as bootstrap}from"./bootstrap-DUNvDJ1a.mjs";import{n as csrfGuard,r as randomHex,t as csrf}from"./csrf-CbFJiyKW.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{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-5ocdxfdh.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-C-sAHakM.mjs";var ModuleList=class extends Array{mount(module){return this.push(module),this}};function defineModules(...initial){let list=new ModuleList;for(let entry of initial)list.push(entry);return list}var CircuitOpenError=class extends Error{breakerName;constructor(breakerName){super(`Circuit breaker "${breakerName}" is open — request rejected`),this.breakerName=breakerName,this.name=`CircuitOpenError`}},CircuitBreaker=class{name;state=`closed`;failures=0;successes=0;halfOpenAttempts=0;lastFailure;openedAt;failureThreshold;resetTimeout;halfOpenMax;constructor(name,options){this.name=name,this.failureThreshold=options.failureThreshold,this.resetTimeout=options.resetTimeout,this.halfOpenMax=options.halfOpenMax??1}async execute(fn){if(this.maybeTransitionToHalfOpen(),this.state===`open`||this.state===`half_open`&&this.halfOpenAttempts>=this.halfOpenMax)throw new CircuitOpenError(this.name);this.state===`half_open`&&this.halfOpenAttempts++;try{let result=await fn();return this.onSuccess(),result}catch(error){throw this.onFailure(),error}}getState(){return this.maybeTransitionToHalfOpen(),this.state}getStats(){return this.maybeTransitionToHalfOpen(),{failures:this.failures,successes:this.successes,state:this.state,...this.lastFailure?{lastFailure:this.lastFailure}:{}}}reset(){this.state=`closed`,this.failures=0,this.successes=0,this.halfOpenAttempts=0,this.lastFailure=void 0,this.openedAt=void 0}onSuccess(){this.state===`half_open`&&(this.state=`closed`,this.failures=0,this.halfOpenAttempts=0,this.openedAt=void 0),this.successes++}onFailure(){this.failures++,this.lastFailure=new Date,this.state===`half_open`?(this.state=`open`,this.openedAt=Date.now(),this.halfOpenAttempts=0):this.state===`closed`&&this.failures>=this.failureThreshold&&(this.state=`open`,this.openedAt=Date.now())}maybeTransitionToHalfOpen(){this.state===`open`&&this.openedAt!=null&&Date.now()-this.openedAt>=this.resetTimeout&&(this.state=`half_open`,this.halfOpenAttempts=0)}};const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function definePlugin(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner,ensureInner=async container=>{if(inner)return inner;let deps=(opts.inject??[]).map(tok=>container.resolve(tok)),config=await opts.useFactory(...deps);return inner=buildSync(instanceName,!1,config),inner};return{name:instanceName,async onReady(container){let built=await ensureInner(container);built.register?.(container),await built.onReady?.(container)},async shutdown(){await inner?.shutdown?.()}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}function createHandler(options){let app,ready=()=>(app??=(async()=>{let instance=new Application(options);try{await instance.startWithoutServer()}catch(err){throw app=void 0,await instance.shutdown().catch(()=>{}),err}return instance})(),app);return{ready,async fetch(request,platform){let response=await(await ready()).fetch(request);return forwardBackgroundWork(platform),response},async node(req,res,platform){if((await ready()).handle(req,res),platform?.waitUntil){let sent=new Promise(resolve=>res.once(`close`,()=>resolve()));platform.waitUntil(sent.then(settleBackgroundWork))}},async close(){let current=app;app=void 0,current&&await(await current).shutdown()}}}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 randomHex(16)}function generateSpanId(){return randomHex(8)}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,CRON_WORKER_ENV,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,ERROR_CHANNEL,FILTER_OPERATORS,FileUpload,Get,HANDLER_CHANNEL,HEALTH_PROBE,HealthController,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,JOB_CONTEXT_FIELD,JOB_DISPATCHER,JOB_META,Job,KickCronAdapter,KickError,KvRateLimitStore,KvSessionStore,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContextValueError,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,NoJobHandlerError,Patch,Post,PostConstruct,PreDestroy,ProblemException,Problems,Process,Put,REQUEST_ID_HEADER,RESPONSE_CHANNEL,Repository,RequestContext,RoutePolicyTable,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,applyHandlerResult,assertFlagTest,assets,baseEnvSchema,bindRoutePolicy,bootstrap,buildMountPath,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createHandler,createLogger,createToken,cronScheduleId,csrf,csrfGuard,defaultProblemTitle,defineAdapter,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,defineRouteFlag,describeError,disposeAll,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,getRouteFlags,groupAssetKeys,hasClassMeta,healthModule,helmet,isClusterPrimary,isCronJobEnabled,isInjectionToken,isRef,isReply,joinPaths,listCronJobs,listJobHandlers,listJobQueues,loadEnv,loadEnvFromSchema,matchesFlagTest,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,offerRoutePolicy,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,rateLimitGuard,reactive,ref,registerDisposable,registerJobContext,reloadEnv,reply,reportError,reportResponse,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,resolveRouteFlags,runContributors,runCronJob,runCronJobs,runJob,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,settleBackgroundWork,stampJobContext,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,waitUntil,watch,withBuilder,withEnv};
|
|
11
|
+
import{a as loadEnvFromSchema,c as withEnv,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-__fzPsHD.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{a as isInjectionToken,d as KickError,f as formatKickError,i as createToken,n as tokenName,r as INJECTION_TOKEN,t as Container}from"./container-CAPN7pBG.mjs";import{ConsoleLoggerProvider,Logger,createLogger,describeError}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-CzzRUnAx.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,PreDestroy,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{a as HEALTH_PROBE,c as ERROR_CHANNEL,d as DuplicateMountNameError,f as MissingMountDepError,h as defineModule,i as materializeRouter,l as HANDLER_CHANNEL,m as mountSort,n as buildRoutes,o as HealthController,p as MountCycleError,r as expressRuntime,s as healthModule,t as Application,u as RESPONSE_CHANNEL}from"./application-BSJpaiSY.mjs";import{t as MutableModuleRegistry}from"./module-registry-BnmRNy-3.mjs";import{i as reportResponse,r as reportError}from"./observers-COlQfinD.mjs";import{CRON_META,Cron,cronScheduleId,getCronJobs,isCronJobEnabled,listCronJobs,runCronJob,runCronJobs}from"./core/cron.mjs";import{a as NoJobHandlerError,c as listJobQueues,d as stampJobContext,i as Job,l as registerJobContext,n as JOB_DISPATCHER,o as Process,r as JOB_META,s as listJobHandlers,t as JOB_CONTEXT_FIELD,u as runJob}from"./jobs-BmpeFvi0.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{r as registerDisposable,t as disposeAll}from"./disposables-NlW6Mibk.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{buildMountPath,joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{l as resolveRouteFlags,n as assertFlagTest,o as getRouteFlags,r as defineRouteFlag,s as matchesFlagTest}from"./route-flag-Bb7n9Mnp.mjs";import{a as clearAssetCache,c as groupAssetKeys,i as assets,l as stripExt,n as ASSET_MANIFEST_VERSION,o as resolveAsset,r as UnknownAssetError,s as useAssets,t as ASSETS,u as defineContextDecorator}from"./core-DKrKVLfj.mjs";import{i as MissingContributorError,n as DuplicateContributorError,r as MissingContextValueError,t as ContributorCycleError}from"./context-errors-DYJjhalQ.mjs";import{i as buildPipeline,r as buildRouteTable}from"./router-builder-DawnRzlp.mjs";import{t as runContributors}from"./contributor-runner-vk9XlOED.mjs";import{t as defineAdapter}from"./define-adapter-C6lFhCL-.mjs";import{n as bindRoutePolicy,r as offerRoutePolicy,t as RoutePolicyTable}from"./route-policy-C_Wsc-b1.mjs";import{n as isReply,r as reply,s as requestScopeMiddleware,t as applyHandlerResult}from"./reply-CbTYIj_e.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{a as forwardBackgroundWork,c as waitUntil,d as requestStore,l as getRequestStore,s as settleBackgroundWork,t as RequestContext,u as getRequestValue}from"./context-gpvcMlG5.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.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-B5DTq_4W.mjs";import{validate}from"./http/middleware/validate.mjs";import{r as resolveMimeTypes}from"./upload-config-D01Ty40f.mjs";import{buildUploadMiddleware,cleanupFiles,upload}from"./http/middleware/upload.mjs";import{i as KickCronAdapter,n as isClusterPrimary,r as CRON_WORKER_ENV,t as bootstrap}from"./bootstrap-CbTF6xvv.mjs";import{n as csrfGuard,r as randomHex,t as csrf}from"./csrf-CxJItZVa.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{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-DJwxntyw.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-DmGiAIuH.mjs";var ModuleList=class extends Array{mount(module){return this.push(module),this}};function defineModules(...initial){let list=new ModuleList;for(let entry of initial)list.push(entry);return list}var CircuitOpenError=class extends Error{breakerName;constructor(breakerName){super(`Circuit breaker "${breakerName}" is open — request rejected`),this.breakerName=breakerName,this.name=`CircuitOpenError`}},CircuitBreaker=class{name;state=`closed`;failures=0;successes=0;halfOpenAttempts=0;lastFailure;openedAt;failureThreshold;resetTimeout;halfOpenMax;constructor(name,options){this.name=name,this.failureThreshold=options.failureThreshold,this.resetTimeout=options.resetTimeout,this.halfOpenMax=options.halfOpenMax??1}async execute(fn){if(this.maybeTransitionToHalfOpen(),this.state===`open`||this.state===`half_open`&&this.halfOpenAttempts>=this.halfOpenMax)throw new CircuitOpenError(this.name);this.state===`half_open`&&this.halfOpenAttempts++;try{let result=await fn();return this.onSuccess(),result}catch(error){throw this.onFailure(),error}}getState(){return this.maybeTransitionToHalfOpen(),this.state}getStats(){return this.maybeTransitionToHalfOpen(),{failures:this.failures,successes:this.successes,state:this.state,...this.lastFailure?{lastFailure:this.lastFailure}:{}}}reset(){this.state=`closed`,this.failures=0,this.successes=0,this.halfOpenAttempts=0,this.lastFailure=void 0,this.openedAt=void 0}onSuccess(){this.state===`half_open`&&(this.state=`closed`,this.failures=0,this.halfOpenAttempts=0,this.openedAt=void 0),this.successes++}onFailure(){this.failures++,this.lastFailure=new Date,this.state===`half_open`?(this.state=`open`,this.openedAt=Date.now(),this.halfOpenAttempts=0):this.state===`closed`&&this.failures>=this.failureThreshold&&(this.state=`open`,this.openedAt=Date.now())}maybeTransitionToHalfOpen(){this.state===`open`&&this.openedAt!=null&&Date.now()-this.openedAt>=this.resetTimeout&&(this.state=`half_open`,this.halfOpenAttempts=0)}};const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function definePlugin(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner,ensureInner=async container=>{if(inner)return inner;let deps=(opts.inject??[]).map(tok=>container.resolve(tok)),config=await opts.useFactory(...deps);return inner=buildSync(instanceName,!1,config),inner};return{name:instanceName,async onReady(container){let built=await ensureInner(container);built.register?.(container),await built.onReady?.(container)},async shutdown(){await inner?.shutdown?.()}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}function createHandler(options){let app,ready=()=>(app??=(async()=>{let instance=new Application(options);try{await instance.startWithoutServer()}catch(err){throw app=void 0,await instance.shutdown().catch(()=>{}),err}return instance})(),app);return{ready,async fetch(request,platform){let response=await(await ready()).fetch(request);return forwardBackgroundWork(platform),response},async node(req,res,platform){if((await ready()).handle(req,res),platform?.waitUntil){let sent=new Promise(resolve=>res.once(`close`,()=>resolve()));platform.waitUntil(sent.then(settleBackgroundWork))}},async close(){let current=app;app=void 0,current&&await(await current).shutdown()}}}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 randomHex(16)}function generateSpanId(){return randomHex(8)}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,CRON_WORKER_ENV,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,ERROR_CHANNEL,FILTER_OPERATORS,FileUpload,Get,HANDLER_CHANNEL,HEALTH_PROBE,HealthController,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,JOB_CONTEXT_FIELD,JOB_DISPATCHER,JOB_META,Job,KickCronAdapter,KickError,KvRateLimitStore,KvSessionStore,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContextValueError,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,NoJobHandlerError,Patch,Post,PostConstruct,PreDestroy,ProblemException,Problems,Process,Put,REQUEST_ID_HEADER,RESPONSE_CHANNEL,Repository,RequestContext,RoutePolicyTable,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,applyHandlerResult,assertFlagTest,assets,baseEnvSchema,bindRoutePolicy,bootstrap,buildMountPath,buildPipeline,buildQueryParams,buildRouteTable,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createHandler,createLogger,createToken,cronScheduleId,csrf,csrfGuard,defaultProblemTitle,defineAdapter,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,defineRouteFlag,describeError,disposeAll,errorHandler,expressRuntime,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getQueryParsingDefaults,getRequestStore,getRequestValue,getRouteFlags,groupAssetKeys,hasClassMeta,healthModule,helmet,isClusterPrimary,isCronJobEnabled,isInjectionToken,isRef,isReply,joinPaths,listCronJobs,listJobHandlers,listJobQueues,loadEnv,loadEnvFromSchema,matchesFlagTest,materializeRouter,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,offerRoutePolicy,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,rateLimitGuard,reactive,ref,registerDisposable,registerJobContext,reloadEnv,reply,reportError,reportResponse,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resetQueryParsingDefaults,resolveAsset,resolveMimeTypes,resolveRouteFlags,runContributors,runCronJob,runCronJobs,runJob,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,setQueryParsingDefaults,settleBackgroundWork,stampJobContext,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,waitUntil,watch,withBuilder,withEnv};
|
|
12
12
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core/define-modules.ts","../src/core/circuit-breaker.ts","../src/core/define-plugin.ts","../src/http/handler.ts","../src/http/define-http-context-decorator.ts","../src/http/middleware/trace-context.ts"],"sourcesContent":["import type { AppModuleEntry } from './app-module'\n\n/**\n * Chainable registry returned by {@link defineModules}. Extends\n * `Array<AppModuleEntry>` so it drops into `bootstrap({ modules })`\n * unchanged — the framework loader already iterates the array, so a\n * `ModuleList` IS a valid `modules` value with no special handling.\n *\n * The added affordance is `.mount(module)` returning `this` so\n * adopters can build the list fluently:\n *\n * ```ts\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * bootstrap({ modules })\n * ```\n *\n * **Why not just use a plain array?** Two reasons. First, the\n * fluent shape reads more naturally for module-list construction —\n * adopters used to `definePlugin().scoped(...)` / `defineAdapter(...)`\n * see the same call-then-call pattern. Second, the registry is the\n * stable extension point: a future `.use(module)` for non-HTTP\n * modules (queues, cron, workers) lands as a method on `ModuleList`\n * without breaking the `Array<AppModuleEntry>` superclass contract.\n */\nexport class ModuleList extends Array<AppModuleEntry> {\n /**\n * Append a module to the list. Returns `this` for chaining.\n *\n * Accepts both class form (`SomeModule extends AppModule`) and\n * `defineModule` factory output — same as the underlying\n * `bootstrap({ modules })` array. The framework loader\n * discriminates `typeof entry === 'function'` at boot.\n */\n mount(module: AppModuleEntry): this {\n this.push(module)\n return this\n }\n}\n\n/**\n * Build a {@link ModuleList} for the application. Optional vararg\n * lets adopters seed the list inline; subsequent `.mount()` calls\n * append.\n *\n * @example\n * ```ts\n * import { bootstrap, defineModules } from '@forinda/kickjs'\n * import { HelloModule } from './modules/hello/hello.module'\n * import { AdminModule } from './modules/admin/admin.module'\n *\n * // Fluent — most common\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * // Or seeded inline + chained\n * const modules2 = defineModules(HelloModule()).mount(AdminModule())\n *\n * await bootstrap({ modules })\n * ```\n *\n * The returned value is an `AppModuleEntry[]` subclass — `bootstrap`\n * accepts it directly without any special-casing. For conditional /\n * dynamic registration at bootstrap time (env flags, runtime config,\n * tenant lists), use the `setup(registry)` callback on `bootstrap`\n * instead — both surfaces compose.\n */\nexport function defineModules(...initial: AppModuleEntry[]): ModuleList {\n const list = new ModuleList()\n for (const entry of initial) list.push(entry)\n return list\n}\n","/**\n * Circuit Breaker pattern for external service calls.\n *\n * Protects your application from cascading failures when downstream\n * services are unhealthy by short-circuiting requests after a\n * configurable failure threshold.\n *\n * @example\n * ```ts\n * const breaker = new CircuitBreaker('payment-api', {\n * failureThreshold: 5,\n * resetTimeout: 30_000,\n * })\n *\n * const result = await breaker.execute(() => fetch('https://payment.example.com/charge'))\n * ```\n */\n\nexport type CircuitBreakerState = 'closed' | 'open' | 'half_open'\n\nexport interface CircuitBreakerOptions {\n /** Number of consecutive failures before the circuit opens. */\n failureThreshold: number\n /** Milliseconds to wait before transitioning from OPEN to HALF_OPEN. */\n resetTimeout: number\n /** Max requests allowed in HALF_OPEN state before deciding (default 1). */\n halfOpenMax?: number\n}\n\nexport interface CircuitBreakerStats {\n failures: number\n successes: number\n state: CircuitBreakerState\n lastFailure?: Date\n}\n\n/**\n * Error thrown when the circuit is open and calls are being rejected.\n */\nexport class CircuitOpenError extends Error {\n constructor(public readonly breakerName: string) {\n super(`Circuit breaker \"${breakerName}\" is open — request rejected`)\n this.name = 'CircuitOpenError'\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitBreakerState = 'closed'\n private failures = 0\n private successes = 0\n private halfOpenAttempts = 0\n private lastFailure?: Date\n private openedAt?: number\n\n private readonly failureThreshold: number\n private readonly resetTimeout: number\n private readonly halfOpenMax: number\n\n constructor(\n public readonly name: string,\n options: CircuitBreakerOptions,\n ) {\n this.failureThreshold = options.failureThreshold\n this.resetTimeout = options.resetTimeout\n this.halfOpenMax = options.halfOpenMax ?? 1\n }\n\n /**\n * Execute an async function through the circuit breaker.\n * Throws `CircuitOpenError` if the circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n this.maybeTransitionToHalfOpen()\n\n if (this.state === 'open') {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open' && this.halfOpenAttempts >= this.halfOpenMax) {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open') {\n this.halfOpenAttempts++\n }\n\n try {\n const result = await fn()\n this.onSuccess()\n return result\n } catch (error) {\n this.onFailure()\n throw error\n }\n }\n\n /** Returns the current circuit state. */\n getState(): CircuitBreakerState {\n this.maybeTransitionToHalfOpen()\n return this.state\n }\n\n /** Returns current statistics. */\n getStats(): CircuitBreakerStats {\n this.maybeTransitionToHalfOpen()\n return {\n failures: this.failures,\n successes: this.successes,\n state: this.state,\n ...(this.lastFailure ? { lastFailure: this.lastFailure } : {}),\n }\n }\n\n /** Manually reset the circuit breaker to CLOSED state. */\n reset(): void {\n this.state = 'closed'\n this.failures = 0\n this.successes = 0\n this.halfOpenAttempts = 0\n this.lastFailure = undefined\n this.openedAt = undefined\n }\n\n private onSuccess(): void {\n if (this.state === 'half_open') {\n // Recovery confirmed — close the circuit\n this.state = 'closed'\n this.failures = 0\n this.halfOpenAttempts = 0\n this.openedAt = undefined\n }\n this.successes++\n }\n\n private onFailure(): void {\n this.failures++\n this.lastFailure = new Date()\n\n if (this.state === 'half_open') {\n // Recovery failed — re-open\n this.state = 'open'\n this.openedAt = Date.now()\n this.halfOpenAttempts = 0\n } else if (this.state === 'closed' && this.failures >= this.failureThreshold) {\n this.state = 'open'\n this.openedAt = Date.now()\n }\n }\n\n private maybeTransitionToHalfOpen(): void {\n if (this.state === 'open' && this.openedAt != null) {\n if (Date.now() - this.openedAt >= this.resetTimeout) {\n this.state = 'half_open'\n this.halfOpenAttempts = 0\n }\n }\n }\n}\n","import type { Container } from './container'\nimport type { KickPlugin } from './plugin'\n\n/**\n * Options passed to {@link definePlugin}. Mirrors the spec in\n * `architecture.md` §21.3.1 + §21.2.2 (Phase A \"lite\").\n */\nexport interface DefinePluginOptions<TConfig> {\n /** Stable identity used for logging, `dependsOn` lookups, and `.scoped()` namespacing. */\n name: string\n\n /** Optional plugin version — surfaced to DevTools and `kick add` compatibility checks (Phase B). */\n version?: string\n\n /**\n * Peer-version ranges this plugin needs from the framework or other\n * plugins. Recorded as metadata; runtime enforcement lands in §21.4.4.\n */\n requires?: { kickjs?: string }\n\n /** Default config merged under any caller overrides. */\n defaults?: Partial<TConfig>\n\n /** Builds the underlying {@link KickPlugin} from the resolved config. */\n build(config: TConfig, ctx: BuildContext): Omit<KickPlugin, 'name'>\n}\n\n/**\n * Hook context handed to a plugin/adapter `build` function. `name` is the\n * resolved instance name (the bare definition name for the bare call,\n * or `${defName}:${scope}` for `.scoped()`). `scoped` is the discriminator\n * authors use to namespace DI tokens or resource keys.\n */\nexport interface BuildContext {\n name: string\n scoped: boolean\n}\n\n/**\n * Async-config form of a plugin or adapter — `inject` lists DI tokens to\n * resolve from the container, then `useFactory` produces the config.\n *\n * Plugins resolve this lazily inside `onReady`, so any module/adapter/\n * middleware/contributor a plugin contributes via `build` is **not\n * available** when `.async()` is used. Use the bare/`.scoped()` form for\n * plugins that need to register modules or middleware.\n */\nexport interface PluginAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link definePlugin}. Callable form is the singleton\n * (`AuthPlugin(config)`); `.scoped()` and `.async()` are the multi-instance\n * and deferred-config forms.\n */\nexport interface PluginFactory<TConfig> {\n (config?: Partial<TConfig>): KickPlugin\n scoped(scopeName: string, config?: Partial<TConfig>): KickPlugin\n async(opts: PluginAsyncOptions<TConfig>): KickPlugin\n /** Read-only access to the original definition — useful for DevTools introspection. */\n readonly definition: Readonly<DefinePluginOptions<TConfig>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build a {@link KickPlugin} factory from a typed definition. See\n * `architecture.md` §21.2.2 for the design rationale.\n *\n * @example\n * ```ts\n * const FlagsPlugin = definePlugin<FlagsConfig>({\n * name: 'FlagsPlugin',\n * defaults: { defaultTtl: 60_000 },\n * build(config) {\n * return {\n * register(container) {\n * container.registerInstance(FLAGS, makeProvider(config))\n * },\n * }\n * },\n * })\n *\n * bootstrap({ plugins: [FlagsPlugin({ provider: launchDarkly })] })\n * ```\n */\nexport function definePlugin<TConfig = Record<string, unknown>>(\n options: DefinePluginOptions<TConfig>,\n): PluginFactory<TConfig> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): KickPlugin => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n const built = options.build(config, { name: instanceName, scoped }) as KickPlugin\n built.name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: PluginAsyncOptions<TConfig>): KickPlugin => {\n let inner: KickPlugin | undefined\n\n const ensureInner = async (container: Container): Promise<KickPlugin> => {\n if (inner) return inner\n const deps = (opts.inject ?? []).map((tok) => container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n return inner\n }\n\n return {\n name: instanceName,\n async onReady(container: Container) {\n const built = await ensureInner(container)\n // Run the inner plugin's register() now that we have the config —\n // late but better than never; `.async()` plugins skip the early\n // module/middleware contribution surface by design.\n built.register?.(container)\n await built.onReady?.(container)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as PluginFactory<TConfig>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: PluginAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","/**\n * The app as a request handler, with no listening server of its own — for\n * platforms that hand you requests instead of a port: Netlify Functions,\n * Vercel Functions, and any other serverless or embedding host.\n *\n * @module @forinda/kickjs/http/handler\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { Application, type ApplicationOptions } from './application'\nimport { forwardBackgroundWork, settleBackgroundWork, type PlatformContext } from './background'\n\n/** A KickJS app exposed as request handlers. See {@link createHandler}. */\nexport interface KickHandler {\n /**\n * Web-standard handler: `fetch(Request) → Response`. Netlify Functions,\n * Vercel's fetch export, and anything else that speaks `Request`.\n *\n * Pass the platform context (Netlify's `context`, `{ waitUntil }` from\n * `@vercel/functions`) so work handed to `ctx.waitUntil()` finishes after\n * the response instead of being frozen with the instance.\n */\n fetch(request: Request, platform?: PlatformContext): Promise<Response>\n /** Node handler: `(req, res)`. Vercel Node functions, `http.createServer`. */\n node(req: IncomingMessage, res: ServerResponse, platform?: PlatformContext): Promise<void>\n /** Set the app up now instead of on the first request (warm-up). */\n ready(): Promise<Application>\n /** Shut adapters down and stop the internal forwarding server, if one started. */\n close(): Promise<void>\n}\n\n/**\n * Build a KickJS app as request handlers instead of a listening server.\n *\n * Takes the same options as `bootstrap()`. The app is set up once per process\n * (per function instance), lazily on the first request, and reused after that.\n * Nothing listens on a port, `afterStart` does not run, and no process signal\n * handlers are registered — the platform owns the process.\n *\n * `fetch` uses the runtime's own fetch when it has one (the h3 v2 runtime).\n * Runtimes built on Node request/response objects — Express, the default —\n * are served through a server bound to `127.0.0.1` on a random port inside the\n * same process, started on first use: the `Request` is forwarded to it and its\n * response returned. Request bodies are buffered on that path; response bodies\n * stream. Adapters that rewrite the `res` prototype (Express does) make a\n * direct Request→`res` bridge unreliable, which is why this forwards instead.\n *\n * One handler per process: the DI container is process-wide.\n *\n * @example\n * ```ts\n * // server/src/serverless.ts\n * import 'reflect-metadata'\n * import './config'\n * import { createHandler } from '@forinda/kickjs'\n * import { modules } from './modules'\n *\n * export const handler = createHandler({ modules })\n *\n * // netlify/functions/api.mjs\n * export default (request, context) => handler.fetch(request, context)\n * export const config = { path: '/api/*' }\n * ```\n */\nexport function createHandler(options: ApplicationOptions): KickHandler {\n let app: Promise<Application> | undefined\n\n const ready = (): Promise<Application> => {\n app ??= (async () => {\n const instance = new Application(options)\n try {\n await instance.startWithoutServer()\n } catch (err) {\n // A failed setup (a database that was briefly down, say) is retried on\n // the next request. Shut this attempt down first so its adapters don't\n // stay live next to the retry's.\n app = undefined\n await instance.shutdown().catch(() => {})\n throw err\n }\n return instance\n })()\n return app\n }\n\n return {\n ready,\n\n async fetch(request, platform) {\n const response = await (await ready()).fetch(request)\n forwardBackgroundWork(platform)\n return response\n },\n\n async node(req, res, platform) {\n const instance = await ready()\n instance.handle(req, res)\n if (platform?.waitUntil) {\n // The response is written asynchronously: start settling only once it\n // has gone out, so work the handler registered along the way is included.\n const sent = new Promise<void>((resolve) => res.once('close', () => resolve()))\n platform.waitUntil(sent.then(settleBackgroundWork))\n }\n },\n\n async close() {\n const current = app\n app = undefined\n if (current) await (await current).shutdown()\n },\n }\n}\n","/**\n * HTTP-flavoured wrapper around {@link defineContextDecorator}.\n *\n * The core factory leaves `Ctx` defaulted to {@link ExecutionContext} so the\n * Context Contributor pipeline stays transport-agnostic — future WebSocket,\n * queue, and cron contexts share the same primitive. The cost is that the\n * vast majority of contributors (which are HTTP) have to spell the fourth\n * generic by hand to get `ctx.req` / `ctx.headers` typing:\n *\n * ```ts\n * // Verbose: spell every generic, including the params shape `P`.\n * defineContextDecorator<'tenant', Record<string, never>, Record<string, never>, RequestContext>({\n * key: 'tenant',\n * resolve: (ctx) => ({ id: ctx.req.headers['x-tenant-id'] as string }),\n * })\n * ```\n *\n * `defineHttpContextDecorator` removes that ceremony — the spec is typed\n * against `RequestContext` from the start, so resolvers can read `ctx.req`,\n * `ctx.headers`, `ctx.params`, etc. without a cast or generic dance:\n *\n * ```ts\n * import { defineHttpContextDecorator } from '@forinda/kickjs'\n *\n * const LoadTenant = defineHttpContextDecorator({\n * key: 'tenant',\n * resolve: (ctx) => ({ id: ctx.req.headers['x-tenant-id'] as string }),\n * })\n *\n * // Parameterised — the positional form forces you to also spell `K`\n * // and `D`, which loses automatic `deps` inference. Prefer the curried\n * // `.withParams<P>()` entry point below.\n * const LoadTenantParameterised = defineHttpContextDecorator<\n * 'tenant',\n * Record<string, never>,\n * { source: 'header' | 'subdomain' }\n * >({\n * key: 'tenant',\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, _deps, params) => {\n * // params.source narrows inside `if` branches.\n * },\n * })\n *\n * // Parameterised + deps — only `P` is spelled; `K` and `D` infer from\n * // the spec, so `deps.repo` is fully typed in the resolver.\n * const LoadTenantWithRepo = defineHttpContextDecorator.withParams<{\n * source: 'header' | 'subdomain'\n * }>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO },\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, { repo }, params) =>\n * repo.findFor(ctx.req.headers['x-tenant-id'] as string, params.source),\n * })\n * ```\n *\n * Use {@link defineContextDecorator} directly when authoring contributors\n * for non-HTTP transports or when you want to share one contributor across\n * HTTP + WS + queue (the resolver then has to stick to the\n * `ExecutionContext` surface — `ctx.get` / `ctx.set` / `ctx.requestId`).\n */\n\nimport {\n defineContextDecorator,\n type ContextDecorator,\n type ContextDecoratorSpec,\n type ContextMetaKey,\n type DepValue,\n} from '../core/context-decorator'\nimport type { RequestContext } from './context'\n\n/**\n * Curried form returned by {@link DefineHttpContextDecoratorFn.withParams}.\n * Spell only the per-call params shape `P`; `K` and `D` are inferred\n * from the spec, and `Ctx` is locked to `RequestContext`.\n */\nexport interface DefineHttpContextDecoratorWithParams<P extends Record<string, unknown>> {\n <K extends ContextMetaKey, D extends Record<string, DepValue> = Record<string, never>>(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext>\n}\n\n/**\n * Public shape of {@link defineHttpContextDecorator} — the positional\n * call signature plus the curried `.withParams<P>()` helper that fixes\n * partial-inference for the parameterised case.\n */\nexport interface DefineHttpContextDecoratorFn {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext>\n\n /**\n * Curried entry point. Spell only `P`; `K` and `D` infer from the\n * spec. `Ctx` is locked to `RequestContext`.\n *\n * ```ts\n * const LoadTenant = defineHttpContextDecorator.withParams<{\n * source: 'header' | 'subdomain'\n * }>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO }, // D inferred\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, { repo }, params) => repo.findFor(ctx, params),\n * })\n * ```\n */\n withParams<P extends Record<string, unknown>>(): DefineHttpContextDecoratorWithParams<P>\n}\n\nfunction defineHttpContextDecoratorImpl<\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n>(spec: ContextDecoratorSpec<K, D, P, RequestContext>): ContextDecorator<K, D, P, RequestContext> {\n return defineContextDecorator<K, D, P, RequestContext>(spec)\n}\n\nexport const defineHttpContextDecorator: DefineHttpContextDecoratorFn = Object.freeze(\n Object.assign(defineHttpContextDecoratorImpl, {\n withParams: <P extends Record<string, unknown>>(): DefineHttpContextDecoratorWithParams<P> => {\n return <K extends ContextMetaKey, D extends Record<string, DepValue> = Record<string, never>>(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext> => defineHttpContextDecoratorImpl<K, D, P>(spec)\n },\n }),\n) as DefineHttpContextDecoratorFn\n","import type { Request, Response, NextFunction } from 'express'\nimport { requestStore } from '../request-store'\n\nimport { randomHex } from '../../core/web-crypto'\n\n/**\n * W3C Trace Context header name.\n * @see https://www.w3.org/TR/trace-context/#traceparent-header\n */\nexport const TRACEPARENT_HEADER = 'traceparent'\n\n/** Regex for a valid W3C traceparent: {version}-{trace-id}-{parent-id}-{flags} */\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/\n\nexport interface TraceContextOptions {\n /**\n * When true, respond with a `traceresponse` header containing the trace ID.\n * Useful for debugging. Default: false.\n */\n propagateResponse?: boolean\n}\n\n/**\n * Parsed W3C trace context fields.\n */\nexport interface TraceContext {\n /** W3C traceparent version (usually '00') */\n version: string\n /** 32-hex-char distributed trace ID */\n traceId: string\n /** 16-hex-char parent span ID */\n parentSpanId: string\n /** 2-hex-char trace flags */\n flags: string\n}\n\n/**\n * Generate a random 32-hex-character trace ID.\n */\nfunction generateTraceId(): string {\n return randomHex(16)\n}\n\n/**\n * Generate a random 16-hex-character span ID.\n */\nfunction generateSpanId(): string {\n return randomHex(8)\n}\n\n/**\n * Parse a W3C traceparent header value.\n * Returns null if the format is invalid.\n */\nexport function parseTraceparent(header: string): TraceContext | null {\n const match = TRACEPARENT_RE.exec(header.trim().toLowerCase())\n if (!match) return null\n\n const version = match[1]\n const traceId = match[2]\n const parentSpanId = match[3]\n const flags = match[4]\n\n // W3C spec: version ff is invalid, all-zero trace-id and parent-id are invalid\n if (\n version === 'ff' ||\n traceId === '00000000000000000000000000000000' ||\n parentSpanId === '0000000000000000'\n ) {\n return null\n }\n\n return { version, traceId, parentSpanId, flags }\n}\n\n/**\n * Middleware that extracts W3C `traceparent` header and stores trace context\n * in the request's AsyncLocalStorage store.\n *\n * If no valid `traceparent` header is present, a new traceId is generated\n * so that every request always has a trace ID available for correlation.\n *\n * Must be mounted **after** `requestScopeMiddleware()` (which creates the store).\n *\n * @example\n * ```ts\n * bootstrap({\n * middlewares: [\n * requestScopeMiddleware(),\n * traceContext(), // extracts or generates traceId\n * requestLogger(),\n * express.json(),\n * ],\n * })\n * ```\n */\nexport function traceContext(options: TraceContextOptions = {}) {\n return (req: Request, res: Response, next: NextFunction) => {\n const store = requestStore.getStore()\n\n const traceparentHeader = req.headers[TRACEPARENT_HEADER]\n const raw = Array.isArray(traceparentHeader) ? traceparentHeader[0] : traceparentHeader\n const parsed = raw ? parseTraceparent(raw) : null\n\n const traceId = parsed?.traceId ?? generateTraceId()\n const spanId = generateSpanId()\n\n // Attach to the request store so logger + downstream code can access it\n if (store) {\n store.values.set('traceId', traceId)\n store.values.set('spanId', spanId)\n if (parsed) {\n store.values.set('parentSpanId', parsed.parentSpanId)\n store.values.set('traceFlags', parsed.flags)\n store.values.set('traceVersion', parsed.version)\n }\n }\n\n // Also expose on req for convenience\n ;(req as any).traceId = traceId\n ;(req as any).spanId = spanId\n\n if (options.propagateResponse) {\n res.setHeader('traceresponse', traceId)\n }\n\n next()\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core/define-modules.ts","../src/core/circuit-breaker.ts","../src/core/define-plugin.ts","../src/http/handler.ts","../src/http/define-http-context-decorator.ts","../src/http/middleware/trace-context.ts"],"sourcesContent":["import type { AppModuleEntry } from './app-module'\n\n/**\n * Chainable registry returned by {@link defineModules}. Extends\n * `Array<AppModuleEntry>` so it drops into `bootstrap({ modules })`\n * unchanged — the framework loader already iterates the array, so a\n * `ModuleList` IS a valid `modules` value with no special handling.\n *\n * The added affordance is `.mount(module)` returning `this` so\n * adopters can build the list fluently:\n *\n * ```ts\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * bootstrap({ modules })\n * ```\n *\n * **Why not just use a plain array?** Two reasons. First, the\n * fluent shape reads more naturally for module-list construction —\n * adopters used to `definePlugin().scoped(...)` / `defineAdapter(...)`\n * see the same call-then-call pattern. Second, the registry is the\n * stable extension point: a future `.use(module)` for non-HTTP\n * modules (queues, cron, workers) lands as a method on `ModuleList`\n * without breaking the `Array<AppModuleEntry>` superclass contract.\n */\nexport class ModuleList extends Array<AppModuleEntry> {\n /**\n * Append a module to the list. Returns `this` for chaining.\n *\n * Accepts both class form (`SomeModule extends AppModule`) and\n * `defineModule` factory output — same as the underlying\n * `bootstrap({ modules })` array. The framework loader\n * discriminates `typeof entry === 'function'` at boot.\n */\n mount(module: AppModuleEntry): this {\n this.push(module)\n return this\n }\n}\n\n/**\n * Build a {@link ModuleList} for the application. Optional vararg\n * lets adopters seed the list inline; subsequent `.mount()` calls\n * append.\n *\n * @example\n * ```ts\n * import { bootstrap, defineModules } from '@forinda/kickjs'\n * import { HelloModule } from './modules/hello/hello.module'\n * import { AdminModule } from './modules/admin/admin.module'\n *\n * // Fluent — most common\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * // Or seeded inline + chained\n * const modules2 = defineModules(HelloModule()).mount(AdminModule())\n *\n * await bootstrap({ modules })\n * ```\n *\n * The returned value is an `AppModuleEntry[]` subclass — `bootstrap`\n * accepts it directly without any special-casing. For conditional /\n * dynamic registration at bootstrap time (env flags, runtime config,\n * tenant lists), use the `setup(registry)` callback on `bootstrap`\n * instead — both surfaces compose.\n */\nexport function defineModules(...initial: AppModuleEntry[]): ModuleList {\n const list = new ModuleList()\n for (const entry of initial) list.push(entry)\n return list\n}\n","/**\n * Circuit Breaker pattern for external service calls.\n *\n * Protects your application from cascading failures when downstream\n * services are unhealthy by short-circuiting requests after a\n * configurable failure threshold.\n *\n * @example\n * ```ts\n * const breaker = new CircuitBreaker('payment-api', {\n * failureThreshold: 5,\n * resetTimeout: 30_000,\n * })\n *\n * const result = await breaker.execute(() => fetch('https://payment.example.com/charge'))\n * ```\n */\n\nexport type CircuitBreakerState = 'closed' | 'open' | 'half_open'\n\nexport interface CircuitBreakerOptions {\n /** Number of consecutive failures before the circuit opens. */\n failureThreshold: number\n /** Milliseconds to wait before transitioning from OPEN to HALF_OPEN. */\n resetTimeout: number\n /** Max requests allowed in HALF_OPEN state before deciding (default 1). */\n halfOpenMax?: number\n}\n\nexport interface CircuitBreakerStats {\n failures: number\n successes: number\n state: CircuitBreakerState\n lastFailure?: Date\n}\n\n/**\n * Error thrown when the circuit is open and calls are being rejected.\n */\nexport class CircuitOpenError extends Error {\n constructor(public readonly breakerName: string) {\n super(`Circuit breaker \"${breakerName}\" is open — request rejected`)\n this.name = 'CircuitOpenError'\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitBreakerState = 'closed'\n private failures = 0\n private successes = 0\n private halfOpenAttempts = 0\n private lastFailure?: Date\n private openedAt?: number\n\n private readonly failureThreshold: number\n private readonly resetTimeout: number\n private readonly halfOpenMax: number\n\n constructor(\n public readonly name: string,\n options: CircuitBreakerOptions,\n ) {\n this.failureThreshold = options.failureThreshold\n this.resetTimeout = options.resetTimeout\n this.halfOpenMax = options.halfOpenMax ?? 1\n }\n\n /**\n * Execute an async function through the circuit breaker.\n * Throws `CircuitOpenError` if the circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n this.maybeTransitionToHalfOpen()\n\n if (this.state === 'open') {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open' && this.halfOpenAttempts >= this.halfOpenMax) {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open') {\n this.halfOpenAttempts++\n }\n\n try {\n const result = await fn()\n this.onSuccess()\n return result\n } catch (error) {\n this.onFailure()\n throw error\n }\n }\n\n /** Returns the current circuit state. */\n getState(): CircuitBreakerState {\n this.maybeTransitionToHalfOpen()\n return this.state\n }\n\n /** Returns current statistics. */\n getStats(): CircuitBreakerStats {\n this.maybeTransitionToHalfOpen()\n return {\n failures: this.failures,\n successes: this.successes,\n state: this.state,\n ...(this.lastFailure ? { lastFailure: this.lastFailure } : {}),\n }\n }\n\n /** Manually reset the circuit breaker to CLOSED state. */\n reset(): void {\n this.state = 'closed'\n this.failures = 0\n this.successes = 0\n this.halfOpenAttempts = 0\n this.lastFailure = undefined\n this.openedAt = undefined\n }\n\n private onSuccess(): void {\n if (this.state === 'half_open') {\n // Recovery confirmed — close the circuit\n this.state = 'closed'\n this.failures = 0\n this.halfOpenAttempts = 0\n this.openedAt = undefined\n }\n this.successes++\n }\n\n private onFailure(): void {\n this.failures++\n this.lastFailure = new Date()\n\n if (this.state === 'half_open') {\n // Recovery failed — re-open\n this.state = 'open'\n this.openedAt = Date.now()\n this.halfOpenAttempts = 0\n } else if (this.state === 'closed' && this.failures >= this.failureThreshold) {\n this.state = 'open'\n this.openedAt = Date.now()\n }\n }\n\n private maybeTransitionToHalfOpen(): void {\n if (this.state === 'open' && this.openedAt != null) {\n if (Date.now() - this.openedAt >= this.resetTimeout) {\n this.state = 'half_open'\n this.halfOpenAttempts = 0\n }\n }\n }\n}\n","import type { Container } from './container'\nimport type { KickPlugin } from './plugin'\n\n/**\n * Options passed to {@link definePlugin}. Mirrors the spec in\n * `architecture.md` §21.3.1 + §21.2.2 (Phase A \"lite\").\n */\nexport interface DefinePluginOptions<TConfig> {\n /** Stable identity used for logging, `dependsOn` lookups, and `.scoped()` namespacing. */\n name: string\n\n /** Optional plugin version — surfaced to DevTools and `kick add` compatibility checks (Phase B). */\n version?: string\n\n /**\n * Peer-version ranges this plugin needs from the framework or other\n * plugins. Recorded as metadata; runtime enforcement lands in §21.4.4.\n */\n requires?: { kickjs?: string }\n\n /** Default config merged under any caller overrides. */\n defaults?: Partial<TConfig>\n\n /** Builds the underlying {@link KickPlugin} from the resolved config. */\n build(config: TConfig, ctx: BuildContext): Omit<KickPlugin, 'name'>\n}\n\n/**\n * Hook context handed to a plugin/adapter `build` function. `name` is the\n * resolved instance name (the bare definition name for the bare call,\n * or `${defName}:${scope}` for `.scoped()`). `scoped` is the discriminator\n * authors use to namespace DI tokens or resource keys.\n */\nexport interface BuildContext {\n name: string\n scoped: boolean\n}\n\n/**\n * Async-config form of a plugin or adapter — `inject` lists DI tokens to\n * resolve from the container, then `useFactory` produces the config.\n *\n * Plugins resolve this lazily inside `onReady`, so any module/adapter/\n * middleware/contributor a plugin contributes via `build` is **not\n * available** when `.async()` is used. Use the bare/`.scoped()` form for\n * plugins that need to register modules or middleware.\n */\nexport interface PluginAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link definePlugin}. Callable form is the singleton\n * (`AuthPlugin(config)`); `.scoped()` and `.async()` are the multi-instance\n * and deferred-config forms.\n */\nexport interface PluginFactory<TConfig> {\n (config?: Partial<TConfig>): KickPlugin\n scoped(scopeName: string, config?: Partial<TConfig>): KickPlugin\n async(opts: PluginAsyncOptions<TConfig>): KickPlugin\n /** Read-only access to the original definition — useful for DevTools introspection. */\n readonly definition: Readonly<DefinePluginOptions<TConfig>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build a {@link KickPlugin} factory from a typed definition. See\n * `architecture.md` §21.2.2 for the design rationale.\n *\n * @example\n * ```ts\n * const FlagsPlugin = definePlugin<FlagsConfig>({\n * name: 'FlagsPlugin',\n * defaults: { defaultTtl: 60_000 },\n * build(config) {\n * return {\n * register(container) {\n * container.registerInstance(FLAGS, makeProvider(config))\n * },\n * }\n * },\n * })\n *\n * bootstrap({ plugins: [FlagsPlugin({ provider: launchDarkly })] })\n * ```\n */\nexport function definePlugin<TConfig = Record<string, unknown>>(\n options: DefinePluginOptions<TConfig>,\n): PluginFactory<TConfig> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): KickPlugin => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n const built = options.build(config, { name: instanceName, scoped }) as KickPlugin\n built.name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: PluginAsyncOptions<TConfig>): KickPlugin => {\n let inner: KickPlugin | undefined\n\n const ensureInner = async (container: Container): Promise<KickPlugin> => {\n if (inner) return inner\n const deps = (opts.inject ?? []).map((tok) => container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n return inner\n }\n\n return {\n name: instanceName,\n async onReady(container: Container) {\n const built = await ensureInner(container)\n // Run the inner plugin's register() now that we have the config —\n // late but better than never; `.async()` plugins skip the early\n // module/middleware contribution surface by design.\n built.register?.(container)\n await built.onReady?.(container)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as PluginFactory<TConfig>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: PluginAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","/**\n * The app as a request handler, with no listening server of its own — for\n * platforms that hand you requests instead of a port: Netlify Functions,\n * Vercel Functions, and any other serverless or embedding host.\n *\n * @module @forinda/kickjs/http/handler\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { Application, type ApplicationOptions } from './application'\nimport { forwardBackgroundWork, settleBackgroundWork, type PlatformContext } from './background'\n\n/** A KickJS app exposed as request handlers. See {@link createHandler}. */\nexport interface KickHandler {\n /**\n * Web-standard handler: `fetch(Request) → Response`. Netlify Functions,\n * Vercel's fetch export, and anything else that speaks `Request`.\n *\n * Pass the platform context (Netlify's `context`, `{ waitUntil }` from\n * `@vercel/functions`) so work handed to `ctx.waitUntil()` finishes after\n * the response instead of being frozen with the instance.\n */\n fetch(request: Request, platform?: PlatformContext): Promise<Response>\n /** Node handler: `(req, res)`. Vercel Node functions, `http.createServer`. */\n node(req: IncomingMessage, res: ServerResponse, platform?: PlatformContext): Promise<void>\n /** Set the app up now instead of on the first request (warm-up). */\n ready(): Promise<Application>\n /** Shut adapters down and stop the internal forwarding server, if one started. */\n close(): Promise<void>\n}\n\n/**\n * Build a KickJS app as request handlers instead of a listening server.\n *\n * Takes the same options as `bootstrap()`. The app is set up once per process\n * (per function instance), lazily on the first request, and reused after that.\n * Nothing listens on a port, `afterStart` does not run, and no process signal\n * handlers are registered — the platform owns the process.\n *\n * `fetch` uses the runtime's own fetch when it has one (the h3 v2 runtime).\n * Runtimes built on Node request/response objects — Express, the default —\n * are served through a server bound to `127.0.0.1` on a random port inside the\n * same process, started on first use: the `Request` is forwarded to it and its\n * response returned. Request bodies are buffered on that path; response bodies\n * stream. Adapters that rewrite the `res` prototype (Express does) make a\n * direct Request→`res` bridge unreliable, which is why this forwards instead.\n *\n * One handler per process: the DI container is process-wide.\n *\n * @example\n * ```ts\n * // server/src/serverless.ts\n * import 'reflect-metadata'\n * import './config'\n * import { createHandler } from '@forinda/kickjs'\n * import { modules } from './modules'\n *\n * export const handler = createHandler({ modules })\n *\n * // netlify/functions/api.mjs\n * export default (request, context) => handler.fetch(request, context)\n * export const config = { path: '/api/*' }\n * ```\n */\nexport function createHandler(options: ApplicationOptions): KickHandler {\n let app: Promise<Application> | undefined\n\n const ready = (): Promise<Application> => {\n app ??= (async () => {\n const instance = new Application(options)\n try {\n await instance.startWithoutServer()\n } catch (err) {\n // A failed setup (a database that was briefly down, say) is retried on\n // the next request. Shut this attempt down first so its adapters don't\n // stay live next to the retry's.\n app = undefined\n await instance.shutdown().catch(() => {})\n throw err\n }\n return instance\n })()\n return app\n }\n\n return {\n ready,\n\n async fetch(request, platform) {\n const response = await (await ready()).fetch(request)\n forwardBackgroundWork(platform)\n return response\n },\n\n async node(req, res, platform) {\n const instance = await ready()\n instance.handle(req, res)\n if (platform?.waitUntil) {\n // The response is written asynchronously: start settling only once it\n // has gone out, so work the handler registered along the way is included.\n const sent = new Promise<void>((resolve) => res.once('close', () => resolve()))\n platform.waitUntil(sent.then(settleBackgroundWork))\n }\n },\n\n async close() {\n const current = app\n app = undefined\n if (current) await (await current).shutdown()\n },\n }\n}\n","/**\n * HTTP-flavoured wrapper around {@link defineContextDecorator}.\n *\n * The core factory leaves `Ctx` defaulted to {@link ExecutionContext} so the\n * Context Contributor pipeline stays transport-agnostic — future WebSocket,\n * queue, and cron contexts share the same primitive. The cost is that the\n * vast majority of contributors (which are HTTP) have to spell the fourth\n * generic by hand to get `ctx.req` / `ctx.headers` typing:\n *\n * ```ts\n * // Verbose: spell every generic, including the params shape `P`.\n * defineContextDecorator<'tenant', Record<string, never>, Record<string, never>, RequestContext>({\n * key: 'tenant',\n * resolve: (ctx) => ({ id: ctx.req.headers['x-tenant-id'] as string }),\n * })\n * ```\n *\n * `defineHttpContextDecorator` removes that ceremony — the spec is typed\n * against `RequestContext` from the start, so resolvers can read `ctx.req`,\n * `ctx.headers`, `ctx.params`, etc. without a cast or generic dance:\n *\n * ```ts\n * import { defineHttpContextDecorator } from '@forinda/kickjs'\n *\n * const LoadTenant = defineHttpContextDecorator({\n * key: 'tenant',\n * resolve: (ctx) => ({ id: ctx.req.headers['x-tenant-id'] as string }),\n * })\n *\n * // Parameterised — the positional form forces you to also spell `K`\n * // and `D`, which loses automatic `deps` inference. Prefer the curried\n * // `.withParams<P>()` entry point below.\n * const LoadTenantParameterised = defineHttpContextDecorator<\n * 'tenant',\n * Record<string, never>,\n * { source: 'header' | 'subdomain' }\n * >({\n * key: 'tenant',\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, _deps, params) => {\n * // params.source narrows inside `if` branches.\n * },\n * })\n *\n * // Parameterised + deps — only `P` is spelled; `K` and `D` infer from\n * // the spec, so `deps.repo` is fully typed in the resolver.\n * const LoadTenantWithRepo = defineHttpContextDecorator.withParams<{\n * source: 'header' | 'subdomain'\n * }>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO },\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, { repo }, params) =>\n * repo.findFor(ctx.req.headers['x-tenant-id'] as string, params.source),\n * })\n * ```\n *\n * Use {@link defineContextDecorator} directly when authoring contributors\n * for non-HTTP transports or when you want to share one contributor across\n * HTTP + WS + queue (the resolver then has to stick to the\n * `ExecutionContext` surface — `ctx.get` / `ctx.set` / `ctx.requestId`).\n */\n\nimport {\n defineContextDecorator,\n type ContextDecorator,\n type ContextDecoratorSpec,\n type ContextMetaKey,\n type DepValue,\n} from '../core/context-decorator'\nimport type { RequestContext } from './context'\n\n/**\n * Curried form returned by {@link DefineHttpContextDecoratorFn.withParams}.\n * Spell only the per-call params shape `P`; `K` and `D` are inferred\n * from the spec, and `Ctx` is locked to `RequestContext`.\n */\nexport interface DefineHttpContextDecoratorWithParams<P extends Record<string, unknown>> {\n <K extends ContextMetaKey, D extends Record<string, DepValue> = Record<string, never>>(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext>\n}\n\n/**\n * Public shape of {@link defineHttpContextDecorator} — the positional\n * call signature plus the curried `.withParams<P>()` helper that fixes\n * partial-inference for the parameterised case.\n */\nexport interface DefineHttpContextDecoratorFn {\n <\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n >(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext>\n\n /**\n * Curried entry point. Spell only `P`; `K` and `D` infer from the\n * spec. `Ctx` is locked to `RequestContext`.\n *\n * ```ts\n * const LoadTenant = defineHttpContextDecorator.withParams<{\n * source: 'header' | 'subdomain'\n * }>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO }, // D inferred\n * paramDefaults: { source: 'header' },\n * resolve: (ctx, { repo }, params) => repo.findFor(ctx, params),\n * })\n * ```\n */\n withParams<P extends Record<string, unknown>>(): DefineHttpContextDecoratorWithParams<P>\n}\n\nfunction defineHttpContextDecoratorImpl<\n K extends ContextMetaKey,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n>(spec: ContextDecoratorSpec<K, D, P, RequestContext>): ContextDecorator<K, D, P, RequestContext> {\n return defineContextDecorator<K, D, P, RequestContext>(spec)\n}\n\nexport const defineHttpContextDecorator: DefineHttpContextDecoratorFn = Object.freeze(\n Object.assign(defineHttpContextDecoratorImpl, {\n withParams: <P extends Record<string, unknown>>(): DefineHttpContextDecoratorWithParams<P> => {\n return <K extends ContextMetaKey, D extends Record<string, DepValue> = Record<string, never>>(\n spec: ContextDecoratorSpec<K, D, P, RequestContext>,\n ): ContextDecorator<K, D, P, RequestContext> => defineHttpContextDecoratorImpl<K, D, P>(spec)\n },\n }),\n) as DefineHttpContextDecoratorFn\n","import type { Request, Response, NextFunction } from 'express'\nimport { requestStore } from '../request-store'\n\nimport { randomHex } from '../../core/web-crypto'\n\n/**\n * W3C Trace Context header name.\n * @see https://www.w3.org/TR/trace-context/#traceparent-header\n */\nexport const TRACEPARENT_HEADER = 'traceparent'\n\n/** Regex for a valid W3C traceparent: {version}-{trace-id}-{parent-id}-{flags} */\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/\n\nexport interface TraceContextOptions {\n /**\n * When true, respond with a `traceresponse` header containing the trace ID.\n * Useful for debugging. Default: false.\n */\n propagateResponse?: boolean\n}\n\n/**\n * Parsed W3C trace context fields.\n */\nexport interface TraceContext {\n /** W3C traceparent version (usually '00') */\n version: string\n /** 32-hex-char distributed trace ID */\n traceId: string\n /** 16-hex-char parent span ID */\n parentSpanId: string\n /** 2-hex-char trace flags */\n flags: string\n}\n\n/**\n * Generate a random 32-hex-character trace ID.\n */\nfunction generateTraceId(): string {\n return randomHex(16)\n}\n\n/**\n * Generate a random 16-hex-character span ID.\n */\nfunction generateSpanId(): string {\n return randomHex(8)\n}\n\n/**\n * Parse a W3C traceparent header value.\n * Returns null if the format is invalid.\n */\nexport function parseTraceparent(header: string): TraceContext | null {\n const match = TRACEPARENT_RE.exec(header.trim().toLowerCase())\n if (!match) return null\n\n const version = match[1]\n const traceId = match[2]\n const parentSpanId = match[3]\n const flags = match[4]\n\n // W3C spec: version ff is invalid, all-zero trace-id and parent-id are invalid\n if (\n version === 'ff' ||\n traceId === '00000000000000000000000000000000' ||\n parentSpanId === '0000000000000000'\n ) {\n return null\n }\n\n return { version, traceId, parentSpanId, flags }\n}\n\n/**\n * Middleware that extracts W3C `traceparent` header and stores trace context\n * in the request's AsyncLocalStorage store.\n *\n * If no valid `traceparent` header is present, a new traceId is generated\n * so that every request always has a trace ID available for correlation.\n *\n * Must be mounted **after** `requestScopeMiddleware()` (which creates the store).\n *\n * @example\n * ```ts\n * bootstrap({\n * middlewares: [\n * requestScopeMiddleware(),\n * traceContext(), // extracts or generates traceId\n * requestLogger(),\n * express.json(),\n * ],\n * })\n * ```\n */\nexport function traceContext(options: TraceContextOptions = {}) {\n return (req: Request, res: Response, next: NextFunction) => {\n const store = requestStore.getStore()\n\n const traceparentHeader = req.headers[TRACEPARENT_HEADER]\n const raw = Array.isArray(traceparentHeader) ? traceparentHeader[0] : traceparentHeader\n const parsed = raw ? parseTraceparent(raw) : null\n\n const traceId = parsed?.traceId ?? generateTraceId()\n const spanId = generateSpanId()\n\n // Attach to the request store so logger + downstream code can access it\n if (store) {\n store.values.set('traceId', traceId)\n store.values.set('spanId', spanId)\n if (parsed) {\n store.values.set('parentSpanId', parsed.parentSpanId)\n store.values.set('traceFlags', parsed.flags)\n store.values.set('traceVersion', parsed.version)\n }\n }\n\n // Also expose on req for convenience\n ;(req as any).traceId = traceId\n ;(req as any).spanId = spanId\n\n if (options.propagateResponse) {\n res.setHeader('traceresponse', traceId)\n }\n\n next()\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v9.0
|
|
2
|
+
* @forinda/kickjs v9.2.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 createToken}from"./container-
|
|
12
|
-
//# sourceMappingURL=jobs-
|
|
11
|
+
import{i as createToken}from"./container-CAPN7pBG.mjs";import{createLogger}from"./core/logger.mjs";import{c as pushClassMeta,n as getClassMetaOrUndefined,t as getClassMeta,u as setClassMeta}from"./metadata-CzzRUnAx.mjs";import{Service}from"./core/decorators.mjs";import{r as reportError}from"./observers-COlQfinD.mjs";const log=createLogger(`KickJobs`),JOB_META={JOB:`kick/queue/job`,PROCESS:`kick/queue/process`},jobClasses=new Map;function Job(queue){return target=>{Service()(target),setClassMeta(JOB_META.JOB,queue,target),jobClasses.set(target.name,target)}}function Process(jobName){return(target,propertyKey)=>{pushClassMeta(JOB_META.PROCESS,target.constructor,{handlerName:propertyKey,jobName})}}function listJobHandlers(container){let handlers=[];for(let target of jobClasses.values()){if(!container.has(target))continue;let queue=getClassMetaOrUndefined(JOB_META.JOB,target);if(queue===void 0)continue;let processes=getClassMeta(JOB_META.PROCESS,target,[]);processes.length===0&&log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`);for(let process of processes)handlers.push({...process,queue,target})}return handlers}function listJobQueues(container){return[...new Set(listJobHandlers(container).map(h=>h.queue))]}var NoJobHandlerError=class extends Error{queue;jobName;constructor(queue,jobName){super(`No @Process handler for job "${jobName}" on queue "${queue}"`),this.queue=queue,this.jobName=jobName,this.name=`NoJobHandlerError`}};async function runJob(container,queue,job,report={}){let handlers=listJobHandlers(container).filter(h=>h.queue===queue),handler=handlers.find(h=>h.jobName===job.name)??handlers.find(h=>h.jobName===void 0);try{if(!handler)throw new NoJobHandlerError(queue,job.name);let instance=container.resolve(handler.target);return await withJobContext(job,()=>instance[handler.handlerName](job))()}catch(err){throw log.error({err},`Job ${queue}/${job.name} failed`),reportError(err,{source:`job`,context:{queue,job:job.name,id:job.id,...report}}),err}}const JOB_CONTEXT_FIELD=`__kickContext`,jobContextCarriers=new Map;function registerJobContext(carrier){jobContextCarriers.set(carrier.key,carrier)}const isPlainObject=v=>!!v&&typeof v==`object`&&Object.getPrototypeOf(v)===Object.prototype;function stampJobContext(data){if(!isPlainObject(data))return data;let{[JOB_CONTEXT_FIELD]:_untrusted,...rest}=data,context={};for(let[key,carrier]of jobContextCarriers){let value=carrier.capture();value!==void 0&&(context[key]=value)}return Object.keys(context).length>0?{...rest,[JOB_CONTEXT_FIELD]:context}:rest}function withJobContext(job,run){let data=job.data;if(!isPlainObject(data)||!(`__kickContext`in data))return run;let context=data[JOB_CONTEXT_FIELD];if(delete data[JOB_CONTEXT_FIELD],!isPlainObject(context))return run;let wrapped=run;for(let[key,value]of Object.entries(context)){let carrier=jobContextCarriers.get(key);if(!carrier)continue;let inner=wrapped;wrapped=()=>carrier.restore(value,inner)}return wrapped}const JOB_DISPATCHER=createToken(`kick/jobs/Dispatcher`);export{NoJobHandlerError as a,listJobQueues as c,stampJobContext as d,Job as i,registerJobContext as l,JOB_DISPATCHER as n,Process as o,JOB_META as r,listJobHandlers as s,JOB_CONTEXT_FIELD as t,runJob as u};
|
|
12
|
+
//# sourceMappingURL=jobs-BmpeFvi0.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jobs-CeP4aLYR.mjs","names":[],"sources":["../src/core/jobs.ts"],"sourcesContent":["/**\n * Background jobs, with whatever runs them.\n *\n * `@Job(queue)` / `@Process(name)` only record which method handles which\n * job — the same way `@Cron` only records a schedule. Any job tool turns that\n * into running code in a few lines: subscribe to each queue from\n * {@link listJobQueues}, and hand every job it receives to {@link runJob}.\n *\n * @Job('email')\n * export class EmailJobs {\n * @Process('welcome')\n * async welcome(job: JobLike<{ to: string }>) { await sendWelcome(job.data.to) }\n * }\n *\n * Enqueueing goes through {@link JOB_DISPATCHER}, which the active adapter\n * provides — code that dispatches never names the tool.\n *\n * `@forinda/kickjs-queue` ships a BullMQ adapter (plus RabbitMQ / Kafka /\n * Redis pub-sub providers); the web entry's `queue()` runs Cloudflare Queues.\n */\nimport { Service } from './decorators'\nimport { getClassMeta, getClassMetaOrUndefined, pushClassMeta, setClassMeta } from './metadata'\nimport { reportError } from './observers'\nimport { createLogger } from './logger'\nimport { createToken } from './token'\n\nconst log = createLogger('KickJobs')\n\n/** Metadata keys — the same ones `@forinda/kickjs-queue` used, so existing readers keep working. */\nexport const JOB_META = {\n JOB: 'kick/queue/job',\n PROCESS: 'kick/queue/process',\n} as const\n\n/** What a `@Process` method records. */\nexport interface ProcessDefinition {\n /** Method name on the class. */\n handlerName: string\n /** Job name it handles; `undefined` handles every job in the queue. */\n jobName?: string\n}\n\n/**\n * The least a job tool hands over. Runners pass their own job object through\n * unchanged — BullMQ's `Job`, a Cloudflare message — so a handler can still\n * reach tool-specific fields (`job.attemptsMade`, `job.updateProgress()`).\n */\nexport interface JobLike<Data = unknown> {\n name: string\n data: Data\n id?: string\n}\n\n/**\n * Every `@Job` class, keyed by class name like the DI registry: an HMR\n * re-evaluation replaces the old class instead of leaving both subscribed.\n */\nconst jobClasses = new Map<string, Function>()\n\n/** Mark a class as handling jobs on `queue`. The class is a DI singleton. */\nexport function Job(queue: string): ClassDecorator {\n return (target) => {\n Service()(target)\n setClassMeta(JOB_META.JOB, queue, target)\n jobClasses.set(target.name, target)\n }\n}\n\n/** Mark a method as handling `jobName` — or, without a name, every job its queue gets. */\nexport function Process(jobName?: string): MethodDecorator {\n return (target, propertyKey) => {\n pushClassMeta<ProcessDefinition>(JOB_META.PROCESS, target.constructor, {\n handlerName: propertyKey as string,\n jobName,\n })\n }\n}\n\n/** One handler: its queue, job name (absent for a catch-all), class and method. */\nexport interface JobHandler extends ProcessDefinition {\n queue: string\n target: Function\n}\n\n/**\n * Every `@Process` method of a `@Job` class the container can resolve.\n * Classes it doesn't know (not in a module, or from before an HMR reload)\n * are skipped.\n */\nexport function listJobHandlers(container: { has(token: unknown): boolean }): JobHandler[] {\n const handlers: JobHandler[] = []\n for (const target of jobClasses.values()) {\n if (!container.has(target)) continue\n const queue = getClassMetaOrUndefined<string>(JOB_META.JOB, target)\n if (queue === undefined) continue\n const processes = getClassMeta<ProcessDefinition[]>(JOB_META.PROCESS, target, [])\n if (processes.length === 0) {\n log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`)\n }\n for (const process of processes) handlers.push({ ...process, queue, target })\n }\n return handlers\n}\n\n/** The queues something handles — what a runner subscribes to. */\nexport function listJobQueues(container: { has(token: unknown): boolean }): string[] {\n return [...new Set(listJobHandlers(container).map((h) => h.queue))]\n}\n\n/** Thrown when a queue receives a job no `@Process` method handles. */\nexport class NoJobHandlerError extends Error {\n constructor(\n readonly queue: string,\n readonly jobName: string,\n ) {\n super(`No @Process handler for job \"${jobName}\" on queue \"${queue}\"`)\n this.name = 'NoJobHandlerError'\n }\n}\n\n/**\n * Run one job: pick the `@Process` method for `job.name` (or the queue's\n * catch-all), resolve its class from the container and call it with `job`\n * as given. A failure is reported to the error observers (`source: 'job'`)\n * and rethrown, so the tool's own retry and dead-letter handling still\n * applies. A job nothing handles throws {@link NoJobHandlerError} rather\n * than being acknowledged and lost.\n */\nexport async function runJob(\n container: { has(token: unknown): boolean; resolve(token: unknown): unknown },\n queue: string,\n job: JobLike,\n /** Extra fields for the error report — e.g. the tool's attempt count. */\n report: Record<string, unknown> = {},\n): Promise<unknown> {\n const handlers = listJobHandlers(container).filter((h) => h.queue === queue)\n const handler =\n handlers.find((h) => h.jobName === job.name) ?? handlers.find((h) => h.jobName === undefined)\n try {\n if (!handler) throw new NoJobHandlerError(queue, job.name)\n const instance = container.resolve(handler.target) as Record<string, (j: JobLike) => unknown>\n return await withJobContext(job, () => instance[handler.handlerName]!(job))()\n } catch (err) {\n log.error({ err }, `Job ${queue}/${job.name} failed`)\n reportError(err, { source: 'job', context: { queue, job: job.name, id: job.id, ...report } })\n throw err\n }\n}\n\n/**\n * Something about where a job was dispatched from that its handler should\n * run with — the tenant, a trace id, the acting user. Captured when the job\n * is dispatched, carried in its data, and restored around the handler.\n */\nexport interface JobContextCarrier<T = unknown> {\n /** Unique: the field it travels under. */\n key: string\n /** The value to carry, or `undefined` for nothing. */\n capture(): T | undefined\n /** Run the handler with the value back in place. */\n restore<R>(value: T, run: () => R): R\n}\n\n/** The field a job's data carries its context in. Removed before the handler sees the data. */\nexport const JOB_CONTEXT_FIELD = '__kickContext'\n\nconst jobContextCarriers = new Map<string, JobContextCarrier>()\n\n/** Carry a value from dispatch to handler for every job. Registering a key again replaces it. */\nexport function registerJobContext<T>(carrier: JobContextCarrier<T>): void {\n jobContextCarriers.set(carrier.key, carrier as JobContextCarrier)\n}\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype\n\n/**\n * `data` with the current job context added — for a {@link JobDispatcher}\n * implementation to call before enqueueing. Any `__kickContext` already in\n * `data` is removed first: only this call may set it, so a dispatcher that\n * enqueues caller-supplied data without it lets that data choose the context. Only plain-object data can carry\n * it; anything else, or nothing to carry, is returned as is.\n */\nexport function stampJobContext<Data>(data: Data): Data {\n if (!isPlainObject(data)) return data\n // Only the dispatcher's own context travels. A caller's data (a request\n // body, say) carrying the field would otherwise pick the job's tenant.\n const { [JOB_CONTEXT_FIELD]: _untrusted, ...rest } = data\n const context: Record<string, unknown> = {}\n for (const [key, carrier] of jobContextCarriers) {\n const value = carrier.capture()\n if (value !== undefined) context[key] = value\n }\n const stamped = Object.keys(context).length > 0 ? { ...rest, [JOB_CONTEXT_FIELD]: context } : rest\n return stamped as Data\n}\n\n/** Take the carried context off the job's data, and wrap `run` in every restore. */\nfunction withJobContext<R>(job: JobLike, run: () => R): () => R {\n const data = job.data\n if (!isPlainObject(data) || !(JOB_CONTEXT_FIELD in data)) return run\n const context = data[JOB_CONTEXT_FIELD]\n delete data[JOB_CONTEXT_FIELD]\n // Only an object of carrier keys is context; anything else is dropped.\n if (!isPlainObject(context)) return run\n let wrapped = run\n for (const [key, value] of Object.entries(context)) {\n const carrier = jobContextCarriers.get(key)\n if (!carrier) continue\n const inner = wrapped\n wrapped = () => carrier.restore(value, inner)\n }\n return wrapped\n}\n\n/** Enqueue a job — implemented by whichever adapter runs the jobs. */\nexport interface JobDispatcher {\n dispatch<Data = unknown>(\n queue: string,\n name: string,\n data: Data,\n options?: Record<string, unknown>,\n ): Promise<unknown>\n}\n\n/**\n * The active job dispatcher. Inject it to enqueue without naming the tool:\n * `@Inject(JOB_DISPATCHER) private jobs!: JobDispatcher`.\n */\nexport const JOB_DISPATCHER = createToken<JobDispatcher>('kick/jobs/Dispatcher')\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"jobs-BmpeFvi0.mjs","names":[],"sources":["../src/core/jobs.ts"],"sourcesContent":["/**\n * Background jobs, with whatever runs them.\n *\n * `@Job(queue)` / `@Process(name)` only record which method handles which\n * job — the same way `@Cron` only records a schedule. Any job tool turns that\n * into running code in a few lines: subscribe to each queue from\n * {@link listJobQueues}, and hand every job it receives to {@link runJob}.\n *\n * @Job('email')\n * export class EmailJobs {\n * @Process('welcome')\n * async welcome(job: JobLike<{ to: string }>) { await sendWelcome(job.data.to) }\n * }\n *\n * Enqueueing goes through {@link JOB_DISPATCHER}, which the active adapter\n * provides — code that dispatches never names the tool.\n *\n * `@forinda/kickjs-queue` ships a BullMQ adapter (plus RabbitMQ / Kafka /\n * Redis pub-sub providers); the web entry's `queue()` runs Cloudflare Queues.\n */\nimport { Service } from './decorators'\nimport { getClassMeta, getClassMetaOrUndefined, pushClassMeta, setClassMeta } from './metadata'\nimport { reportError } from './observers'\nimport { createLogger } from './logger'\nimport { createToken } from './token'\n\nconst log = createLogger('KickJobs')\n\n/** Metadata keys — the same ones `@forinda/kickjs-queue` used, so existing readers keep working. */\nexport const JOB_META = {\n JOB: 'kick/queue/job',\n PROCESS: 'kick/queue/process',\n} as const\n\n/** What a `@Process` method records. */\nexport interface ProcessDefinition {\n /** Method name on the class. */\n handlerName: string\n /** Job name it handles; `undefined` handles every job in the queue. */\n jobName?: string\n}\n\n/**\n * The least a job tool hands over. Runners pass their own job object through\n * unchanged — BullMQ's `Job`, a Cloudflare message — so a handler can still\n * reach tool-specific fields (`job.attemptsMade`, `job.updateProgress()`).\n */\nexport interface JobLike<Data = unknown> {\n name: string\n data: Data\n id?: string\n}\n\n/**\n * Every `@Job` class, keyed by class name like the DI registry: an HMR\n * re-evaluation replaces the old class instead of leaving both subscribed.\n */\nconst jobClasses = new Map<string, Function>()\n\n/** Mark a class as handling jobs on `queue`. The class is a DI singleton. */\nexport function Job(queue: string): ClassDecorator {\n return (target) => {\n Service()(target)\n setClassMeta(JOB_META.JOB, queue, target)\n jobClasses.set(target.name, target)\n }\n}\n\n/** Mark a method as handling `jobName` — or, without a name, every job its queue gets. */\nexport function Process(jobName?: string): MethodDecorator {\n return (target, propertyKey) => {\n pushClassMeta<ProcessDefinition>(JOB_META.PROCESS, target.constructor, {\n handlerName: propertyKey as string,\n jobName,\n })\n }\n}\n\n/** One handler: its queue, job name (absent for a catch-all), class and method. */\nexport interface JobHandler extends ProcessDefinition {\n queue: string\n target: Function\n}\n\n/**\n * Every `@Process` method of a `@Job` class the container can resolve.\n * Classes it doesn't know (not in a module, or from before an HMR reload)\n * are skipped.\n */\nexport function listJobHandlers(container: { has(token: unknown): boolean }): JobHandler[] {\n const handlers: JobHandler[] = []\n for (const target of jobClasses.values()) {\n if (!container.has(target)) continue\n const queue = getClassMetaOrUndefined<string>(JOB_META.JOB, target)\n if (queue === undefined) continue\n const processes = getClassMeta<ProcessDefinition[]>(JOB_META.PROCESS, target, [])\n if (processes.length === 0) {\n log.warn(`@Job('${queue}') class ${target.name} has no @Process methods`)\n }\n for (const process of processes) handlers.push({ ...process, queue, target })\n }\n return handlers\n}\n\n/** The queues something handles — what a runner subscribes to. */\nexport function listJobQueues(container: { has(token: unknown): boolean }): string[] {\n return [...new Set(listJobHandlers(container).map((h) => h.queue))]\n}\n\n/** Thrown when a queue receives a job no `@Process` method handles. */\nexport class NoJobHandlerError extends Error {\n constructor(\n readonly queue: string,\n readonly jobName: string,\n ) {\n super(`No @Process handler for job \"${jobName}\" on queue \"${queue}\"`)\n this.name = 'NoJobHandlerError'\n }\n}\n\n/**\n * Run one job: pick the `@Process` method for `job.name` (or the queue's\n * catch-all), resolve its class from the container and call it with `job`\n * as given. A failure is reported to the error observers (`source: 'job'`)\n * and rethrown, so the tool's own retry and dead-letter handling still\n * applies. A job nothing handles throws {@link NoJobHandlerError} rather\n * than being acknowledged and lost.\n */\nexport async function runJob(\n container: { has(token: unknown): boolean; resolve(token: unknown): unknown },\n queue: string,\n job: JobLike,\n /** Extra fields for the error report — e.g. the tool's attempt count. */\n report: Record<string, unknown> = {},\n): Promise<unknown> {\n const handlers = listJobHandlers(container).filter((h) => h.queue === queue)\n const handler =\n handlers.find((h) => h.jobName === job.name) ?? handlers.find((h) => h.jobName === undefined)\n try {\n if (!handler) throw new NoJobHandlerError(queue, job.name)\n const instance = container.resolve(handler.target) as Record<string, (j: JobLike) => unknown>\n return await withJobContext(job, () => instance[handler.handlerName]!(job))()\n } catch (err) {\n log.error({ err }, `Job ${queue}/${job.name} failed`)\n reportError(err, { source: 'job', context: { queue, job: job.name, id: job.id, ...report } })\n throw err\n }\n}\n\n/**\n * Something about where a job was dispatched from that its handler should\n * run with — the tenant, a trace id, the acting user. Captured when the job\n * is dispatched, carried in its data, and restored around the handler.\n */\nexport interface JobContextCarrier<T = unknown> {\n /** Unique: the field it travels under. */\n key: string\n /** The value to carry, or `undefined` for nothing. */\n capture(): T | undefined\n /** Run the handler with the value back in place. */\n restore<R>(value: T, run: () => R): R\n}\n\n/** The field a job's data carries its context in. Removed before the handler sees the data. */\nexport const JOB_CONTEXT_FIELD = '__kickContext'\n\nconst jobContextCarriers = new Map<string, JobContextCarrier>()\n\n/** Carry a value from dispatch to handler for every job. Registering a key again replaces it. */\nexport function registerJobContext<T>(carrier: JobContextCarrier<T>): void {\n jobContextCarriers.set(carrier.key, carrier as JobContextCarrier)\n}\n\nconst isPlainObject = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype\n\n/**\n * `data` with the current job context added — for a {@link JobDispatcher}\n * implementation to call before enqueueing. Any `__kickContext` already in\n * `data` is removed first: only this call may set it, so a dispatcher that\n * enqueues caller-supplied data without it lets that data choose the context. Only plain-object data can carry\n * it; anything else, or nothing to carry, is returned as is.\n */\nexport function stampJobContext<Data>(data: Data): Data {\n if (!isPlainObject(data)) return data\n // Only the dispatcher's own context travels. A caller's data (a request\n // body, say) carrying the field would otherwise pick the job's tenant.\n const { [JOB_CONTEXT_FIELD]: _untrusted, ...rest } = data\n const context: Record<string, unknown> = {}\n for (const [key, carrier] of jobContextCarriers) {\n const value = carrier.capture()\n if (value !== undefined) context[key] = value\n }\n const stamped = Object.keys(context).length > 0 ? { ...rest, [JOB_CONTEXT_FIELD]: context } : rest\n return stamped as Data\n}\n\n/** Take the carried context off the job's data, and wrap `run` in every restore. */\nfunction withJobContext<R>(job: JobLike, run: () => R): () => R {\n const data = job.data\n if (!isPlainObject(data) || !(JOB_CONTEXT_FIELD in data)) return run\n const context = data[JOB_CONTEXT_FIELD]\n delete data[JOB_CONTEXT_FIELD]\n // Only an object of carrier keys is context; anything else is dropped.\n if (!isPlainObject(context)) return run\n let wrapped = run\n for (const [key, value] of Object.entries(context)) {\n const carrier = jobContextCarriers.get(key)\n if (!carrier) continue\n const inner = wrapped\n wrapped = () => carrier.restore(value, inner)\n }\n return wrapped\n}\n\n/** Enqueue a job — implemented by whichever adapter runs the jobs. */\nexport interface JobDispatcher {\n dispatch<Data = unknown>(\n queue: string,\n name: string,\n data: Data,\n options?: Record<string, unknown>,\n ): Promise<unknown>\n}\n\n/**\n * The active job dispatcher. Inject it to enqueue without naming the tool:\n * `@Inject(JOB_DISPATCHER) private jobs!: JobDispatcher`.\n */\nexport const JOB_DISPATCHER = createToken<JobDispatcher>('kick/jobs/Dispatcher')\n"],"mappings":"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"}
|