@forinda/kickjs 5.18.0-alpha.0 → 5.18.0-alpha.1
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/{application-BU5N6o_M.mjs → application-DwTrT9cq.mjs} +3 -3
- package/dist/{application-BU5N6o_M.mjs.map → application-DwTrT9cq.mjs.map} +1 -1
- package/dist/{bootstrap-CFog4v_L.mjs → bootstrap-BaqYQe4i.mjs} +3 -3
- package/dist/{bootstrap-CFog4v_L.mjs.map → bootstrap-BaqYQe4i.mjs.map} +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-Di3HlRTS.mjs → config-okfM1SlN.mjs} +3 -3
- package/dist/{config-Di3HlRTS.mjs.map → config-okfM1SlN.mjs.map} +1 -1
- package/dist/{container-BiGimG12.mjs → container-B0BU5TY4.mjs} +3 -3
- package/dist/{container-BiGimG12.mjs.map → container-B0BU5TY4.mjs.map} +1 -1
- package/dist/{context-leZajQAj.mjs → context-DGsCYQRP.mjs} +3 -3
- package/dist/{context-leZajQAj.mjs.map → context-DGsCYQRP.mjs.map} +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.mjs +2 -2
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-Y_xSqZ7v.mjs → core-BV0AxVuK.mjs} +3 -3
- package/dist/{core-Y_xSqZ7v.mjs.map → core-BV0AxVuK.mjs.map} +1 -1
- package/dist/{env-BoMBrGWs.mjs → env-wT1GSzGW.mjs} +3 -3
- package/dist/{env-BoMBrGWs.mjs.map → env-wT1GSzGW.mjs.map} +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.mjs +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 +1 -1
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.mjs +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +2 -2
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +3 -3
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3.d.mts.map +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{metadata-Pvgc-K8a.mjs → metadata-B069pGq2.mjs} +2 -2
- package/dist/{metadata-Pvgc-K8a.mjs.map → metadata-B069pGq2.mjs.map} +1 -1
- package/dist/{query-B3PhfU6M.mjs → query-CimrAth7.mjs} +2 -2
- package/dist/{query-B3PhfU6M.mjs.map → query-CimrAth7.mjs.map} +1 -1
- package/dist/{request-scope-BaM8_e_8.mjs → request-scope-DiBal5Ln.mjs} +3 -3
- package/dist/{request-scope-BaM8_e_8.mjs.map → request-scope-DiBal5Ln.mjs.map} +1 -1
- package/package.json +7 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-BoMBrGWs.mjs","names":[],"sources":["../src/config/env.ts"],"sourcesContent":["import type { z } from 'zod'\nimport { detectSchema, type InferSchemaOutput } from '@forinda/kickjs-schema'\nimport { Container } from '../core/container'\nimport type { EnvKey } from '../core/decorators'\n\n/**\n * Lazy `zod` accessor. `zod` is an **optional** peer of\n * `@forinda/kickjs` — only the Zod-based env helpers (`baseEnvSchema`,\n * `defineEnv`, `loadEnv`) need it. Apps that validate env via Valibot /\n * Yup / Standard Schema go through `loadEnvFromSchema` (which only\n * touches the validator-agnostic `detectSchema`) and never load zod.\n *\n * Importing `zod` at module top-level made it a *hard* dependency of\n * the whole framework: `import { anything } from '@forinda/kickjs'`\n * eagerly evaluated this module, so a non-Zod app with no `zod`\n * installed crashed at module load / build time (\"Cannot find module\n * 'zod'\"). Deferring the require to first use keeps the import out of\n * the eager graph.\n */\nlet _zod: typeof import('zod') | undefined\nfunction getZod(): typeof import('zod') {\n if (_zod) return _zod\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n _zod = require('zod') as typeof import('zod')\n } catch {\n throw new Error(\n \"@forinda/kickjs: the 'zod' peer dependency is required for \" +\n 'baseEnvSchema / defineEnv / loadEnv but is not installed. ' +\n 'Install it (`pnpm add zod`), or define your env with a non-Zod ' +\n 'schema and call loadEnvFromSchema() instead — @forinda/kickjs-schema ' +\n 'supports Valibot, Yup, and any Standard Schema validator without zod.',\n )\n }\n return _zod\n}\n\n/**\n * Lazily load `.env` via dotenv if it is installed in the consumer app.\n * `dotenv` is an **optional peer dependency** of `@forinda/kickjs` —\n * apps that want `.env` file support install it themselves (the CLI\n * generators add it to every scaffolded `package.json`). Apps that\n * inject env via the shell, Docker, or a secret manager can skip it\n * entirely and `process.env` is read as-is.\n *\n * We call `dotenv.config()` directly rather than `require('dotenv/config')`\n * so that a re-evaluation of this module (e.g. Vite SSR full-reload after\n * editing `.env`) can re-read the file. `dotenv/config` is a side-effect\n * shim that CJS caches aggressively, so the second `require` is a no-op\n * and new keys never reach `process.env` — the root cause of the\n * \"DATABASE_URL undefined until restart\" bug on Windows dev.\n */\nfunction tryLoadDotenv(): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const dotenv = require('dotenv')\n // `quiet: true` suppresses dotenv 17's tip banner that goes to stdout.\n // Required for CLI tools whose stdout is parsed by callers (`kick explain\n // --json`, etc) — the banner would otherwise corrupt the JSON output.\n dotenv.config({ override: false, quiet: true })\n } catch {\n // dotenv not installed — fall through, env vars must come from\n // the environment directly. This is a valid deployment style.\n }\n}\ntryLoadDotenv()\n\n/**\n * Build the base env schema. Memoized so repeated calls reuse one\n * schema instance (matters for the `cachedSchema === s` identity check\n * in {@link loadEnv}). Loads zod lazily via {@link getZod}.\n */\n// Build the concrete base schema. This function is the single source of\n// the *type* of `baseEnvSchema` (via `ReturnType<typeof buildBaseEnvSchema>`)\n// AND its runtime value (via `getBaseEnvSchema`). It is NEVER called at\n// module load — only lazily through `getBaseEnvSchema()` — so referencing\n// its return type stays compile-time-only and pulls no eager zod import.\n// Spelling the type this way (rather than `z.ZodObject<any>`) preserves the\n// precise PORT/NODE_ENV/LOG_LEVEL shape, which `defineEnv` composition and\n// the `kick typegen` `KickEnv` inference depend on.\nfunction buildBaseEnvSchema() {\n const z = getZod()\n return z.object({\n // Server\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n })\n}\n\n/** Precise type of the base env schema — no runtime value, no eager zod. */\nexport type BaseEnvSchema = ReturnType<typeof buildBaseEnvSchema>\n\n/**\n * Memoized base schema. Reuses one instance so the `cachedSchema === s`\n * identity check in {@link loadEnv} holds. Loads zod lazily.\n */\nlet _baseEnvSchema: BaseEnvSchema | undefined\nexport function getBaseEnvSchema(): BaseEnvSchema {\n if (!_baseEnvSchema) _baseEnvSchema = buildBaseEnvSchema()\n return _baseEnvSchema\n}\n\n/**\n * Base environment schema with common server variables.\n * Users extend this with their own application-specific vars.\n *\n * Back-compat lazy view over {@link getBaseEnvSchema}: constructing the\n * Proxy does NOT load zod — zod is only required the moment a property\n * (`.extend`, `.parse`, `.shape`, …) is actually accessed. This keeps\n * `import { baseEnvSchema } from '@forinda/kickjs'` zero-cost for apps\n * that don't use the Zod env path, while keeping the precise schema type.\n */\nexport const baseEnvSchema: BaseEnvSchema = new Proxy({} as BaseEnvSchema, {\n get(_t, prop) {\n const real = getBaseEnvSchema() as unknown as Record<string | symbol, unknown>\n const value = real[prop]\n // Bind methods to the real schema so zod's internal `this` works.\n return typeof value === 'function' ? (value as (...a: unknown[]) => unknown).bind(real) : value\n },\n has(_t, prop) {\n return prop in (getBaseEnvSchema() as object)\n },\n}) as BaseEnvSchema\n\n/** Cached env config to avoid re-parsing — keyed by schema reference */\nlet cachedEnv: any = null\nlet cachedSchema: any = null\n\n/**\n * Define a custom env schema. The base schema (`PORT`, `NODE_ENV`,\n * `LOG_LEVEL`) is always merged in, so users never need to remember to\n * extend it themselves — both styles below produce the same result and\n * both surface the base keys in `KickEnv`.\n *\n * Returns a Zod schema that validates `process.env`.\n *\n * @example\n * ```ts\n * // Style A — return a fresh object; base fields are merged automatically\n * export default defineEnv(() =>\n * z.object({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Style B — explicitly extend `base` (still works, identical result)\n * export default defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * })\n * )\n * ```\n *\n * User-defined keys override base keys when they collide, so a project\n * can re-type `PORT` or tighten `NODE_ENV` without losing the rest of\n * the base shape.\n */\nexport function defineEnv<T extends z.ZodRawShape>(\n extend: (base: typeof baseEnvSchema) => z.ZodObject<T>,\n): z.ZodObject<typeof baseEnvSchema.shape & T> {\n const base = getBaseEnvSchema()\n const userSchema = extend(base as typeof baseEnvSchema)\n // Always merge the base in. `extend` lets the user's shape override\n // any colliding base keys, which preserves the escape hatch for\n // projects that want to re-type `PORT` etc.\n return base.extend(userSchema.shape) as z.ZodObject<typeof baseEnvSchema.shape & T>\n}\n\n/**\n * Parse and validate `process.env` against a Zod schema. Caches result per schema.\n *\n * **No-arg behaviour is sticky.** The first time `loadEnv(schema)` is called\n * with an extended schema, the cache holds that schema. Subsequent\n * `loadEnv()` calls (no arg) will reuse the cached extended schema rather\n * than falling back to `baseEnvSchema`.\n *\n * Stickiness matters because `ConfigService` and the `@Value()` decorator\n * both read through the no-arg form internally. Without it, resolving\n * either of them after a `loadEnv(extendedSchema)` call would silently\n * downgrade the env to the base shape and drop every user-defined key.\n *\n * To force a re-parse with a different schema, pass the new schema explicitly.\n * To start fresh, call `resetEnvCache()` (full reset) or `reloadEnv()`\n * (re-parse against the same schema after a `.env` file change).\n *\n * **Typing:**\n * - With an explicit schema → returns `z.infer<typeof schema>`.\n * - With no schema and `KickEnv` populated by typegen → returns\n * `KickEnv` (the project-wide typed env shape).\n * - With no schema and `KickEnv` empty → returns the base `Env`.\n */\nexport function loadEnv<T extends z.ZodObject<any>>(schema: T): z.infer<T>\nexport function loadEnv(): [EnvKey] extends [never] ? Env : KickEnv\nexport function loadEnv<T extends z.ZodObject<any>>(schema?: T): any {\n // Sticky: when called with no arg, prefer the most recently cached\n // schema over re-parsing with the base schema.\n const s = schema ?? cachedSchema ?? getBaseEnvSchema()\n // Re-parse if schema changed or no cache yet\n if (cachedEnv && cachedSchema === s) return cachedEnv\n cachedSchema = s\n cachedEnv = s.parse(process.env)\n\n // Register env resolver so @Value() reads validated values\n Container._envResolver = (key: string) => cachedEnv?.[key]\n\n return cachedEnv\n}\n\n/**\n * Get a single typed environment variable value.\n *\n * Three forms:\n * - **No-arg, KickEnv populated** (`kick typegen` has run) → key is\n * constrained to known `KickEnv` keys; return type inferred from\n * the schema.\n * - **With explicit schema** → key constrained to that schema's keys;\n * return type is `z.infer<typeof schema>[K]`.\n * - **No-arg, KickEnv empty** → loose `string` key, `any` return.\n *\n * @example\n * ```ts\n * // After `kick typegen` (KickEnv populated):\n * const port = getEnv('PORT') // typed as number\n * const url = getEnv('DATABASE_URL') // typed as string\n *\n * // With an inline schema (legacy / one-off):\n * const dbUrl = getEnv('DATABASE_URL', envSchema)\n * ```\n */\nexport function getEnv<K extends EnvKey>(key: K): KickEnv[K]\nexport function getEnv<T extends z.ZodObject<any>, K extends string & keyof z.infer<T>>(\n key: K,\n schema: T,\n): z.infer<T>[K]\nexport function getEnv(key: string, schema?: z.ZodObject<any>): any {\n const env = schema ? loadEnv(schema) : (loadEnv() as Record<string, any>)\n return env[key]\n}\n\n/**\n * Reload env from `process.env`. Re-reads `.env` via dotenv (if\n * installed), then re-parses the result against the **same** schema\n * the project registered at startup. Called during HMR rebuild and by\n * `envWatchPlugin` to pick up `.env` file changes mid-session.\n *\n * Crucially, this preserves the registered schema. Wiping it would\n * silently downgrade `ConfigService` and `@Value()` back to the base\n * schema on the next read, so every user-defined key would start\n * resolving as `undefined` after the first `.env` save — which was the\n * original \"config disappears in dev\" bug.\n *\n * If you need to fully forget the schema (e.g. tests that swap schemas\n * between cases), call {@link resetEnvCache} instead.\n */\nexport function reloadEnv(): void {\n // Re-read .env file into process.env if dotenv is installed.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('dotenv').config({ override: true, quiet: true })\n } catch {\n // dotenv not installed — nothing to reload, env comes from the\n // environment directly.\n }\n\n // Drop the parsed snapshot but keep the schema. Re-parse eagerly so\n // existing consumers (including ConfigService getters and @Value\n // resolvers that may already hold references to the env state) see\n // the new values on their very next read instead of having to\n // re-trigger any cache-population step.\n cachedEnv = null\n if (cachedSchema) {\n if (typeof cachedSchema.parse === 'function') {\n loadEnv(cachedSchema)\n } else {\n loadEnvFromSchema(cachedSchema)\n }\n }\n}\n\n/**\n * Fully reset the env cache, including the registered schema. Use this\n * in tests that need to swap schemas between cases — `reloadEnv()`\n * deliberately preserves the schema to avoid the \"user keys vanish on\n * `.env` reload\" footgun.\n */\nexport function resetEnvCache(): void {\n cachedEnv = null\n cachedSchema = null\n}\n\n/**\n * Schema-agnostic env loader. Accepts any schema supported by\n * `@forinda/kickjs-schema` (Zod, Valibot, Yup, Joi, Standard Schema,\n * KickSchema adapters, or plain validator functions).\n *\n * Unlike `loadEnv()` which uses Zod's `.parse()` and throws a\n * `ZodError`, this function uses `detectSchema().safeParse()` and\n * throws a plain `Error` with structured issue messages on failure.\n *\n * @example\n * ```ts\n * import * as v from 'valibot'\n * import { fromValibot } from '@forinda/kickjs-schema/valibot'\n *\n * const envSchema = fromValibot(v.object({\n * PORT: v.pipe(v.string(), v.transform(Number)),\n * NODE_ENV: v.picklist(['development', 'production', 'test']),\n * }))\n *\n * export const env = loadEnvFromSchema(envSchema)\n * ```\n */\n/**\n * Single inferring signature. `TSchema` is inferred from the call site;\n * the conditional return type maps the unknown-input case (TSchema\n * resolves to `unknown` only when the argument is `unknown`-typed\n * because nothing on the value side carried a richer brand) to\n * `Record<string, unknown>` rather than letting `InferSchemaOutput`\n * fall through to `unknown`. The previous two-overload form had the\n * generic overload win for `unknown`-typed inputs (`unknown` is\n * assignable to any `TSchema`), so the documented \"Record\" fallback\n * never actually applied.\n */\nexport function loadEnvFromSchema<TSchema>(\n schema: TSchema,\n): unknown extends TSchema ? Record<string, unknown> : InferSchemaOutput<TSchema>\nexport function loadEnvFromSchema(schema: unknown): unknown {\n const wrapped = detectSchema(schema)\n const result = wrapped.safeParse(process.env)\n if (!result.success) {\n const details = result.issues\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new Error(`Environment validation failed:\\n${details}`)\n }\n cachedEnv = result.data\n cachedSchema = schema\n Container._envResolver = (key: string) => cachedEnv?.[key]\n return result.data\n}\n\n/**\n * Output type of {@link baseEnvSchema}. Hand-written (rather than\n * `z.infer<typeof baseEnvSchema>`) so the type is available without the\n * eager `zod` value import — `baseEnvSchema` is now a lazy Proxy typed\n * as `z.ZodObject<any>`, from which `z.infer` could not recover the\n * precise shape anyway.\n */\nexport type Env = {\n PORT: number\n NODE_ENV: 'development' | 'production' | 'test'\n LOG_LEVEL: string\n}\n"],"mappings":";;;;;;;;;;qHAmBA,IAAI,KACJ,SAAS,QAA+B,CACtC,GAAI,KAAM,OAAO,KACjB,GAAI,CAEF,KAAA,UAAe,KAAK,CACtB,MAAQ,CACN,MAAU,MACR,gUAKF,CACF,CACA,OAAO,IACT,CAiBA,SAAS,eAAsB,CAC7B,GAAI,CAMF,UAJuB,QAIlB,CAAC,CAAC,OAAO,CAAE,SAAU,GAAO,MAAO,EAAK,CAAC,CAChD,MAAQ,CAGR,CACF,CACA,cAAc,EAed,SAAS,oBAAqB,CAC5B,IAAM,EAAI,OAAO,EACjB,OAAO,EAAE,OAAO,CAEd,KAAM,EAAE,OAAO,OAAO,CAAC,CAAC,QAAQ,GAAI,EACpC,SAAU,EAAE,KAAK,CAAC,cAAe,aAAc,MAAM,CAAC,CAAC,CAAC,QAAQ,aAAa,EAC7E,UAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,MAAM,CACtC,CAAC,CACH,CASA,IAAI,eACJ,SAAgB,kBAAkC,CAEhD,MADA,CAAqB,iBAAiB,mBAAmB,EAClD,cACT,CAYA,MAAa,cAA+B,IAAI,MAAM,CAAC,EAAoB,CACzE,IAAI,GAAI,KAAM,CACZ,IAAM,KAAO,iBAAiB,EACxB,MAAQ,KAAK,MAEnB,OAAO,OAAO,OAAU,WAAc,MAAuC,KAAK,IAAI,EAAI,KAC5F,EACA,IAAI,GAAI,KAAM,CACZ,OAAO,QAAS,iBAAiB,CACnC,CACF,CAAC,EAGD,IAAI,UAAiB,KACjB,aAAoB,KAgCxB,SAAgB,UACd,OAC6C,CAC7C,IAAM,KAAO,iBAAiB,EACxB,WAAa,OAAO,IAA4B,EAItD,OAAO,KAAK,OAAO,WAAW,KAAK,CACrC,CA2BA,SAAgB,QAAoC,OAAiB,CAGnE,IAAM,EAAI,QAAU,cAAgB,iBAAiB,EASrD,OAPI,WAAa,eAAiB,EAAU,WAC5C,aAAe,EACf,UAAY,EAAE,MAAM,QAAQ,GAAG,EAG/B,UAAU,aAAgB,KAAgB,YAAY,KAE/C,UACT,CA4BA,SAAgB,OAAO,IAAa,OAAgC,CAElE,OADY,OAAS,QAAQ,MAAM,EAAK,QAAQ,EAAA,CACrC,IACb,CAiBA,SAAgB,WAAkB,CAEhC,GAAI,CAEF,UAAQ,QAAQ,CAAC,CAAC,OAAO,CAAE,SAAU,GAAM,MAAO,EAAK,CAAC,CAC1D,MAAQ,CAGR,CAOA,UAAY,KACR,eACE,OAAO,aAAa,OAAU,WAChC,QAAQ,YAAY,EAEpB,kBAAkB,YAAY,EAGpC,CAQA,SAAgB,eAAsB,CACpC,UAAY,KACZ,aAAe,IACjB,CAsCA,SAAgB,kBAAkB,OAA0B,CAE1D,IAAM,OADU,aAAa,MACR,CAAC,CAAC,UAAU,QAAQ,GAAG,EAC5C,GAAI,CAAC,OAAO,QAAS,CACnB,IAAM,QAAU,OAAO,OACpB,IAAK,GAAM,KAAK,EAAE,KAAK,KAAK,GAAG,GAAK,SAAS,IAAI,EAAE,SAAS,CAAC,CAC7D,KAAK;CAAI,EACZ,MAAU,MAAM,mCAAmC,SAAS,CAC9D,CAIA,MAHA,WAAY,OAAO,KACnB,aAAe,OACf,UAAU,aAAgB,KAAgB,YAAY,KAC/C,OAAO,IAChB"}
|
|
1
|
+
{"version":3,"file":"env-wT1GSzGW.mjs","names":[],"sources":["../src/config/env.ts"],"sourcesContent":["import type { z } from 'zod'\nimport { detectSchema, type InferSchemaOutput } from '@forinda/kickjs-schema'\nimport { Container } from '../core/container'\nimport type { EnvKey } from '../core/decorators'\n\n/**\n * Lazy `zod` accessor. `zod` is an **optional** peer of\n * `@forinda/kickjs` — only the Zod-based env helpers (`baseEnvSchema`,\n * `defineEnv`, `loadEnv`) need it. Apps that validate env via Valibot /\n * Yup / Standard Schema go through `loadEnvFromSchema` (which only\n * touches the validator-agnostic `detectSchema`) and never load zod.\n *\n * Importing `zod` at module top-level made it a *hard* dependency of\n * the whole framework: `import { anything } from '@forinda/kickjs'`\n * eagerly evaluated this module, so a non-Zod app with no `zod`\n * installed crashed at module load / build time (\"Cannot find module\n * 'zod'\"). Deferring the require to first use keeps the import out of\n * the eager graph.\n */\nlet _zod: typeof import('zod') | undefined\nfunction getZod(): typeof import('zod') {\n if (_zod) return _zod\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n _zod = require('zod') as typeof import('zod')\n } catch {\n throw new Error(\n \"@forinda/kickjs: the 'zod' peer dependency is required for \" +\n 'baseEnvSchema / defineEnv / loadEnv but is not installed. ' +\n 'Install it (`pnpm add zod`), or define your env with a non-Zod ' +\n 'schema and call loadEnvFromSchema() instead — @forinda/kickjs-schema ' +\n 'supports Valibot, Yup, and any Standard Schema validator without zod.',\n )\n }\n return _zod\n}\n\n/**\n * Lazily load `.env` via dotenv if it is installed in the consumer app.\n * `dotenv` is an **optional peer dependency** of `@forinda/kickjs` —\n * apps that want `.env` file support install it themselves (the CLI\n * generators add it to every scaffolded `package.json`). Apps that\n * inject env via the shell, Docker, or a secret manager can skip it\n * entirely and `process.env` is read as-is.\n *\n * We call `dotenv.config()` directly rather than `require('dotenv/config')`\n * so that a re-evaluation of this module (e.g. Vite SSR full-reload after\n * editing `.env`) can re-read the file. `dotenv/config` is a side-effect\n * shim that CJS caches aggressively, so the second `require` is a no-op\n * and new keys never reach `process.env` — the root cause of the\n * \"DATABASE_URL undefined until restart\" bug on Windows dev.\n */\nfunction tryLoadDotenv(): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const dotenv = require('dotenv')\n // `quiet: true` suppresses dotenv 17's tip banner that goes to stdout.\n // Required for CLI tools whose stdout is parsed by callers (`kick explain\n // --json`, etc) — the banner would otherwise corrupt the JSON output.\n dotenv.config({ override: false, quiet: true })\n } catch {\n // dotenv not installed — fall through, env vars must come from\n // the environment directly. This is a valid deployment style.\n }\n}\ntryLoadDotenv()\n\n/**\n * Build the base env schema. Memoized so repeated calls reuse one\n * schema instance (matters for the `cachedSchema === s` identity check\n * in {@link loadEnv}). Loads zod lazily via {@link getZod}.\n */\n// Build the concrete base schema. This function is the single source of\n// the *type* of `baseEnvSchema` (via `ReturnType<typeof buildBaseEnvSchema>`)\n// AND its runtime value (via `getBaseEnvSchema`). It is NEVER called at\n// module load — only lazily through `getBaseEnvSchema()` — so referencing\n// its return type stays compile-time-only and pulls no eager zod import.\n// Spelling the type this way (rather than `z.ZodObject<any>`) preserves the\n// precise PORT/NODE_ENV/LOG_LEVEL shape, which `defineEnv` composition and\n// the `kick typegen` `KickEnv` inference depend on.\nfunction buildBaseEnvSchema() {\n const z = getZod()\n return z.object({\n // Server\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n })\n}\n\n/** Precise type of the base env schema — no runtime value, no eager zod. */\nexport type BaseEnvSchema = ReturnType<typeof buildBaseEnvSchema>\n\n/**\n * Memoized base schema. Reuses one instance so the `cachedSchema === s`\n * identity check in {@link loadEnv} holds. Loads zod lazily.\n */\nlet _baseEnvSchema: BaseEnvSchema | undefined\nexport function getBaseEnvSchema(): BaseEnvSchema {\n if (!_baseEnvSchema) _baseEnvSchema = buildBaseEnvSchema()\n return _baseEnvSchema\n}\n\n/**\n * Base environment schema with common server variables.\n * Users extend this with their own application-specific vars.\n *\n * Back-compat lazy view over {@link getBaseEnvSchema}: constructing the\n * Proxy does NOT load zod — zod is only required the moment a property\n * (`.extend`, `.parse`, `.shape`, …) is actually accessed. This keeps\n * `import { baseEnvSchema } from '@forinda/kickjs'` zero-cost for apps\n * that don't use the Zod env path, while keeping the precise schema type.\n */\nexport const baseEnvSchema: BaseEnvSchema = new Proxy({} as BaseEnvSchema, {\n get(_t, prop) {\n const real = getBaseEnvSchema() as unknown as Record<string | symbol, unknown>\n const value = real[prop]\n // Bind methods to the real schema so zod's internal `this` works.\n return typeof value === 'function' ? (value as (...a: unknown[]) => unknown).bind(real) : value\n },\n has(_t, prop) {\n return prop in (getBaseEnvSchema() as object)\n },\n}) as BaseEnvSchema\n\n/** Cached env config to avoid re-parsing — keyed by schema reference */\nlet cachedEnv: any = null\nlet cachedSchema: any = null\n\n/**\n * Define a custom env schema. The base schema (`PORT`, `NODE_ENV`,\n * `LOG_LEVEL`) is always merged in, so users never need to remember to\n * extend it themselves — both styles below produce the same result and\n * both surface the base keys in `KickEnv`.\n *\n * Returns a Zod schema that validates `process.env`.\n *\n * @example\n * ```ts\n * // Style A — return a fresh object; base fields are merged automatically\n * export default defineEnv(() =>\n * z.object({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Style B — explicitly extend `base` (still works, identical result)\n * export default defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * })\n * )\n * ```\n *\n * User-defined keys override base keys when they collide, so a project\n * can re-type `PORT` or tighten `NODE_ENV` without losing the rest of\n * the base shape.\n */\nexport function defineEnv<T extends z.ZodRawShape>(\n extend: (base: typeof baseEnvSchema) => z.ZodObject<T>,\n): z.ZodObject<typeof baseEnvSchema.shape & T> {\n const base = getBaseEnvSchema()\n const userSchema = extend(base as typeof baseEnvSchema)\n // Always merge the base in. `extend` lets the user's shape override\n // any colliding base keys, which preserves the escape hatch for\n // projects that want to re-type `PORT` etc.\n return base.extend(userSchema.shape) as z.ZodObject<typeof baseEnvSchema.shape & T>\n}\n\n/**\n * Parse and validate `process.env` against a Zod schema. Caches result per schema.\n *\n * **No-arg behaviour is sticky.** The first time `loadEnv(schema)` is called\n * with an extended schema, the cache holds that schema. Subsequent\n * `loadEnv()` calls (no arg) will reuse the cached extended schema rather\n * than falling back to `baseEnvSchema`.\n *\n * Stickiness matters because `ConfigService` and the `@Value()` decorator\n * both read through the no-arg form internally. Without it, resolving\n * either of them after a `loadEnv(extendedSchema)` call would silently\n * downgrade the env to the base shape and drop every user-defined key.\n *\n * To force a re-parse with a different schema, pass the new schema explicitly.\n * To start fresh, call `resetEnvCache()` (full reset) or `reloadEnv()`\n * (re-parse against the same schema after a `.env` file change).\n *\n * **Typing:**\n * - With an explicit schema → returns `z.infer<typeof schema>`.\n * - With no schema and `KickEnv` populated by typegen → returns\n * `KickEnv` (the project-wide typed env shape).\n * - With no schema and `KickEnv` empty → returns the base `Env`.\n */\nexport function loadEnv<T extends z.ZodObject<any>>(schema: T): z.infer<T>\nexport function loadEnv(): [EnvKey] extends [never] ? Env : KickEnv\nexport function loadEnv<T extends z.ZodObject<any>>(schema?: T): any {\n // Sticky: when called with no arg, prefer the most recently cached\n // schema over re-parsing with the base schema.\n const s = schema ?? cachedSchema ?? getBaseEnvSchema()\n // Re-parse if schema changed or no cache yet\n if (cachedEnv && cachedSchema === s) return cachedEnv\n cachedSchema = s\n cachedEnv = s.parse(process.env)\n\n // Register env resolver so @Value() reads validated values\n Container._envResolver = (key: string) => cachedEnv?.[key]\n\n return cachedEnv\n}\n\n/**\n * Get a single typed environment variable value.\n *\n * Three forms:\n * - **No-arg, KickEnv populated** (`kick typegen` has run) → key is\n * constrained to known `KickEnv` keys; return type inferred from\n * the schema.\n * - **With explicit schema** → key constrained to that schema's keys;\n * return type is `z.infer<typeof schema>[K]`.\n * - **No-arg, KickEnv empty** → loose `string` key, `any` return.\n *\n * @example\n * ```ts\n * // After `kick typegen` (KickEnv populated):\n * const port = getEnv('PORT') // typed as number\n * const url = getEnv('DATABASE_URL') // typed as string\n *\n * // With an inline schema (legacy / one-off):\n * const dbUrl = getEnv('DATABASE_URL', envSchema)\n * ```\n */\nexport function getEnv<K extends EnvKey>(key: K): KickEnv[K]\nexport function getEnv<T extends z.ZodObject<any>, K extends string & keyof z.infer<T>>(\n key: K,\n schema: T,\n): z.infer<T>[K]\nexport function getEnv(key: string, schema?: z.ZodObject<any>): any {\n const env = schema ? loadEnv(schema) : (loadEnv() as Record<string, any>)\n return env[key]\n}\n\n/**\n * Reload env from `process.env`. Re-reads `.env` via dotenv (if\n * installed), then re-parses the result against the **same** schema\n * the project registered at startup. Called during HMR rebuild and by\n * `envWatchPlugin` to pick up `.env` file changes mid-session.\n *\n * Crucially, this preserves the registered schema. Wiping it would\n * silently downgrade `ConfigService` and `@Value()` back to the base\n * schema on the next read, so every user-defined key would start\n * resolving as `undefined` after the first `.env` save — which was the\n * original \"config disappears in dev\" bug.\n *\n * If you need to fully forget the schema (e.g. tests that swap schemas\n * between cases), call {@link resetEnvCache} instead.\n */\nexport function reloadEnv(): void {\n // Re-read .env file into process.env if dotenv is installed.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('dotenv').config({ override: true, quiet: true })\n } catch {\n // dotenv not installed — nothing to reload, env comes from the\n // environment directly.\n }\n\n // Drop the parsed snapshot but keep the schema. Re-parse eagerly so\n // existing consumers (including ConfigService getters and @Value\n // resolvers that may already hold references to the env state) see\n // the new values on their very next read instead of having to\n // re-trigger any cache-population step.\n cachedEnv = null\n if (cachedSchema) {\n if (typeof cachedSchema.parse === 'function') {\n loadEnv(cachedSchema)\n } else {\n loadEnvFromSchema(cachedSchema)\n }\n }\n}\n\n/**\n * Fully reset the env cache, including the registered schema. Use this\n * in tests that need to swap schemas between cases — `reloadEnv()`\n * deliberately preserves the schema to avoid the \"user keys vanish on\n * `.env` reload\" footgun.\n */\nexport function resetEnvCache(): void {\n cachedEnv = null\n cachedSchema = null\n}\n\n/**\n * Schema-agnostic env loader. Accepts any schema supported by\n * `@forinda/kickjs-schema` (Zod, Valibot, Yup, Joi, Standard Schema,\n * KickSchema adapters, or plain validator functions).\n *\n * Unlike `loadEnv()` which uses Zod's `.parse()` and throws a\n * `ZodError`, this function uses `detectSchema().safeParse()` and\n * throws a plain `Error` with structured issue messages on failure.\n *\n * @example\n * ```ts\n * import * as v from 'valibot'\n * import { fromValibot } from '@forinda/kickjs-schema/valibot'\n *\n * const envSchema = fromValibot(v.object({\n * PORT: v.pipe(v.string(), v.transform(Number)),\n * NODE_ENV: v.picklist(['development', 'production', 'test']),\n * }))\n *\n * export const env = loadEnvFromSchema(envSchema)\n * ```\n */\n/**\n * Single inferring signature. `TSchema` is inferred from the call site;\n * the conditional return type maps the unknown-input case (TSchema\n * resolves to `unknown` only when the argument is `unknown`-typed\n * because nothing on the value side carried a richer brand) to\n * `Record<string, unknown>` rather than letting `InferSchemaOutput`\n * fall through to `unknown`. The previous two-overload form had the\n * generic overload win for `unknown`-typed inputs (`unknown` is\n * assignable to any `TSchema`), so the documented \"Record\" fallback\n * never actually applied.\n */\nexport function loadEnvFromSchema<TSchema>(\n schema: TSchema,\n): unknown extends TSchema ? Record<string, unknown> : InferSchemaOutput<TSchema>\nexport function loadEnvFromSchema(schema: unknown): unknown {\n const wrapped = detectSchema(schema)\n const result = wrapped.safeParse(process.env)\n if (!result.success) {\n const details = result.issues\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new Error(`Environment validation failed:\\n${details}`)\n }\n cachedEnv = result.data\n cachedSchema = schema\n Container._envResolver = (key: string) => cachedEnv?.[key]\n return result.data\n}\n\n/**\n * Output type of {@link baseEnvSchema}. Hand-written (rather than\n * `z.infer<typeof baseEnvSchema>`) so the type is available without the\n * eager `zod` value import — `baseEnvSchema` is now a lazy Proxy typed\n * as `z.ZodObject<any>`, from which `z.infer` could not recover the\n * precise shape anyway.\n */\nexport type Env = {\n PORT: number\n NODE_ENV: 'development' | 'production' | 'test'\n LOG_LEVEL: string\n}\n"],"mappings":";;;;;;;;;;qHAmBA,IAAI,KACJ,SAAS,QAA+B,CACtC,GAAI,KAAM,OAAO,KACjB,GAAI,CAEF,KAAA,UAAe,KAAK,CACtB,MAAQ,CACN,MAAU,MACR,gUAKF,CACF,CACA,OAAO,IACT,CAiBA,SAAS,eAAsB,CAC7B,GAAI,CAMF,UAJuB,QAIlB,CAAC,CAAC,OAAO,CAAE,SAAU,GAAO,MAAO,EAAK,CAAC,CAChD,MAAQ,CAGR,CACF,CACA,cAAc,EAed,SAAS,oBAAqB,CAC5B,IAAM,EAAI,OAAO,EACjB,OAAO,EAAE,OAAO,CAEd,KAAM,EAAE,OAAO,OAAO,CAAC,CAAC,QAAQ,GAAI,EACpC,SAAU,EAAE,KAAK,CAAC,cAAe,aAAc,MAAM,CAAC,CAAC,CAAC,QAAQ,aAAa,EAC7E,UAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,MAAM,CACtC,CAAC,CACH,CASA,IAAI,eACJ,SAAgB,kBAAkC,CAEhD,MADA,CAAqB,iBAAiB,mBAAmB,EAClD,cACT,CAYA,MAAa,cAA+B,IAAI,MAAM,CAAC,EAAoB,CACzE,IAAI,GAAI,KAAM,CACZ,IAAM,KAAO,iBAAiB,EACxB,MAAQ,KAAK,MAEnB,OAAO,OAAO,OAAU,WAAc,MAAuC,KAAK,IAAI,EAAI,KAC5F,EACA,IAAI,GAAI,KAAM,CACZ,OAAO,QAAS,iBAAiB,CACnC,CACF,CAAC,EAGD,IAAI,UAAiB,KACjB,aAAoB,KAgCxB,SAAgB,UACd,OAC6C,CAC7C,IAAM,KAAO,iBAAiB,EACxB,WAAa,OAAO,IAA4B,EAItD,OAAO,KAAK,OAAO,WAAW,KAAK,CACrC,CA2BA,SAAgB,QAAoC,OAAiB,CAGnE,IAAM,EAAI,QAAU,cAAgB,iBAAiB,EASrD,OAPI,WAAa,eAAiB,EAAU,WAC5C,aAAe,EACf,UAAY,EAAE,MAAM,QAAQ,GAAG,EAG/B,UAAU,aAAgB,KAAgB,YAAY,KAE/C,UACT,CA4BA,SAAgB,OAAO,IAAa,OAAgC,CAElE,OADY,OAAS,QAAQ,MAAM,EAAK,QAAQ,EAAA,CACrC,IACb,CAiBA,SAAgB,WAAkB,CAEhC,GAAI,CAEF,UAAQ,QAAQ,CAAC,CAAC,OAAO,CAAE,SAAU,GAAM,MAAO,EAAK,CAAC,CAC1D,MAAQ,CAGR,CAOA,UAAY,KACR,eACE,OAAO,aAAa,OAAU,WAChC,QAAQ,YAAY,EAEpB,kBAAkB,YAAY,EAGpC,CAQA,SAAgB,eAAsB,CACpC,UAAY,KACZ,aAAe,IACjB,CAsCA,SAAgB,kBAAkB,OAA0B,CAE1D,IAAM,OADU,aAAa,MACR,CAAC,CAAC,UAAU,QAAQ,GAAG,EAC5C,GAAI,CAAC,OAAO,QAAS,CACnB,IAAM,QAAU,OAAO,OACpB,IAAK,GAAM,KAAK,EAAE,KAAK,KAAK,GAAG,GAAK,SAAS,IAAI,EAAE,SAAS,CAAC,CAC7D,KAAK;CAAI,EACZ,MAAU,MAAM,mCAAmC,SAAS,CAC9D,CAIA,MAHA,WAAY,OAAO,KACnB,aAAe,OACf,UAAU,aAAgB,KAAgB,YAAY,KAC/C,OAAO,IAChB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as Application}from"../application-
|
|
11
|
+
import{t as Application}from"../application-DwTrT9cq.mjs";export{Application};
|
package/dist/http/bootstrap.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as bootstrap}from"../bootstrap-
|
|
11
|
+
import{t as bootstrap}from"../bootstrap-BaqYQe4i.mjs";export{bootstrap};
|
package/dist/http/context.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as RequestContext}from"../context-
|
|
11
|
+
import{t as RequestContext}from"../context-DGsCYQRP.mjs";export{RequestContext};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{createLogger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{createLogger}from"../../core/logger.mjs";import"../../core-BV0AxVuK.mjs";import{HttpException,ProblemException,normalizeProblem}from"../../core/errors.mjs";const log=createLogger(`ErrorHandler`);function notFoundHandler(){return(_req,res,_next)=>{res.status(404).json({message:`Not Found`})}}function errorHandler(){let isProduction=process.env.NODE_ENV===`production`;return(err,req,res,_next)=>{if(res.headersSent){log.warn(`Error after headers sent: ${err?.message||`Unknown`}`);return}if(err instanceof ProblemException){if(err.problem.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let body=normalizeProblem(err.problem);return res.setHeader(`Content-Type`,`application/problem+json`),res.status(body.status).json(body)}if(err?.name===`ZodError`){let firstIssue=err.issues?.[0];return res.status(422).json({message:firstIssue?.message||`Validation failed`,...isProduction?{}:{errors:err.issues}})}if(err instanceof HttpException){if(err.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let exposeDetails=!isProduction&&err.details!==void 0;return res.status(err.status).json({message:err.message,...exposeDetails?{errors:err.details}:{}})}let status=err.status||err.statusCode||500;log.error(err,`${req.method} ${req.originalUrl} — ${err.message||`Unhandled error`}`);let message=status===500?`Internal Server Error`:err.message||`Error`;res.status(status).json({message})}}export{errorHandler,notFoundHandler};
|
|
12
12
|
//# sourceMappingURL=error-handler.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{createLogger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{createLogger}from"../../core/logger.mjs";import"../../core-BV0AxVuK.mjs";function requestLogger(options={}){let log=createLogger(options.name??`HTTP`),level=options.level??`info`,skip=options.skip??[];return(req,res,next)=>{if(skip.some(prefix=>req.path.startsWith(prefix)))return next();let start=Date.now();res.on(`finish`,()=>{let duration=Date.now()-start,requestId=req.requestId||req.headers[`x-request-id`]||`-`,status=res.statusCode;log[level](`${req.method} ${req.originalUrl} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
|
|
12
12
|
//# sourceMappingURL=request-logger.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{v as __require}from"../../container-
|
|
11
|
+
import{v as __require}from"../../container-B0BU5TY4.mjs";import{Logger}from"../../core/logger.mjs";import"../../core-BV0AxVuK.mjs";import{existsSync,readFileSync}from"node:fs";import{join,resolve}from"node:path";const log=Logger.for(`SpaAdapter`);var SpaAdapter=class{options;name=`SpaAdapter`;clientDir;apiPrefixes;excludePaths;cacheControl;indexCacheControl;indexHtml=null;constructor(options={}){this.options=options,this.clientDir=resolve(options.clientDir??`dist/client`),this.excludePaths=options.exclude??[],this.cacheControl=options.cacheControl??`public, max-age=31536000, immutable`,this.indexCacheControl=options.indexCacheControl??`no-cache`;let prefix=options.apiPrefix??`/api`;this.apiPrefixes=Array.isArray(prefix)?prefix:[prefix]}beforeMount({app}){if(!existsSync(this.clientDir)){log.warn(`SPA client directory not found: ${this.clientDir}`),log.warn(`Build your frontend first, or set clientDir to the correct path.`);return}let indexPath=join(this.clientDir,`index.html`);if(existsSync(indexPath))this.indexHtml=readFileSync(indexPath,`utf-8`);else{log.warn(`index.html not found in ${this.clientDir}`);return}try{let express=__require(`express`),staticOpts={};this.cacheControl&&(staticOpts.setHeaders=(res,filePath)=>{filePath.endsWith(`.html`)?res.setHeader(`Cache-Control`,this.indexCacheControl):res.setHeader(`Cache-Control`,this.cacheControl)}),app.use(express.static(this.clientDir,staticOpts))}catch{log.error(`express.static not available — SPA static file serving disabled`);return}log.debug(`Serving SPA from ${this.clientDir}`)}beforeStart({app}){this.indexHtml&&app.use((req,res,next)=>{for(let prefix of this.apiPrefixes)if(req.path.startsWith(prefix))return next();for(let path of this.excludePaths)if(req.path.startsWith(path))return next();if(req.path.includes(`.`)||req.method!==`GET`)return next();res.setHeader(`Content-Type`,`text/html`),res.setHeader(`Cache-Control`,this.indexCacheControl),res.send(this.indexHtml)})}};export{SpaAdapter};
|
|
12
12
|
//# sourceMappingURL=spa.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import"../../core-
|
|
11
|
+
import"../../core-BV0AxVuK.mjs";import{HttpException,HttpStatus}from"../../core/errors.mjs";import{createRequire}from"node:module";import{unlink}from"node:fs/promises";const requireFromHere=createRequire(import.meta.url);let _multer;function loadMulter(){if(_multer)return _multer;try{return _multer=requireFromHere(`multer`),_multer}catch{throw Error(`@forinda/kickjs: file uploads require the 'multer' package, which is not installed.
|
|
12
12
|
Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).`)}}const MIME_MAP={jpg:`image/jpeg`,jpeg:`image/jpeg`,png:`image/png`,gif:`image/gif`,webp:`image/webp`,svg:`image/svg+xml`,bmp:`image/bmp`,ico:`image/x-icon`,tiff:`image/tiff`,tif:`image/tiff`,avif:`image/avif`,pdf:`application/pdf`,doc:`application/msword`,docx:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,xls:`application/vnd.ms-excel`,xlsx:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,ppt:`application/vnd.ms-powerpoint`,pptx:`application/vnd.openxmlformats-officedocument.presentationml.presentation`,odt:`application/vnd.oasis.opendocument.text`,ods:`application/vnd.oasis.opendocument.spreadsheet`,odp:`application/vnd.oasis.opendocument.presentation`,rtf:`application/rtf`,txt:`text/plain`,csv:`text/csv`,zip:`application/zip`,gz:`application/gzip`,tar:`application/x-tar`,rar:`application/vnd.rar`,"7z":`application/x-7z-compressed`,mp3:`audio/mpeg`,wav:`audio/wav`,ogg:`audio/ogg`,flac:`audio/flac`,aac:`audio/aac`,mp4:`video/mp4`,webm:`video/webm`,avi:`video/x-msvideo`,mov:`video/quicktime`,mkv:`video/x-matroska`,json:`application/json`,xml:`application/xml`,html:`text/html`};function resolveMimeTypes(types){return types.map(t=>MIME_MAP[t.toLowerCase().replace(/^\./,``)]??t)}function buildFileTypeFilter(allowedTypes,customMimeMap){if(!allowedTypes)return()=>!0;if(typeof allowedTypes==`function`)return allowedTypes;let mimeMap=customMimeMap?{...MIME_MAP,...customMimeMap}:MIME_MAP,resolved=allowedTypes.map(t=>mimeMap[t.toLowerCase().replace(/^\./,``)]??t);return mimetype=>resolved.some(type=>type.endsWith(`/*`)?mimetype.startsWith(type.replace(`/*`,`/`)):mimetype===type)}function applyUploadConfig(parts,config){if(config.mode===`none`)return{};let field=config.fieldName??`file`,maxSize=config.maxSize??5242880,typeAllowed=buildFileTypeFilter(config.allowedTypes,config.customMimeMap),files=parts.filter(p=>p.fieldname===field).map(p=>{if(!typeAllowed(p.mimetype,p.filename))throw new HttpException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,`File type ${p.mimetype} is not allowed`);if(p.buffer.length>maxSize)throw new HttpException(HttpStatus.PAYLOAD_TOO_LARGE,`File ${p.filename} exceeds the ${maxSize}-byte limit`);return{fieldname:p.fieldname,originalname:p.filename,encoding:`7bit`,mimetype:p.mimetype,size:p.buffer.length,buffer:p.buffer}});if(config.mode===`single`)return{file:files[0]};let maxCount=config.maxCount??10;return{files:files.slice(0,maxCount)}}function createMulter(options){let limits={fileSize:options.maxSize??5*1024*1024},fileFilter;if(options.allowedTypes){let typeAllowed=buildFileTypeFilter(options.allowedTypes,options.customMimeMap);fileFilter=(_req,file,cb)=>{typeAllowed(file.mimetype,file.originalname)?cb(null,!0):cb(Error(`File type ${file.mimetype} is not allowed`))}}let multerOptions={limits,...fileFilter?{fileFilter}:{},...options.storage?{storage:options.storage}:{},...options.dest?{dest:options.dest}:{}};return loadMulter()(multerOptions)}function single(fieldName,options={}){return createMulter(options).single(fieldName)}function array(fieldName,maxCount=10,options={}){return createMulter(options).array(fieldName,maxCount)}function none(options={}){return createMulter(options).none()}function cleanupFiles(){return(req,res,next)=>{res.on(`finish`,async()=>{let files=[];if(req.file?.path&&files.push(req.file),Array.isArray(req.files))for(let f of req.files)f?.path&&files.push(f);for(let file of files)try{await unlink(file.path)}catch{}}),next()}}function buildUploadMiddleware(config){let options={};config.maxSize&&(options.maxSize=config.maxSize),config.allowedTypes&&(options.allowedTypes=config.allowedTypes),config.customMimeMap&&(options.customMimeMap=config.customMimeMap);let fieldName=config.fieldName??`file`;switch(config.mode){case`single`:return single(fieldName,options);case`array`:return array(fieldName,config.maxCount??10,options);case`none`:return none(options)}}const upload={single,array,none};export{applyUploadConfig,buildFileTypeFilter,buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload};
|
|
13
13
|
//# sourceMappingURL=upload.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import"../../core-
|
|
11
|
+
import"../../core-BV0AxVuK.mjs";import{HttpException,HttpStatus}from"../../core/errors.mjs";import{detectSchema}from"@forinda/kickjs-schema";function toValidationException(issues,message,useFirstIssueMessage){let details=issues.map(i=>({field:i.path.join(`.`)||``,message:i.message})),finalMessage=useFirstIssueMessage?issues[0]?.message??message:message;return new HttpException(HttpStatus.UNPROCESSABLE_ENTITY,finalMessage,details)}function assignReqProperty(req,key,value){Object.defineProperty(req,key,{value,writable:!0,configurable:!0,enumerable:!0})}function validate(schema){return(req,_res,next)=>{try{if(schema.body){let result=detectSchema(schema.body).safeParse(req.body);if(!result.success)return next(toValidationException(result.issues,`Validation failed`,!0));req.body=result.data}if(schema.query){let result=detectSchema(schema.query).safeParse(req.query);if(!result.success)return next(toValidationException(result.issues,`Invalid query parameters`,!1));assignReqProperty(req,`query`,result.data)}if(schema.params){let result=detectSchema(schema.params).safeParse(req.params);if(!result.success)return next(toValidationException(result.issues,`Invalid path parameters`,!1));assignReqProperty(req,`params`,result.data)}next()}catch(err){next(err)}}}export{validate};
|
|
12
12
|
//# sourceMappingURL=validate.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{Logger}from"../../core/logger.mjs";import{n as defineAdapter}from"../../core-
|
|
11
|
+
import{Logger}from"../../core/logger.mjs";import{n as defineAdapter}from"../../core-BV0AxVuK.mjs";import{resolve}from"node:path";const log=Logger.for(`ViewEngine`),ViewAdapter=defineAdapter({name:`ViewAdapter`,build:options=>({beforeMount({app}){let{engine,ext,viewsDir=`src/views`}=options;engine.__express?app.engine(ext,engine.__express):typeof engine.renderFile==`function`?app.engine(ext,(path,opts,callback)=>{engine.renderFile(path,opts,callback)}):(typeof engine==`function`||log.warn(`Engine for .${ext} does not have __express or renderFile. Trying as-is.`),app.engine(ext,engine)),app.set(`view engine`,ext),app.set(`views`,resolve(viewsDir)),log.debug(`View engine: ${ext} (${resolve(viewsDir)})`)}})});export{ViewAdapter};
|
|
12
12
|
//# sourceMappingURL=views.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
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-
|
|
11
|
+
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-CimrAth7.mjs";export{FILTER_OPERATORS,buildQueryParams,getQueryParsingDefaults,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,resetQueryParsingDefaults,setQueryParsingDefaults};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{t as Container}from"../container-
|
|
11
|
+
import{t as Container}from"../container-B0BU5TY4.mjs";import{METADATA}from"../core/interfaces.mjs";import{a as getMethodMeta,o as getMethodMetaOrUndefined,t as getClassMeta}from"../metadata-B069pGq2.mjs";import{c as runContributors,l as buildPipeline}from"../core-BV0AxVuK.mjs";let _externalContributorSources=[];function _setExternalContributorSources(sources){_externalContributorSources=sources}function buildRouteTable(controllerClass,options={}){let container=Container.getInstance(),externalSources=options.externalSources??_externalContributorSources,routes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddlewares=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]),classContributors=getClassMeta(METADATA.CLASS_CONTRIBUTORS,controllerClass,[]),entries=[];for(let route of routes){let method=route.method.toUpperCase(),fullPath=route.path||`/`,methodMiddlewares=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass,route.handlerName,[]),fileUploadConfig=getMethodMetaOrUndefined(METADATA.FILE_UPLOAD,controllerClass,route.handlerName),methodContributors=getMethodMeta(METADATA.METHOD_CONTRIBUTORS,controllerClass,route.handlerName,[]),contributorRunner=null;if(classContributors.length>0||methodContributors.length>0||externalSources.length>0){let pipeline=buildPipeline([...methodContributors.map((registration,i)=>({source:`method`,registration,label:`${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`})),...classContributors.map((registration,i)=>({source:`class`,registration,label:`${controllerClass.name}.@class#${i}(${registration.key})`})),...externalSources],{route:`${method} ${fullPath}`});contributorRunner=ctx=>runContributors({pipeline,ctx,container})}let handler=ctx=>container.resolve(controllerClass)[route.handlerName](ctx);entries.push({method,path:fullPath,middlewares:[...classMiddlewares,...methodMiddlewares],contributorRunner,handler,meta:{controller:controllerClass,handlerName:route.handlerName,validation:route.validation,upload:fileUploadConfig}})}return entries}export{_setExternalContributorSources,buildRouteTable};
|
|
12
12
|
//# sourceMappingURL=router-builder.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;;UA4CU,gBAAA;EACR,IAAA,CAAK,UAAA,WAAqB,gBAAA;EAC1B,IAAA,CAAK,OAAA,aAAoB,gBAAA;EACzB,MAAA,CAAO,IAAA,UAAc,KAAA,YAAiB,gBAAA;EACtC,IAAA,CAAK,WAAA,WAAsB,gBAAA;EAC3B,MAAA;EAAA,SACS,IAAA;EAAA,SACA,GAAA,EAAK,cAAA;AAAA;AAAA,UAEN,kBAAA;EACR,GAAA,EAAK,eAAA;EACL,IAAA;EACA,MAAA;EACA,KAAA;EAZ0B;EAc1B,KAAA,SAAc,qBAAA,CAAsB,aAAA;EACpC,WAAA;AAAA;AAAA,KAEG,cAAA,IAAkB,OAAA,EAAS,kBAAA,EAAoB,KAAA,EAAO,gBAAA,YAA4B,OAAA;AAAA,UAC7E,cAAA;EACR,QAAA,CAAS,MAAA,WAAiB,IAAA,aAAiB,OAAA,SAAgB,cAAA;EAC3D,GAAA,CAAI,IAAA,WAAe,EAAA;EACnB,KAAA,CAAM,IAAA;IAAQ,MAAA;IAAgB,GAAA;IAAa,OAAA,EAAS,cAAA;EAAA;EACpD,kBAAA,CAAmB,OAAA,EAAS,cAAA;EAC5B,eAAA,CACE,OAAA,GAAU,GAAA,WAAc,GAAA,EAAK,kBAAA,EAAoB,KAAA,EAAO,gBAAA;EAE1D,KAAA,IAAS,OAAA;EACT,OAAA,CAAQ,GAAA,EAAK,eAAA,EAAiB,GAAA,EAAK,cAAA;AAAA;AAAA,UAY3B,aAAA;EACR,IAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA;EACA,QAAA,SAAiB,OAAO,CAAC,MAAA;AAAA;;;;;;iBAoKX,cAAA,IAAkB,WAAW,CAAC,cAAA;;;;AAlMjC;AAAA;;;
|
|
1
|
+
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;;UA4CU,gBAAA;EACR,IAAA,CAAK,UAAA,WAAqB,gBAAA;EAC1B,IAAA,CAAK,OAAA,aAAoB,gBAAA;EACzB,MAAA,CAAO,IAAA,UAAc,KAAA,YAAiB,gBAAA;EACtC,IAAA,CAAK,WAAA,WAAsB,gBAAA;EAC3B,MAAA;EAAA,SACS,IAAA;EAAA,SACA,GAAA,EAAK,cAAA;AAAA;AAAA,UAEN,kBAAA;EACR,GAAA,EAAK,eAAA;EACL,IAAA;EACA,MAAA;EACA,KAAA;EAZ0B;EAc1B,KAAA,SAAc,qBAAA,CAAsB,aAAA;EACpC,WAAA;AAAA;AAAA,KAEG,cAAA,IAAkB,OAAA,EAAS,kBAAA,EAAoB,KAAA,EAAO,gBAAA,YAA4B,OAAA;AAAA,UAC7E,cAAA;EACR,QAAA,CAAS,MAAA,WAAiB,IAAA,aAAiB,OAAA,SAAgB,cAAA;EAC3D,GAAA,CAAI,IAAA,WAAe,EAAA;EACnB,KAAA,CAAM,IAAA;IAAQ,MAAA;IAAgB,GAAA;IAAa,OAAA,EAAS,cAAA;EAAA;EACpD,kBAAA,CAAmB,OAAA,EAAS,cAAA;EAC5B,eAAA,CACE,OAAA,GAAU,GAAA,WAAc,GAAA,EAAK,kBAAA,EAAoB,KAAA,EAAO,gBAAA;EAE1D,KAAA,IAAS,OAAA;EACT,OAAA,CAAQ,GAAA,EAAK,eAAA,EAAiB,GAAA,EAAK,cAAA;AAAA;AAAA,UAY3B,aAAA;EACR,IAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA;EACA,QAAA,SAAiB,OAAO,CAAC,MAAA;AAAA;;;;;;iBAoKX,cAAA,IAAkB,WAAW,CAAC,cAAA;;;;AAlMjC;AAAA;;;UAqUI,mBAAA;EACf,OAAA,oBAA2B,cAAA;EAC3B,QAAA,oBAA4B,YAAA;EAC5B,GAAA,oBAAuB,eAAA;AAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{i as requestStore,t as RequestContext}from"../../context-
|
|
12
|
-
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],store=createRequestStore((Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId);raw.requestId=store.requestId,await requestStore.run(store,async()=>{let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.meta.validation){let v=validate(entry.meta.validation);await new Promise((resolve,reject)=>{v(raw,void 0,err=>err?reject(err):resolve())})}for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}entry.contributorRunner&&await entry.contributorRunner(ctx),!reply.sent&&await entry.handler(ctx)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,trustProxy:typeof options.trustProxy==`function`?!1:options.trustProxy});return app[QUEUE]=[],app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let
|
|
11
|
+
import{i as requestStore,t as RequestContext}from"../../context-DGsCYQRP.mjs";import{t as createRequestStore}from"../../request-scope-DiBal5Ln.mjs";import{validate}from"../middleware/validate.mjs";import{applyUploadConfig}from"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),loadPeer=name=>{let mod=peerRequire(name);return mod&&mod.__esModule&&mod.default?mod.default:mod.default??mod},QUEUE=Symbol(`kickjs.fastify.connectQueue`),READY=Symbol(`kickjs.fastify.ready`),NEXT=Symbol(`kickjs.fastify.next`),NEEDS_MULTIPART=Symbol(`kickjs.fastify.needsMultipart`),NOOP_NEXT=()=>{};function replyDriver(reply){let driver={status(code){return reply.code(code),driver},json(data){return reply.send(data),driver},send(data){return reply.send(data),driver},type(contentType){return reply.type(contentType),driver},setHeader(name,value){return reply.header(name,value),driver},render(){throw Error(`ctx.render() is not supported on the Fastify runtime (no view engine).`)},writeHead(statusCode,headers){return reply.hijack(),reply.raw.writeHead(statusCode,headers),driver},flushHeaders(){return reply.raw.flushHeaders?.(),driver},write(chunk){return reply.raw.write(chunk)},end(data){return reply.raw.end(data),driver},once(event,listener){return reply.raw.once(event,listener),driver},get headersSent(){return reply.sent||reply.raw.headersSent}};return driver}function routeHandler(entry){return async(request,reply)=>{let raw=request.raw;if(raw.body=request.body,raw.params=request.params,raw.query=request.query,entry.meta.upload&&entry.meta.upload.mode!==`none`){if(typeof request.parts!=`function`)throw Error(`@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.
|
|
12
|
+
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],store=createRequestStore((Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId);raw.requestId=store.requestId,await requestStore.run(store,async()=>{let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.meta.validation){let v=validate(entry.meta.validation);await new Promise((resolve,reject)=>{v(raw,void 0,err=>err?reject(err):resolve())})}for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}entry.contributorRunner&&await entry.contributorRunner(ctx),!reply.sent&&await entry.handler(ctx)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,trustProxy:typeof options.trustProxy==`function`?!1:options.trustProxy});return app[QUEUE]=[],app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let serveStatic=loadPeer(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app.setNotFoundHandler((request,reply)=>{let next=request.raw[NEXT];if(next){reply.hijack(),next();return}mw(request.raw,replyDriver(reply),NOOP_NEXT)})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{mw(err,request.raw,replyDriver(reply),NOOP_NEXT)})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{fastifyRuntime};
|
|
13
13
|
//# sourceMappingURL=fastify.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.mjs","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"sourcesContent":["// The Fastify runtime — `@forinda/kickjs/fastify`.\n//\n// Implements the same HttpRuntime contract as expressRuntime, over Fastify 5.\n// KickJS still owns decorators → RouteTable, the contributor pipeline, and the\n// RequestContext surface; this runtime materializes routes as NATIVE Fastify\n// routes and wraps `reply` in a RuntimeResponse so `ctx.json` / `ctx.html` /\n// `ctx.sse` work unchanged.\n//\n// `fastify`, `@fastify/middie` are optional peers — install them alongside\n// `@forinda/kickjs` to use this subpath.\n//\n// Lifecycle note: Fastify registration (`register` / `use` / `route`) is\n// deferred until `ready()`. Connect middleware needs `@fastify/middie`\n// registered first, so `useConnect` only QUEUES middleware; `nodeHandler`\n// registers middie, then the queued middleware, then awaits `ready()`.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\n// ESM-safe require for the optional peers (fastify / @fastify/middie / express),\n// resolved from wherever this module is installed.\nconst peerRequire = createRequire(import.meta.url)\nconst loadPeer = (name: string): any => {\n const mod = peerRequire(name)\n return mod && mod.__esModule && mod.default ? mod.default : (mod.default ?? mod)\n}\n\n// Minimal structural surface we use from Fastify — avoids a hard type dep so\n// the root package never needs `fastify` installed to typecheck.\ninterface FastifyReplyLike {\n code(statusCode: number): FastifyReplyLike\n send(payload?: unknown): FastifyReplyLike\n header(name: string, value: unknown): FastifyReplyLike\n type(contentType: string): FastifyReplyLike\n hijack(): void\n readonly sent: boolean\n readonly raw: ServerResponse\n}\ninterface FastifyRequestLike {\n raw: IncomingMessage\n body?: unknown\n params?: unknown\n query?: unknown\n /** Present once @fastify/multipart is registered — async iterator of parts. */\n parts?: () => AsyncIterableIterator<MultipartPart>\n isMultipart?: () => boolean\n}\ntype FastifyHandler = (request: FastifyRequestLike, reply: FastifyReplyLike) => void | Promise<void>\ninterface FastifyAppLike {\n register(plugin: unknown, opts?: unknown): Promise<void> | FastifyAppLike\n use(path: unknown, mw?: unknown): unknown\n route(opts: { method: string; url: string; handler: FastifyHandler }): unknown\n setNotFoundHandler(handler: FastifyHandler): unknown\n setErrorHandler(\n handler: (err: unknown, req: FastifyRequestLike, reply: FastifyReplyLike) => void,\n ): unknown\n ready(): Promise<void>\n routing(req: IncomingMessage, res: ServerResponse): void\n}\n\nconst QUEUE = Symbol('kickjs.fastify.connectQueue')\nconst READY = Symbol('kickjs.fastify.ready')\nconst NEXT = Symbol('kickjs.fastify.next')\n// Set during mountRoutes when any route carries @FileUpload metadata, so\n// nodeHandler knows to register @fastify/multipart before ready().\nconst NEEDS_MULTIPART = Symbol('kickjs.fastify.needsMultipart')\nconst NOOP_NEXT = (): void => {}\n\n// A @fastify/multipart part as we consume it from `request.parts()`.\ninterface MultipartPart {\n type: 'file' | 'field'\n fieldname: string\n filename?: string\n mimetype?: string\n value?: unknown\n toBuffer?: () => Promise<Buffer>\n}\n\n/** Wrap a Fastify reply so the RequestContext response helpers drive it. */\nfunction replyDriver(reply: FastifyReplyLike): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n reply.code(code)\n return driver\n },\n json(data) {\n reply.send(data)\n return driver\n },\n send(data) {\n reply.send(data)\n return driver\n },\n type(contentType) {\n reply.type(contentType)\n return driver\n },\n setHeader(name, value) {\n reply.header(name, value)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the Fastify runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n // Streaming / SSE: take over the raw socket so Fastify doesn't try to\n // serialize a reply we're writing by hand.\n reply.hijack()\n reply.raw.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n ;(reply.raw as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.()\n return driver\n },\n write(chunk) {\n return reply.raw.write(chunk)\n },\n end(data) {\n reply.raw.end(data)\n return driver\n },\n once(event, listener) {\n reply.raw.once(event, listener)\n return driver\n },\n get headersSent() {\n return reply.sent || reply.raw.headersSent\n },\n }\n return driver\n}\n\n/** Build the Fastify per-route handler that runs the kickjs pipeline. */\nfunction routeHandler(entry: RouteEntry): FastifyHandler {\n return async (request, reply) => {\n // Fastify runs the route handler OUTSIDE the connect-middleware chain, so\n // (unlike Express) the requestScopeMiddleware ALS frame isn't active here.\n // Establish it around the pipeline so REQUEST-scoped DI, contributors, and\n // ctx.set/get work. Reuse the inbound x-request-id, or the id a connect\n // middleware (requestId / requestScope, run earlier via middie) already\n // stamped on the raw request, before generating a fresh one.\n // Use the raw node request as `ctx.req`: it natively provides the stream\n // events (`on('close')` / `once`) that SSE and `ctx.signal` need — Fastify's\n // request object doesn't. Copy Fastify's parsed body/params/query onto it so\n // `ctx.body` / `ctx.params` / `ctx.query` keep working.\n const raw = request.raw as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n raw.body = request.body\n raw.params = request.params\n raw.query = request.query\n\n // @FileUpload: buffer multipart parts into the multer-shaped file(s) that\n // `ctx.file` / `ctx.files` expose. Fastify doesn't parse multipart natively\n // — @fastify/multipart (registered in nodeHandler when any route needs it)\n // provides `request.parts()`. Non-file fields are merged onto `raw.body` so\n // validation / `ctx.body` see them, matching multer's req.body behavior.\n if (entry.meta.upload && entry.meta.upload.mode !== 'none') {\n if (typeof request.parts !== 'function') {\n throw new Error(\n \"@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.\\n\" +\n 'Install it as a peer dependency: pnpm add @fastify/multipart.',\n )\n }\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for await (const part of request.parts()) {\n if (part.type === 'file' && part.toBuffer) {\n rawParts.push({\n fieldname: part.fieldname,\n filename: part.filename ?? '',\n mimetype: part.mimetype ?? 'application/octet-stream',\n buffer: await part.toBuffer(),\n })\n } else {\n fields[part.fieldname] = part.value\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload)\n raw.file = file\n raw.files = files\n raw.body = { ...(raw.body as Record<string, unknown> | undefined), ...fields }\n }\n\n const headerId = raw.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || raw.requestId\n const store = createRequestStore(requestId)\n // Propagate the resolved id back onto the raw request so anything reading\n // `req.requestId` inside the frame (e.g. request-logger) matches the store\n // and the X-Request-Id response header — parity with Express.\n raw.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(raw as never, reply as never, NOOP_NEXT, replyDriver(reply))\n\n // Validation (from @Get(path, schema) / route.validation). `validate` is a\n // connect-style middleware that mutates req.body/query/params to the parsed\n // value and calls next(err) on failure — it never touches `res`, so it runs\n // cleanly here. A rejection propagates to the error handler (422).\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(raw as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n // Class + method middleware — `(ctx, next)`; await each, stop if it ended\n // the response or called next(err).\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 || reply.sent) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (reply.sent) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The Fastify HTTP runtime. Pass to `bootstrap({ runtime: fastifyRuntime() })`.\n * Per spec §10: Fastify's built-in pino logger is disabled (`logger: false`) so\n * the kickjs `requestLogger` middleware stays the single log format.\n */\nexport function fastifyRuntime(): HttpRuntime<FastifyAppLike> {\n return {\n name: 'fastify',\n\n createApp(options: RuntimeAppOptions = {}): FastifyAppLike {\n const Fastify = loadPeer('fastify')\n const app = Fastify({\n logger: false,\n // trustProxy passed through for now; core X-Forwarded-For\n // normalization (spec §10 Q1) is a follow-up.\n trustProxy: typeof options.trustProxy === 'function' ? false : options.trustProxy,\n }) as FastifyAppLike & {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n }\n app[QUEUE] = []\n return app\n },\n\n nodeHandler(app) {\n // Register middie + queued connect middleware + finalize `ready()` exactly\n // ONCE per app, even though Application may call nodeHandler per request.\n const state = app as {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n [READY]?: Promise<void>\n [NEEDS_MULTIPART]?: boolean\n }\n if (!state[READY]) {\n state[READY] = (async () => {\n // Register @fastify/multipart first when any route uses @FileUpload so\n // `request.parts()` is available. Our applyUploadConfig enforces the\n // real per-file maxSize, so give the plugin a generous fileSize ceiling\n // (its 1MB default would silently truncate larger uploads first).\n if (state[NEEDS_MULTIPART]) {\n const multipart = loadPeer('@fastify/multipart')\n await app.register(multipart, { limits: { fileSize: Number.MAX_SAFE_INTEGER } })\n }\n const middie = loadPeer('@fastify/middie')\n await app.register(middie)\n for (const { mw, opts } of state[QUEUE] ?? []) {\n if (opts?.path !== undefined)\n (app.use as (p: unknown, m: unknown) => void)(opts.path, mw)\n else (app.use as (m: unknown) => void)(mw)\n }\n await app.ready()\n })()\n }\n const readyP = state[READY]\n\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n readyP\n .then(() => app.routing(req, res))\n .catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n if (entry.meta.upload) (app as { [NEEDS_MULTIPART]?: boolean })[NEEDS_MULTIPART] = true\n const url = joinPath(mountPath, entry.path)\n app.route({ method: entry.method, url, handler: routeHandler(entry) })\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const queue = (\n app as { [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }> }\n )[QUEUE]\n queue?.push({ mw, opts })\n },\n\n serveStatic(app, prefix, dir) {\n // Serve via express.static as a connect middleware, queued like any other.\n const expressStatic = loadPeer('express').static\n this.useConnect(app, expressStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n app.setNotFoundHandler((request, reply) => {\n const next = (request.raw as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT]\n if (next) {\n // Vite dev fall-through: hand the request back to the outer chain.\n reply.hijack()\n next()\n return\n }\n // Pass the reply DRIVER (not raw res) so the connect-style\n // notFoundHandler's `res.status().json()` lands on the Fastify reply.\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n app.setErrorHandler((err, request, reply) => {\n // Reply driver (not raw res) so the connect-style errorHandler's\n // `res.status().json()` / `setHeader` land on the Fastify reply.\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n err,\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The Fastify runtime's engine-native types — what the runtime-typed escape\n * hatches resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become `FastifyInstance`; `ctx.req` / `ctx.res` the Fastify request / reply.\n * Mirrors {@link import('../runtime').ExpressRuntimeTypes} for the default engine.\n */\nexport interface FastifyRuntimeTypes {\n request: import('fastify').FastifyRequest\n response: import('fastify').FastifyReply\n app: import('fastify').FastifyInstance\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n // A root route path ('/' or '') maps to the mount prefix itself — otherwise a\n // controller `@Post('/')` would register at `${prefix}/`, and a request to\n // `${prefix}` (no trailing slash) wouldn't match under Fastify's strict router.\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":"fastify.mjs","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"sourcesContent":["// The Fastify runtime — `@forinda/kickjs/fastify`.\n//\n// Implements the same HttpRuntime contract as expressRuntime, over Fastify 5.\n// KickJS still owns decorators → RouteTable, the contributor pipeline, and the\n// RequestContext surface; this runtime materializes routes as NATIVE Fastify\n// routes and wraps `reply` in a RuntimeResponse so `ctx.json` / `ctx.html` /\n// `ctx.sse` work unchanged.\n//\n// `fastify`, `@fastify/middie` are optional peers — install them alongside\n// `@forinda/kickjs` to use this subpath.\n//\n// Lifecycle note: Fastify registration (`register` / `use` / `route`) is\n// deferred until `ready()`. Connect middleware needs `@fastify/middie`\n// registered first, so `useConnect` only QUEUES middleware; `nodeHandler`\n// registers middie, then the queued middleware, then awaits `ready()`.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\n// ESM-safe require for the optional peers (fastify / @fastify/middie /\n// serve-static), resolved from wherever this module is installed.\nconst peerRequire = createRequire(import.meta.url)\nconst loadPeer = (name: string): any => {\n const mod = peerRequire(name)\n return mod && mod.__esModule && mod.default ? mod.default : (mod.default ?? mod)\n}\n\n// Minimal structural surface we use from Fastify — avoids a hard type dep so\n// the root package never needs `fastify` installed to typecheck.\ninterface FastifyReplyLike {\n code(statusCode: number): FastifyReplyLike\n send(payload?: unknown): FastifyReplyLike\n header(name: string, value: unknown): FastifyReplyLike\n type(contentType: string): FastifyReplyLike\n hijack(): void\n readonly sent: boolean\n readonly raw: ServerResponse\n}\ninterface FastifyRequestLike {\n raw: IncomingMessage\n body?: unknown\n params?: unknown\n query?: unknown\n /** Present once @fastify/multipart is registered — async iterator of parts. */\n parts?: () => AsyncIterableIterator<MultipartPart>\n isMultipart?: () => boolean\n}\ntype FastifyHandler = (request: FastifyRequestLike, reply: FastifyReplyLike) => void | Promise<void>\ninterface FastifyAppLike {\n register(plugin: unknown, opts?: unknown): Promise<void> | FastifyAppLike\n use(path: unknown, mw?: unknown): unknown\n route(opts: { method: string; url: string; handler: FastifyHandler }): unknown\n setNotFoundHandler(handler: FastifyHandler): unknown\n setErrorHandler(\n handler: (err: unknown, req: FastifyRequestLike, reply: FastifyReplyLike) => void,\n ): unknown\n ready(): Promise<void>\n routing(req: IncomingMessage, res: ServerResponse): void\n}\n\nconst QUEUE = Symbol('kickjs.fastify.connectQueue')\nconst READY = Symbol('kickjs.fastify.ready')\nconst NEXT = Symbol('kickjs.fastify.next')\n// Set during mountRoutes when any route carries @FileUpload metadata, so\n// nodeHandler knows to register @fastify/multipart before ready().\nconst NEEDS_MULTIPART = Symbol('kickjs.fastify.needsMultipart')\nconst NOOP_NEXT = (): void => {}\n\n// A @fastify/multipart part as we consume it from `request.parts()`.\ninterface MultipartPart {\n type: 'file' | 'field'\n fieldname: string\n filename?: string\n mimetype?: string\n value?: unknown\n toBuffer?: () => Promise<Buffer>\n}\n\n/** Wrap a Fastify reply so the RequestContext response helpers drive it. */\nfunction replyDriver(reply: FastifyReplyLike): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n reply.code(code)\n return driver\n },\n json(data) {\n reply.send(data)\n return driver\n },\n send(data) {\n reply.send(data)\n return driver\n },\n type(contentType) {\n reply.type(contentType)\n return driver\n },\n setHeader(name, value) {\n reply.header(name, value)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the Fastify runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n // Streaming / SSE: take over the raw socket so Fastify doesn't try to\n // serialize a reply we're writing by hand.\n reply.hijack()\n reply.raw.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n ;(reply.raw as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.()\n return driver\n },\n write(chunk) {\n return reply.raw.write(chunk)\n },\n end(data) {\n reply.raw.end(data)\n return driver\n },\n once(event, listener) {\n reply.raw.once(event, listener)\n return driver\n },\n get headersSent() {\n return reply.sent || reply.raw.headersSent\n },\n }\n return driver\n}\n\n/** Build the Fastify per-route handler that runs the kickjs pipeline. */\nfunction routeHandler(entry: RouteEntry): FastifyHandler {\n return async (request, reply) => {\n // Fastify runs the route handler OUTSIDE the connect-middleware chain, so\n // (unlike Express) the requestScopeMiddleware ALS frame isn't active here.\n // Establish it around the pipeline so REQUEST-scoped DI, contributors, and\n // ctx.set/get work. Reuse the inbound x-request-id, or the id a connect\n // middleware (requestId / requestScope, run earlier via middie) already\n // stamped on the raw request, before generating a fresh one.\n // Use the raw node request as `ctx.req`: it natively provides the stream\n // events (`on('close')` / `once`) that SSE and `ctx.signal` need — Fastify's\n // request object doesn't. Copy Fastify's parsed body/params/query onto it so\n // `ctx.body` / `ctx.params` / `ctx.query` keep working.\n const raw = request.raw as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n raw.body = request.body\n raw.params = request.params\n raw.query = request.query\n\n // @FileUpload: buffer multipart parts into the multer-shaped file(s) that\n // `ctx.file` / `ctx.files` expose. Fastify doesn't parse multipart natively\n // — @fastify/multipart (registered in nodeHandler when any route needs it)\n // provides `request.parts()`. Non-file fields are merged onto `raw.body` so\n // validation / `ctx.body` see them, matching multer's req.body behavior.\n if (entry.meta.upload && entry.meta.upload.mode !== 'none') {\n if (typeof request.parts !== 'function') {\n throw new Error(\n \"@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.\\n\" +\n 'Install it as a peer dependency: pnpm add @fastify/multipart.',\n )\n }\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for await (const part of request.parts()) {\n if (part.type === 'file' && part.toBuffer) {\n rawParts.push({\n fieldname: part.fieldname,\n filename: part.filename ?? '',\n mimetype: part.mimetype ?? 'application/octet-stream',\n buffer: await part.toBuffer(),\n })\n } else {\n fields[part.fieldname] = part.value\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload)\n raw.file = file\n raw.files = files\n raw.body = { ...(raw.body as Record<string, unknown> | undefined), ...fields }\n }\n\n const headerId = raw.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || raw.requestId\n const store = createRequestStore(requestId)\n // Propagate the resolved id back onto the raw request so anything reading\n // `req.requestId` inside the frame (e.g. request-logger) matches the store\n // and the X-Request-Id response header — parity with Express.\n raw.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(raw as never, reply as never, NOOP_NEXT, replyDriver(reply))\n\n // Validation (from @Get(path, schema) / route.validation). `validate` is a\n // connect-style middleware that mutates req.body/query/params to the parsed\n // value and calls next(err) on failure — it never touches `res`, so it runs\n // cleanly here. A rejection propagates to the error handler (422).\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(raw as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n // Class + method middleware — `(ctx, next)`; await each, stop if it ended\n // the response or called next(err).\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 || reply.sent) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (reply.sent) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The Fastify HTTP runtime. Pass to `bootstrap({ runtime: fastifyRuntime() })`.\n * Per spec §10: Fastify's built-in pino logger is disabled (`logger: false`) so\n * the kickjs `requestLogger` middleware stays the single log format.\n */\nexport function fastifyRuntime(): HttpRuntime<FastifyAppLike> {\n return {\n name: 'fastify',\n\n createApp(options: RuntimeAppOptions = {}): FastifyAppLike {\n const Fastify = loadPeer('fastify')\n const app = Fastify({\n logger: false,\n // trustProxy passed through for now; core X-Forwarded-For\n // normalization (spec §10 Q1) is a follow-up.\n trustProxy: typeof options.trustProxy === 'function' ? false : options.trustProxy,\n }) as FastifyAppLike & {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n }\n app[QUEUE] = []\n return app\n },\n\n nodeHandler(app) {\n // Register middie + queued connect middleware + finalize `ready()` exactly\n // ONCE per app, even though Application may call nodeHandler per request.\n const state = app as {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n [READY]?: Promise<void>\n [NEEDS_MULTIPART]?: boolean\n }\n if (!state[READY]) {\n state[READY] = (async () => {\n // Register @fastify/multipart first when any route uses @FileUpload so\n // `request.parts()` is available. Our applyUploadConfig enforces the\n // real per-file maxSize, so give the plugin a generous fileSize ceiling\n // (its 1MB default would silently truncate larger uploads first).\n if (state[NEEDS_MULTIPART]) {\n const multipart = loadPeer('@fastify/multipart')\n await app.register(multipart, { limits: { fileSize: Number.MAX_SAFE_INTEGER } })\n }\n const middie = loadPeer('@fastify/middie')\n await app.register(middie)\n for (const { mw, opts } of state[QUEUE] ?? []) {\n if (opts?.path !== undefined)\n (app.use as (p: unknown, m: unknown) => void)(opts.path, mw)\n else (app.use as (m: unknown) => void)(mw)\n }\n await app.ready()\n })()\n }\n const readyP = state[READY]\n\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n readyP\n .then(() => app.routing(req, res))\n .catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n if (entry.meta.upload) (app as { [NEEDS_MULTIPART]?: boolean })[NEEDS_MULTIPART] = true\n const url = joinPath(mountPath, entry.path)\n app.route({ method: entry.method, url, handler: routeHandler(entry) })\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const queue = (\n app as { [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }> }\n )[QUEUE]\n queue?.push({ mw, opts })\n },\n\n serveStatic(app, prefix, dir) {\n // Serve via `serve-static` (the standalone connect middleware behind\n // express.static) so the Fastify runtime carries no `express` dependency —\n // queued like any other connect middleware, bridged through middie.\n const serveStatic = loadPeer('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n app.setNotFoundHandler((request, reply) => {\n const next = (request.raw as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT]\n if (next) {\n // Vite dev fall-through: hand the request back to the outer chain.\n reply.hijack()\n next()\n return\n }\n // Pass the reply DRIVER (not raw res) so the connect-style\n // notFoundHandler's `res.status().json()` lands on the Fastify reply.\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n app.setErrorHandler((err, request, reply) => {\n // Reply driver (not raw res) so the connect-style errorHandler's\n // `res.status().json()` / `setHeader` land on the Fastify reply.\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n err,\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The Fastify runtime's engine-native types — what the runtime-typed escape\n * hatches resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become `FastifyInstance`; `ctx.req` / `ctx.res` the Fastify request / reply.\n * Mirrors {@link import('../runtime').ExpressRuntimeTypes} for the default engine.\n */\nexport interface FastifyRuntimeTypes {\n request: import('fastify').FastifyRequest\n response: import('fastify').FastifyReply\n app: import('fastify').FastifyInstance\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n // A root route path ('/' or '') maps to the mount prefix itself — otherwise a\n // controller `@Post('/')` would register at `${prefix}/`, and a request to\n // `${prefix}` (no trailing slash) wouldn't match under Fastify's strict router.\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":";;;;;;;;;;oSAoCA,MAAM,YAAc,cAAc,OAAO,KAAK,GAAG,EAC3C,SAAY,MAAsB,CACtC,IAAM,IAAM,YAAY,IAAI,EAC5B,OAAO,KAAO,IAAI,YAAc,IAAI,QAAU,IAAI,QAAW,IAAI,SAAW,GAC9E,EAmCM,MAAQ,OAAO,6BAA6B,EAC5C,MAAQ,OAAO,sBAAsB,EACrC,KAAO,OAAO,qBAAqB,EAGnC,gBAAkB,OAAO,+BAA+B,EACxD,cAAwB,CAAC,EAa/B,SAAS,YAAY,MAA0C,CAC7D,IAAM,OAA0B,CAC9B,OAAO,KAAM,CAEX,OADA,MAAM,KAAK,IAAI,EACR,MACT,EACA,KAAK,KAAM,CAET,OADA,MAAM,KAAK,IAAI,EACR,MACT,EACA,KAAK,KAAM,CAET,OADA,MAAM,KAAK,IAAI,EACR,MACT,EACA,KAAK,YAAa,CAEhB,OADA,MAAM,KAAK,WAAW,EACf,MACT,EACA,UAAU,KAAM,MAAO,CAErB,OADA,MAAM,OAAO,KAAM,KAAK,EACjB,MACT,EACA,QAAS,CACP,MAAU,MAAM,wEAAwE,CAC1F,EACA,UAAU,WAAY,QAAS,CAK7B,OAFA,MAAM,OAAO,EACb,MAAM,IAAI,UAAU,WAAY,OAAO,EAChC,MACT,EACA,cAAe,CAEb,OADC,MAAO,IAAuD,eAAe,EACvE,MACT,EACA,MAAM,MAAO,CACX,OAAO,MAAM,IAAI,MAAM,KAAK,CAC9B,EACA,IAAI,KAAM,CAER,OADA,MAAM,IAAI,IAAI,IAAI,EACX,MACT,EACA,KAAK,MAAO,SAAU,CAEpB,OADA,MAAM,IAAI,KAAK,MAAO,QAAQ,EACvB,MACT,EACA,IAAI,aAAc,CAChB,OAAO,MAAM,MAAQ,MAAM,IAAI,WACjC,CACF,EACA,OAAO,MACT,CAGA,SAAS,aAAa,MAAmC,CACvD,OAAO,MAAO,QAAS,QAAU,CAW/B,IAAM,IAAM,QAAQ,IAiBpB,GATA,IAAI,KAAO,QAAQ,KACnB,IAAI,OAAS,QAAQ,OACrB,IAAI,MAAQ,QAAQ,MAOhB,MAAM,KAAK,QAAU,MAAM,KAAK,OAAO,OAAS,OAAQ,CAC1D,GAAI,OAAO,QAAQ,OAAU,WAC3B,MAAU,MACR;8DAEF,EAEF,IAAM,SAA4B,CAAC,EAC7B,OAAkC,CAAC,EACzC,UAAW,IAAM,QAAQ,QAAQ,MAAM,EACjC,KAAK,OAAS,QAAU,KAAK,SAC/B,SAAS,KAAK,CACZ,UAAW,KAAK,UAChB,SAAU,KAAK,UAAY,GAC3B,SAAU,KAAK,UAAY,2BAC3B,OAAQ,MAAM,KAAK,SAAS,CAC9B,CAAC,EAED,OAAO,KAAK,WAAa,KAAK,MAGlC,GAAM,CAAE,KAAM,OAAU,kBAAkB,SAAU,MAAM,KAAK,MAAM,EACrE,IAAI,KAAO,KACX,IAAI,MAAQ,MACZ,IAAI,KAAO,CAAE,GAAI,IAAI,KAA8C,GAAG,MAAO,CAC/E,CAEA,IAAM,SAAW,IAAI,QAAQ,gBAEvB,MAAQ,oBADK,MAAM,QAAQ,QAAQ,EAAI,SAAS,GAAK,WAAa,IAAI,SAClC,EAI1C,IAAI,UAAY,MAAM,UAEtB,MAAM,aAAa,IAAI,MAAO,SAAY,CACxC,IAAM,IAAM,IAAI,eAAe,IAAc,MAAgB,UAAW,YAAY,KAAK,CAAC,EAM1F,GAAI,MAAM,KAAK,WAAY,CACzB,IAAM,EAAI,SAAS,MAAM,KAAK,UAAU,EACxC,MAAM,IAAI,SAAe,QAAS,SAAW,CAC3C,EAAE,IAAc,IAAA,GAAqB,KAAmB,IAAM,OAAO,GAAG,EAAI,QAAQ,CAAE,CACxF,CAAC,CACH,CAIA,IAAK,IAAM,MAAM,MAAM,YAAa,CAClC,IAAI,SAAW,GASf,GARA,MAAM,IAAI,SAAe,QAAS,SAAW,CAM3C,QAAQ,QAAQ,GAAG,IALL,KAAwB,CACpC,SAAW,GACP,IAAK,OAAO,GAAG,EACd,QAAQ,CACf,CAC4B,CAAC,CAAC,CAAC,MAAM,MAAM,CAC7C,CAAC,EACG,CAAC,UAAY,MAAM,KAAM,MAC/B,CAEI,MAAM,mBAAmB,MAAM,MAAM,kBAAkB,GAAG,EAC1D,OAAM,MACV,MAAM,MAAM,QAAQ,GAAG,CACzB,CAAC,CACH,CACF,CAOA,SAAgB,gBAA8C,CAC5D,MAAO,CACL,KAAM,UAEN,UAAU,QAA6B,CAAC,EAAmB,CAEzD,IAAM,IADU,SAAS,SACP,CAAC,CAAC,CAClB,OAAQ,GAGR,WAAY,OAAO,QAAQ,YAAe,WAAa,GAAQ,QAAQ,UACzE,CAAC,EAID,MADA,KAAI,OAAS,CAAC,EACP,GACT,EAEA,YAAY,IAAK,CAGf,IAAM,MAAQ,IAKT,MAAM,SACT,MAAM,QAAU,SAAY,CAK1B,GAAI,MAAM,iBAAkB,CAC1B,IAAM,UAAY,SAAS,oBAAoB,EAC/C,MAAM,IAAI,SAAS,UAAW,CAAE,OAAQ,CAAE,gBAAkC,CAAE,CAAC,CACjF,CACA,IAAM,OAAS,SAAS,iBAAiB,EACzC,MAAM,IAAI,SAAS,MAAM,EACzB,IAAK,GAAM,CAAE,GAAI,QAAU,MAAM,QAAU,CAAC,EACtC,MAAM,OAAS,IAAA,GAEd,IAAK,IAA6B,EAAE,EADvC,IAAK,IAAyC,KAAK,KAAM,EAAE,EAG/D,MAAM,IAAI,MAAM,CAClB,EAAA,CAAG,GAEL,IAAM,OAAS,MAAM,OAErB,OAAQ,IAAsB,IAAqB,OAAmC,CAChF,OAAM,IAAgE,MAAQ,MAClF,OACG,SAAW,IAAI,QAAQ,IAAK,GAAG,CAAC,CAAC,CACjC,MAAO,KAAQ,CACV,MAAM,KAAK,GAAG,CACpB,CAAC,CACL,CACF,EAEA,YAAY,IAAK,MAAmB,CAClC,IAAK,GAAM,CAAE,UAAW,UAAY,MAClC,IAAK,IAAM,SAAS,OAAQ,CACtB,MAAM,KAAK,SAAQ,IAAyC,iBAAmB,IACnF,IAAM,IAAM,SAAS,UAAW,MAAM,IAAI,EAC1C,IAAI,MAAM,CAAE,OAAQ,MAAM,OAAQ,IAAK,QAAS,aAAa,KAAK,CAAE,CAAC,CACvE,CAEJ,EAEA,WAAW,IAAK,GAAuB,KAA0B,CAI/D,IADE,MACG,EAAE,KAAK,CAAE,GAAI,IAAK,CAAC,CAC1B,EAEA,YAAY,IAAK,OAAQ,IAAK,CAI5B,IAAM,YAAc,SAAS,cAAc,EAC3C,KAAK,WAAW,IAAK,YAAY,GAAG,EAAwB,CAAE,KAAM,MAAO,CAAC,CAC9E,EAEA,YAAY,IAAK,GAAuB,CACtC,IAAI,oBAAoB,QAAS,QAAU,CACzC,IAAM,KAAQ,QAAQ,IAA+D,MACrF,GAAI,KAAM,CAER,MAAM,OAAO,EACb,KAAK,EACL,MACF,CAGC,GACC,QAAQ,IACR,YAAY,KAAK,EACjB,SACF,CACF,CAAC,CACH,EAEA,gBAAgB,IAAK,GAAuB,CAC1C,IAAI,iBAAiB,IAAK,QAAS,QAAU,CAG1C,GACC,IACA,QAAQ,IACR,YAAY,KAAK,EACjB,SACF,CACF,CAAC,CACH,EAEA,aAAc,CACZ,OAAQ,GACR,QAAS,GACT,kBAAmB,GACnB,kBAAmB,EACrB,CACF,CACF,CAgBA,SAAS,SAAS,UAAmB,KAAsB,CACzD,IAAM,EAAI,UAAU,SAAS,GAAG,EAAI,UAAU,MAAM,EAAG,EAAE,EAAI,UAM7D,OAFI,OAAS,KAAO,OAAS,GAAW,IAAM,GAAK,IAAM,EAElD,GAAG,IADA,KAAK,WAAW,GAAG,EAAI,KAAO,IAAI,QAE9C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;;UA2CU,SAAA;EACR,GAAA,CAAI,IAAA,WAAe,IAAA;EACnB,OAAA;AAAA;;;;;;iBAoJc,SAAA,IAAa,WAAW,CAAC,SAAA;;AApJhC;AAoJT;;;;AAAkD;
|
|
1
|
+
{"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;;UA2CU,SAAA;EACR,GAAA,CAAI,IAAA,WAAe,IAAA;EACnB,OAAA;AAAA;;;;;;iBAoJc,SAAA,IAAa,WAAW,CAAC,SAAA;;AApJhC;AAoJT;;;;AAAkD;UAoGjC,cAAA;EACf,OAAA,eAAsB,OAAA;EACtB,QAAA,eAAuB,OAAA;EACvB,GAAA,eAAkB,GAAA;AAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.18.0-alpha.
|
|
2
|
+
* @forinda/kickjs v5.18.0-alpha.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{i as requestStore,t as RequestContext}from"../../context-
|
|
11
|
+
import{i as requestStore,t as RequestContext}from"../../context-DGsCYQRP.mjs";import{t as createRequestStore}from"../../request-scope-DiBal5Ln.mjs";import{validate}from"../middleware/validate.mjs";import{applyUploadConfig}from"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),h3=()=>peerRequire(`h3`),NEXT=Symbol(`kickjs.h3.next`),ERROR_MW=Symbol(`kickjs.h3.errorMw`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);function resDriver(res){let driver={status(code){return res.statusCode=code,driver},json(data){return res.headersSent||res.setHeader(`content-type`,`application/json; charset=utf-8`),res.end(JSON.stringify(data)),driver},send(data){return res.end(data),driver},type(contentType){return res.setHeader(`content-type`,contentType),driver},setHeader(name,value){return res.setHeader(name,value),driver},render(){throw Error(`ctx.render() is not supported on the h3 runtime (no view engine).`)},writeHead(statusCode,headers){return res.writeHead(statusCode,headers),driver},flushHeaders(){return res.flushHeaders(),driver},write(chunk){return res.write(chunk)},end(data){return res.end(data),driver},once(event,listener){return res.once(event,listener),driver},get headersSent(){return res.headersSent}};return driver}function makeEventHandler(entry){let{getRouterParams,getQuery,readBody,readMultipartFormData}=h3(),upload=entry.meta.upload;return async event=>{let req=event.node.req,res=event.node.res;if(req.params=getRouterParams(event)??{},req.query=getQuery(event)??{},upload&&upload.mode!==`none`){let form=await readMultipartFormData(event).catch(()=>void 0),rawParts=[],fields={};for(let part of form??[])part.filename?rawParts.push({fieldname:part.name??``,filename:part.filename,mimetype:part.type??`application/octet-stream`,buffer:part.data}):part.name&&(fields[part.name]=part.data.toString(`utf-8`));let{file,files}=applyUploadConfig(rawParts,upload);req.file=file,req.files=files,req.body=fields}else BODY_METHODS.has(req.method??`GET`)&&(req.body=await readBody(event).catch(()=>void 0));let headerId=req.headers[`x-request-id`],store=createRequestStore((Array.isArray(headerId)?headerId[0]:headerId)||req.requestId);req.requestId=store.requestId,await requestStore.run(store,async()=>{let ctx=new RequestContext(req,res,NOOP_NEXT,resDriver(res));if(entry.meta.validation){let v=validate(entry.meta.validation);await new Promise((resolve,reject)=>{v(req,void 0,err=>err?reject(err):resolve())})}for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||res.writableEnded)return}entry.contributorRunner&&await entry.contributorRunner(ctx),!res.writableEnded&&await entry.handler(ctx)})}}function h3Runtime(){return{name:`h3`,createApp(_options={}){let{createApp}=h3(),app=createApp({onError:(error,event)=>{let mw=app[ERROR_MW];mw&&!event.node.res.writableEnded&&mw(error,event.node.req,resDriver(event.node.res),NOOP_NEXT)}});return app},nodeHandler(app){let{toNodeListener}=h3(),listener=toNodeListener(app);return(req,res,next)=>{next&&(req[NEXT]=next),Promise.resolve(listener(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){let{createRouter,eventHandler}=h3(),router=createRouter();for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path);router[entry.method.toLowerCase()](url,eventHandler(makeEventHandler(entry)))}app.use(router)},useConnect(app,mw,opts){let{fromNodeMiddleware}=h3(),handler=fromNodeMiddleware(mw);opts?.path===void 0?app.use(handler):app.use(opts.path,handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){let{eventHandler}=h3();app.use(eventHandler(event=>{let next=event.node.req[NEXT];if(next){next();return}mw(event.node.req,resDriver(event.node.res),NOOP_NEXT)}))},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3Runtime};
|
|
12
12
|
//# sourceMappingURL=h3.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3.mjs","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"sourcesContent":["// The h3 runtime — `@forinda/kickjs/h3`.\n//\n// h3 (unjs) is the HTTP layer behind Nitro / Nuxt. This binds it to the same\n// HttpRuntime contract as Express and Fastify: routes become native h3 router\n// routes, and the node `ServerResponse` (h3 exposes it as `event.node.res`) is\n// wrapped in a RuntimeResponse so `ctx.json` / `ctx.html` / `ctx.sse` work\n// unchanged.\n//\n// Targets h3 **v1** (stable, node-based: `createApp` / `createRouter` /\n// `toNodeListener`, with `event.node.req` / `event.node.res`). `h3` is an\n// optional peer pinned to `^1`.\n//\n// h3 v2 (https://h3.dev/migration) moves to a web-standard `Request`/`Response`\n// core. Adopting it is the spec's §8 \"web-standard driver\" — `RuntimeResponse`\n// was shaped so a future `webRuntime()` can implement it over WHATWG streams,\n// at which point h3 v2 (and edge/Bun/Deno) become a driver swap rather than a\n// rewrite. Until that lands, this binding uses the node-compatible v1 surface.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\nconst peerRequire = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst h3 = (): any => peerRequire('h3')\n\ninterface H3EventLike {\n node: { req: IncomingMessage; res: ServerResponse }\n}\ninterface H3AppLike {\n use(arg1: unknown, arg2?: unknown): unknown\n handler: unknown\n}\n\nconst NEXT = Symbol('kickjs.h3.next')\nconst ERROR_MW = Symbol('kickjs.h3.errorMw')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse. */\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n res.statusCode = code\n return driver\n },\n json(data) {\n if (!res.headersSent) res.setHeader('content-type', 'application/json; charset=utf-8')\n res.end(JSON.stringify(data))\n return driver\n },\n send(data) {\n res.end(data as never)\n return driver\n },\n type(contentType) {\n res.setHeader('content-type', contentType)\n return driver\n },\n setHeader(name, value) {\n res.setHeader(name, value as never)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n res.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n res.flushHeaders()\n return driver\n },\n write(chunk) {\n return res.write(chunk)\n },\n end(data) {\n res.end(data as never)\n return driver\n },\n once(event, listener) {\n res.once(event, listener)\n return driver\n },\n get headersSent() {\n return res.headersSent\n },\n }\n return driver\n}\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(entry: RouteEntry): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n return async (event: H3EventLike) => {\n const req = event.node.req as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n const res = event.node.res\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n if (upload && upload.mode !== 'none') {\n // @FileUpload: read the multipart body once (readBody would consume the\n // same stream, so the two are mutually exclusive). File parts → the\n // multer-shaped ctx.file/ctx.files; non-file fields → req.body.\n const form = (await readMultipartFormData(event).catch(() => undefined)) as\n | Array<{ name?: string; filename?: string; type?: string; data: Buffer }>\n | undefined\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const part of form ?? []) {\n if (part.filename) {\n rawParts.push({\n fieldname: part.name ?? '',\n filename: part.filename,\n mimetype: part.type ?? 'application/octet-stream',\n buffer: part.data,\n })\n } else if (part.name) {\n fields[part.name] = part.data.toString('utf-8')\n }\n }\n const { file, files } = applyUploadConfig(rawParts, upload)\n req.file = file\n req.files = files\n req.body = fields\n } else if (BODY_METHODS.has(req.method ?? 'GET')) {\n req.body = await readBody(event).catch(() => undefined)\n }\n\n const headerId = req.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || req.requestId\n const store = createRequestStore(requestId)\n req.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(req as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || res.writableEnded) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (res.writableEnded) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The h3 HTTP runtime. Pass to `bootstrap({ runtime: h3Runtime() })`.\n * h3 parses bodies itself (`readBody`), so the Application skips its default\n * `express.json()` (see `nativeBodyParsing`).\n */\nexport function h3Runtime(): HttpRuntime<H3AppLike> {\n return {\n name: 'h3',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const { createApp } = h3()\n const app = createApp({\n onError: (error: unknown, event: H3EventLike) => {\n const mw = (app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW]\n if (mw && !event.node.res.writableEnded) {\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n error,\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }\n },\n })\n return app as H3AppLike\n },\n\n nodeHandler(app) {\n const { toNodeListener } = h3()\n const listener = toNodeListener(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n Promise.resolve(listener(req, res)).catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n const { createRouter, eventHandler } = h3()\n const router = createRouter()\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const method = entry.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n router[method](url, eventHandler(makeEventHandler(entry)))\n }\n }\n app.use(router)\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const { fromNodeMiddleware } = h3()\n const handler = fromNodeMiddleware(mw)\n if (opts?.path !== undefined) app.use(opts.path, handler)\n else app.use(handler)\n },\n\n serveStatic(app, prefix, dir) {\n const expressStatic = peerRequire('express').static\n this.useConnect(app, expressStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n const { eventHandler } = h3()\n // Registered after the router, so it only runs when nothing matched.\n app.use(\n eventHandler((event: H3EventLike) => {\n const next = (event.node.req as IncomingMessage & { [NEXT]?: () => void })[NEXT]\n if (next) {\n next()\n return\n }\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }),\n )\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The h3 runtime's engine-native types — what the runtime-typed escape hatches\n * resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become the h3 `App`; `ctx.req` / `ctx.res` the per-request `H3Event` (h3 v1\n * routes everything through the event). Mirrors `ExpressRuntimeTypes`.\n */\nexport interface H3RuntimeTypes {\n request: import('h3').H3Event\n response: import('h3').H3Event\n app: import('h3').App\n}\n\n/** Join a mount prefix and a route path; a root path maps to the prefix itself. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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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 { RequestContext } from '../context'\nimport { requestStore } from '../request-store'\nimport { createRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\nconst peerRequire = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst h3 = (): any => peerRequire('h3')\n\ninterface H3EventLike {\n node: { req: IncomingMessage; res: ServerResponse }\n}\ninterface H3AppLike {\n use(arg1: unknown, arg2?: unknown): unknown\n handler: unknown\n}\n\nconst NEXT = Symbol('kickjs.h3.next')\nconst ERROR_MW = Symbol('kickjs.h3.errorMw')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Wrap a node ServerResponse (from `event.node.res`) as a RuntimeResponse. */\nfunction resDriver(res: ServerResponse): RuntimeResponse {\n const driver: RuntimeResponse = {\n status(code) {\n res.statusCode = code\n return driver\n },\n json(data) {\n if (!res.headersSent) res.setHeader('content-type', 'application/json; charset=utf-8')\n res.end(JSON.stringify(data))\n return driver\n },\n send(data) {\n res.end(data as never)\n return driver\n },\n type(contentType) {\n res.setHeader('content-type', contentType)\n return driver\n },\n setHeader(name, value) {\n res.setHeader(name, value as never)\n return driver\n },\n render() {\n throw new Error('ctx.render() is not supported on the h3 runtime (no view engine).')\n },\n writeHead(statusCode, headers) {\n res.writeHead(statusCode, headers)\n return driver\n },\n flushHeaders() {\n res.flushHeaders()\n return driver\n },\n write(chunk) {\n return res.write(chunk)\n },\n end(data) {\n res.end(data as never)\n return driver\n },\n once(event, listener) {\n res.once(event, listener)\n return driver\n },\n get headersSent() {\n return res.headersSent\n },\n }\n return driver\n}\n\n/** Build the h3 event handler that runs the kickjs pipeline for one route. */\nfunction makeEventHandler(entry: RouteEntry): (event: H3EventLike) => Promise<void> {\n const { getRouterParams, getQuery, readBody, readMultipartFormData } = h3()\n const upload = entry.meta.upload\n return async (event: H3EventLike) => {\n const req = event.node.req as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n const res = event.node.res\n\n // Populate the express-shaped request fields h3 keeps elsewhere.\n req.params = getRouterParams(event) ?? {}\n req.query = getQuery(event) ?? {}\n if (upload && upload.mode !== 'none') {\n // @FileUpload: read the multipart body once (readBody would consume the\n // same stream, so the two are mutually exclusive). File parts → the\n // multer-shaped ctx.file/ctx.files; non-file fields → req.body.\n const form = (await readMultipartFormData(event).catch(() => undefined)) as\n | Array<{ name?: string; filename?: string; type?: string; data: Buffer }>\n | undefined\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const part of form ?? []) {\n if (part.filename) {\n rawParts.push({\n fieldname: part.name ?? '',\n filename: part.filename,\n mimetype: part.type ?? 'application/octet-stream',\n buffer: part.data,\n })\n } else if (part.name) {\n fields[part.name] = part.data.toString('utf-8')\n }\n }\n const { file, files } = applyUploadConfig(rawParts, upload)\n req.file = file\n req.files = files\n req.body = fields\n } else if (BODY_METHODS.has(req.method ?? 'GET')) {\n req.body = await readBody(event).catch(() => undefined)\n }\n\n const headerId = req.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || req.requestId\n const store = createRequestStore(requestId)\n req.requestId = store.requestId\n\n await requestStore.run(store, async () => {\n const ctx = new RequestContext(req as never, res as never, NOOP_NEXT, resDriver(res))\n\n if (entry.meta.validation) {\n const v = validate(entry.meta.validation)\n await new Promise<void>((resolve, reject) => {\n v(req as never, undefined as never, (err?: unknown) => (err ? reject(err) : resolve()))\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || res.writableEnded) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (res.writableEnded) return\n await entry.handler(ctx)\n })\n }\n}\n\n/**\n * The h3 HTTP runtime. Pass to `bootstrap({ runtime: h3Runtime() })`.\n * h3 parses bodies itself (`readBody`), so the Application skips its default\n * `express.json()` (see `nativeBodyParsing`).\n */\nexport function h3Runtime(): HttpRuntime<H3AppLike> {\n return {\n name: 'h3',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const { createApp } = h3()\n const app = createApp({\n onError: (error: unknown, event: H3EventLike) => {\n const mw = (app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW]\n if (mw && !event.node.res.writableEnded) {\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n error,\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }\n },\n })\n return app as H3AppLike\n },\n\n nodeHandler(app) {\n const { toNodeListener } = h3()\n const listener = toNodeListener(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n Promise.resolve(listener(req, res)).catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n const { createRouter, eventHandler } = h3()\n const router = createRouter()\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const method = entry.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n router[method](url, eventHandler(makeEventHandler(entry)))\n }\n }\n app.use(router)\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const { fromNodeMiddleware } = h3()\n const handler = fromNodeMiddleware(mw)\n if (opts?.path !== undefined) app.use(opts.path, handler)\n else app.use(handler)\n },\n\n serveStatic(app, prefix, dir) {\n // `serve-static` (the standalone connect middleware behind express.static)\n // so the h3 runtime carries no `express` dependency — bridged via\n // fromNodeMiddleware like any other connect middleware.\n const serveStatic = peerRequire('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n const { eventHandler } = h3()\n // Registered after the router, so it only runs when nothing matched.\n app.use(\n eventHandler((event: H3EventLike) => {\n const next = (event.node.req as IncomingMessage & { [NEXT]?: () => void })[NEXT]\n if (next) {\n next()\n return\n }\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n event.node.req,\n resDriver(event.node.res),\n NOOP_NEXT,\n )\n }),\n )\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as { [ERROR_MW]?: ConnectMiddleware })[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The h3 runtime's engine-native types — what the runtime-typed escape hatches\n * resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become the h3 `App`; `ctx.req` / `ctx.res` the per-request `H3Event` (h3 v1\n * routes everything through the event). Mirrors `ExpressRuntimeTypes`.\n */\nexport interface H3RuntimeTypes {\n request: import('h3').H3Event\n response: import('h3').H3Event\n app: import('h3').App\n}\n\n/** Join a mount prefix and a route path; a root path maps to the prefix itself. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":";;;;;;;;;;oSAoCA,MAAM,YAAc,cAAc,OAAO,KAAK,GAAG,EAE3C,OAAgB,YAAY,IAAI,EAUhC,KAAO,OAAO,gBAAgB,EAC9B,SAAW,OAAO,mBAAmB,EACrC,cAAwB,CAAC,EACzB,aAAe,IAAI,IAAI,CAAC,OAAQ,MAAO,QAAS,QAAQ,CAAC,EAG/D,SAAS,UAAU,IAAsC,CACvD,IAAM,OAA0B,CAC9B,OAAO,KAAM,CAEX,MADA,KAAI,WAAa,KACV,MACT,EACA,KAAK,KAAM,CAGT,OAFK,IAAI,aAAa,IAAI,UAAU,eAAgB,iCAAiC,EACrF,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,EACrB,MACT,EACA,KAAK,KAAM,CAET,OADA,IAAI,IAAI,IAAa,EACd,MACT,EACA,KAAK,YAAa,CAEhB,OADA,IAAI,UAAU,eAAgB,WAAW,EAClC,MACT,EACA,UAAU,KAAM,MAAO,CAErB,OADA,IAAI,UAAU,KAAM,KAAc,EAC3B,MACT,EACA,QAAS,CACP,MAAU,MAAM,mEAAmE,CACrF,EACA,UAAU,WAAY,QAAS,CAE7B,OADA,IAAI,UAAU,WAAY,OAAO,EAC1B,MACT,EACA,cAAe,CAEb,OADA,IAAI,aAAa,EACV,MACT,EACA,MAAM,MAAO,CACX,OAAO,IAAI,MAAM,KAAK,CACxB,EACA,IAAI,KAAM,CAER,OADA,IAAI,IAAI,IAAa,EACd,MACT,EACA,KAAK,MAAO,SAAU,CAEpB,OADA,IAAI,KAAK,MAAO,QAAQ,EACjB,MACT,EACA,IAAI,aAAc,CAChB,OAAO,IAAI,WACb,CACF,EACA,OAAO,MACT,CAGA,SAAS,iBAAiB,MAA0D,CAClF,GAAM,CAAE,gBAAiB,SAAU,SAAU,uBAA0B,GAAG,EACpE,OAAS,MAAM,KAAK,OAC1B,OAAO,KAAO,QAAuB,CACnC,IAAM,IAAM,MAAM,KAAK,IAQjB,IAAM,MAAM,KAAK,IAKvB,GAFA,IAAI,OAAS,gBAAgB,KAAK,GAAK,CAAC,EACxC,IAAI,MAAQ,SAAS,KAAK,GAAK,CAAC,EAC5B,QAAU,OAAO,OAAS,OAAQ,CAIpC,IAAM,KAAQ,MAAM,sBAAsB,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EAGhE,SAA4B,CAAC,EAC7B,OAAkC,CAAC,EACzC,IAAK,IAAM,QAAQ,MAAQ,CAAC,EACtB,KAAK,SACP,SAAS,KAAK,CACZ,UAAW,KAAK,MAAQ,GACxB,SAAU,KAAK,SACf,SAAU,KAAK,MAAQ,2BACvB,OAAQ,KAAK,IACf,CAAC,EACQ,KAAK,OACd,OAAO,KAAK,MAAQ,KAAK,KAAK,SAAS,OAAO,GAGlD,GAAM,CAAE,KAAM,OAAU,kBAAkB,SAAU,MAAM,EAC1D,IAAI,KAAO,KACX,IAAI,MAAQ,MACZ,IAAI,KAAO,MACb,MAAW,aAAa,IAAI,IAAI,QAAU,KAAK,IAC7C,IAAI,KAAO,MAAM,SAAS,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,GAGxD,IAAM,SAAW,IAAI,QAAQ,gBAEvB,MAAQ,oBADK,MAAM,QAAQ,QAAQ,EAAI,SAAS,GAAK,WAAa,IAAI,SAClC,EAC1C,IAAI,UAAY,MAAM,UAEtB,MAAM,aAAa,IAAI,MAAO,SAAY,CACxC,IAAM,IAAM,IAAI,eAAe,IAAc,IAAc,UAAW,UAAU,GAAG,CAAC,EAEpF,GAAI,MAAM,KAAK,WAAY,CACzB,IAAM,EAAI,SAAS,MAAM,KAAK,UAAU,EACxC,MAAM,IAAI,SAAe,QAAS,SAAW,CAC3C,EAAE,IAAc,IAAA,GAAqB,KAAmB,IAAM,OAAO,GAAG,EAAI,QAAQ,CAAE,CACxF,CAAC,CACH,CAEA,IAAK,IAAM,MAAM,MAAM,YAAa,CAClC,IAAI,SAAW,GASf,GARA,MAAM,IAAI,SAAe,QAAS,SAAW,CAM3C,QAAQ,QAAQ,GAAG,IALL,KAAwB,CACpC,SAAW,GACP,IAAK,OAAO,GAAG,EACd,QAAQ,CACf,CAC4B,CAAC,CAAC,CAAC,MAAM,MAAM,CAC7C,CAAC,EACG,CAAC,UAAY,IAAI,cAAe,MACtC,CAEI,MAAM,mBAAmB,MAAM,MAAM,kBAAkB,GAAG,EAC1D,KAAI,eACR,MAAM,MAAM,QAAQ,GAAG,CACzB,CAAC,CACH,CACF,CAOA,SAAgB,WAAoC,CAClD,MAAO,CACL,KAAM,KAEN,UAAU,SAA8B,CAAC,EAAc,CACrD,GAAM,CAAE,WAAc,GAAG,EACnB,IAAM,UAAU,CACpB,SAAU,MAAgB,QAAuB,CAC/C,IAAM,GAAM,IAA2C,UACnD,IAAM,CAAC,MAAM,KAAK,IAAI,eACvB,GACC,MACA,MAAM,KAAK,IACX,UAAU,MAAM,KAAK,GAAG,EACxB,SACF,CAEJ,CACF,CAAC,EACD,OAAO,GACT,EAEA,YAAY,IAAK,CACf,GAAM,CAAE,gBAAmB,GAAG,EACxB,SAAW,eAAe,GAAG,EACnC,OAAQ,IAAsB,IAAqB,OAAmC,CAChF,OAAM,IAAgE,MAAQ,MAClF,QAAQ,QAAQ,SAAS,IAAK,GAAG,CAAC,CAAC,CAAC,MAAO,KAAQ,CAC7C,MAAM,KAAK,GAAG,CACpB,CAAC,CACH,CACF,EAEA,YAAY,IAAK,MAAmB,CAClC,GAAM,CAAE,aAAc,cAAiB,GAAG,EACpC,OAAS,aAAa,EAC5B,IAAK,GAAM,CAAE,UAAW,UAAY,MAClC,IAAK,IAAM,SAAS,OAAQ,CAC1B,IAAM,IAAM,SAAS,UAAW,MAAM,IAAI,EAE1C,OADe,MAAM,OAAO,YAChB,EAAE,CAAC,IAAK,aAAa,iBAAiB,KAAK,CAAC,CAAC,CAC3D,CAEF,IAAI,IAAI,MAAM,CAChB,EAEA,WAAW,IAAK,GAAuB,KAA0B,CAC/D,GAAM,CAAE,oBAAuB,GAAG,EAC5B,QAAU,mBAAmB,EAAE,EACjC,MAAM,OAAS,IAAA,GACd,IAAI,IAAI,OAAO,EADU,IAAI,IAAI,KAAK,KAAM,OAAO,CAE1D,EAEA,YAAY,IAAK,OAAQ,IAAK,CAI5B,IAAM,YAAc,YAAY,cAAc,EAC9C,KAAK,WAAW,IAAK,YAAY,GAAG,EAAwB,CAAE,KAAM,MAAO,CAAC,CAC9E,EAEA,YAAY,IAAK,GAAuB,CACtC,GAAM,CAAE,cAAiB,GAAG,EAE5B,IAAI,IACF,aAAc,OAAuB,CACnC,IAAM,KAAQ,MAAM,KAAK,IAAkD,MAC3E,GAAI,KAAM,CACR,KAAK,EACL,MACF,CACC,GACC,MAAM,KAAK,IACX,UAAU,MAAM,KAAK,GAAG,EACxB,SACF,CACF,CAAC,CACH,CACF,EAEA,gBAAgB,IAAK,GAAuB,CACzC,IAA4C,UAAY,EAC3D,EAEA,aAAc,CACZ,OAAQ,GACR,QAAS,GACT,kBAAmB,GACnB,kBAAmB,EACrB,CACF,CACF,CAgBA,SAAS,SAAS,UAAmB,KAAsB,CACzD,IAAM,EAAI,UAAU,SAAS,GAAG,EAAI,UAAU,MAAM,EAAG,EAAE,EAAI,UAG7D,OAFI,OAAS,KAAO,OAAS,GAAW,IAAM,GAAK,IAAM,EAElD,GAAG,IADA,KAAK,WAAW,GAAG,EAAI,KAAO,IAAI,QAE9C"}
|