@forinda/kickjs 5.15.0 → 5.15.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.
Files changed (51) hide show
  1. package/dist/application-BiyBwafB.d.mts.map +1 -1
  2. package/dist/{application-CHkpRiHo.mjs → application-CBXFiVW8.mjs} +3 -3
  3. package/dist/{application-CHkpRiHo.mjs.map → application-CBXFiVW8.mjs.map} +1 -1
  4. package/dist/{bootstrap-Bwtats8z.mjs → bootstrap-Bh1Dt4Zj.mjs} +3 -3
  5. package/dist/{bootstrap-Bwtats8z.mjs.map → bootstrap-Bh1Dt4Zj.mjs.map} +1 -1
  6. package/dist/config/index.mjs +2 -2
  7. package/dist/{config-C7r8WBXM.mjs → config-IfNt6uOv.mjs} +3 -3
  8. package/dist/{config-C7r8WBXM.mjs.map → config-IfNt6uOv.mjs.map} +1 -1
  9. package/dist/{container-RkKhAKP7.mjs → container-F-qfN8Bx.mjs} +3 -3
  10. package/dist/{container-RkKhAKP7.mjs.map → container-F-qfN8Bx.mjs.map} +1 -1
  11. package/dist/{context-BLZGcP_k.mjs → context-Dj12VEWB.mjs} +3 -3
  12. package/dist/{context-BLZGcP_k.mjs.map → context-Dj12VEWB.mjs.map} +1 -1
  13. package/dist/core/adapter.mjs +1 -1
  14. package/dist/core/app-module.mjs +1 -1
  15. package/dist/core/cache.mjs +1 -1
  16. package/dist/core/container.mjs +2 -2
  17. package/dist/core/cron.mjs +2 -2
  18. package/dist/core/decorators.mjs +2 -2
  19. package/dist/core/errors.mjs +1 -1
  20. package/dist/core/interfaces.mjs +1 -1
  21. package/dist/core/logger.mjs +1 -1
  22. package/dist/core/path.mjs +1 -1
  23. package/dist/core/plugin.mjs +1 -1
  24. package/dist/core/reactivity.mjs +1 -1
  25. package/dist/{core-DQB-4vvK.mjs → core-BK8Rpn_4.mjs} +3 -3
  26. package/dist/{core-DQB-4vvK.mjs.map → core-BK8Rpn_4.mjs.map} +1 -1
  27. package/dist/{env-Y-B4xhwA.mjs → env-B5XGgcxZ.mjs} +3 -3
  28. package/dist/{env-Y-B4xhwA.mjs.map → env-B5XGgcxZ.mjs.map} +1 -1
  29. package/dist/http/application.mjs +2 -2
  30. package/dist/http/bootstrap.mjs +2 -2
  31. package/dist/http/context.mjs +2 -2
  32. package/dist/http/middleware/cors.mjs +1 -1
  33. package/dist/http/middleware/csrf.mjs +1 -1
  34. package/dist/http/middleware/error-handler.mjs +2 -2
  35. package/dist/http/middleware/helmet.mjs +1 -1
  36. package/dist/http/middleware/rate-limit.mjs +1 -1
  37. package/dist/http/middleware/request-id.mjs +1 -1
  38. package/dist/http/middleware/request-logger.mjs +2 -2
  39. package/dist/http/middleware/session.mjs +1 -1
  40. package/dist/http/middleware/spa.mjs +2 -2
  41. package/dist/http/middleware/upload.mjs +1 -1
  42. package/dist/http/middleware/validate.mjs +2 -2
  43. package/dist/http/middleware/views.mjs +2 -2
  44. package/dist/http/query/index.mjs +2 -2
  45. package/dist/http/router-builder.mjs +2 -2
  46. package/dist/index.mjs +2 -2
  47. package/dist/{metadata-CR5EKL_4.mjs → metadata-DbrSf8E-.mjs} +2 -2
  48. package/dist/{metadata-CR5EKL_4.mjs.map → metadata-DbrSf8E-.mjs.map} +1 -1
  49. package/dist/{query-CVJ_WX-p.mjs → query-BkA-clOb.mjs} +2 -2
  50. package/dist/{query-CVJ_WX-p.mjs.map → query-BkA-clOb.mjs.map} +1 -1
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"env-Y-B4xhwA.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,EAAE,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,EAAE,QAAQ,GAAI,EACpC,SAAU,EAAE,KAAK,CAAC,cAAe,aAAc,MAAM,CAAC,EAAE,QAAQ,aAAa,EAC7E,UAAW,EAAE,OAAO,EAAE,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,GACrC,IACb,CAiBA,SAAgB,WAAkB,CAEhC,GAAI,CAEF,UAAQ,QAAQ,EAAE,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,EAAE,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,EAC5D,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-B5XGgcxZ.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,EAAE,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,EAAE,QAAQ,GAAI,EACpC,SAAU,EAAE,KAAK,CAAC,cAAe,aAAc,MAAM,CAAC,EAAE,QAAQ,aAAa,EAC7E,UAAW,EAAE,OAAO,EAAE,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,GACrC,IACb,CAiBA,SAAgB,WAAkB,CAEhC,GAAI,CAEF,UAAQ,QAAQ,EAAE,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,EAAE,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,EAC5D,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.15.0
2
+ * @forinda/kickjs v5.15.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-CHkpRiHo.mjs";export{Application};
11
+ import{t as Application}from"../application-CBXFiVW8.mjs";export{Application};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-Bwtats8z.mjs";export{bootstrap};
11
+ import{t as bootstrap}from"../bootstrap-Bh1Dt4Zj.mjs";export{bootstrap};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-BLZGcP_k.mjs";export{RequestContext};
11
+ import{t as RequestContext}from"../context-Dj12VEWB.mjs";export{RequestContext};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-DQB-4vvK.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};
11
+ import{createLogger}from"../../core/logger.mjs";import"../../core-BK8Rpn_4.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.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-DQB-4vvK.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};
11
+ import{createLogger}from"../../core/logger.mjs";import"../../core-BK8Rpn_4.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.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-RkKhAKP7.mjs";import{Logger}from"../../core/logger.mjs";import"../../core-DQB-4vvK.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};
11
+ import{v as __require}from"../../container-F-qfN8Bx.mjs";import{Logger}from"../../core/logger.mjs";import"../../core-BK8Rpn_4.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.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-DQB-4vvK.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};
11
+ import"../../core-BK8Rpn_4.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.15.0
2
+ * @forinda/kickjs v5.15.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-DQB-4vvK.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};
11
+ import{Logger}from"../../core/logger.mjs";import{n as defineAdapter}from"../../core-BK8Rpn_4.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.15.0
2
+ * @forinda/kickjs v5.15.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 parseSearchQuery,i as parseQuery,n as parseFilters,o as parseSort,r as parsePagination,s as FILTER_OPERATORS,t as buildQueryParams}from"../../query-CVJ_WX-p.mjs";export{FILTER_OPERATORS,buildQueryParams,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort};
11
+ import{a as parseSearchQuery,i as parseQuery,n as parseFilters,o as parseSort,r as parsePagination,s as FILTER_OPERATORS,t as buildQueryParams}from"../../query-BkA-clOb.mjs";export{FILTER_OPERATORS,buildQueryParams,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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-RkKhAKP7.mjs";import{METADATA}from"../core/interfaces.mjs";import{a as getMethodMeta,o as getMethodMetaOrUndefined,t as getClassMeta}from"../metadata-CR5EKL_4.mjs";import{c as runContributors,l as buildPipeline}from"../core-DQB-4vvK.mjs";import{t as RequestContext}from"../context-BLZGcP_k.mjs";import{validate}from"./middleware/validate.mjs";import{buildUploadMiddleware}from"./middleware/upload.mjs";import{Router}from"express";let _externalContributorSources=[];function _setExternalContributorSources(sources){_externalContributorSources=sources}function buildRoutes(controllerClass,options={}){let router=Router(),container=Container.getInstance(),externalSources=options.externalSources??_externalContributorSources,routes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddlewares=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]),classContributors=getClassMeta(METADATA.CLASS_CONTRIBUTORS,controllerClass,[]);for(let route of routes){let method=route.method.toLowerCase(),fullPath=route.path||`/`,methodMiddlewares=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass,route.handlerName,[]),handlers=[];route.validation&&handlers.push(validate(route.validation));let fileUploadConfig=getMethodMetaOrUndefined(METADATA.FILE_UPLOAD,controllerClass,route.handlerName);fileUploadConfig&&handlers.push(buildUploadMiddleware(fileUploadConfig));for(let mw of[...classMiddlewares,...methodMiddlewares])handlers.push((req,res,next)=>{let ctx=new RequestContext(req,res,next);Promise.resolve(mw(ctx,next)).catch(next)});let methodContributors=getMethodMeta(METADATA.METHOD_CONTRIBUTORS,controllerClass,route.handlerName,[]);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:`${route.method.toUpperCase()} ${fullPath}`});handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await runContributors({pipeline,ctx,container}),next()}catch(err){next(err)}})}handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await container.resolve(controllerClass)[route.handlerName](ctx)}catch(err){next(err)}}),router[method](fullPath,...handlers)}return router}export{_setExternalContributorSources,buildRoutes};
11
+ import{t as Container}from"../container-F-qfN8Bx.mjs";import{METADATA}from"../core/interfaces.mjs";import{a as getMethodMeta,o as getMethodMetaOrUndefined,t as getClassMeta}from"../metadata-DbrSf8E-.mjs";import{c as runContributors,l as buildPipeline}from"../core-BK8Rpn_4.mjs";import{t as RequestContext}from"../context-Dj12VEWB.mjs";import{validate}from"./middleware/validate.mjs";import{buildUploadMiddleware}from"./middleware/upload.mjs";import{Router}from"express";let _externalContributorSources=[];function _setExternalContributorSources(sources){_externalContributorSources=sources}function buildRoutes(controllerClass,options={}){let router=Router(),container=Container.getInstance(),externalSources=options.externalSources??_externalContributorSources,routes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddlewares=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]),classContributors=getClassMeta(METADATA.CLASS_CONTRIBUTORS,controllerClass,[]);for(let route of routes){let method=route.method.toLowerCase(),fullPath=route.path||`/`,methodMiddlewares=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass,route.handlerName,[]),handlers=[];route.validation&&handlers.push(validate(route.validation));let fileUploadConfig=getMethodMetaOrUndefined(METADATA.FILE_UPLOAD,controllerClass,route.handlerName);fileUploadConfig&&handlers.push(buildUploadMiddleware(fileUploadConfig));for(let mw of[...classMiddlewares,...methodMiddlewares])handlers.push((req,res,next)=>{let ctx=new RequestContext(req,res,next);Promise.resolve(mw(ctx,next)).catch(next)});let methodContributors=getMethodMeta(METADATA.METHOD_CONTRIBUTORS,controllerClass,route.handlerName,[]);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:`${route.method.toUpperCase()} ${fullPath}`});handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await runContributors({pipeline,ctx,container}),next()}catch(err){next(err)}})}handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await container.resolve(controllerClass)[route.handlerName](ctx)}catch(err){next(err)}}),router[method](fullPath,...handlers)}return router}export{_setExternalContributorSources,buildRoutes};
12
12
  //# sourceMappingURL=router-builder.mjs.map
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.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{_ as isInjectionToken,a as formatKickError,c as UnknownAssetError,d as resolveAsset,f as useAssets,g as createToken,h as INJECTION_TOKEN,i as KickError,l as assets,m as stripExt,n as tokenName,o as ASSETS,p as groupAssetKeys,s as ASSET_MANIFEST_VERSION,t as Container,u as clearAssetCache}from"./container-RkKhAKP7.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{ConsoleLoggerProvider,Logger,createLogger}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-CR5EKL_4.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{_ as ModuleList,a as MissingMountDepError,c as runContributors,d as DuplicateContributorError,f as MissingContributorError,g as MutableModuleRegistry,h as CircuitOpenError,i as DuplicateMountNameError,l as buildPipeline,m as CircuitBreaker,n as defineAdapter,o as MountCycleError,p as defineContextDecorator,r as definePlugin,s as mountSort,t as defineAugmentation,u as ContributorCycleError,v as defineModules,y as defineModule}from"./core-DQB-4vvK.mjs";import{CRON_META,Cron,getCronJobs}from"./core/cron.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{i as requestStore,n as getRequestStore,r as getRequestValue,t as RequestContext}from"./context-BLZGcP_k.mjs";import{n as requestScopeMiddleware,t as Application}from"./application-CHkpRiHo.mjs";import{a as parseSearchQuery,i as parseQuery,n as parseFilters,o as parseSort,r as parsePagination,s as FILTER_OPERATORS,t as buildQueryParams}from"./query-CVJ_WX-p.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload}from"./http/middleware/upload.mjs";import{buildRoutes}from"./http/router-builder.mjs";import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-Y-B4xhwA.mjs";import{n as isClusterPrimary,t as bootstrap}from"./bootstrap-Bwtats8z.mjs";import{csrf}from"./http/middleware/csrf.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-C7r8WBXM.mjs";import crypto from"node:crypto";function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return crypto.randomBytes(16).toString(`hex`)}function generateSpanId(){return crypto.randomBytes(8).toString(`hex`)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,FILTER_OPERATORS,FileUpload,Get,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,KickError,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,Patch,Post,PostConstruct,ProblemException,Problems,Put,REQUEST_ID_HEADER,Repository,RequestContext,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,assets,baseEnvSchema,bootstrap,buildPipeline,buildQueryParams,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createLogger,createToken,csrf,defaultProblemTitle,defineAdapter,defineAugmentation,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,errorHandler,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getRequestStore,getRequestValue,groupAssetKeys,hasClassMeta,helmet,isClusterPrimary,isInjectionToken,isRef,joinPaths,loadEnv,loadEnvFromSchema,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,reactive,ref,reloadEnv,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resolveAsset,resolveMimeTypes,runContributors,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,watch,withBuilder};
11
+ import{_ as isInjectionToken,a as formatKickError,c as UnknownAssetError,d as resolveAsset,f as useAssets,g as createToken,h as INJECTION_TOKEN,i as KickError,l as assets,m as stripExt,n as tokenName,o as ASSETS,p as groupAssetKeys,s as ASSET_MANIFEST_VERSION,t as Container,u as clearAssetCache}from"./container-F-qfN8Bx.mjs";import{METADATA,Scope}from"./core/interfaces.mjs";import{ConsoleLoggerProvider,Logger,createLogger}from"./core/logger.mjs";import{a as getMethodMeta,c as pushClassMeta,d as setInMetaMap,f as setInMetaRecord,i as getMetaRecord,l as pushMethodMeta,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,p as setMethodMeta,r as getMetaMap,s as hasClassMeta,t as getClassMeta,u as setClassMeta}from"./metadata-DbrSf8E-.mjs";import{ApiQueryParams,Asset,Autowired,Builder,Component,Controller,Delete,FileUpload,Get,Inject,Injectable,Middleware,Patch,Post,PostConstruct,Put,Repository,Service,Value,normalizeApiQueryParamsConfig,withBuilder}from"./core/decorators.mjs";import{_ as ModuleList,a as MissingMountDepError,c as runContributors,d as DuplicateContributorError,f as MissingContributorError,g as MutableModuleRegistry,h as CircuitOpenError,i as DuplicateMountNameError,l as buildPipeline,m as CircuitBreaker,n as defineAdapter,o as MountCycleError,p as defineContextDecorator,r as definePlugin,s as mountSort,t as defineAugmentation,u as ContributorCycleError,v as defineModules,y as defineModule}from"./core-BK8Rpn_4.mjs";import{CRON_META,Cron,getCronJobs}from"./core/cron.mjs";import{CacheEvict,Cacheable,MemoryCacheProvider,getCacheProvider,setCacheProvider}from"./core/cache.mjs";import{HttpException,HttpStatus,ProblemException,Problems,defaultProblemTitle,normalizeProblem}from"./core/errors.mjs";import{joinPaths,normalizePath}from"./core/path.mjs";import{computed,isRef,reactive,ref,toRefs,unref,watch}from"./core/reactivity.mjs";import{REQUEST_ID_HEADER,requestId}from"./http/middleware/request-id.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{i as requestStore,n as getRequestStore,r as getRequestValue,t as RequestContext}from"./context-Dj12VEWB.mjs";import{n as requestScopeMiddleware,t as Application}from"./application-CBXFiVW8.mjs";import{a as parseSearchQuery,i as parseQuery,n as parseFilters,o as parseSort,r as parsePagination,s as FILTER_OPERATORS,t as buildQueryParams}from"./query-BkA-clOb.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload}from"./http/middleware/upload.mjs";import{buildRoutes}from"./http/router-builder.mjs";import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"./env-B5XGgcxZ.mjs";import{n as isClusterPrimary,t as bootstrap}from"./bootstrap-Bh1Dt4Zj.mjs";import{csrf}from"./http/middleware/csrf.mjs";import{requestLogger}from"./http/middleware/request-logger.mjs";import{helmet}from"./http/middleware/helmet.mjs";import{cors}from"./http/middleware/cors.mjs";import{rateLimit}from"./http/middleware/rate-limit.mjs";import{session}from"./http/middleware/session.mjs";import{ViewAdapter}from"./http/middleware/views.mjs";import{SpaAdapter}from"./http/middleware/spa.mjs";import{n as createConfigService,t as ConfigService}from"./config-IfNt6uOv.mjs";import crypto from"node:crypto";function defineHttpContextDecoratorImpl(spec){return defineContextDecorator(spec)}const defineHttpContextDecorator=Object.freeze(Object.assign(defineHttpContextDecoratorImpl,{withParams:()=>spec=>defineHttpContextDecoratorImpl(spec)})),TRACEPARENT_HEADER=`traceparent`,TRACEPARENT_RE=/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;function generateTraceId(){return crypto.randomBytes(16).toString(`hex`)}function generateSpanId(){return crypto.randomBytes(8).toString(`hex`)}function parseTraceparent(header){let match=TRACEPARENT_RE.exec(header.trim().toLowerCase());if(!match)return null;let version=match[1],traceId=match[2],parentSpanId=match[3],flags=match[4];return version===`ff`||traceId===`00000000000000000000000000000000`||parentSpanId===`0000000000000000`?null:{version,traceId,parentSpanId,flags}}function traceContext(options={}){return(req,res,next)=>{let store=requestStore.getStore(),traceparentHeader=req.headers[TRACEPARENT_HEADER],raw=Array.isArray(traceparentHeader)?traceparentHeader[0]:traceparentHeader,parsed=raw?parseTraceparent(raw):null,traceId=parsed?.traceId??generateTraceId(),spanId=generateSpanId();store&&(store.values.set(`traceId`,traceId),store.values.set(`spanId`,spanId),parsed&&(store.values.set(`parentSpanId`,parsed.parentSpanId),store.values.set(`traceFlags`,parsed.flags),store.values.set(`traceVersion`,parsed.version))),req.traceId=traceId,req.spanId=spanId,options.propagateResponse&&res.setHeader(`traceresponse`,traceId),next()}}export{ASSETS,ASSET_MANIFEST_VERSION,ApiQueryParams,Application,Asset,Autowired,Builder,CRON_META,CacheEvict,Cacheable,CircuitBreaker,CircuitOpenError,Component,ConfigService,ConsoleLoggerProvider,Container,ContributorCycleError,Controller,Cron,Delete,DuplicateContributorError,DuplicateMountNameError,FILTER_OPERATORS,FileUpload,Get,HttpException,HttpStatus,INJECTION_TOKEN,Inject,Injectable,KickError,Logger,METADATA,MemoryCacheProvider,Middleware,MissingContributorError,MissingMountDepError,ModuleList,MountCycleError,MutableModuleRegistry,Patch,Post,PostConstruct,ProblemException,Problems,Put,REQUEST_ID_HEADER,Repository,RequestContext,Scope,Service,SpaAdapter,TRACEPARENT_HEADER,UnknownAssetError,Value,ViewAdapter,assets,baseEnvSchema,bootstrap,buildPipeline,buildQueryParams,buildRoutes,buildUploadMiddleware,cleanupFiles,clearAssetCache,computed,cors,createConfigService,createLogger,createToken,csrf,defaultProblemTitle,defineAdapter,defineAugmentation,defineContextDecorator,defineEnv,defineHttpContextDecorator,defineModule,defineModules,definePlugin,errorHandler,formatKickError,getCacheProvider,getClassMeta,getClassMetaOrUndefined,getCronJobs,getEnv,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,getRequestStore,getRequestValue,groupAssetKeys,hasClassMeta,helmet,isClusterPrimary,isInjectionToken,isRef,joinPaths,loadEnv,loadEnvFromSchema,mountSort,normalizeApiQueryParamsConfig,normalizePath,normalizeProblem,notFoundHandler,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,parseTraceparent,pushClassMeta,pushMethodMeta,rateLimit,reactive,ref,reloadEnv,requestId,requestLogger,requestScopeMiddleware,requestStore,resetEnvCache,resolveAsset,resolveMimeTypes,runContributors,session,setCacheProvider,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,stripExt,toRefs,tokenName,traceContext,unref,upload,useAssets,validate,watch,withBuilder};
12
12
  //# sourceMappingURL=index.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  import"reflect-metadata";function setClassMeta(key,value,target){Reflect.defineMetadata(key,value,target)}function setMethodMeta(key,value,target,method){Reflect.defineMetadata(key,value,target,method)}function getClassMeta(key,target,fallback){return Reflect.getMetadata(key,target)??fallback}function getMethodMeta(key,target,method,fallback){return Reflect.getMetadata(key,target,method)??fallback}function hasClassMeta(key,target){return Reflect.hasMetadata(key,target)}function getClassMetaOrUndefined(key,target){return Reflect.getMetadata(key,target)}function getMethodMetaOrUndefined(key,target,method){return Reflect.getMetadata(key,target,method)}function pushClassMeta(key,target,...items){let existing=Reflect.getOwnMetadata(key,target)??[];Reflect.defineMetadata(key,[...existing,...items],target)}function pushMethodMeta(key,target,method,...items){let existing=Reflect.getOwnMetadata(key,target,method)??[];Reflect.defineMetadata(key,[...existing,...items],target,method)}function setInMetaMap(key,target,mapKey,mapValue){let inherited=Reflect.getOwnMetadata(key,target),map=inherited?new Map(inherited):new Map;map.set(mapKey,mapValue),Reflect.defineMetadata(key,map,target)}function getMetaMap(key,target){return Reflect.getMetadata(key,target)??new Map}function setInMetaRecord(key,target,recKey,recValue){let inherited=Reflect.getOwnMetadata(key,target),rec=inherited?{...inherited}:{};rec[recKey]=recValue,Reflect.defineMetadata(key,rec,target)}function getMetaRecord(key,target){return Reflect.getMetadata(key,target)??{}}export{getMethodMeta as a,pushClassMeta as c,setInMetaMap as d,setInMetaRecord as f,getMetaRecord as i,pushMethodMeta as l,getClassMetaOrUndefined as n,getMethodMetaOrUndefined as o,setMethodMeta as p,getMetaMap as r,hasClassMeta as s,getClassMeta as t,setClassMeta as u};
12
- //# sourceMappingURL=metadata-CR5EKL_4.mjs.map
12
+ //# sourceMappingURL=metadata-DbrSf8E-.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-CR5EKL_4.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
1
+ {"version":3,"file":"metadata-DbrSf8E-.mjs","names":[],"sources":["../src/core/metadata.ts"],"sourcesContent":["import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n"],"mappings":";;;;;;;;;;yBAoBA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,EAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v5.15.0
2
+ * @forinda/kickjs v5.15.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  const FILTER_OPERATORS=new Set([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`between`,`in`,`contains`,`starts`,`ends`]);function parseFilters(filterParam,allowedFields){if(!filterParam)return[];let items=Array.isArray(filterParam)?filterParam:[filterParam],results=[],allowed=allowedFields?new Set(allowedFields):null;for(let item of items){let firstColon=item.indexOf(`:`);if(firstColon===-1)continue;let secondColon=item.indexOf(`:`,firstColon+1);if(secondColon===-1)continue;let field=item.slice(0,firstColon),operator=item.slice(firstColon+1,secondColon),value=item.slice(secondColon+1);!field||!value||FILTER_OPERATORS.has(operator)&&(allowed&&!allowed.has(field)||results.push({field,operator,value}))}return results}function parseSort(sortParam,allowedFields){if(!sortParam)return[];let items=Array.isArray(sortParam)?sortParam:[sortParam],results=[],allowed=allowedFields?new Set(allowedFields):null;for(let item of items){let lastColon=item.lastIndexOf(`:`);if(lastColon===-1)continue;let field=item.slice(0,lastColon),dir=item.slice(lastColon+1).toLowerCase();field&&(dir!==`asc`&&dir!==`desc`||allowed&&!allowed.has(field)||results.push({field,direction:dir}))}return results}function parsePagination(params){let page=typeof params.page==`string`?Number.parseInt(params.page,10):params.page??1,limit=typeof params.limit==`string`?Number.parseInt(params.limit,10):params.limit??20;return(Number.isNaN(page)||page<1)&&(page=1),(Number.isNaN(limit)||limit<1)&&(limit=20),limit>100&&(limit=100),{page,limit,offset:(page-1)*limit}}function parseSearchQuery(q){return q?q.trim().slice(0,200):``}function normalizeFieldConfig(config){if(config)return`columns`in config&&config.columns&&typeof config.columns==`object`?{filterable:Object.keys(config.columns),sortable:config.sortable?Object.keys(config.sortable):void 0,searchable:void 0}:config}function parseQuery(query,fieldConfig){let normalized=normalizeFieldConfig(fieldConfig);return{filters:parseFilters(query.filter,normalized?.filterable),sort:parseSort(query.sort,normalized?.sortable),pagination:parsePagination({page:query.page,limit:query.limit}),search:parseSearchQuery(query.q)}}function buildQueryParams(parsed){let params={};return parsed.filters?.length&&(params.filter=parsed.filters.map(f=>`${f.field}:${f.operator}:${f.value}`)),parsed.sort?.length&&(params.sort=parsed.sort.map(s=>`${s.field}:${s.direction}`)),parsed.pagination&&(params.page=parsed.pagination.page,params.limit=parsed.pagination.limit),parsed.search&&(params.q=parsed.search),params}export{parseSearchQuery as a,parseQuery as i,parseFilters as n,parseSort as o,parsePagination as r,FILTER_OPERATORS as s,buildQueryParams as t};
12
- //# sourceMappingURL=query-CVJ_WX-p.mjs.map
12
+ //# sourceMappingURL=query-BkA-clOb.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-CVJ_WX-p.mjs","names":[],"sources":["../src/http/query/types.ts","../src/http/query/parse-query.ts"],"sourcesContent":["/** Supported filter operators for query string parsing */\nexport type FilterOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'between'\n | 'in'\n | 'contains'\n | 'starts'\n | 'ends'\n\nexport const FILTER_OPERATORS = new Set<string>([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'in',\n 'contains',\n 'starts',\n 'ends',\n])\n\nexport interface FilterItem {\n field: string\n operator: FilterOperator\n value: string\n}\n\nexport interface SortItem {\n field: string\n direction: 'asc' | 'desc'\n}\n\nexport interface PaginationParams {\n page: number\n limit: number\n offset: number\n}\n\n/**\n * The result of parsing a query string. ORM-agnostic — pass this to\n * a query builder adapter (Drizzle, Prisma, Sequelize, etc.) to produce\n * database-specific query objects.\n */\nexport interface ParsedQuery {\n filters: FilterItem[]\n sort: SortItem[]\n pagination: PaginationParams\n search: string\n}\n\n/**\n * Helper that extracts the literal field union from a `QueryFieldConfig`\n * key. When the user passes `{ filterable: ['status', 'category'] } as const`,\n * `FieldsOf<typeof config, 'filterable'>` resolves to `'status' | 'category'`.\n *\n * Without `as const`, the arrays widen to `string[]` and the union falls\n * back to `string`. This is the documented escape hatch.\n */\nexport type FieldsOf<\n TConfig extends QueryFieldConfig | undefined,\n K extends 'filterable' | 'sortable' | 'searchable',\n> = TConfig extends { readonly [P in K]: readonly (infer F)[] }\n ? F extends string\n ? F\n : string\n : TConfig extends { columns: infer C }\n ? K extends 'filterable'\n ? keyof C & string\n : string\n : string\n\n/**\n * Narrowed `ParsedQuery` whose `filters[].field` and `sort[].field`\n * unions are constrained to the literal whitelist passed to\n * `ctx.qs()` / `parseQuery()`. Extends `ParsedQuery`, so any function\n * accepting `ParsedQuery` accepts a `TypedParsedQuery<X>` automatically\n * (subtype assignment, no codegen, backward compatible).\n *\n * @example\n * ```ts\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt'],\n * } as const)\n * parsed.filters[0]?.field // 'status' | 'priority'\n * parsed.sort[0]?.field // 'createdAt'\n * ```\n */\nexport interface TypedParsedQuery<\n TConfig extends QueryFieldConfig | undefined,\n> extends ParsedQuery {\n filters: Array<FilterItem & { field: FieldsOf<TConfig, 'filterable'> }>\n sort: Array<SortItem & { field: FieldsOf<TConfig, 'sortable'> }>\n}\n\n/**\n * Restrict which fields can be filtered, sorted, or searched.\n * Fields not in the allow-list are silently ignored.\n *\n * Accepts two forms:\n * 1. **String arrays** (standard): `{ filterable: ['name', 'status'], sortable: ['createdAt'] }`\n * 2. **Column-object maps** (Drizzle-style): `{ columns: { name: col, status: col }, sortable: { createdAt: col } }`\n *\n * When column-object maps are provided, `Object.keys()` is used to derive the field names.\n */\nexport type QueryFieldConfig = StringQueryFieldConfig | ColumnQueryFieldConfig\n\nexport interface StringQueryFieldConfig {\n filterable?: string[]\n sortable?: string[]\n searchable?: string[]\n}\n\n/**\n * Column-object-based field config (e.g., from DrizzleQueryParamsConfig).\n * Keys are the field names used in query strings; values are ORM column references.\n */\nexport interface ColumnQueryFieldConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/** Standardized paginated response shape */\nexport interface PaginatedResponse<T = any> {\n data: T[]\n meta: {\n page: number\n limit: number\n total: number\n totalPages: number\n hasNext: boolean\n hasPrev: boolean\n }\n}\n\n/**\n * Interface for ORM-specific query builder adapters.\n * Implement this to translate a `ParsedQuery` into your ORM's query format.\n *\n * @example\n * ```ts\n * // Drizzle adapter\n * class DrizzleQueryAdapter implements QueryBuilderAdapter<DrizzleQueryResult> {\n * build(parsed: ParsedQuery, config: DrizzleConfig): DrizzleQueryResult {\n * // Convert filters → Drizzle SQL conditions\n * // Convert sort → Drizzle orderBy\n * return { where, orderBy, limit, offset }\n * }\n * }\n *\n * // Prisma adapter\n * class PrismaQueryAdapter implements QueryBuilderAdapter<PrismaQueryResult> {\n * build(parsed: ParsedQuery, config: PrismaConfig): PrismaQueryResult {\n * return { where, orderBy, skip, take }\n * }\n * }\n * ```\n */\nexport interface QueryBuilderAdapter<TResult = any, TConfig = any> {\n /** Human-readable name for debugging */\n readonly name: string\n\n /**\n * Convert a ParsedQuery into an ORM-specific query object.\n * @param parsed - The ORM-agnostic parsed query\n * @param config - ORM-specific configuration (column maps, search columns, etc.)\n */\n build(parsed: ParsedQuery, config: TConfig): TResult\n}\n","import {\n FILTER_OPERATORS,\n type FilterItem,\n type FilterOperator,\n type SortItem,\n type PaginationParams,\n type ParsedQuery,\n type QueryFieldConfig,\n type StringQueryFieldConfig,\n type TypedParsedQuery,\n} from './types'\n\nconst MAX_SEARCH_LENGTH = 200\nconst DEFAULT_PAGE = 1\nconst DEFAULT_LIMIT = 20\nconst MAX_LIMIT = 100\n\n// ── Individual parsers ──────────────────────────────────────────────────\n\n/** Parse filter strings like \"status:eq:active\" into structured objects */\nexport function parseFilters(\n filterParam: string | string[] | undefined,\n allowedFields?: string[],\n): FilterItem[] {\n if (!filterParam) return []\n\n const items = Array.isArray(filterParam) ? filterParam : [filterParam]\n const results: FilterItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n\n for (const item of items) {\n // Split on first two colons only — value may contain colons (e.g. timestamps)\n const firstColon = item.indexOf(':')\n if (firstColon === -1) continue\n\n const secondColon = item.indexOf(':', firstColon + 1)\n if (secondColon === -1) continue\n\n const field = item.slice(0, firstColon)\n const operator = item.slice(firstColon + 1, secondColon)\n const value = item.slice(secondColon + 1)\n\n if (!field || !value) continue\n if (!FILTER_OPERATORS.has(operator)) continue\n if (allowed && !allowed.has(field)) continue\n\n results.push({ field, operator: operator as FilterOperator, value })\n }\n\n return results\n}\n\n/** Parse sort strings like \"firstName:asc\" into structured objects */\nexport function parseSort(\n sortParam: string | string[] | undefined,\n allowedFields?: string[],\n): SortItem[] {\n if (!sortParam) return []\n\n const items = Array.isArray(sortParam) ? sortParam : [sortParam]\n const results: SortItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n\n for (const item of items) {\n // Split on last colon so field names with colons work\n const lastColon = item.lastIndexOf(':')\n if (lastColon === -1) continue\n\n const field = item.slice(0, lastColon)\n const dir = item.slice(lastColon + 1).toLowerCase()\n\n if (!field) continue\n if (dir !== 'asc' && dir !== 'desc') continue\n if (allowed && !allowed.has(field)) continue\n\n results.push({ field, direction: dir })\n }\n\n return results\n}\n\n/** Parse page/limit into pagination with computed offset */\nexport function parsePagination(params: {\n page?: string | number\n limit?: string | number\n}): PaginationParams {\n let page =\n typeof params.page === 'string'\n ? Number.parseInt(params.page, 10)\n : (params.page ?? DEFAULT_PAGE)\n let limit =\n typeof params.limit === 'string'\n ? Number.parseInt(params.limit, 10)\n : (params.limit ?? DEFAULT_LIMIT)\n\n if (Number.isNaN(page) || page < 1) page = DEFAULT_PAGE\n if (Number.isNaN(limit) || limit < 1) limit = DEFAULT_LIMIT\n if (limit > MAX_LIMIT) limit = MAX_LIMIT\n\n return { page, limit, offset: (page - 1) * limit }\n}\n\n/** Sanitize and truncate search query */\nexport function parseSearchQuery(q: string | undefined): string {\n if (!q) return ''\n return q.trim().slice(0, MAX_SEARCH_LENGTH)\n}\n\n// ── Config normalizer ───────────────────────────────────────────────────\n\n/**\n * Normalize a QueryFieldConfig into the string-based form.\n * Detects column-object-based configs (DrizzleQueryParamsConfig) by checking\n * for a `columns` property and extracts `Object.keys()`.\n */\nfunction normalizeFieldConfig(config?: QueryFieldConfig): StringQueryFieldConfig | undefined {\n if (!config) return undefined\n\n // Column-object-based config (e.g., DrizzleQueryParamsConfig)\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: undefined, // searchColumns are Column objects, not used for string filtering\n }\n }\n\n // Already string-based\n return config as StringQueryFieldConfig\n}\n\n// ── Combined parser ─────────────────────────────────────────────────────\n\n/**\n * Parse a raw Express query object into a structured, ORM-agnostic ParsedQuery.\n *\n * @param query - Raw query string object from `req.query` or Zod-validated object\n * @param fieldConfig - Optional field restrictions (whitelist filterable/sortable/searchable).\n * Accepts both string-based configs (`{ filterable, sortable, searchable }`) and\n * column-object configs (`{ columns, sortable, searchColumns }`).\n *\n * @example\n * ```ts\n * // String-based config\n * const parsed = parseQuery(ctx.query, {\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n *\n * // Column-object config (DrizzleQueryParamsConfig)\n * const parsed = parseQuery(ctx.query, TASK_QUERY_CONFIG)\n * ```\n *\n * Query string format:\n * - Filters: `?filter=field:operator:value` (repeatable)\n * - Sort: `?sort=field:asc|desc` (repeatable)\n * - Pagination: `?page=1&limit=20`\n * - Search: `?q=search+term`\n *\n * Filter operators: eq, neq, gt, gte, lt, lte, between, in, contains, starts, ends\n */\nexport function parseQuery<TConfig extends QueryFieldConfig | undefined = undefined>(\n query: Record<string, any>,\n fieldConfig?: TConfig,\n): TypedParsedQuery<TConfig> {\n const normalized = normalizeFieldConfig(fieldConfig)\n return {\n filters: parseFilters(query.filter, normalized?.filterable),\n sort: parseSort(query.sort, normalized?.sortable),\n pagination: parsePagination({ page: query.page, limit: query.limit }),\n search: parseSearchQuery(query.q),\n } as TypedParsedQuery<TConfig>\n}\n\n// ── Query URL builder (for client-side or testing) ──────────────────────\n\n/** Convert ParsedQuery back into query string parameters */\nexport function buildQueryParams(\n parsed: Partial<ParsedQuery>,\n): Record<string, string | string[] | number> {\n const params: Record<string, string | string[] | number> = {}\n\n if (parsed.filters?.length) {\n params.filter = parsed.filters.map((f) => `${f.field}:${f.operator}:${f.value}`)\n }\n\n if (parsed.sort?.length) {\n params.sort = parsed.sort.map((s) => `${s.field}:${s.direction}`)\n }\n\n if (parsed.pagination) {\n params.page = parsed.pagination.page\n params.limit = parsed.pagination.limit\n }\n\n if (parsed.search) {\n params.q = parsed.search\n }\n\n return params\n}\n"],"mappings":";;;;;;;;;;AAcA,MAAa,iBAAmB,IAAI,IAAY,CAC9C,KACA,MACA,KACA,MACA,KACA,MACA,UACA,KACA,WACA,SACA,MACF,CAAC,ECND,SAAgB,aACd,YACA,cACc,CACd,GAAI,CAAC,YAAa,MAAO,CAAC,EAE1B,IAAM,MAAQ,MAAM,QAAQ,WAAW,EAAI,YAAc,CAAC,WAAW,EAC/D,QAAwB,CAAC,EACzB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KAEzD,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,WAAa,KAAK,QAAQ,GAAG,EACnC,GAAI,aAAe,GAAI,SAEvB,IAAM,YAAc,KAAK,QAAQ,IAAK,WAAa,CAAC,EACpD,GAAI,cAAgB,GAAI,SAExB,IAAM,MAAQ,KAAK,MAAM,EAAG,UAAU,EAChC,SAAW,KAAK,MAAM,WAAa,EAAG,WAAW,EACjD,MAAQ,KAAK,MAAM,YAAc,CAAC,EAEpC,CAAC,OAAS,CAAC,OACV,iBAAiB,IAAI,QAAQ,IAC9B,SAAW,CAAC,QAAQ,IAAI,KAAK,GAEjC,QAAQ,KAAK,CAAE,MAAiB,SAA4B,KAAM,CAAC,EACrE,CAEA,OAAO,OACT,CAGA,SAAgB,UACd,UACA,cACY,CACZ,GAAI,CAAC,UAAW,MAAO,CAAC,EAExB,IAAM,MAAQ,MAAM,QAAQ,SAAS,EAAI,UAAY,CAAC,SAAS,EACzD,QAAsB,CAAC,EACvB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KAEzD,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,UAAY,KAAK,YAAY,GAAG,EACtC,GAAI,YAAc,GAAI,SAEtB,IAAM,MAAQ,KAAK,MAAM,EAAG,SAAS,EAC/B,IAAM,KAAK,MAAM,UAAY,CAAC,EAAE,YAAY,EAE7C,QACD,MAAQ,OAAS,MAAQ,QACzB,SAAW,CAAC,QAAQ,IAAI,KAAK,GAEjC,QAAQ,KAAK,CAAE,MAAO,UAAW,GAAI,CAAC,EACxC,CAEA,OAAO,OACT,CAGA,SAAgB,gBAAgB,OAGX,CACnB,IAAI,KACF,OAAO,OAAO,MAAS,SACnB,OAAO,SAAS,OAAO,KAAM,EAAE,EAC9B,OAAO,MAAQ,EAClB,MACF,OAAO,OAAO,OAAU,SACpB,OAAO,SAAS,OAAO,MAAO,EAAE,EAC/B,OAAO,OAAS,GAMvB,OAJI,OAAO,MAAM,IAAI,GAAK,KAAO,KAAG,KAAO,IACvC,OAAO,MAAM,KAAK,GAAK,MAAQ,KAAG,MAAQ,IAC1C,MAAQ,MAAW,MAAQ,KAExB,CAAE,KAAM,MAAO,QAAS,KAAO,GAAK,KAAM,CACnD,CAGA,SAAgB,iBAAiB,EAA+B,CAE9D,OADK,EACE,EAAE,KAAK,EAAE,MAAM,EAAG,GAAiB,EAD3B,EAEjB,CASA,SAAS,qBAAqB,OAA+D,CACtF,UAYL,MATI,YAAa,QAAU,OAAO,SAAW,OAAO,OAAO,SAAY,SAC9D,CACL,WAAY,OAAO,KAAK,OAAO,OAAO,EACtC,SAAU,OAAO,SAAW,OAAO,KAAK,OAAO,QAAQ,EAAI,IAAA,GAC3D,WAAY,IAAA,EACd,EAIK,MACT,CAgCA,SAAgB,WACd,MACA,YAC2B,CAC3B,IAAM,WAAa,qBAAqB,WAAW,EACnD,MAAO,CACL,QAAS,aAAa,MAAM,OAAQ,YAAY,UAAU,EAC1D,KAAM,UAAU,MAAM,KAAM,YAAY,QAAQ,EAChD,WAAY,gBAAgB,CAAE,KAAM,MAAM,KAAM,MAAO,MAAM,KAAM,CAAC,EACpE,OAAQ,iBAAiB,MAAM,CAAC,CAClC,CACF,CAKA,SAAgB,iBACd,OAC4C,CAC5C,IAAM,OAAqD,CAAC,EAmB5D,OAjBI,OAAO,SAAS,SAClB,OAAO,OAAS,OAAO,QAAQ,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,OAAO,GAG7E,OAAO,MAAM,SACf,OAAO,KAAO,OAAO,KAAK,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,WAAW,GAG9D,OAAO,aACT,OAAO,KAAO,OAAO,WAAW,KAChC,OAAO,MAAQ,OAAO,WAAW,OAG/B,OAAO,SACT,OAAO,EAAI,OAAO,QAGb,MACT"}
1
+ {"version":3,"file":"query-BkA-clOb.mjs","names":[],"sources":["../src/http/query/types.ts","../src/http/query/parse-query.ts"],"sourcesContent":["/** Supported filter operators for query string parsing */\nexport type FilterOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'between'\n | 'in'\n | 'contains'\n | 'starts'\n | 'ends'\n\nexport const FILTER_OPERATORS = new Set<string>([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'in',\n 'contains',\n 'starts',\n 'ends',\n])\n\nexport interface FilterItem {\n field: string\n operator: FilterOperator\n value: string\n}\n\nexport interface SortItem {\n field: string\n direction: 'asc' | 'desc'\n}\n\nexport interface PaginationParams {\n page: number\n limit: number\n offset: number\n}\n\n/**\n * The result of parsing a query string. ORM-agnostic — pass this to\n * a query builder adapter (Drizzle, Prisma, Sequelize, etc.) to produce\n * database-specific query objects.\n */\nexport interface ParsedQuery {\n filters: FilterItem[]\n sort: SortItem[]\n pagination: PaginationParams\n search: string\n}\n\n/**\n * Helper that extracts the literal field union from a `QueryFieldConfig`\n * key. When the user passes `{ filterable: ['status', 'category'] } as const`,\n * `FieldsOf<typeof config, 'filterable'>` resolves to `'status' | 'category'`.\n *\n * Without `as const`, the arrays widen to `string[]` and the union falls\n * back to `string`. This is the documented escape hatch.\n */\nexport type FieldsOf<\n TConfig extends QueryFieldConfig | undefined,\n K extends 'filterable' | 'sortable' | 'searchable',\n> = TConfig extends { readonly [P in K]: readonly (infer F)[] }\n ? F extends string\n ? F\n : string\n : TConfig extends { columns: infer C }\n ? K extends 'filterable'\n ? keyof C & string\n : string\n : string\n\n/**\n * Narrowed `ParsedQuery` whose `filters[].field` and `sort[].field`\n * unions are constrained to the literal whitelist passed to\n * `ctx.qs()` / `parseQuery()`. Extends `ParsedQuery`, so any function\n * accepting `ParsedQuery` accepts a `TypedParsedQuery<X>` automatically\n * (subtype assignment, no codegen, backward compatible).\n *\n * @example\n * ```ts\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt'],\n * } as const)\n * parsed.filters[0]?.field // 'status' | 'priority'\n * parsed.sort[0]?.field // 'createdAt'\n * ```\n */\nexport interface TypedParsedQuery<\n TConfig extends QueryFieldConfig | undefined,\n> extends ParsedQuery {\n filters: Array<FilterItem & { field: FieldsOf<TConfig, 'filterable'> }>\n sort: Array<SortItem & { field: FieldsOf<TConfig, 'sortable'> }>\n}\n\n/**\n * Restrict which fields can be filtered, sorted, or searched.\n * Fields not in the allow-list are silently ignored.\n *\n * Accepts two forms:\n * 1. **String arrays** (standard): `{ filterable: ['name', 'status'], sortable: ['createdAt'] }`\n * 2. **Column-object maps** (Drizzle-style): `{ columns: { name: col, status: col }, sortable: { createdAt: col } }`\n *\n * When column-object maps are provided, `Object.keys()` is used to derive the field names.\n */\nexport type QueryFieldConfig = StringQueryFieldConfig | ColumnQueryFieldConfig\n\nexport interface StringQueryFieldConfig {\n filterable?: string[]\n sortable?: string[]\n searchable?: string[]\n}\n\n/**\n * Column-object-based field config (e.g., from DrizzleQueryParamsConfig).\n * Keys are the field names used in query strings; values are ORM column references.\n */\nexport interface ColumnQueryFieldConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/** Standardized paginated response shape */\nexport interface PaginatedResponse<T = any> {\n data: T[]\n meta: {\n page: number\n limit: number\n total: number\n totalPages: number\n hasNext: boolean\n hasPrev: boolean\n }\n}\n\n/**\n * Interface for ORM-specific query builder adapters.\n * Implement this to translate a `ParsedQuery` into your ORM's query format.\n *\n * @example\n * ```ts\n * // Drizzle adapter\n * class DrizzleQueryAdapter implements QueryBuilderAdapter<DrizzleQueryResult> {\n * build(parsed: ParsedQuery, config: DrizzleConfig): DrizzleQueryResult {\n * // Convert filters → Drizzle SQL conditions\n * // Convert sort → Drizzle orderBy\n * return { where, orderBy, limit, offset }\n * }\n * }\n *\n * // Prisma adapter\n * class PrismaQueryAdapter implements QueryBuilderAdapter<PrismaQueryResult> {\n * build(parsed: ParsedQuery, config: PrismaConfig): PrismaQueryResult {\n * return { where, orderBy, skip, take }\n * }\n * }\n * ```\n */\nexport interface QueryBuilderAdapter<TResult = any, TConfig = any> {\n /** Human-readable name for debugging */\n readonly name: string\n\n /**\n * Convert a ParsedQuery into an ORM-specific query object.\n * @param parsed - The ORM-agnostic parsed query\n * @param config - ORM-specific configuration (column maps, search columns, etc.)\n */\n build(parsed: ParsedQuery, config: TConfig): TResult\n}\n","import {\n FILTER_OPERATORS,\n type FilterItem,\n type FilterOperator,\n type SortItem,\n type PaginationParams,\n type ParsedQuery,\n type QueryFieldConfig,\n type StringQueryFieldConfig,\n type TypedParsedQuery,\n} from './types'\n\nconst MAX_SEARCH_LENGTH = 200\nconst DEFAULT_PAGE = 1\nconst DEFAULT_LIMIT = 20\nconst MAX_LIMIT = 100\n\n// ── Individual parsers ──────────────────────────────────────────────────\n\n/** Parse filter strings like \"status:eq:active\" into structured objects */\nexport function parseFilters(\n filterParam: string | string[] | undefined,\n allowedFields?: string[],\n): FilterItem[] {\n if (!filterParam) return []\n\n const items = Array.isArray(filterParam) ? filterParam : [filterParam]\n const results: FilterItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n\n for (const item of items) {\n // Split on first two colons only — value may contain colons (e.g. timestamps)\n const firstColon = item.indexOf(':')\n if (firstColon === -1) continue\n\n const secondColon = item.indexOf(':', firstColon + 1)\n if (secondColon === -1) continue\n\n const field = item.slice(0, firstColon)\n const operator = item.slice(firstColon + 1, secondColon)\n const value = item.slice(secondColon + 1)\n\n if (!field || !value) continue\n if (!FILTER_OPERATORS.has(operator)) continue\n if (allowed && !allowed.has(field)) continue\n\n results.push({ field, operator: operator as FilterOperator, value })\n }\n\n return results\n}\n\n/** Parse sort strings like \"firstName:asc\" into structured objects */\nexport function parseSort(\n sortParam: string | string[] | undefined,\n allowedFields?: string[],\n): SortItem[] {\n if (!sortParam) return []\n\n const items = Array.isArray(sortParam) ? sortParam : [sortParam]\n const results: SortItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n\n for (const item of items) {\n // Split on last colon so field names with colons work\n const lastColon = item.lastIndexOf(':')\n if (lastColon === -1) continue\n\n const field = item.slice(0, lastColon)\n const dir = item.slice(lastColon + 1).toLowerCase()\n\n if (!field) continue\n if (dir !== 'asc' && dir !== 'desc') continue\n if (allowed && !allowed.has(field)) continue\n\n results.push({ field, direction: dir })\n }\n\n return results\n}\n\n/** Parse page/limit into pagination with computed offset */\nexport function parsePagination(params: {\n page?: string | number\n limit?: string | number\n}): PaginationParams {\n let page =\n typeof params.page === 'string'\n ? Number.parseInt(params.page, 10)\n : (params.page ?? DEFAULT_PAGE)\n let limit =\n typeof params.limit === 'string'\n ? Number.parseInt(params.limit, 10)\n : (params.limit ?? DEFAULT_LIMIT)\n\n if (Number.isNaN(page) || page < 1) page = DEFAULT_PAGE\n if (Number.isNaN(limit) || limit < 1) limit = DEFAULT_LIMIT\n if (limit > MAX_LIMIT) limit = MAX_LIMIT\n\n return { page, limit, offset: (page - 1) * limit }\n}\n\n/** Sanitize and truncate search query */\nexport function parseSearchQuery(q: string | undefined): string {\n if (!q) return ''\n return q.trim().slice(0, MAX_SEARCH_LENGTH)\n}\n\n// ── Config normalizer ───────────────────────────────────────────────────\n\n/**\n * Normalize a QueryFieldConfig into the string-based form.\n * Detects column-object-based configs (DrizzleQueryParamsConfig) by checking\n * for a `columns` property and extracts `Object.keys()`.\n */\nfunction normalizeFieldConfig(config?: QueryFieldConfig): StringQueryFieldConfig | undefined {\n if (!config) return undefined\n\n // Column-object-based config (e.g., DrizzleQueryParamsConfig)\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: undefined, // searchColumns are Column objects, not used for string filtering\n }\n }\n\n // Already string-based\n return config as StringQueryFieldConfig\n}\n\n// ── Combined parser ─────────────────────────────────────────────────────\n\n/**\n * Parse a raw Express query object into a structured, ORM-agnostic ParsedQuery.\n *\n * @param query - Raw query string object from `req.query` or Zod-validated object\n * @param fieldConfig - Optional field restrictions (whitelist filterable/sortable/searchable).\n * Accepts both string-based configs (`{ filterable, sortable, searchable }`) and\n * column-object configs (`{ columns, sortable, searchColumns }`).\n *\n * @example\n * ```ts\n * // String-based config\n * const parsed = parseQuery(ctx.query, {\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n *\n * // Column-object config (DrizzleQueryParamsConfig)\n * const parsed = parseQuery(ctx.query, TASK_QUERY_CONFIG)\n * ```\n *\n * Query string format:\n * - Filters: `?filter=field:operator:value` (repeatable)\n * - Sort: `?sort=field:asc|desc` (repeatable)\n * - Pagination: `?page=1&limit=20`\n * - Search: `?q=search+term`\n *\n * Filter operators: eq, neq, gt, gte, lt, lte, between, in, contains, starts, ends\n */\nexport function parseQuery<TConfig extends QueryFieldConfig | undefined = undefined>(\n query: Record<string, any>,\n fieldConfig?: TConfig,\n): TypedParsedQuery<TConfig> {\n const normalized = normalizeFieldConfig(fieldConfig)\n return {\n filters: parseFilters(query.filter, normalized?.filterable),\n sort: parseSort(query.sort, normalized?.sortable),\n pagination: parsePagination({ page: query.page, limit: query.limit }),\n search: parseSearchQuery(query.q),\n } as TypedParsedQuery<TConfig>\n}\n\n// ── Query URL builder (for client-side or testing) ──────────────────────\n\n/** Convert ParsedQuery back into query string parameters */\nexport function buildQueryParams(\n parsed: Partial<ParsedQuery>,\n): Record<string, string | string[] | number> {\n const params: Record<string, string | string[] | number> = {}\n\n if (parsed.filters?.length) {\n params.filter = parsed.filters.map((f) => `${f.field}:${f.operator}:${f.value}`)\n }\n\n if (parsed.sort?.length) {\n params.sort = parsed.sort.map((s) => `${s.field}:${s.direction}`)\n }\n\n if (parsed.pagination) {\n params.page = parsed.pagination.page\n params.limit = parsed.pagination.limit\n }\n\n if (parsed.search) {\n params.q = parsed.search\n }\n\n return params\n}\n"],"mappings":";;;;;;;;;;AAcA,MAAa,iBAAmB,IAAI,IAAY,CAC9C,KACA,MACA,KACA,MACA,KACA,MACA,UACA,KACA,WACA,SACA,MACF,CAAC,ECND,SAAgB,aACd,YACA,cACc,CACd,GAAI,CAAC,YAAa,MAAO,CAAC,EAE1B,IAAM,MAAQ,MAAM,QAAQ,WAAW,EAAI,YAAc,CAAC,WAAW,EAC/D,QAAwB,CAAC,EACzB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KAEzD,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,WAAa,KAAK,QAAQ,GAAG,EACnC,GAAI,aAAe,GAAI,SAEvB,IAAM,YAAc,KAAK,QAAQ,IAAK,WAAa,CAAC,EACpD,GAAI,cAAgB,GAAI,SAExB,IAAM,MAAQ,KAAK,MAAM,EAAG,UAAU,EAChC,SAAW,KAAK,MAAM,WAAa,EAAG,WAAW,EACjD,MAAQ,KAAK,MAAM,YAAc,CAAC,EAEpC,CAAC,OAAS,CAAC,OACV,iBAAiB,IAAI,QAAQ,IAC9B,SAAW,CAAC,QAAQ,IAAI,KAAK,GAEjC,QAAQ,KAAK,CAAE,MAAiB,SAA4B,KAAM,CAAC,EACrE,CAEA,OAAO,OACT,CAGA,SAAgB,UACd,UACA,cACY,CACZ,GAAI,CAAC,UAAW,MAAO,CAAC,EAExB,IAAM,MAAQ,MAAM,QAAQ,SAAS,EAAI,UAAY,CAAC,SAAS,EACzD,QAAsB,CAAC,EACvB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KAEzD,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,UAAY,KAAK,YAAY,GAAG,EACtC,GAAI,YAAc,GAAI,SAEtB,IAAM,MAAQ,KAAK,MAAM,EAAG,SAAS,EAC/B,IAAM,KAAK,MAAM,UAAY,CAAC,EAAE,YAAY,EAE7C,QACD,MAAQ,OAAS,MAAQ,QACzB,SAAW,CAAC,QAAQ,IAAI,KAAK,GAEjC,QAAQ,KAAK,CAAE,MAAO,UAAW,GAAI,CAAC,EACxC,CAEA,OAAO,OACT,CAGA,SAAgB,gBAAgB,OAGX,CACnB,IAAI,KACF,OAAO,OAAO,MAAS,SACnB,OAAO,SAAS,OAAO,KAAM,EAAE,EAC9B,OAAO,MAAQ,EAClB,MACF,OAAO,OAAO,OAAU,SACpB,OAAO,SAAS,OAAO,MAAO,EAAE,EAC/B,OAAO,OAAS,GAMvB,OAJI,OAAO,MAAM,IAAI,GAAK,KAAO,KAAG,KAAO,IACvC,OAAO,MAAM,KAAK,GAAK,MAAQ,KAAG,MAAQ,IAC1C,MAAQ,MAAW,MAAQ,KAExB,CAAE,KAAM,MAAO,QAAS,KAAO,GAAK,KAAM,CACnD,CAGA,SAAgB,iBAAiB,EAA+B,CAE9D,OADK,EACE,EAAE,KAAK,EAAE,MAAM,EAAG,GAAiB,EAD3B,EAEjB,CASA,SAAS,qBAAqB,OAA+D,CACtF,UAYL,MATI,YAAa,QAAU,OAAO,SAAW,OAAO,OAAO,SAAY,SAC9D,CACL,WAAY,OAAO,KAAK,OAAO,OAAO,EACtC,SAAU,OAAO,SAAW,OAAO,KAAK,OAAO,QAAQ,EAAI,IAAA,GAC3D,WAAY,IAAA,EACd,EAIK,MACT,CAgCA,SAAgB,WACd,MACA,YAC2B,CAC3B,IAAM,WAAa,qBAAqB,WAAW,EACnD,MAAO,CACL,QAAS,aAAa,MAAM,OAAQ,YAAY,UAAU,EAC1D,KAAM,UAAU,MAAM,KAAM,YAAY,QAAQ,EAChD,WAAY,gBAAgB,CAAE,KAAM,MAAM,KAAM,MAAO,MAAM,KAAM,CAAC,EACpE,OAAQ,iBAAiB,MAAM,CAAC,CAClC,CACF,CAKA,SAAgB,iBACd,OAC4C,CAC5C,IAAM,OAAqD,CAAC,EAmB5D,OAjBI,OAAO,SAAS,SAClB,OAAO,OAAS,OAAO,QAAQ,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,OAAO,GAG7E,OAAO,MAAM,SACf,OAAO,KAAO,OAAO,KAAK,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,WAAW,GAG9D,OAAO,aACT,OAAO,KAAO,OAAO,WAAW,KAChC,OAAO,MAAQ,OAAO,WAAW,OAG/B,OAAO,SACT,OAAO,EAAI,OAAO,QAGb,MACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs",
3
- "version": "5.15.0",
3
+ "version": "5.15.1",
4
4
  "description": "A production-grade, decorator-driven Node.js framework built on Express 5 and TypeScript.",
5
5
  "keywords": [
6
6
  "kickjs",