@forinda/kickjs 5.16.0 → 5.18.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/adapter-D_uzU-QA.d.mts +2121 -0
  2. package/dist/adapter-D_uzU-QA.d.mts.map +1 -0
  3. package/dist/{context-decorator-DDZ30BIW.d.mts → app-module-CuAS6hJC.d.mts} +184 -4
  4. package/dist/app-module-CuAS6hJC.d.mts.map +1 -0
  5. package/dist/application-BU5N6o_M.mjs +12 -0
  6. package/dist/application-BU5N6o_M.mjs.map +1 -0
  7. package/dist/{application-CAnpDQ8d.d.mts → application-pbx53JnX.d.mts} +31 -5
  8. package/dist/application-pbx53JnX.d.mts.map +1 -0
  9. package/dist/{bootstrap-CFK2aAjA.mjs → bootstrap-CFog4v_L.mjs} +3 -3
  10. package/dist/{bootstrap-CFK2aAjA.mjs.map → bootstrap-CFog4v_L.mjs.map} +1 -1
  11. package/dist/{bootstrap-8DDzBjhx.d.mts → bootstrap-DzfBj6Ja.d.mts} +2 -2
  12. package/dist/{bootstrap-8DDzBjhx.d.mts.map → bootstrap-DzfBj6Ja.d.mts.map} +1 -1
  13. package/dist/config/index.mjs +2 -2
  14. package/dist/{config-BW5taOVX.mjs → config-Di3HlRTS.mjs} +3 -3
  15. package/dist/{config-BW5taOVX.mjs.map → config-Di3HlRTS.mjs.map} +1 -1
  16. package/dist/{container-D1gfs6__.mjs → container-BiGimG12.mjs} +3 -3
  17. package/dist/{container-D1gfs6__.mjs.map → container-BiGimG12.mjs.map} +1 -1
  18. package/dist/context-leZajQAj.mjs +12 -0
  19. package/dist/context-leZajQAj.mjs.map +1 -0
  20. package/dist/core/adapter.d.mts +1 -1
  21. package/dist/core/adapter.mjs +1 -1
  22. package/dist/core/app-module.d.mts +1 -1
  23. package/dist/core/app-module.mjs +1 -1
  24. package/dist/core/cache.mjs +1 -1
  25. package/dist/core/container.mjs +2 -2
  26. package/dist/core/cron.mjs +2 -2
  27. package/dist/core/decorators.mjs +2 -2
  28. package/dist/core/errors.d.mts +1 -1
  29. package/dist/core/errors.mjs +2 -2
  30. package/dist/core/errors.mjs.map +1 -1
  31. package/dist/core/interfaces.mjs +1 -1
  32. package/dist/core/logger.mjs +1 -1
  33. package/dist/core/path.mjs +1 -1
  34. package/dist/core/plugin.d.mts +1 -1
  35. package/dist/core/plugin.mjs +1 -1
  36. package/dist/core/reactivity.mjs +1 -1
  37. package/dist/{core-Db2uyqtM.mjs → core-Y_xSqZ7v.mjs} +3 -3
  38. package/dist/{core-Db2uyqtM.mjs.map → core-Y_xSqZ7v.mjs.map} +1 -1
  39. package/dist/{env-nwsfCpdy.mjs → env-BoMBrGWs.mjs} +3 -3
  40. package/dist/{env-nwsfCpdy.mjs.map → env-BoMBrGWs.mjs.map} +1 -1
  41. package/dist/{errors-CRCTjEUK.d.mts → errors-n0H96eEz.d.mts} +3 -1
  42. package/dist/errors-n0H96eEz.d.mts.map +1 -0
  43. package/dist/http/application.d.mts +1 -1
  44. package/dist/http/application.mjs +2 -2
  45. package/dist/http/bootstrap.d.mts +1 -1
  46. package/dist/http/bootstrap.mjs +2 -2
  47. package/dist/http/context.d.mts +2 -2
  48. package/dist/http/context.mjs +2 -2
  49. package/dist/http/middleware/cors.mjs +1 -1
  50. package/dist/http/middleware/csrf.mjs +1 -1
  51. package/dist/http/middleware/error-handler.mjs +2 -2
  52. package/dist/http/middleware/helmet.mjs +1 -1
  53. package/dist/http/middleware/rate-limit.mjs +1 -1
  54. package/dist/http/middleware/request-id.mjs +1 -1
  55. package/dist/http/middleware/request-logger.mjs +2 -2
  56. package/dist/http/middleware/session.mjs +1 -1
  57. package/dist/http/middleware/spa.d.mts +1 -1
  58. package/dist/http/middleware/spa.mjs +2 -2
  59. package/dist/http/middleware/upload.d.mts +2 -2
  60. package/dist/http/middleware/upload.mjs +3 -3
  61. package/dist/http/middleware/upload.mjs.map +1 -1
  62. package/dist/http/middleware/validate.mjs +2 -2
  63. package/dist/http/middleware/views.d.mts +1 -1
  64. package/dist/http/middleware/views.mjs +2 -2
  65. package/dist/http/query/index.d.mts +2 -2
  66. package/dist/http/query/index.mjs +2 -2
  67. package/dist/http/router-builder.d.mts +2 -2
  68. package/dist/http/router-builder.mjs +2 -2
  69. package/dist/http/router-builder.mjs.map +1 -1
  70. package/dist/http/runtimes/fastify.d.mts +66 -0
  71. package/dist/http/runtimes/fastify.d.mts.map +1 -0
  72. package/dist/http/runtimes/fastify.mjs +13 -0
  73. package/dist/http/runtimes/fastify.mjs.map +1 -0
  74. package/dist/http/runtimes/h3.d.mts +29 -0
  75. package/dist/http/runtimes/h3.d.mts.map +1 -0
  76. package/dist/http/runtimes/h3.mjs +12 -0
  77. package/dist/http/runtimes/h3.mjs.map +1 -0
  78. package/dist/{index-kEmG8nWU.d.mts → index-BtWJ9SLP.d.mts} +53 -7
  79. package/dist/{index-kEmG8nWU.d.mts.map → index-BtWJ9SLP.d.mts.map} +1 -1
  80. package/dist/index.d.mts +12 -17
  81. package/dist/index.mjs +2 -2
  82. package/dist/index.mjs.map +1 -1
  83. package/dist/{metadata-Cll3P0N0.mjs → metadata-Pvgc-K8a.mjs} +2 -2
  84. package/dist/{metadata-Cll3P0N0.mjs.map → metadata-Pvgc-K8a.mjs.map} +1 -1
  85. package/dist/query-B3PhfU6M.mjs +12 -0
  86. package/dist/query-B3PhfU6M.mjs.map +1 -0
  87. package/dist/request-scope-BaM8_e_8.mjs +12 -0
  88. package/dist/request-scope-BaM8_e_8.mjs.map +1 -0
  89. package/dist/router-builder-Bh3qSiDz.d.mts +39 -0
  90. package/dist/router-builder-Bh3qSiDz.d.mts.map +1 -0
  91. package/dist/{spa-IRFC2eMN.d.mts → spa-BdvZFXJz.d.mts} +3 -2
  92. package/dist/spa-BdvZFXJz.d.mts.map +1 -0
  93. package/dist/{upload-D6edAELH.d.mts → upload-Bc_rBRtE.d.mts} +37 -2
  94. package/dist/upload-Bc_rBRtE.d.mts.map +1 -0
  95. package/dist/{views-BCl-mETX.d.mts → views-DcTVDiOh.d.mts} +31 -7
  96. package/dist/views-DcTVDiOh.d.mts.map +1 -0
  97. package/package.json +29 -1
  98. package/dist/adapter-CkpcZwzZ.d.mts +0 -407
  99. package/dist/adapter-CkpcZwzZ.d.mts.map +0 -1
  100. package/dist/app-module-BTzb-Vd6.d.mts +0 -92
  101. package/dist/app-module-BTzb-Vd6.d.mts.map +0 -1
  102. package/dist/application-CAnpDQ8d.d.mts.map +0 -1
  103. package/dist/application-DnnqoDWx.mjs +0 -12
  104. package/dist/application-DnnqoDWx.mjs.map +0 -1
  105. package/dist/context-Dnt2Di7p.d.mts +0 -434
  106. package/dist/context-Dnt2Di7p.d.mts.map +0 -1
  107. package/dist/context-MD7Mc_o_.mjs +0 -12
  108. package/dist/context-MD7Mc_o_.mjs.map +0 -1
  109. package/dist/context-decorator-DDZ30BIW.d.mts.map +0 -1
  110. package/dist/errors-CRCTjEUK.d.mts.map +0 -1
  111. package/dist/execution-context-B4cfhivz.d.mts +0 -98
  112. package/dist/execution-context-B4cfhivz.d.mts.map +0 -1
  113. package/dist/index-frSEHiL_.d.mts +0 -796
  114. package/dist/index-frSEHiL_.d.mts.map +0 -1
  115. package/dist/plugin-BQCn6zyj.d.mts +0 -260
  116. package/dist/plugin-BQCn6zyj.d.mts.map +0 -1
  117. package/dist/query-D-FiLfmd.mjs +0 -12
  118. package/dist/query-D-FiLfmd.mjs.map +0 -1
  119. package/dist/router-builder-oQpcQMWl.d.mts +0 -31
  120. package/dist/router-builder-oQpcQMWl.d.mts.map +0 -1
  121. package/dist/spa-IRFC2eMN.d.mts.map +0 -1
  122. package/dist/upload-D6edAELH.d.mts.map +0 -1
  123. package/dist/views-BCl-mETX.d.mts.map +0 -1
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs v5.18.0-alpha.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ const FILTER_OPERATORS=new Set([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`between`,`in`,`contains`,`starts`,`ends`]),BUILTIN_DEFAULTS={maxLimit:100,defaultLimit:20,maxSearchLength:200,defaultPage:1},defaults={...BUILTIN_DEFAULTS};function setQueryParsingDefaults(partial){partial.maxLimit!==void 0&&(defaults.maxLimit=partial.maxLimit),partial.defaultLimit!==void 0&&(defaults.defaultLimit=partial.defaultLimit),partial.maxSearchLength!==void 0&&(defaults.maxSearchLength=partial.maxSearchLength)}function getQueryParsingDefaults(){return{...defaults}}function resetQueryParsingDefaults(){Object.assign(defaults,BUILTIN_DEFAULTS)}function parseFilters(filterParam,allowedFields,options){if(!filterParam)return[];let items=Array.isArray(filterParam)?filterParam:[filterParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let firstColon=item.indexOf(`:`);if(firstColon===-1)continue;let secondColon=item.indexOf(`:`,firstColon+1);if(secondColon===-1)continue;let field=item.slice(0,firstColon),operator=item.slice(firstColon+1,secondColon),value=item.slice(secondColon+1);if(!(!field||!value)){if(!FILTER_OPERATORS.has(operator)){onReject?.({kind:`filter`,field,reason:`unknown-operator`});continue}if(allowed&&!allowed.has(field)){onReject?.({kind:`filter`,field,reason:`field-not-allowed`});continue}results.push({field,operator,value})}}return results}function parseSort(sortParam,allowedFields,options){if(!sortParam)return[];let items=Array.isArray(sortParam)?sortParam:[sortParam],results=[],allowed=allowedFields?new Set(allowedFields):null,onReject=options?.onReject;for(let item of items){let lastColon=item.lastIndexOf(`:`);if(lastColon===-1)continue;let field=item.slice(0,lastColon),dir=item.slice(lastColon+1).toLowerCase();if(field&&!(dir!==`asc`&&dir!==`desc`)){if(allowed&&!allowed.has(field)){onReject?.({kind:`sort`,field,reason:`field-not-allowed`});continue}results.push({field,direction:dir})}}return results}function parsePagination(params,options){let defaultLimit=options?.defaultLimit??defaults.defaultLimit,maxLimit=options?.maxLimit??defaults.maxLimit,page=typeof params.page==`string`?Number.parseInt(params.page,10):params.page??defaults.defaultPage,limit=typeof params.limit==`string`?Number.parseInt(params.limit,10):params.limit??defaultLimit;return(Number.isNaN(page)||page<1)&&(page=defaults.defaultPage),(Number.isNaN(limit)||limit<1)&&(limit=defaultLimit),limit>maxLimit&&(limit=maxLimit),{page,limit,offset:(page-1)*limit}}function parseSearchQuery(q,options){if(!q)return``;let maxLen=options?.maxSearchLength??defaults.maxSearchLength,trimmed=q.trim();return trimmed.length>maxLen?(options?.onReject?.({kind:`search`,field:`q`,reason:`truncated`}),trimmed.slice(0,maxLen)):trimmed}function normalizeFieldConfig(config){if(config)return`columns`in config&&config.columns&&typeof config.columns==`object`?{filterable:Object.keys(config.columns),sortable:config.sortable?Object.keys(config.sortable):void 0,searchable:void 0}:config}function parseQuery(query,fieldConfig,options){let normalized=normalizeFieldConfig(fieldConfig),rejectOpts=options?.onReject?{onReject:options.onReject}:void 0;return{filters:parseFilters(query.filter,normalized?.filterable,rejectOpts),sort:parseSort(query.sort,normalized?.sortable,rejectOpts),pagination:parsePagination({page:query.page,limit:query.limit},options),search:parseSearchQuery(query.q,options)}}function buildQueryParams(parsed){let params={};return parsed.filters?.length&&(params.filter=parsed.filters.map(f=>`${f.field}:${f.operator}:${f.value}`)),parsed.sort?.length&&(params.sort=parsed.sort.map(s=>`${s.field}:${s.direction}`)),parsed.pagination&&(params.page=parsed.pagination.page,params.limit=parsed.pagination.limit),parsed.search&&(params.q=parsed.search),params}export{parseQuery as a,resetQueryParsingDefaults as c,parsePagination as i,setQueryParsingDefaults as l,getQueryParsingDefaults as n,parseSearchQuery as o,parseFilters as r,parseSort as s,buildQueryParams as t,FILTER_OPERATORS as u};
12
+ //# sourceMappingURL=query-B3PhfU6M.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-B3PhfU6M.mjs","names":[],"sources":["../src/http/query/types.ts","../src/http/query/parse-query.ts"],"sourcesContent":["/** Supported filter operators for query string parsing */\nexport type FilterOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'between'\n | 'in'\n | 'contains'\n | 'starts'\n | 'ends'\n\nexport const FILTER_OPERATORS = new Set<string>([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'between',\n 'in',\n 'contains',\n 'starts',\n 'ends',\n])\n\nexport interface FilterItem {\n field: string\n operator: FilterOperator\n value: string\n}\n\nexport interface SortItem {\n field: string\n direction: 'asc' | 'desc'\n}\n\nexport interface PaginationParams {\n page: number\n limit: number\n offset: number\n}\n\n/**\n * The result of parsing a query string. ORM-agnostic — pass this to\n * a query builder adapter (Drizzle, Prisma, Sequelize, etc.) to produce\n * database-specific query objects.\n */\nexport interface ParsedQuery {\n filters: FilterItem[]\n sort: SortItem[]\n pagination: PaginationParams\n search: string\n}\n\n/**\n * Helper that extracts the literal field union from a `QueryFieldConfig`\n * key. When the user passes `{ filterable: ['status', 'category'] } as const`,\n * `FieldsOf<typeof config, 'filterable'>` resolves to `'status' | 'category'`.\n *\n * Without `as const`, the arrays widen to `string[]` and the union falls\n * back to `string`. This is the documented escape hatch.\n */\nexport type FieldsOf<\n TConfig extends QueryFieldConfig | undefined,\n K extends 'filterable' | 'sortable' | 'searchable',\n> = TConfig extends { readonly [P in K]: readonly (infer F)[] }\n ? F extends string\n ? F\n : string\n : TConfig extends { columns: infer C }\n ? K extends 'filterable'\n ? keyof C & string\n : string\n : string\n\n/**\n * Narrowed `ParsedQuery` whose `filters[].field` and `sort[].field`\n * unions are constrained to the literal whitelist passed to\n * `ctx.qs()` / `parseQuery()`. Extends `ParsedQuery`, so any function\n * accepting `ParsedQuery` accepts a `TypedParsedQuery<X>` automatically\n * (subtype assignment, no codegen, backward compatible).\n *\n * @example\n * ```ts\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt'],\n * } as const)\n * parsed.filters[0]?.field // 'status' | 'priority'\n * parsed.sort[0]?.field // 'createdAt'\n * ```\n */\nexport interface TypedParsedQuery<\n TConfig extends QueryFieldConfig | undefined,\n> extends ParsedQuery {\n filters: Array<FilterItem & { field: FieldsOf<TConfig, 'filterable'> }>\n sort: Array<SortItem & { field: FieldsOf<TConfig, 'sortable'> }>\n}\n\n/**\n * Restrict which fields can be filtered, sorted, or searched.\n * Fields not in the allow-list are silently ignored.\n *\n * Accepts two forms:\n * 1. **String arrays** (standard): `{ filterable: ['name', 'status'], sortable: ['createdAt'] }`\n * 2. **Column-object maps** (Drizzle-style): `{ columns: { name: col, status: col }, sortable: { createdAt: col } }`\n *\n * When column-object maps are provided, `Object.keys()` is used to derive the field names.\n */\nexport type QueryFieldConfig = StringQueryFieldConfig | ColumnQueryFieldConfig\n\nexport interface StringQueryFieldConfig {\n filterable?: string[]\n sortable?: string[]\n searchable?: string[]\n}\n\n/**\n * Column-object-based field config (e.g., from DrizzleQueryParamsConfig).\n * Keys are the field names used in query strings; values are ORM column references.\n */\nexport interface ColumnQueryFieldConfig {\n columns: Record<string, any>\n sortable?: Record<string, any>\n searchColumns?: any[]\n [key: string]: any\n}\n\n/** Standardized paginated response shape */\nexport interface PaginatedResponse<T = any> {\n data: T[]\n meta: {\n page: number\n limit: number\n total: number\n totalPages: number\n hasNext: boolean\n hasPrev: boolean\n }\n}\n\n/**\n * Interface for ORM-specific query builder adapters.\n * Implement this to translate a `ParsedQuery` into your ORM's query format.\n *\n * @example\n * ```ts\n * // Drizzle adapter\n * class DrizzleQueryAdapter implements QueryBuilderAdapter<DrizzleQueryResult> {\n * build(parsed: ParsedQuery, config: DrizzleConfig): DrizzleQueryResult {\n * // Convert filters → Drizzle SQL conditions\n * // Convert sort → Drizzle orderBy\n * return { where, orderBy, limit, offset }\n * }\n * }\n *\n * // Prisma adapter\n * class PrismaQueryAdapter implements QueryBuilderAdapter<PrismaQueryResult> {\n * build(parsed: ParsedQuery, config: PrismaConfig): PrismaQueryResult {\n * return { where, orderBy, skip, take }\n * }\n * }\n * ```\n */\nexport interface QueryBuilderAdapter<TResult = any, TConfig = any> {\n /** Human-readable name for debugging */\n readonly name: string\n\n /**\n * Convert a ParsedQuery into an ORM-specific query object.\n * @param parsed - The ORM-agnostic parsed query\n * @param config - ORM-specific configuration (column maps, search columns, etc.)\n */\n build(parsed: ParsedQuery, config: TConfig): TResult\n}\n","import {\n FILTER_OPERATORS,\n type FilterItem,\n type FilterOperator,\n type SortItem,\n type PaginationParams,\n type ParsedQuery,\n type QueryFieldConfig,\n type StringQueryFieldConfig,\n type TypedParsedQuery,\n} from './types'\n\n// ── Configurable defaults ───────────────────────────────────────────────\n\n/**\n * Why a rejected field/value was dropped. Surfaced through\n * {@link ParseQueryOptions.onReject} so a caller can warn, count, or\n * return a 400 instead of the historical silent drop.\n */\nexport type QueryRejectReason = 'field-not-allowed' | 'unknown-operator' | 'truncated'\n\nexport interface QueryRejection {\n kind: 'filter' | 'sort' | 'search'\n field: string\n reason: QueryRejectReason\n}\n\nexport interface ParseQueryOptions {\n /** Hard cap on `limit`. Default: global (100, override via {@link setQueryParsingDefaults}). */\n maxLimit?: number\n /** `limit` when the query omits one. Default: global (20). */\n defaultLimit?: number\n /** Max `q` length before truncation. Default: global (200). */\n maxSearchLength?: number\n /**\n * Called once per dropped filter/sort field or truncated search.\n * The historical behaviour is a silent drop; wire this to a logger or\n * a 400 to make over-broad / typo'd queries visible.\n */\n onReject?: (rejection: QueryRejection) => void\n}\n\ninterface QueryParsingDefaults {\n maxLimit: number\n defaultLimit: number\n maxSearchLength: number\n defaultPage: number\n}\n\nconst BUILTIN_DEFAULTS: QueryParsingDefaults = {\n maxLimit: 100,\n defaultLimit: 20,\n maxSearchLength: 200,\n defaultPage: 1,\n}\n\nconst defaults: QueryParsingDefaults = { ...BUILTIN_DEFAULTS }\n\n/**\n * Globally override the query-parsing limits — call once at bootstrap.\n * Per-call {@link ParseQueryOptions} still take precedence over these.\n */\nexport function setQueryParsingDefaults(\n partial: Partial<Omit<QueryParsingDefaults, 'defaultPage'>>,\n): void {\n if (partial.maxLimit !== undefined) defaults.maxLimit = partial.maxLimit\n if (partial.defaultLimit !== undefined) defaults.defaultLimit = partial.defaultLimit\n if (partial.maxSearchLength !== undefined) defaults.maxSearchLength = partial.maxSearchLength\n}\n\n/** Read the current global query-parsing defaults (a copy). */\nexport function getQueryParsingDefaults(): Readonly<QueryParsingDefaults> {\n return { ...defaults }\n}\n\n/**\n * Restore the built-in query-parsing defaults (maxLimit 100, defaultLimit\n * 20, maxSearchLength 200). Useful in test teardown after a\n * {@link setQueryParsingDefaults} override so global state can't leak\n * between tests.\n */\nexport function resetQueryParsingDefaults(): void {\n Object.assign(defaults, BUILTIN_DEFAULTS)\n}\n\n// ── Individual parsers ──────────────────────────────────────────────────\n\n/** Parse filter strings like \"status:eq:active\" into structured objects */\nexport function parseFilters(\n filterParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): FilterItem[] {\n if (!filterParam) return []\n\n const items = Array.isArray(filterParam) ? filterParam : [filterParam]\n const results: FilterItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on first two colons only — value may contain colons (e.g. timestamps)\n const firstColon = item.indexOf(':')\n if (firstColon === -1) continue\n\n const secondColon = item.indexOf(':', firstColon + 1)\n if (secondColon === -1) continue\n\n const field = item.slice(0, firstColon)\n const operator = item.slice(firstColon + 1, secondColon)\n const value = item.slice(secondColon + 1)\n\n if (!field || !value) continue\n if (!FILTER_OPERATORS.has(operator)) {\n onReject?.({ kind: 'filter', field, reason: 'unknown-operator' })\n continue\n }\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'filter', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, operator: operator as FilterOperator, value })\n }\n\n return results\n}\n\n/** Parse sort strings like \"firstName:asc\" into structured objects */\nexport function parseSort(\n sortParam: string | string[] | undefined,\n allowedFields?: string[],\n options?: Pick<ParseQueryOptions, 'onReject'>,\n): SortItem[] {\n if (!sortParam) return []\n\n const items = Array.isArray(sortParam) ? sortParam : [sortParam]\n const results: SortItem[] = []\n const allowed = allowedFields ? new Set(allowedFields) : null\n const onReject = options?.onReject\n\n for (const item of items) {\n // Split on last colon so field names with colons work\n const lastColon = item.lastIndexOf(':')\n if (lastColon === -1) continue\n\n const field = item.slice(0, lastColon)\n const dir = item.slice(lastColon + 1).toLowerCase()\n\n if (!field) continue\n if (dir !== 'asc' && dir !== 'desc') continue\n if (allowed && !allowed.has(field)) {\n onReject?.({ kind: 'sort', field, reason: 'field-not-allowed' })\n continue\n }\n\n results.push({ field, direction: dir })\n }\n\n return results\n}\n\n/** Parse page/limit into pagination with computed offset */\nexport function parsePagination(\n params: {\n page?: string | number\n limit?: string | number\n },\n options?: Pick<ParseQueryOptions, 'maxLimit' | 'defaultLimit'>,\n): PaginationParams {\n const defaultLimit = options?.defaultLimit ?? defaults.defaultLimit\n const maxLimit = options?.maxLimit ?? defaults.maxLimit\n\n let page =\n typeof params.page === 'string'\n ? Number.parseInt(params.page, 10)\n : (params.page ?? defaults.defaultPage)\n let limit =\n typeof params.limit === 'string'\n ? Number.parseInt(params.limit, 10)\n : (params.limit ?? defaultLimit)\n\n if (Number.isNaN(page) || page < 1) page = defaults.defaultPage\n if (Number.isNaN(limit) || limit < 1) limit = defaultLimit\n if (limit > maxLimit) limit = maxLimit\n\n return { page, limit, offset: (page - 1) * limit }\n}\n\n/** Sanitize and truncate search query */\nexport function parseSearchQuery(\n q: string | undefined,\n options?: Pick<ParseQueryOptions, 'maxSearchLength' | 'onReject'>,\n): string {\n if (!q) return ''\n const maxLen = options?.maxSearchLength ?? defaults.maxSearchLength\n const trimmed = q.trim()\n if (trimmed.length > maxLen) {\n options?.onReject?.({ kind: 'search', field: 'q', reason: 'truncated' })\n return trimmed.slice(0, maxLen)\n }\n return trimmed\n}\n\n// ── Config normalizer ───────────────────────────────────────────────────\n\n/**\n * Normalize a QueryFieldConfig into the string-based form.\n * Detects column-object-based configs (DrizzleQueryParamsConfig) by checking\n * for a `columns` property and extracts `Object.keys()`.\n */\nfunction normalizeFieldConfig(config?: QueryFieldConfig): StringQueryFieldConfig | undefined {\n if (!config) return undefined\n\n // Column-object-based config (e.g., DrizzleQueryParamsConfig)\n if ('columns' in config && config.columns && typeof config.columns === 'object') {\n return {\n filterable: Object.keys(config.columns),\n sortable: config.sortable ? Object.keys(config.sortable) : undefined,\n searchable: undefined, // searchColumns are Column objects, not used for string filtering\n }\n }\n\n // Already string-based\n return config as StringQueryFieldConfig\n}\n\n// ── Combined parser ─────────────────────────────────────────────────────\n\n/**\n * Parse a raw Express query object into a structured, ORM-agnostic ParsedQuery.\n *\n * @param query - Raw query string object from `req.query` or Zod-validated object\n * @param fieldConfig - Optional field restrictions (whitelist filterable/sortable/searchable).\n * Accepts both string-based configs (`{ filterable, sortable, searchable }`) and\n * column-object configs (`{ columns, sortable, searchColumns }`).\n * @param options - Limits + an `onReject` hook for dropped/truncated input.\n *\n * @example\n * ```ts\n * // String-based config\n * const parsed = parseQuery(ctx.query, {\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * })\n *\n * // Column-object config (DrizzleQueryParamsConfig)\n * const parsed = parseQuery(ctx.query, TASK_QUERY_CONFIG)\n * ```\n *\n * Query string format:\n * - Filters: `?filter=field:operator:value` (repeatable)\n * - Sort: `?sort=field:asc|desc` (repeatable)\n * - Pagination: `?page=1&limit=20`\n * - Search: `?q=search+term`\n *\n * Filter operators: eq, neq, gt, gte, lt, lte, between, in, contains, starts, ends\n */\nexport function parseQuery<TConfig extends QueryFieldConfig | undefined = undefined>(\n query: Record<string, any>,\n fieldConfig?: TConfig,\n options?: ParseQueryOptions,\n): TypedParsedQuery<TConfig> {\n const normalized = normalizeFieldConfig(fieldConfig)\n const rejectOpts = options?.onReject ? { onReject: options.onReject } : undefined\n return {\n filters: parseFilters(query.filter, normalized?.filterable, rejectOpts),\n sort: parseSort(query.sort, normalized?.sortable, rejectOpts),\n pagination: parsePagination({ page: query.page, limit: query.limit }, options),\n search: parseSearchQuery(query.q, options),\n } as TypedParsedQuery<TConfig>\n}\n\n// ── Query URL builder (for client-side or testing) ──────────────────────\n\n/** Convert ParsedQuery back into query string parameters */\nexport function buildQueryParams(\n parsed: Partial<ParsedQuery>,\n): Record<string, string | string[] | number> {\n const params: Record<string, string | string[] | number> = {}\n\n if (parsed.filters?.length) {\n params.filter = parsed.filters.map((f) => `${f.field}:${f.operator}:${f.value}`)\n }\n\n if (parsed.sort?.length) {\n params.sort = parsed.sort.map((s) => `${s.field}:${s.direction}`)\n }\n\n if (parsed.pagination) {\n params.page = parsed.pagination.page\n params.limit = parsed.pagination.limit\n }\n\n if (parsed.search) {\n params.q = parsed.search\n }\n\n return params\n}\n"],"mappings":";;;;;;;;;;AAcA,MAAa,iBAAmB,IAAI,IAAY,CAC9C,KACA,MACA,KACA,MACA,KACA,MACA,UACA,KACA,WACA,SACA,MACF,CAAC,ECuBK,iBAAyC,CAC7C,SAAU,IACV,aAAc,GACd,gBAAiB,IACjB,YAAa,CACf,EAEM,SAAiC,CAAE,GAAG,gBAAiB,EAM7D,SAAgB,wBACd,QACM,CACF,QAAQ,WAAa,IAAA,KAAW,SAAS,SAAW,QAAQ,UAC5D,QAAQ,eAAiB,IAAA,KAAW,SAAS,aAAe,QAAQ,cACpE,QAAQ,kBAAoB,IAAA,KAAW,SAAS,gBAAkB,QAAQ,gBAChF,CAGA,SAAgB,yBAA0D,CACxE,MAAO,CAAE,GAAG,QAAS,CACvB,CAQA,SAAgB,2BAAkC,CAChD,OAAO,OAAO,SAAU,gBAAgB,CAC1C,CAKA,SAAgB,aACd,YACA,cACA,QACc,CACd,GAAI,CAAC,YAAa,MAAO,CAAC,EAE1B,IAAM,MAAQ,MAAM,QAAQ,WAAW,EAAI,YAAc,CAAC,WAAW,EAC/D,QAAwB,CAAC,EACzB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KACnD,SAAW,SAAS,SAE1B,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,WAAa,KAAK,QAAQ,GAAG,EACnC,GAAI,aAAe,GAAI,SAEvB,IAAM,YAAc,KAAK,QAAQ,IAAK,WAAa,CAAC,EACpD,GAAI,cAAgB,GAAI,SAExB,IAAM,MAAQ,KAAK,MAAM,EAAG,UAAU,EAChC,SAAW,KAAK,MAAM,WAAa,EAAG,WAAW,EACjD,MAAQ,KAAK,MAAM,YAAc,CAAC,EAEpC,MAAC,OAAS,CAAC,OACf,IAAI,CAAC,iBAAiB,IAAI,QAAQ,EAAG,CACnC,WAAW,CAAE,KAAM,SAAU,MAAO,OAAQ,kBAAmB,CAAC,EAChE,QACF,CACA,GAAI,SAAW,CAAC,QAAQ,IAAI,KAAK,EAAG,CAClC,WAAW,CAAE,KAAM,SAAU,MAAO,OAAQ,mBAAoB,CAAC,EACjE,QACF,CAEA,QAAQ,KAAK,CAAE,MAAiB,SAA4B,KAAM,CAAC,CANnE,CAOF,CAEA,OAAO,OACT,CAGA,SAAgB,UACd,UACA,cACA,QACY,CACZ,GAAI,CAAC,UAAW,MAAO,CAAC,EAExB,IAAM,MAAQ,MAAM,QAAQ,SAAS,EAAI,UAAY,CAAC,SAAS,EACzD,QAAsB,CAAC,EACvB,QAAU,cAAgB,IAAI,IAAI,aAAa,EAAI,KACnD,SAAW,SAAS,SAE1B,IAAK,IAAM,QAAQ,MAAO,CAExB,IAAM,UAAY,KAAK,YAAY,GAAG,EACtC,GAAI,YAAc,GAAI,SAEtB,IAAM,MAAQ,KAAK,MAAM,EAAG,SAAS,EAC/B,IAAM,KAAK,MAAM,UAAY,CAAC,CAAC,CAAC,YAAY,EAE7C,UACD,QAAQ,OAAS,MAAQ,QAC7B,IAAI,SAAW,CAAC,QAAQ,IAAI,KAAK,EAAG,CAClC,WAAW,CAAE,KAAM,OAAQ,MAAO,OAAQ,mBAAoB,CAAC,EAC/D,QACF,CAEA,QAAQ,KAAK,CAAE,MAAO,UAAW,GAAI,CAAC,CAFtC,CAGF,CAEA,OAAO,OACT,CAGA,SAAgB,gBACd,OAIA,QACkB,CAClB,IAAM,aAAe,SAAS,cAAgB,SAAS,aACjD,SAAW,SAAS,UAAY,SAAS,SAE3C,KACF,OAAO,OAAO,MAAS,SACnB,OAAO,SAAS,OAAO,KAAM,EAAE,EAC9B,OAAO,MAAQ,SAAS,YAC3B,MACF,OAAO,OAAO,OAAU,SACpB,OAAO,SAAS,OAAO,MAAO,EAAE,EAC/B,OAAO,OAAS,aAMvB,OAJI,OAAO,MAAM,IAAI,GAAK,KAAO,KAAG,KAAO,SAAS,cAChD,OAAO,MAAM,KAAK,GAAK,MAAQ,KAAG,MAAQ,cAC1C,MAAQ,WAAU,MAAQ,UAEvB,CAAE,KAAM,MAAO,QAAS,KAAO,GAAK,KAAM,CACnD,CAGA,SAAgB,iBACd,EACA,QACQ,CACR,GAAI,CAAC,EAAG,MAAO,GACf,IAAM,OAAS,SAAS,iBAAmB,SAAS,gBAC9C,QAAU,EAAE,KAAK,EAKvB,OAJI,QAAQ,OAAS,QACnB,SAAS,WAAW,CAAE,KAAM,SAAU,MAAO,IAAK,OAAQ,WAAY,CAAC,EAChE,QAAQ,MAAM,EAAG,MAAM,GAEzB,OACT,CASA,SAAS,qBAAqB,OAA+D,CACtF,UAYL,MATI,YAAa,QAAU,OAAO,SAAW,OAAO,OAAO,SAAY,SAC9D,CACL,WAAY,OAAO,KAAK,OAAO,OAAO,EACtC,SAAU,OAAO,SAAW,OAAO,KAAK,OAAO,QAAQ,EAAI,IAAA,GAC3D,WAAY,IAAA,EACd,EAIK,MACT,CAiCA,SAAgB,WACd,MACA,YACA,QAC2B,CAC3B,IAAM,WAAa,qBAAqB,WAAW,EAC7C,WAAa,SAAS,SAAW,CAAE,SAAU,QAAQ,QAAS,EAAI,IAAA,GACxE,MAAO,CACL,QAAS,aAAa,MAAM,OAAQ,YAAY,WAAY,UAAU,EACtE,KAAM,UAAU,MAAM,KAAM,YAAY,SAAU,UAAU,EAC5D,WAAY,gBAAgB,CAAE,KAAM,MAAM,KAAM,MAAO,MAAM,KAAM,EAAG,OAAO,EAC7E,OAAQ,iBAAiB,MAAM,EAAG,OAAO,CAC3C,CACF,CAKA,SAAgB,iBACd,OAC4C,CAC5C,IAAM,OAAqD,CAAC,EAmB5D,OAjBI,OAAO,SAAS,SAClB,OAAO,OAAS,OAAO,QAAQ,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,OAAO,GAG7E,OAAO,MAAM,SACf,OAAO,KAAO,OAAO,KAAK,IAAK,GAAM,GAAG,EAAE,MAAM,GAAG,EAAE,WAAW,GAG9D,OAAO,aACT,OAAO,KAAO,OAAO,WAAW,KAChC,OAAO,MAAQ,OAAO,WAAW,OAG/B,OAAO,SACT,OAAO,EAAI,OAAO,QAGb,MACT"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs v5.18.0-alpha.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import{i as requestStore}from"./context-leZajQAj.mjs";import crypto from"node:crypto";const REQUEST_SCOPE_MIDDLEWARE_MARKER=Symbol.for(`@kickjs/requestScopeMiddleware`);function createRequestStore(requestId){return{requestId:requestId||crypto.randomUUID(),instances:new Map,values:new Map}}function requestScopeMiddleware(){let mw=(req,_res,next)=>{let requestIdHeader=req.headers[`x-request-id`],requestId=(Array.isArray(requestIdHeader)?requestIdHeader[0]:requestIdHeader)||crypto.randomUUID(),store=createRequestStore(requestId);req.requestId=requestId,requestStore.run(store,()=>next())};return mw[REQUEST_SCOPE_MIDDLEWARE_MARKER]=!0,mw}function isRequestScopeMiddleware(value){return typeof value==`function`?value[REQUEST_SCOPE_MIDDLEWARE_MARKER]===!0:!1}export{isRequestScopeMiddleware as n,requestScopeMiddleware as r,createRequestStore as t};
12
+ //# sourceMappingURL=request-scope-BaM8_e_8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-scope-BaM8_e_8.mjs","names":[],"sources":["../src/http/middleware/request-scope.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\nimport { requestStore, type RequestStore } from '../request-store'\n\n/**\n * Marker symbol stamped on the function returned by {@link requestScopeMiddleware}.\n *\n * `Application.setup()` uses {@link isRequestScopeMiddleware} to detect whether\n * the user-supplied middleware list already contains the request-scope wrapper,\n * and skips its automatic auto-mount when one is found. Lets adopters control\n * the exact position of the ALS frame in their pipeline (e.g., after a custom\n * tracing wrapper) without ending up with two nested frames.\n */\n// `Symbol.for(...)` returns a plain `symbol` (not `unique symbol`), but the\n// runtime detection marker doesn't need the unique nominal property — it only\n// needs the interned-slot reference equality that `Symbol.for` already provides.\nconst REQUEST_SCOPE_MIDDLEWARE_MARKER = Symbol.for('@kickjs/requestScopeMiddleware')\n\n/**\n * Build a fresh per-request store. Shared by {@link requestScopeMiddleware}\n * (Express) and by runtimes whose handler runs outside the connect-middleware\n * chain (e.g. Fastify), so both establish an identical ALS frame.\n */\nexport function createRequestStore(requestId?: string): RequestStore {\n return {\n requestId: requestId || crypto.randomUUID(),\n instances: new Map(),\n // Explicit generic locks the \"string keys only\" decision.\n values: new Map<string, unknown>(),\n }\n}\n\n/**\n * Wraps each request in an AsyncLocalStorage context.\n * Enables REQUEST-scoped DI and automatic requestId propagation to logs.\n *\n * Should be mounted early in the middleware pipeline (before route handlers).\n * `Application` mounts this automatically unless `contextStore: 'manual'` is set\n * or the user already includes one in their middleware list.\n */\nexport function requestScopeMiddleware() {\n const mw = (req: Request, _res: Response, next: NextFunction) => {\n const requestIdHeader = req.headers['x-request-id']\n const requestId =\n (Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader) || crypto.randomUUID()\n const store = createRequestStore(requestId)\n // Surface the requestId on req so the standalone requestId() middleware\n // (when also mounted) reuses this value instead of generating a divergent\n // second one. Without this, log lines that read from the ALS store would\n // disagree with the X-Request-Id response header for any request that\n // didn't carry an inbound x-request-id header.\n ;(req as Request & { requestId?: string }).requestId = requestId\n requestStore.run(store, () => next())\n }\n ;(mw as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] = true\n return mw\n}\n\n/**\n * Returns `true` when `value` is the function produced by {@link requestScopeMiddleware}.\n *\n * Detection uses a `Symbol.for(...)` marker so the check survives different\n * copies of `@forinda/kickjs` loaded under different module identities (e.g.,\n * a published package alongside a workspace-linked one during local dev).\n */\nexport function isRequestScopeMiddleware(value: unknown): boolean {\n if (typeof value !== 'function') return false\n return (value as unknown as Record<symbol, unknown>)[REQUEST_SCOPE_MIDDLEWARE_MARKER] === true\n}\n"],"mappings":";;;;;;;;;;sFAgBA,MAAM,gCAAkC,OAAO,IAAI,gCAAgC,EAOnF,SAAgB,mBAAmB,UAAkC,CACnE,MAAO,CACL,UAAW,WAAa,OAAO,WAAW,EAC1C,UAAW,IAAI,IAEf,OAAQ,IAAI,GACd,CACF,CAUA,SAAgB,wBAAyB,CACvC,IAAM,IAAM,IAAc,KAAgB,OAAuB,CAC/D,IAAM,gBAAkB,IAAI,QAAQ,gBAC9B,WACH,MAAM,QAAQ,eAAe,EAAI,gBAAgB,GAAK,kBAAoB,OAAO,WAAW,EACzF,MAAQ,mBAAmB,SAAS,EAMzC,IAA0C,UAAY,UACvD,aAAa,IAAI,UAAa,KAAK,CAAC,CACtC,EAEA,MADC,IAA2C,iCAAmC,GACxE,EACT,CASA,SAAgB,yBAAyB,MAAyB,CAEhE,OADI,OAAO,OAAU,WACb,MAA6C,mCAAqC,GADlD,EAE1C"}
@@ -0,0 +1,39 @@
1
+
2
+ import { J as SourcedRegistration, m as RouteEntry } from "./adapter-D_uzU-QA.mjs";
3
+
4
+ //#region src/http/router-builder.d.ts
5
+ /** @internal — set by Application.setup() before each `mod.routes()` call. */
6
+ declare function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void;
7
+ interface BuildRoutesOptions {
8
+ /**
9
+ * Extra contributors to merge into the per-route pipeline at their declared
10
+ * precedence levels. Pass explicitly when calling buildRouteTable/buildRoutes
11
+ * outside the Application route-mount loop (typically in tests). When omitted,
12
+ * falls back to the slot set by Application.setup().
13
+ */
14
+ externalSources?: readonly SourcedRegistration[];
15
+ }
16
+ /**
17
+ * Turn a controller class decorated with @Get, @Post, etc. into a plain-data
18
+ * {@link RouteEntry}[] — the engine-neutral route table an {@link HttpRuntime}
19
+ * materializes (see `docs/http/spec-http-runtimes.md`, Avenue B).
20
+ *
21
+ * What used to be per-handler Express `(req, res, next)` closures is now
22
+ * captured as data: `middlewares` keep their `(ctx, next)` shape, the contributor
23
+ * pipeline is pre-built into a `contributorRunner` closure, validation / upload
24
+ * stay as metadata, and the terminal `handler` resolves the controller per-request
25
+ * (to respect DI scoping) and invokes it. The runtime owns how these get wrapped
26
+ * onto its engine.
27
+ *
28
+ * Routes use only the method-level decorator paths (e.g. @Get('/me') → '/me').
29
+ * The @Controller path is NOT baked in — it is metadata for Swagger/introspection.
30
+ * The module's routes().path is the single source of truth for the mount prefix,
31
+ * which avoids path doubling when both the module and controller specify the path.
32
+ *
33
+ * The contributor pipeline is built here, so a cycle or missing `dependsOn`
34
+ * throws at table-build time (boot) rather than on first request.
35
+ */
36
+ declare function buildRouteTable(controllerClass: any, options?: BuildRoutesOptions): RouteEntry[];
37
+ //#endregion
38
+ export { _setExternalContributorSources as n, buildRouteTable as r, BuildRoutesOptions as t };
39
+ //# sourceMappingURL=router-builder-Bh3qSiDz.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-builder-Bh3qSiDz.d.mts","names":[],"sources":["../src/http/router-builder.ts"],"mappings":";;;;;iBAgCgB,8BAAA,CAA+B,OAAuC,WAArB,mBAAmB;AAAA,UAInE,kBAAA;;;AAJqE;AAItF;;;EAOE,eAAA,YAA2B,mBAAmB;AAAA;AAuBhD;;;;;;;;;AAGa;;;;;;;;;;;AAHb,iBAAgB,eAAA,CACd,eAAA,OACA,OAAA,GAAS,kBAAA,GACR,UAAU"}
@@ -1,5 +1,6 @@
1
1
 
2
- import { r as AppAdapter, t as AdapterContext } from "./adapter-CkpcZwzZ.mjs";
2
+ import { r as AppAdapter, t as AdapterContext } from "./adapter-D_uzU-QA.mjs";
3
+
3
4
  //#region src/http/middleware/spa.d.ts
4
5
  interface SpaAdapterOptions {
5
6
  /**
@@ -98,4 +99,4 @@ declare class SpaAdapter implements AppAdapter {
98
99
  }
99
100
  //#endregion
100
101
  export { SpaAdapterOptions as n, SpaAdapter as t };
101
- //# sourceMappingURL=spa-IRFC2eMN.d.mts.map
102
+ //# sourceMappingURL=spa-BdvZFXJz.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spa-BdvZFXJz.d.mts","names":[],"sources":["../src/http/middleware/spa.ts"],"mappings":";;;;UAMiB,iBAAA;;AAAjB;;;;;;;;;;AAiDmB;AAkCnB;EArEE,SAAA;;;;;;;;;;;EAYA,SAAA;EA0DA;;;;;;;;;EA/CA,OAAA;EAiEA;;;;EA3DA,YAAA;EAoGc;;;;EA9Fd,iBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCW,UAAA,YAAsB,UAAA;EAAA,QASb,OAAA;EARpB,IAAA;EAAA,QACQ,SAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,YAAA;EAAA,QACA,iBAAA;EAAA,QACA,SAAA;EAER,WAAA,CAAoB,OAAA,GAAS,iBAAA;EAU7B,WAAA;IAAc;EAAA,GAAO,cAAA;EAyCrB,WAAA;IAAc;EAAA,GAAO,cAAA;AAAA"}
@@ -15,6 +15,41 @@ import { Options } from "multer";
15
15
  * ```
16
16
  */
17
17
  declare function resolveMimeTypes(types: string[]): string[];
18
+ /**
19
+ * Build a runtime-agnostic `(mimetype, filename) => boolean` file-type filter
20
+ * from `allowedTypes` (a predicate, or a list of extensions / MIME types /
21
+ * wildcards). Returned by both the multer middleware (Express) and the
22
+ * Fastify / h3 multipart paths so the accept/reject rule stays identical
23
+ * across engines. No `allowedTypes` → accept everything.
24
+ */
25
+ declare function buildFileTypeFilter(allowedTypes: BaseUploadOptions['allowedTypes'], customMimeMap?: Record<string, string>): (mimetype: string, filename: string) => boolean;
26
+ /** A multer-shaped uploaded file — what `ctx.file` / `ctx.files` expose. */
27
+ interface UploadedFileLike {
28
+ fieldname: string;
29
+ originalname: string;
30
+ encoding: string;
31
+ mimetype: string;
32
+ size: number;
33
+ buffer: Buffer;
34
+ }
35
+ /** A buffered multipart file part, as the runtime hands it to {@link applyUploadConfig}. */
36
+ interface RawUploadPart {
37
+ fieldname: string;
38
+ filename: string;
39
+ mimetype: string;
40
+ buffer: Buffer;
41
+ }
42
+ /**
43
+ * Apply a `@FileUpload` config to already-buffered multipart parts and return
44
+ * the multer-shaped `file` / `files` for `ctx`. Enforces the field name, type
45
+ * filter, per-file size limit, and (array mode) max count — throwing
46
+ * `HttpException` on a violation, mirroring the Express/multer error path.
47
+ * Used by the Fastify and h3 runtimes; Express keeps using multer directly.
48
+ */
49
+ declare function applyUploadConfig(parts: RawUploadPart[], config: FileUploadConfig): {
50
+ file?: UploadedFileLike;
51
+ files?: UploadedFileLike[];
52
+ };
18
53
  /**
19
54
  * Upload options for the middleware.
20
55
  * Extends BaseUploadOptions from core (shared with @FileUpload decorator)
@@ -75,5 +110,5 @@ declare const upload: {
75
110
  none: typeof none;
76
111
  };
77
112
  //#endregion
78
- export { upload as a, resolveMimeTypes as i, buildUploadMiddleware as n, cleanupFiles as r, UploadOptions as t };
79
- //# sourceMappingURL=upload-D6edAELH.d.mts.map
113
+ export { buildFileTypeFilter as a, resolveMimeTypes as c, applyUploadConfig as i, upload as l, UploadOptions as n, buildUploadMiddleware as o, UploadedFileLike as r, cleanupFiles as s, RawUploadPart as t };
114
+ //# sourceMappingURL=upload-Bc_rBRtE.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-Bc_rBRtE.d.mts","names":[],"sources":["../src/http/middleware/upload.ts"],"mappings":";;;;;;;;;AAsGA;;;;AAAgD;AAchD;;iBAdgB,gBAAA,CAAiB,KAAe;;;;;;;;iBAchC,mBAAA,CACd,YAAA,EAAc,iBAAA,kBACd,aAAA,GAAgB,MAAM,oBACpB,QAAA,UAAkB,QAAA;AAAgB;AAAA,UAYrB,gBAAA;EACf,SAAA;EACA,YAAA;EACA,QAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA,EAAQ,MAAM;AAAA;;UAIC,aAAA;EACf,SAAA;EACA,QAAA;EACA,QAAA;EACA,MAAA,EAAQ,MAAM;AAAA;;;;;;;;iBAYA,iBAAA,CACd,KAAA,EAAO,aAAA,IACP,MAAA,EAAQ,gBAAA;EACL,IAAA,GAAO,gBAAA;EAAkB,KAAA,GAAQ,gBAAA;AAAA;;;;;;UAyCrB,aAAA,SAAsB,iBAAiB;EAzCF;EA2CpD,OAAA,GAAU,OAAA;EA7CV;EA+CA,IAAA;AAAA;;;;;;AA7CoD;AAyCtD;;;;;;iBA8CS,MAAA,CAAO,SAAA,UAAmB,OAAA,GAAS,aAAA,GAAqB,cAAc;;;AA1CzE;iBAkDG,KAAA,CAAM,SAAA,UAAmB,QAAA,WAAe,OAAA,GAAS,aAAA,GAAqB,cAAc;;;;iBAQpF,IAAA,CAAK,OAAA,GAAS,aAAA,GAAqB,cAAc;;;;;AAhBqB;AAAA;;;;;;;;;iBAmC/D,YAAA,KAAY,GAAA,EACb,OAAA,EAAO,GAAA,EAAO,QAAA,EAAQ,IAAA,EAAQ,YAAA;AA5BgD;AAAA;;;;AAAA,iBA2D7E,qBAAA,CAAsB,MAAA,EAAQ,gBAAA,GAAmB,cAAc;;cAmBlE,MAAA"}
@@ -1,9 +1,8 @@
1
1
 
2
- import { i as MetaValue } from "./execution-context-B4cfhivz.mjs";
3
- import { c as DepValue, n as ContextDecorator, r as ContextDecoratorSpec } from "./context-decorator-DDZ30BIW.mjs";
4
- import { c as AdapterFactory } from "./index-frSEHiL_.mjs";
5
- import { r as RequestContext } from "./context-Dnt2Di7p.mjs";
6
- import { NextFunction, Request, Response } from "express";
2
+ import { f as DepValue, o as ContextDecorator, s as ContextDecoratorSpec, v as MetaValue } from "./app-module-CuAS6hJC.mjs";
3
+ import { P as AdapterFactory, T as RequestContext, f as HttpRuntime, m as RouteEntry } from "./adapter-D_uzU-QA.mjs";
4
+ import { t as BuildRoutesOptions } from "./router-builder-Bh3qSiDz.mjs";
5
+ import { Express, NextFunction, Request, Response, Router } from "express";
7
6
  import { AsyncLocalStorage } from "node:async_hooks";
8
7
 
9
8
  //#region src/http/cluster.d.ts
@@ -57,6 +56,31 @@ interface DefineHttpContextDecoratorFn {
57
56
  }
58
57
  declare const defineHttpContextDecorator: DefineHttpContextDecoratorFn;
59
58
  //#endregion
59
+ //#region src/http/runtimes/express.d.ts
60
+ /**
61
+ * Materialize a controller's {@link RouteEntry}[] onto a fresh `express.Router`.
62
+ * Reproduces the legacy handler ordering precisely: validation → upload →
63
+ * `(ctx, next)` middleware → contributor runner → terminal handler. Each step
64
+ * constructs its own `RequestContext` over the same `req`/`res` — request-scoped
65
+ * state lives in the AsyncLocalStorage store, not on the ctx instance, so the
66
+ * per-step instances share state exactly as before.
67
+ */
68
+ declare function materializeRouter(entries: RouteEntry[]): Router;
69
+ /**
70
+ * Build an Express Router from a controller class decorated with @Get, @Post,
71
+ * etc. The public, back-compatible entry point: a thin shim over
72
+ * {@link buildRouteTable} + {@link materializeRouter}. Returns an
73
+ * `express.Router` exactly as before — modules pass it to `app.use(path, router)`.
74
+ *
75
+ * @see buildRouteTable for the engine-neutral route-table form.
76
+ */
77
+ declare function buildRoutes(controllerClass: any, options?: BuildRoutesOptions): Router;
78
+ /**
79
+ * The default HTTP runtime. Express stays a peer dependency of `@forinda/kickjs`;
80
+ * apps that pass no `runtime` get this and behave exactly as before.
81
+ */
82
+ declare function expressRuntime(): HttpRuntime<Express>;
83
+ //#endregion
60
84
  //#region src/http/request-store.d.ts
61
85
  /** Per-request storage for REQUEST-scoped DI and request context propagation */
62
86
  interface RequestStore {
@@ -245,5 +269,5 @@ interface ViewAdapterOptions {
245
269
  */
246
270
  declare const ViewAdapter: AdapterFactory<ViewAdapterOptions, unknown>;
247
271
  //#endregion
248
- export { TraceContextOptions as a, requestScopeMiddleware as c, getRequestValue as d, requestStore as f, isClusterPrimary as h, TraceContext as i, RequestStore as l, ClusterOptions as m, ViewAdapterOptions as n, parseTraceparent as o, defineHttpContextDecorator as p, TRACEPARENT_HEADER as r, traceContext as s, ViewAdapter as t, getRequestStore as u };
249
- //# sourceMappingURL=views-BCl-mETX.d.mts.map
272
+ export { ClusterOptions as _, TraceContextOptions as a, requestScopeMiddleware as c, getRequestValue as d, requestStore as f, defineHttpContextDecorator as g, materializeRouter as h, TraceContext as i, RequestStore as l, expressRuntime as m, ViewAdapterOptions as n, parseTraceparent as o, buildRoutes as p, TRACEPARENT_HEADER as r, traceContext as s, ViewAdapter as t, getRequestStore as u, isClusterPrimary as v };
273
+ //# sourceMappingURL=views-DcTVDiOh.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"views-DcTVDiOh.d.mts","names":[],"sources":["../src/http/cluster.ts","../src/http/define-http-context-decorator.ts","../src/http/runtimes/express.ts","../src/http/request-store.ts","../src/http/middleware/request-scope.ts","../src/http/middleware/trace-context.ts","../src/http/middleware/views.ts"],"mappings":";;;;;;;;UAOiB,cAAA;;EAEf,OAAA;;EAEA,uBAAA;;EAEA,YAAA;;EAEA,WAAA;;EAEA,aAAA;AAAA;;iBAIc,gBAAA;;;;;;;;UCuDC,oCAAA,WAA+C,MAAA;EAAA,6BACjC,MAAA,SAAe,QAAA,IAAY,MAAA,iBACtD,IAAA,EAAM,oBAAA,CAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,cAAA,IACnC,gBAAA,CAAiB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,cAAA;AAAA;;;;;;UAQd,4BAAA;EAAA,6BAGH,MAAA,SAAe,QAAA,IAAY,MAAA,2BAC3B,MAAA,oBAA0B,MAAA,iBAEpC,IAAA,EAAM,oBAAA,CAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,cAAA,IACnC,gBAAA,CAAiB,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,cAAA;EAPd;;;;;;;;;;;;;;;EAwBf,UAAA,WAAqB,MAAA,sBAA4B,oCAAA,CAAqC,CAAA;AAAA;AAAA,cAW3E,0BAAA,EAA4B,4BAQR;;;;;;;;;;;iBC5FjB,iBAAA,CAAkB,OAAA,EAAS,UAAA,KAAe,MAAM;;;;;;;;;iBA8DhD,WAAA,CAAY,eAAA,OAAsB,OAAA,GAAS,kBAAA,GAA0B,MAAM;AF7F3F;;;;AAAA,iBEqGgB,cAAA,IAAkB,WAAW,CAAC,OAAA;;;;UCxG7B,YAAA;;EAEf,SAAA;;;;;;EAMA,SAAA,EAAW,GAAA;;;;;;;;;;AHLb;;;;;;;;;;AAUe;EGgBb,MAAA,EAAQ,GAAG;AAAA;;cAIA,YAAA,EAAY,iBAAA,CAAA,YAAA;AHhBO;;;;AAAA,iBGsBhB,eAAA,IAAmB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoC/B,eAAA,mBAAkC,GAAA,EAAK,CAAA,GAAI,SAAA,CAAU,CAAA;;;;;;;;;;;iBCvCrD,sBAAA,KAAsB,GAAA,EACnB,OAAA,EAAO,IAAA,EAAQ,QAAA,EAAQ,IAAA,EAAQ,YAAA;;;;;;;cCjCrC,kBAAA;AAAA,UAKI,mBAAA;;;;;EAKf,iBAAiB;AAAA;;;;UAMF,YAAA;;EAEf,OAAA;;EAEA,OAAA;ELrBe;EKuBf,YAAA;;EAEA,KAAA;AAAA;;;;;iBAqBc,gBAAA,CAAiB,MAAA,WAAiB,YAAY;ALpC/C;AAIf;;;;AAAgC;;;;ACuDhC;;;;;;;;;;;;AD3De,iBK8EC,YAAA,CAAa,OAAA,GAAS,mBAAA,IAAwB,GAAA,EAC/C,OAAA,EAAO,GAAA,EAAO,QAAA,EAAQ,IAAA,EAAQ,YAAA;;;UC3F5B,kBAAA;;;;;;;;;;;;;;;EAef,MAAA;;EAGA,GAAA;;EAGA,QAAA;;EAGA,MAAA;AAAA;;;;;;;;;ANZa;AAIf;;;;AAAgC;;;;ACuDhC;;;;;;;;;;;;cKfa,WAAA,EAAW,cAAA,CAAA,kBAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs",
3
- "version": "5.16.0",
3
+ "version": "5.18.0-alpha.0",
4
4
  "description": "A production-grade, decorator-driven Node.js framework built on Express 5 and TypeScript.",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -89,6 +89,14 @@
89
89
  "./config": {
90
90
  "import": "./dist/config/index.mjs",
91
91
  "types": "./dist/config/index.d.mts"
92
+ },
93
+ "./fastify": {
94
+ "import": "./dist/http/runtimes/fastify.mjs",
95
+ "types": "./dist/http/runtimes/fastify.d.mts"
96
+ },
97
+ "./h3": {
98
+ "import": "./dist/http/runtimes/h3.mjs",
99
+ "types": "./dist/http/runtimes/h3.d.mts"
92
100
  }
93
101
  },
94
102
  "files": [
@@ -101,15 +109,31 @@
101
109
  "@forinda/kickjs-schema": "0.1.2"
102
110
  },
103
111
  "peerDependencies": {
112
+ "@fastify/middie": "^9.0.0",
113
+ "@fastify/multipart": "^9.0.0",
104
114
  "dotenv": "^17.0.0",
105
115
  "express": "^5.1.0",
116
+ "fastify": "^5.0.0",
117
+ "h3": "^1.0.0",
106
118
  "multer": "^2.0.0",
107
119
  "zod": "^4.0.0"
108
120
  },
109
121
  "peerDependenciesMeta": {
122
+ "@fastify/middie": {
123
+ "optional": true
124
+ },
125
+ "@fastify/multipart": {
126
+ "optional": true
127
+ },
110
128
  "dotenv": {
111
129
  "optional": true
112
130
  },
131
+ "h3": {
132
+ "optional": true
133
+ },
134
+ "fastify": {
135
+ "optional": true
136
+ },
113
137
  "multer": {
114
138
  "optional": true
115
139
  },
@@ -118,10 +142,14 @@
118
142
  }
119
143
  },
120
144
  "devDependencies": {
145
+ "@fastify/middie": "^9.3.2",
146
+ "@fastify/multipart": "^9.0.0",
121
147
  "@swc/core": "^1.15.40",
122
148
  "@types/express": "^5.0.6",
123
149
  "@types/multer": "^2.1.0",
124
150
  "@types/node": "^25.9.2",
151
+ "fastify": "^5.8.5",
152
+ "h3": "1.15.11",
125
153
  "multer": "^2.1.1",
126
154
  "typescript": "^6.0.3",
127
155
  "vite": "^8.0.16",