@forinda/kickjs 8.7.0 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -164
- package/dist/{adapter-DK8WDonT.d.mts → adapter-DhjoIYVI.d.mts} +23 -103
- package/dist/adapter-DhjoIYVI.d.mts.map +1 -0
- package/dist/application-5IWcGDdu.mjs +12 -0
- package/dist/application-5IWcGDdu.mjs.map +1 -0
- package/dist/{application-B6Gp1nqK.d.mts → application-DqD58rhF.d.mts} +17 -9
- package/dist/{application-B6Gp1nqK.d.mts.map → application-DqD58rhF.d.mts.map} +1 -1
- package/dist/{bootstrap-Dds2PVAn.mjs → bootstrap-CiMR9rTB.mjs} +3 -3
- package/dist/{bootstrap-Dds2PVAn.mjs.map → bootstrap-CiMR9rTB.mjs.map} +1 -1
- package/dist/{client-ip-BuY7_pDD.mjs → client-ip-B-LhFjcb.mjs} +2 -2
- package/dist/{client-ip-BuY7_pDD.mjs.map → client-ip-B-LhFjcb.mjs.map} +1 -1
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +2 -2
- package/dist/config-DNkSu_Hl.mjs +12 -0
- package/dist/config-DNkSu_Hl.mjs.map +1 -0
- package/dist/{container-Cqow_bOU.mjs → container-BfmPa7gi.mjs} +3 -3
- package/dist/{container-Cqow_bOU.mjs.map → container-BfmPa7gi.mjs.map} +1 -1
- package/dist/{context-CHuuc9r6.mjs → context-DivVCLKt.mjs} +3 -3
- package/dist/context-DivVCLKt.mjs.map +1 -0
- package/dist/{context-errors-VduncZFQ.mjs → context-errors-MoctmZdH.mjs} +2 -2
- package/dist/{context-errors-VduncZFQ.mjs.map → context-errors-MoctmZdH.mjs.map} +1 -1
- package/dist/{contributor-runner-BoVekHyj.mjs → contributor-runner-DJEVHfm_.mjs} +3 -3
- package/dist/{contributor-runner-BoVekHyj.mjs.map → contributor-runner-DJEVHfm_.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/core-BpHbZ7P8.mjs +12 -0
- package/dist/{core-Chc61ahe.mjs.map → core-BpHbZ7P8.mjs.map} +1 -1
- package/dist/{csrf-D-8W1iv7.mjs → csrf-BVEqmJ4c.mjs} +3 -3
- package/dist/{csrf-D-8W1iv7.mjs.map → csrf-BVEqmJ4c.mjs.map} +1 -1
- package/dist/{decorate-D9n58F0b.mjs → decorate-DlbfE8dD.mjs} +1 -1
- package/dist/{define-adapter-BJunWqi0.mjs → define-adapter-CyVSGUFp.mjs} +2 -2
- package/dist/{define-adapter-BJunWqi0.mjs.map → define-adapter-CyVSGUFp.mjs.map} +1 -1
- package/dist/{disposables-BFIEp5VA.mjs → disposables-CLTUN_9Z.mjs} +2 -2
- package/dist/{disposables-BFIEp5VA.mjs.map → disposables-CLTUN_9Z.mjs.map} +1 -1
- package/dist/{env-8BLpzy-M.mjs → env-BjxVzWFa.mjs} +4 -4
- package/dist/env-BjxVzWFa.mjs.map +1 -0
- package/dist/{error-handler-DxF2EogM.d.mts → error-handler-DiueQHes.d.mts} +2 -2
- package/dist/{error-handler-DxF2EogM.d.mts.map → error-handler-DiueQHes.d.mts.map} +1 -1
- package/dist/{handler-MSvgGVk0.mjs → handler-BEqBMxZH.mjs} +3 -3
- package/dist/{handler-MSvgGVk0.mjs.map → handler-BEqBMxZH.mjs.map} +1 -1
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +2 -2
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/error-handler.mjs.map +1 -1
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.d.mts +1 -1
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/session.mjs.map +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +3 -3
- package/dist/http/middleware/upload.mjs.map +1 -1
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +1 -1
- package/dist/http/runtimes/fastify.mjs +2 -2
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/{index-ClP0EE9p.d.mts → index-CDn-p3tq.d.mts} +26 -17
- package/dist/index-CDn-p3tq.d.mts.map +1 -0
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/jobs-D9pQyr8d.mjs +12 -0
- package/dist/jobs-D9pQyr8d.mjs.map +1 -0
- package/dist/{kv-stores-BDcvhJBw.mjs → kv-stores-9ntcqpGU.mjs} +3 -3
- package/dist/{kv-stores-BDcvhJBw.mjs.map → kv-stores-9ntcqpGU.mjs.map} +1 -1
- package/dist/{kv-stores-B-1WZ2Fh.d.mts → kv-stores-CetFF9N_.d.mts} +104 -4
- package/dist/kv-stores-CetFF9N_.d.mts.map +1 -0
- package/dist/{metadata-BClL-Eb0.mjs → metadata-CW58nTQM.mjs} +2 -2
- package/dist/{metadata-BClL-Eb0.mjs.map → metadata-CW58nTQM.mjs.map} +1 -1
- package/dist/{module-registry-kVXS3peW.mjs → module-registry-BLB0gwEq.mjs} +2 -2
- package/dist/{module-registry-kVXS3peW.mjs.map → module-registry-BLB0gwEq.mjs.map} +1 -1
- package/dist/{observers-C5Jid4FV.mjs → observers-Ac2ZdgCN.mjs} +2 -2
- package/dist/{observers-C5Jid4FV.mjs.map → observers-Ac2ZdgCN.mjs.map} +1 -1
- package/dist/{query-1Q69dPhY.mjs → query-U25WhMfT.mjs} +2 -2
- package/dist/{query-1Q69dPhY.mjs.map → query-U25WhMfT.mjs.map} +1 -1
- package/dist/{respond-DBRvpEI5.mjs → respond-CUPNdYfi.mjs} +2 -2
- package/dist/{respond-DBRvpEI5.mjs.map → respond-CUPNdYfi.mjs.map} +1 -1
- package/dist/{route-flag-BCBTtP_4.mjs → route-flag-BeSiIg98.mjs} +3 -3
- package/dist/{route-flag-BCBTtP_4.mjs.map → route-flag-BeSiIg98.mjs.map} +1 -1
- package/dist/{route-policy-FeJHstvF.mjs → route-policy-Ch0TlY_K.mjs} +2 -2
- package/dist/{route-policy-FeJHstvF.mjs.map → route-policy-Ch0TlY_K.mjs.map} +1 -1
- package/dist/{router-builder-Brup4e82.mjs → router-builder-BL0kmIIu.mjs} +3 -3
- package/dist/{router-builder-Brup4e82.mjs.map → router-builder-BL0kmIIu.mjs.map} +1 -1
- package/dist/{router-builder-CUs9Gq4X.d.mts → router-builder-BSBSYgYW.d.mts} +2 -2
- package/dist/{router-builder-CUs9Gq4X.d.mts.map → router-builder-BSBSYgYW.d.mts.map} +1 -1
- package/dist/{runtime-CV0SS-X5.mjs → runtime-npBaLlo7.mjs} +3 -3
- package/dist/{runtime-CV0SS-X5.mjs.map → runtime-npBaLlo7.mjs.map} +1 -1
- package/dist/{session-BHgRyfqZ.d.mts → session-4Tb6q0Ny.d.mts} +11 -1
- package/dist/session-4Tb6q0Ny.d.mts.map +1 -0
- package/dist/{upload-BukLMawc.d.mts → upload-DjhnyHHm.d.mts} +2 -14
- package/dist/{upload-BukLMawc.d.mts.map → upload-DjhnyHHm.d.mts.map} +1 -1
- package/dist/{upload-config-BBp5QfWQ.mjs → upload-config-DvN699K_.mjs} +2 -2
- package/dist/{upload-config-BBp5QfWQ.mjs.map → upload-config-DvN699K_.mjs.map} +1 -1
- package/dist/web.d.mts +20 -3
- package/dist/web.d.mts.map +1 -1
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/adapter-DK8WDonT.d.mts.map +0 -1
- package/dist/application-D2scwBz-.mjs +0 -12
- package/dist/application-D2scwBz-.mjs.map +0 -1
- package/dist/config-DBebCpg_.mjs +0 -12
- package/dist/config-DBebCpg_.mjs.map +0 -1
- package/dist/context-CHuuc9r6.mjs.map +0 -1
- package/dist/core-Chc61ahe.mjs +0 -12
- package/dist/env-8BLpzy-M.mjs.map +0 -1
- package/dist/index-ClP0EE9p.d.mts.map +0 -1
- package/dist/kv-stores-B-1WZ2Fh.d.mts.map +0 -1
- package/dist/session-BHgRyfqZ.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -1,212 +1,75 @@
|
|
|
1
1
|
# @forinda/kickjs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern type-safe backend framework, powered by Vite. Built for developer experience, and pluggable as you need: DI, modules, a typed request context, validation and generators, on Express (default), Fastify or h3, and on Workers, Bun and Deno through a web-standard entry.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Scaffold a new project (recommended) — gets you a complete layout in seconds
|
|
9
8
|
npx @forinda/kickjs-cli new my-api && cd my-api && pnpm dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add it to an existing project:
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
```bash
|
|
12
14
|
pnpm add @forinda/kickjs express reflect-metadata zod
|
|
13
15
|
pnpm add -D @forinda/kickjs-cli @forinda/kickjs-vite
|
|
14
|
-
|
|
15
|
-
# Prefer another engine? Install its peer and swap one line (see HTTP Runtimes):
|
|
16
|
-
# pnpm add fastify @fastify/middie → runtime: fastifyRuntime()
|
|
17
|
-
# pnpm add h3 → runtime: h3Runtime()
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
The CLI scaffolds a complete layout. The files below show the canonical shapes — copy them into an existing project if you're not using `kick new`.
|
|
23
|
-
|
|
24
|
-
### 1. Service (`@Service` + DI)
|
|
18
|
+
## Quick example
|
|
25
19
|
|
|
26
20
|
```ts
|
|
27
|
-
// src/modules/users/user.
|
|
28
|
-
import { Service } from '@forinda/kickjs'
|
|
21
|
+
// src/modules/users/user.controller.ts
|
|
22
|
+
import { Autowired, Controller, Get, Post, Service, type Ctx } from '@forinda/kickjs'
|
|
23
|
+
import { z } from 'zod'
|
|
29
24
|
|
|
30
25
|
@Service()
|
|
31
26
|
export class UserService {
|
|
32
27
|
list() {
|
|
33
28
|
return [{ id: '1', name: 'Alice' }]
|
|
34
29
|
}
|
|
35
|
-
|
|
36
|
-
create(input: { name: string; email: string }) {
|
|
37
|
-
return { id: '2', ...input }
|
|
38
|
-
}
|
|
39
30
|
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 2. Controller (`@Controller`, decorators, typed `ctx`)
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
// src/modules/users/user.controller.ts
|
|
46
|
-
import { Controller, Get, Post, Autowired, type Ctx } from '@forinda/kickjs'
|
|
47
|
-
import { z } from 'zod'
|
|
48
|
-
import { UserService } from './user.service'
|
|
49
|
-
|
|
50
|
-
const createUserSchema = z.object({
|
|
51
|
-
name: z.string().min(1),
|
|
52
|
-
email: z.string().email(),
|
|
53
|
-
})
|
|
54
31
|
|
|
55
32
|
@Controller()
|
|
56
33
|
export class UserController {
|
|
57
34
|
@Autowired() private readonly users!: UserService
|
|
58
35
|
|
|
59
36
|
@Get('/')
|
|
60
|
-
list(
|
|
61
|
-
|
|
37
|
+
list() {
|
|
38
|
+
return this.users.list()
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
@Post('/', { body:
|
|
41
|
+
@Post('/', { body: z.object({ name: z.string().min(1) }) })
|
|
65
42
|
create(ctx: Ctx<KickRoutes.UserController['create']>) {
|
|
66
|
-
|
|
67
|
-
ctx.created(this.users.create(ctx.body))
|
|
43
|
+
ctx.created({ id: '2', name: ctx.body.name })
|
|
68
44
|
}
|
|
69
45
|
}
|
|
70
46
|
```
|
|
71
47
|
|
|
72
|
-
`Ctx<KickRoutes.UserController['list']>` is generated by `kick typegen` (auto-runs on `kick dev`) — `ctx.params`, `ctx.body`, `ctx.query` all narrow to the Zod-derived shape as you save.
|
|
73
|
-
|
|
74
|
-
### 3. Module (`defineModule()` factory)
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
// src/modules/users/user.module.ts
|
|
78
|
-
import { defineModule } from '@forinda/kickjs'
|
|
79
|
-
import { UserController } from './user.controller'
|
|
80
|
-
|
|
81
|
-
export const UserModule = defineModule({
|
|
82
|
-
name: 'UserModule',
|
|
83
|
-
build: () => ({
|
|
84
|
-
routes() {
|
|
85
|
-
return { path: '/users', controller: UserController }
|
|
86
|
-
},
|
|
87
|
-
}),
|
|
88
|
-
})
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The framework derives the router from `controller` — never import `buildRoutes` directly. `path` is the **single source of truth** for the mount prefix; `@Controller()` takes no path argument.
|
|
92
|
-
|
|
93
|
-
### 4. Module registry (`defineModules()` fluent chain)
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
// src/modules/index.ts
|
|
97
|
-
import { defineModules } from '@forinda/kickjs'
|
|
98
|
-
import { UserModule } from './users/user.module'
|
|
99
|
-
|
|
100
|
-
// Factories are called at the registration site — `UserModule()` produces
|
|
101
|
-
// the AppModule instance bootstrap registers. Each `.mount(...)` call adds
|
|
102
|
-
// one module; chain as many as you need.
|
|
103
|
-
export const modules = defineModules().mount(UserModule())
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### 5. Bootstrap entry
|
|
107
|
-
|
|
108
48
|
```ts
|
|
109
49
|
// src/index.ts
|
|
110
50
|
import 'reflect-metadata'
|
|
111
|
-
import
|
|
112
|
-
import {
|
|
113
|
-
import { modules } from './modules'
|
|
114
|
-
|
|
115
|
-
// Always export the app. The Vite plugin reads this symbol to wire HMR;
|
|
116
|
-
// skipping `export` works in production but degrades dev mode to full restarts.
|
|
117
|
-
export const app = await bootstrap({ modules })
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
That's it — `http://localhost:3000/api/v1/users` returns Alice; `POST /api/v1/users` with `{ name, email }` validates against the Zod schema.
|
|
121
|
-
|
|
122
|
-
## HTTP Runtimes — Express, Fastify, or h3
|
|
123
|
-
|
|
124
|
-
The HTTP engine is pluggable. Controllers, DI, decorators, and `ctx` are engine-neutral; pick the engine in `bootstrap()` (Express is the default — no import needed):
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import { bootstrap } from '@forinda/kickjs'
|
|
128
|
-
import { fastifyRuntime } from '@forinda/kickjs/fastify' // or @forinda/kickjs/h3 → h3Runtime
|
|
129
|
-
|
|
130
|
-
export const app = await bootstrap({ modules, runtime: fastifyRuntime() })
|
|
131
|
-
```
|
|
51
|
+
import { bootstrap, defineModule, defineModules } from '@forinda/kickjs'
|
|
52
|
+
import { UserController } from './modules/users/user.controller'
|
|
132
53
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
import { h3WebRuntime } from '@forinda/kickjs/h3-web' // bootstrap() on node, WHATWG pipeline
|
|
139
|
-
import { createWebApp } from '@forinda/kickjs/web' // fetch(Request) → Response — Workers / Bun / Deno
|
|
140
|
-
import * as h3 from 'h3' // v2 — passed in: edge bundlers have no createRequire
|
|
141
|
-
import { modules } from './modules'
|
|
142
|
-
|
|
143
|
-
const app = createWebApp({ h3, modules })
|
|
144
|
-
export default { fetch: (req: Request) => app.fetch(req) }
|
|
145
|
-
```
|
|
54
|
+
const UserModule = defineModule({
|
|
55
|
+
name: 'UserModule',
|
|
56
|
+
build: () => ({ routes: () => ({ path: '/users', controller: UserController }) }),
|
|
57
|
+
})
|
|
146
58
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
## Project Layout
|
|
150
|
-
|
|
151
|
-
`kick new` produces this convention; everything except the bootstrap entry is configurable via `kick.config.ts`:
|
|
152
|
-
|
|
153
|
-
```text
|
|
154
|
-
src/
|
|
155
|
-
index.ts # bootstrap entry — exports `app`
|
|
156
|
-
config/index.ts # env schema via defineEnv() + loadEnv()
|
|
157
|
-
modules/
|
|
158
|
-
index.ts # defineModules() chain
|
|
159
|
-
<name>/<name>.module.ts # one module per feature
|
|
160
|
-
<name>/<name>.controller.ts
|
|
161
|
-
<name>/<name>.service.ts
|
|
162
|
-
middleware/ # optional — `@Middleware()` factories + global stack
|
|
163
|
-
adapters/ # optional — `defineAdapter()` integrations
|
|
164
|
-
plugins/ # optional — `definePlugin()` bundles
|
|
165
|
-
.agents/ # AI agent context — AGENTS.md + skills/<slug>/SKILL.md
|
|
166
|
-
CLAUDE.md # Claude Code root (thin pointer to .agents/)
|
|
167
|
-
kick.config.ts # CLI configuration (optional)
|
|
168
|
-
vite.config.ts # Vite + KickJS HMR
|
|
59
|
+
export const app = await bootstrap({ modules: defineModules().mount(UserModule()) })
|
|
169
60
|
```
|
|
170
61
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- **Pluggable HTTP runtimes** — one `HttpRuntime` seam over Express (default), Fastify, or h3. Swap the engine in one line; everything above stays the same. See [HTTP Runtimes](https://kickjs.app/guide/http-runtimes.html).
|
|
174
|
-
- **Return-value handlers + typed client** — `return` the payload (`reply(201, body)` for other statuses) and the response type flows through `kick typegen` into `KickRoutes.Api`, consumed by [`@forinda/kickjs-client`](https://kickjs.app/guide/typed-client.html) on the frontend. Declare `{ response: schema }` on a route and the same contract feeds the OpenAPI success response. See [Controllers](https://kickjs.app/guide/controllers.html#return-value-handlers), [Typed Client](https://kickjs.app/guide/typed-client.html).
|
|
175
|
-
- **First-class database** — `kick/db`: code-first schema, fully typed queries, and migrations for PostgreSQL / SQLite / MySQL. `kick add db` (or `pg` / `sqlite` / `mysql`). See [Database](https://kickjs.app/guide/database/).
|
|
176
|
-
- **Custom DI container** — three scopes (singleton / transient / request), slash-delimited tokens (`createToken<T>('app/users/repository')`), constructor + property injection. No external DI dep. See [Dependency Injection](https://kickjs.app/guide/dependency-injection.html).
|
|
177
|
-
- **Factory-first** — `defineAdapter()`, `definePlugin()`, `defineModule()`, `defineHttpContextDecorator()`. No class hierarchies to inherit from. See [Adapters](https://kickjs.app/guide/adapters.html), [Plugins](https://kickjs.app/guide/plugins.html).
|
|
178
|
-
- **Context Contributors** — replace single-purpose middleware that only sets `ctx` values. Typed `dependsOn` (typos are TS errors, not boot-time crashes), topo-sorted at startup. (`@forinda/kickjs-ws` handlers don't run contributors — a socket isn't a request.) See [Context Decorators](https://kickjs.app/guide/context-decorators.html).
|
|
179
|
-
- **Built-in health probes** — `GET /health/live` and `GET /health/ready` ship as a module, mounted at the root outside `apiPrefix` so a probe URL an orchestrator is configured against does not move. Readiness runs every adapter's `onHealthCheck()`. They sit inside your middleware chain, so app-wide auth applies — pass `bootstrap({ health: false })` to mount your own.
|
|
180
|
-
- **Env wiring** — `defineEnv()` + `loadEnv()` in `src/config/index.ts`. The `import './config'` side-effect import at the top of `src/index.ts` MUST happen before any `@Value()` injection resolves. See [Configuration](https://kickjs.app/guide/configuration.html).
|
|
181
|
-
- **Asset Manager** — typed `assets.<ns>.<key>()` accessor, configured via `assetMap` in `kick.config.ts`. Drops the `__dirname` arithmetic that production builds get wrong. See [Asset Manager](https://kickjs.app/guide/asset-manager.html).
|
|
182
|
-
- **Reactive primitives** — `ref()` / `computed()` / `reactive()` / `watch()` Vue-style; `ref` and `computed` auto-unwrap on `JSON.stringify` so they drop straight into `introspect()` snapshots.
|
|
183
|
-
- **DevTools** — `/_debug` browser panel with topology, container, contributors, metrics. Adapters expose state via `introspect(): IntrospectionSnapshot` (type lives in `@forinda/kickjs` — no extra import needed). See [DevTools](https://kickjs.app/guide/devtools.html).
|
|
184
|
-
|
|
185
|
-
## Common Add-Ons
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
kick add db # kick/db — typed, code-first DB (+ pg / sqlite / mysql)
|
|
189
|
-
kick add upload # file-upload driver for your runtime (multer / @fastify/multipart / native)
|
|
190
|
-
kick add swagger # OpenAPI docs from decorators + Zod schemas
|
|
191
|
-
kick add devtools # /_debug dashboard
|
|
192
|
-
kick add ws # WebSocket with @WsController, rooms, heartbeat
|
|
193
|
-
kick add queue # BullMQ / RabbitMQ / Kafka jobs
|
|
194
|
-
kick add testing # createTestApp + createTestModule helpers
|
|
195
|
-
kick add --list # full live catalog
|
|
196
|
-
|
|
197
|
-
kick doctor # pre-flight checks: engine peers, upload driver, env wiring
|
|
198
|
-
```
|
|
62
|
+
`GET /api/v1/users` returns the list. `KickRoutes` comes from `kick typegen`, which `kick dev` runs for you. To use another engine, pass `runtime: fastifyRuntime()` (from `@forinda/kickjs/fastify`) or `h3Runtime()` to `bootstrap()`.
|
|
199
63
|
|
|
200
64
|
## Documentation
|
|
201
65
|
|
|
202
|
-
**[kickjs.app](https://kickjs.app/)
|
|
203
|
-
|
|
204
|
-
Start here:
|
|
66
|
+
**[kickjs.app](https://kickjs.app/)**. Good places to start:
|
|
205
67
|
|
|
206
|
-
- [Getting Started](https://kickjs.app/guide/getting-started
|
|
207
|
-
- [Modules](https://kickjs.app/guide/modules
|
|
208
|
-
- [
|
|
209
|
-
- [
|
|
68
|
+
- [Getting Started](https://kickjs.app/guide/getting-started)
|
|
69
|
+
- [Modules](https://kickjs.app/guide/modules), [Controllers](https://kickjs.app/guide/controllers), [Dependency Injection](https://kickjs.app/guide/dependency-injection)
|
|
70
|
+
- [HTTP Runtimes](https://kickjs.app/guide/http-runtimes), [Edge Deployment](https://kickjs.app/guide/edge-deployment)
|
|
71
|
+
- [Context Decorators](https://kickjs.app/guide/context-decorators), [Adapters](https://kickjs.app/guide/adapters), [Plugins](https://kickjs.app/guide/plugins)
|
|
72
|
+
- [BYO Recipes](https://kickjs.app/guide/byo-recipes): auth, GraphQL, OpenTelemetry, cron, mailer, multi-tenancy
|
|
210
73
|
|
|
211
74
|
## License
|
|
212
75
|
|
|
@@ -6,6 +6,7 @@ import { p as RouteFlags } from "./route-flag-B1lQht6v.mjs";
|
|
|
6
6
|
import { C as MetaValue, S as ExecutionContext, m as ContributorRegistrations, o as AnyContributorRegistration, r as AppModuleEntry, t as AppModule, x as ContextMetaKey } from "./app-module-DCA65GZZ.mjs";
|
|
7
7
|
import "./cache-C0qL0NSD.mjs";
|
|
8
8
|
import { i as ProblemDetails } from "./errors-By6UueCm.mjs";
|
|
9
|
+
import { t as Session } from "./session-4Tb6q0Ny.mjs";
|
|
9
10
|
import { S as QueryFieldConfig, T as TypedParsedQuery, t as ParseQueryOptions, v as PaginatedResponse } from "./index-BfT5KZ-M.mjs";
|
|
10
11
|
import "reflect-metadata";
|
|
11
12
|
import http, { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -117,8 +118,8 @@ type IntrospectFn = () => IntrospectionSnapshot | Promise<IntrospectionSnapshot>
|
|
|
117
118
|
//#endregion
|
|
118
119
|
//#region src/core/augmentation.d.ts
|
|
119
120
|
/**
|
|
120
|
-
*
|
|
121
|
-
* `
|
|
121
|
+
* The canonical `KickJsPluginRegistry` interface that `kick typegen`
|
|
122
|
+
* augments, for typed `dependsOn` between plugins and adapters.
|
|
122
123
|
*
|
|
123
124
|
* See `architecture.md` §21.2.1 (typegen for `dependsOn`) and §21.3.3
|
|
124
125
|
* (standardized augmentation registry) for the design rationale.
|
|
@@ -146,103 +147,6 @@ interface KickJsPluginRegistry {}
|
|
|
146
147
|
* narrowing is a progressive enhancement, not a hard prerequisite.
|
|
147
148
|
*/
|
|
148
149
|
type KickJsPluginName = keyof KickJsPluginRegistry extends never ? string : keyof KickJsPluginRegistry & string;
|
|
149
|
-
/**
|
|
150
|
-
* Metadata attached to a `defineAugmentation` call. The fields are
|
|
151
|
-
* documentation-only — `kick typegen` surfaces them in the generated
|
|
152
|
-
* `.kickjs/types/kick__augmentations.d.ts` so adopters can browse every
|
|
153
|
-
* augmentable interface from one place.
|
|
154
|
-
*
|
|
155
|
-
* `description` and `example` may both be multi-line — typegen preserves
|
|
156
|
-
* line breaks when rendering them as JSDoc. There is no upper limit on
|
|
157
|
-
* the example size; entire shape definitions or worked snippets are fine.
|
|
158
|
-
*/
|
|
159
|
-
interface AugmentationMeta {
|
|
160
|
-
/**
|
|
161
|
-
* Free-form description of what the augmentation customises. May be
|
|
162
|
-
* multi-line — newlines are preserved in the generated JSDoc.
|
|
163
|
-
*/
|
|
164
|
-
description?: string;
|
|
165
|
-
/**
|
|
166
|
-
* TS snippet showing a typical shape. May be multi-line; rendered
|
|
167
|
-
* inside a fenced code block in the generated catalogue. Use this
|
|
168
|
-
* for anything from `'{ foo: string }'` up to a full interface body
|
|
169
|
-
* — typegen no longer flattens newlines.
|
|
170
|
-
*/
|
|
171
|
-
example?: string;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* @deprecated Since 7.2. This was tooling for a time when Context
|
|
175
|
-
* Contributors were still settling — a catalogue so adopters could discover
|
|
176
|
-
* which interfaces were augmentable. Contributors are a stable, typed API now
|
|
177
|
-
* (`defineContextDecorator`, `ContextMeta`), and the catalogue adds a second
|
|
178
|
-
* call that must be kept in step with the `declare module` block for no
|
|
179
|
-
* type-level benefit.
|
|
180
|
-
*
|
|
181
|
-
* Nothing breaks: `kick typegen` still discovers the call and still writes the
|
|
182
|
-
* catalogue. Just drop it — the `declare module '@forinda/kickjs'` block alone
|
|
183
|
-
* gives you the types, which is all it ever gave you.
|
|
184
|
-
*
|
|
185
|
-
* What it actually emits, for the record: one empty marker interface per call
|
|
186
|
-
* in `.kickjs/types/kick__augmentations.d.ts` —
|
|
187
|
-
*
|
|
188
|
-
* ```ts
|
|
189
|
-
* // from defineAugmentation('ContextMeta', { description, example })
|
|
190
|
-
* export interface ContextMetaAugmentation {}
|
|
191
|
-
* ```
|
|
192
|
-
*
|
|
193
|
-
* with your `description` / `example` rendered as its JSDoc and a `@see` back
|
|
194
|
-
* to the call site. The interface is empty and unreferenced; nothing consumes
|
|
195
|
-
* it at type level.
|
|
196
|
-
*
|
|
197
|
-
* Advertise an augmentable interface so `kick typegen` can list it in
|
|
198
|
-
* `.kickjs/types/kick__augmentations.d.ts` for project-wide discovery.
|
|
199
|
-
*
|
|
200
|
-
* **This function is a runtime AND type-level no-op.** It does NOT
|
|
201
|
-
* augment the interface for TypeScript. The actual augmentation is
|
|
202
|
-
* the `declare module` block — `defineAugmentation` only adds an
|
|
203
|
-
* entry to the typegen catalogue (`.kickjs/types/kick__augmentations.d.ts`)
|
|
204
|
-
* so other contributors / adopters can find the interface and know its
|
|
205
|
-
* expected shape.
|
|
206
|
-
*
|
|
207
|
-
* Both calls are needed:
|
|
208
|
-
*
|
|
209
|
-
* ```ts
|
|
210
|
-
* import { defineAugmentation } from '@forinda/kickjs'
|
|
211
|
-
*
|
|
212
|
-
* // (1) The actual TS augmentation — what `ctx.get('tenant')` reads.
|
|
213
|
-
* declare module '@forinda/kickjs' {
|
|
214
|
-
* interface ContextMeta {
|
|
215
|
-
* tenant: { id: string; name: string }
|
|
216
|
-
* }
|
|
217
|
-
* }
|
|
218
|
-
*
|
|
219
|
-
* // (2) Catalogue entry — what `kick typegen` lists in
|
|
220
|
-
* // `.kickjs/types/kick__augmentations.d.ts` for browseability.
|
|
221
|
-
* defineAugmentation('ContextMeta', {
|
|
222
|
-
* description: 'Tenant resolved from x-tenant-id by TenantAdapter',
|
|
223
|
-
* example: `{
|
|
224
|
-
* tenant: {
|
|
225
|
-
* id: string
|
|
226
|
-
* name: string
|
|
227
|
-
* plan: 'free' | 'pro' | 'enterprise'
|
|
228
|
-
* featureFlags: Record<string, boolean>
|
|
229
|
-
* }
|
|
230
|
-
* }`,
|
|
231
|
-
* })
|
|
232
|
-
* ```
|
|
233
|
-
*
|
|
234
|
-
* Skip `defineAugmentation` if you don't care about the catalogue — the
|
|
235
|
-
* `declare module` block alone is enough for runtime + type behaviour.
|
|
236
|
-
*
|
|
237
|
-
* @deprecated Low value in practice: it does nothing at runtime or at
|
|
238
|
-
* the type level — the `declare module` block alone gives you the
|
|
239
|
-
* augmentation, and the generated `.kickjs/types/kick__augmentations.d.ts`
|
|
240
|
-
* catalogue it feeds is documentation-only. Prefer a plain
|
|
241
|
-
* `declare module '@forinda/kickjs' { ... }` block and (optionally) a
|
|
242
|
-
* JSDoc comment on your own interface. `defineAugmentation` and the
|
|
243
|
-
* `kick/augmentations` typegen plugin will be removed in a future major.
|
|
244
|
-
*/
|
|
245
|
-
declare function defineAugmentation(name: string, meta?: AugmentationMeta): void;
|
|
246
150
|
//#endregion
|
|
247
151
|
//#region src/core/module-registry.d.ts
|
|
248
152
|
/**
|
|
@@ -1239,8 +1143,17 @@ TKeys extends ContextMetaKey = ContextMetaKey> implements ExecutionContext<TKeys
|
|
|
1239
1143
|
* idempotent).
|
|
1240
1144
|
*/
|
|
1241
1145
|
get signal(): AbortSignal;
|
|
1242
|
-
/**
|
|
1243
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* The request's session — set by the `session()` middleware, which must be
|
|
1148
|
+
* installed. Type your keys once by augmenting `SessionData`:
|
|
1149
|
+
*
|
|
1150
|
+
* ```ts
|
|
1151
|
+
* declare module '@forinda/kickjs' {
|
|
1152
|
+
* interface SessionData { userId?: string }
|
|
1153
|
+
* }
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
get session(): Session;
|
|
1244
1157
|
/**
|
|
1245
1158
|
* The authenticated user set by AuthAdapter.
|
|
1246
1159
|
* Reads from the per-request metadata store first (`ctx.set('user', ...)`),
|
|
@@ -1489,6 +1402,11 @@ TKeys extends ContextMetaKey = ContextMetaKey> implements ExecutionContext<TKeys
|
|
|
1489
1402
|
*/
|
|
1490
1403
|
redirect(url: string, status?: number): unknown;
|
|
1491
1404
|
html(content: string, status?: number): RuntimeResponse;
|
|
1405
|
+
/**
|
|
1406
|
+
* Send `buffer` as a file download named `filename`. The name is written
|
|
1407
|
+
* per RFC 6266 — an ASCII fallback plus `filename*` in UTF-8 — so a name
|
|
1408
|
+
* from an upload (quotes, line breaks, non-Latin text) can't break the header.
|
|
1409
|
+
*/
|
|
1492
1410
|
download(buffer: Buffer, filename: string, contentType?: string): RuntimeResponse;
|
|
1493
1411
|
/**
|
|
1494
1412
|
* Render a template using the registered view engine (EJS, Pug, Handlebars, etc.).
|
|
@@ -1649,6 +1567,8 @@ type Ctx<TRoute extends RouteShape = RouteShape> = RequestContext<TRoute extends
|
|
|
1649
1567
|
declare global {
|
|
1650
1568
|
namespace KickRoutes {}
|
|
1651
1569
|
}
|
|
1570
|
+
/** `attachment; filename="…"; filename*=UTF-8''…` for any file name (RFC 6266 / RFC 8187). */
|
|
1571
|
+
declare function contentDisposition(filename: string): string;
|
|
1652
1572
|
//#endregion
|
|
1653
1573
|
//#region src/http/runtime.d.ts
|
|
1654
1574
|
/** HTTP verbs the router emits. Mirrors the decorator surface (@Get/@Post/...). */
|
|
@@ -2198,5 +2118,5 @@ interface AppAdapter {
|
|
|
2198
2118
|
/** Constructor type for AppAdapter classes */
|
|
2199
2119
|
type AppAdapterClass = new () => AppAdapter;
|
|
2200
2120
|
//#endregion
|
|
2201
|
-
export {
|
|
2202
|
-
//# sourceMappingURL=adapter-
|
|
2121
|
+
export { setInMetaRecord as $, contentDisposition as A, SourcedRegistration as B, RuntimeTypeMap as C, reportResponse as Ct, RequestContext as D, DeepReadonly as E, RunContributorsOptions as F, getMetaRecord as G, getClassMeta as H, runContributors as I, hasClassMeta as J, getMethodMeta as K, BuildPipelineOptions as L, AdapterFactory as M, DefineAdapterOptions as N, RouteShape as O, defineAdapter as P, setInMetaMap as Q, ContributorPipeline as R, RuntimeResponse as S, reportError as St, Ctx as T, getClassMetaOrUndefined as U, buildPipeline as V, getMetaMap as W, pushMethodMeta as X, pushClassMeta as Y, setClassMeta as Z, RouteMeta as _, IntrospectionKind as _t, KickServer as a, defineModule as at, RuntimeAppOptions as b, ErrorSource as bt, ActiveRuntime as c, PluginAsyncOptions as ct, CtxHandler as d, KickPlugin as dt, setMethodMeta as et, ExpressRuntimeTypes as f, ModuleRegistry as ft, RouteEntry as g, IntrospectFn as gt, MatchedRoute as h, KickJsPluginRegistry as ht, AppAdapterClass as i, ModuleFactory as it, AdapterAsyncOptions as j, SseHandler as k, AdapterHttp as l, PluginFactory as lt, KickRuntimeRegister as m, KickJsPluginName as mt, AdapterMiddleware as n, defineModules as nt, MiddlewarePath as o, BuildContext as ot, HttpRuntime as p, MutableModuleRegistry as pt, getMethodMetaOrUndefined as q, AppAdapter as r, DefineModuleOptions as rt, MiddlewarePhase as s, DefinePluginOptions as st, AdapterContext as t, ModuleList as tt, ConnectMiddleware as u, definePlugin as ut, RouteMethod as v, IntrospectionSnapshot as vt, UseConnectOptions as w, RuntimeCapabilities as x, ResponseInfo as xt, RouteTable as y, ErrorInfo as yt, ContributorSource as z };
|
|
2122
|
+
//# sourceMappingURL=adapter-DhjoIYVI.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-DhjoIYVI.d.mts","names":[],"sources":["../src/core/observers.ts","../src/core/introspect.ts","../src/core/augmentation.ts","../src/core/module-registry.ts","../src/core/plugin.ts","../src/core/define-plugin.ts","../src/core/define-module.ts","../src/core/define-modules.ts","../src/core/metadata.ts","../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/core/define-adapter.ts","../src/http/context.ts","../src/http/runtime.ts","../src/core/adapter.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forinda/kickjs v9.0.1
|
|
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{METADATA}from"./core/interfaces.mjs";import{c as http2UnsupportedRuntimeError,i as createToken,l as moduleRouteMissingControllerError,n as tokenName,s as http2RequiresTlsError,t as Container,u as tlsCredentialsError}from"./container-BfmPa7gi.mjs";import{Logger,createLogger}from"./core/logger.mjs";import{t as getClassMeta}from"./metadata-CW58nTQM.mjs";import{Controller,Get}from"./core/decorators.mjs";import{t as MutableModuleRegistry}from"./module-registry-BLB0gwEq.mjs";import{a as setObserverPublisher,i as reportResponse,n as releaseObservers,o as setObservers,r as reportError,t as hasResponseObservers}from"./observers-Ac2ZdgCN.mjs";import{runCronJobs}from"./core/cron.mjs";import{i as runDisposables,n as drainDisposables,r as registerDisposable,t as disposeAll}from"./disposables-CLTUN_9Z.mjs";import{HttpException}from"./core/errors.mjs";import{buildMountPath,joinPaths}from"./core/path.mjs";import{t as ROUTE_SLOT}from"./route-flag-BeSiIg98.mjs";import"./core-BpHbZ7P8.mjs";import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"./router-builder-BL0kmIIu.mjs";import{r as offerRoutePolicy,t as RoutePolicyTable}from"./route-policy-Ch0TlY_K.mjs";import{c as requestScopeMiddleware,d as rejectUnsupportedBody,i as reply,l as EXPRESS_JSON_TYPES,n as applyHandlerResult,s as isRequestScopeMiddleware,t as publishMatchedRoute}from"./runtime-npBaLlo7.mjs";import{requestId}from"./http/middleware/request-id.mjs";import{a as settleBackgroundWork,i as pendingBackgroundWork,l as requestStore,t as RequestContext}from"./context-DivVCLKt.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{t as __decorate}from"./decorate-DlbfE8dD.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware}from"./http/middleware/upload.mjs";import http from"node:http";import{randomBytes,timingSafeEqual}from"node:crypto";import https from"node:https";import http2 from"node:http2";import tls from"node:tls";import express,{Router}from"express";import diagnostics from"node:diagnostics_channel";const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function defineModule(options){if(typeof options!=`object`||!options)throw TypeError(`defineModule: options must be an object literal. See architecture.md §21 for the spec shape.`);if(typeof options.name!=`string`||options.name.length===0){let got=typeof options.name==`string`?`""`:typeof options.name;throw TypeError(`defineModule: options.name must be a non-empty string (got ${got}). Module names appear in diagnostics and \`.scoped()\` namespacing — anonymous modules make cycle / missing-dep errors hard to localize.`)}if(typeof options.build!=`function`)throw TypeError(`defineModule(${options.name}): options.build is required and must be a function (got ${typeof options.build}). The build function returns the AppModule shape — register() / routes() / contributors() — wired with the resolved config.`);let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return Object.defineProperty(built,"__moduleName",{value:instanceName,enumerable:!1,writable:!1,configurable:!1}),built},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),Object.defineProperty(factory,"definition",{value:Object.freeze({...options}),writable:!1,enumerable:!0,configurable:!1}),factory}var DuplicateMountNameError=class extends Error{kind;mountName;constructor(kind,mountName){super(`Duplicate ${kind} name '${mountName}' — every ${kind} must declare a unique name`),this.name=`DuplicateMountNameError`,this.kind=kind,this.mountName=mountName}},MissingMountDepError=class extends Error{kind;missing;dependent;constructor(kind,missing,dependent){super(`Missing ${kind} dependency '${missing}' required by '${dependent}' — is the ${kind} installed and named correctly?`),this.name=`MissingMountDepError`,this.kind=kind,this.missing=missing,this.dependent=dependent}},MountCycleError=class extends Error{kind;cycle;constructor(kind,cycle){super(`Cycle in ${kind} dependsOn declarations: ${cycle.join(` → `)}`),this.name=`MountCycleError`,this.kind=kind,this.cycle=Object.freeze([...cycle])}};function mountSort(items,kind){let byName=new Map;for(let item of items){if(byName.has(item.name))throw new DuplicateMountNameError(kind,item.name);byName.set(item.name,item)}let indegree=new Map,dependents=new Map;for(let item of items)indegree.set(item.name,item.dependsOn?.length??0);for(let item of items)for(let dep of item.dependsOn??[]){if(!byName.has(dep))throw new MissingMountDepError(kind,dep,item.name);let list=dependents.get(dep)??[];list.push(item),dependents.set(dep,list)}let queue=[];for(let item of items)indegree.get(item.name)===0&&queue.push(item);let sorted=[];for(;queue.length>0;){let node=queue.shift();sorted.push(node);for(let dependent of dependents.get(node.name)??[]){let next=(indegree.get(dependent.name)??0)-1;indegree.set(dependent.name,next),next===0&&queue.push(dependent)}}if(sorted.length!==items.length)throw new MountCycleError(kind,reconstructCycle(items.filter(it=>(indegree.get(it.name)??0)>0),byName));return sorted}function reconstructCycle(residual,byName){let residualNames=new Set(residual.map(r=>r.name)),start=residual[0].name,path=[start],seen=new Set([start]),cursor=start;for(;;){let nextDep=byName.get(cursor).dependsOn?.find(d=>residualNames.has(d));if(nextDep===void 0)break;if(seen.has(nextDep)){let cycleStart=path.indexOf(nextDep);return[...path.slice(cycleStart),nextDep]}path.push(nextDep),seen.add(nextDep),cursor=nextDep}return path}const HANDLER_CHANNEL=`kickjs:handler`,ERROR_CHANNEL=`kickjs:error`,RESPONSE_CHANNEL=`kickjs:response`,handlerChannel=diagnostics.tracingChannel(HANDLER_CHANNEL),errorChannel=diagnostics.channel(ERROR_CHANNEL),responseChannel=diagnostics.channel(RESPONSE_CHANNEL);function installChannelPublisher(){setObserverPublisher({name:`diagnostics_channel`,onError(error,info){errorChannel.hasSubscribers&&errorChannel.publish({error,...info})},onResponse(info){responseChannel.hasSubscribers&&responseChannel.publish(info)}},()=>responseChannel.hasSubscribers)}function traceHandler(handler,meta){return ctx=>handlerChannel.hasSubscribers?handlerChannel.tracePromise(async()=>handler(ctx),{...meta,requestId:ctx.requestId,ctx}):handler(ctx)}function __decorateMetadata(k,v){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(k,v)}const HEALTH_PROBE=createToken(`kick/Health/probe`);let HealthController=class{probe;constructor(probe){this.probe=probe}live(){return this.probe.isDraining()?reply(503,{status:`draining`,uptime:process.uptime()}):reply(200,{status:`ok`,uptime:process.uptime()})}async ready(){if(this.probe.isDraining())return reply(503,{status:`draining`,checks:[]});let checks=await this.probe.checks(),healthy=checks.every(c=>c.status===`up`);return reply(healthy?200:503,{status:healthy?`ready`:`degraded`,checks})}};__decorate([Get(`/live`),__decorateMetadata(`design:type`,Function),__decorateMetadata(`design:paramtypes`,[]),__decorateMetadata(`design:returntype`,void 0)],HealthController.prototype,`live`,null),__decorate([Get(`/ready`),__decorateMetadata(`design:type`,Function),__decorateMetadata(`design:paramtypes`,[]),__decorateMetadata(`design:returntype`,Promise)],HealthController.prototype,`ready`,null),HealthController=__decorate([Controller(),__decorateMetadata(`design:paramtypes`,[Object])],HealthController);const healthModule=defineModule({name:`HealthModule`,build:config=>({register(container){container.registerFactory(HealthController,()=>{let probe=container.resolve(HEALTH_PROBE);return new HealthController(probe)})},routes(){return{path:`/health`,controller:HealthController,version:!1,prefix:!1,flags:config.flags}}})});function materializeRouter(entries){let router=Router();for(let entry of entries){let method=entry.method.toLowerCase(),handlers=[];handlers.push((req,_res,next)=>{publishMatchedRoute(req,entry),next()});let contributorStep=run=>async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await run(ctx),res.headersSent||next()}catch(err){next(err)}};entry.earlyContributorRunner&&handlers.push(contributorStep(entry.earlyContributorRunner)),entry.meta.validation&&handlers.push(validate(entry.meta.validation)),entry.meta.upload&&handlers.push(buildUploadMiddleware(entry.meta.upload));for(let mw of entry.middlewares)handlers.push((req,res,next)=>{let ctx=new RequestContext(req,res,next);Promise.resolve(mw(ctx,next)).catch(next)});entry.contributorRunner&&handlers.push(contributorStep(entry.contributorRunner)),handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{let result=await entry.handler(ctx);res.headersSent||applyHandlerResult(ctx,result)}catch(err){next(err)}}),router[method](entry.path,...handlers)}return router}function buildRoutes(controllerClass,options={}){return materializeRouter(buildRouteTable(controllerClass,options))}function expressRuntime(){return{name:`express`,createApp(options={}){let app=express();return app.disable(`x-powered-by`),app.set(`trust proxy`,options.trustProxy??!1),app},nodeHandler(app){return(req,res,next)=>{next?app(req,res,next):app(req,res)}},mountRoutes(app,table){for(let{mountPath,routes}of table)app.use(mountPath,materializeRouter(routes))},useConnect(app,mw,opts={}){opts.path===void 0?app.use(mw):app.use(opts.path,mw)},serveStatic(app,prefix,dir){app.use(prefix,express.static(dir))},setNotFound(app,mw){app.use(mw)},setErrorHandler(app,mw){app.use(mw)},capabilities:{render:!0,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!1,http2:!1}}}const HOP_BY_HOP_REQUEST=[`connection`,`keep-alive`,`proxy-connection`,`transfer-encoding`,`upgrade`,`te`,`trailer`,`host`,`content-length`],HOP_BY_HOP_RESPONSE=[`connection`,`keep-alive`,`proxy-connection`,`transfer-encoding`,`upgrade`,`trailer`,`content-encoding`,`content-length`],LOOPBACK_HOST=`x-kick-loopback-host`,LOOPBACK_SECRET=`x-kick-loopback-secret`;async function streamedBody(request){if(!request.body||request.method===`GET`||request.method===`HEAD`)return;let reader=request.body.getReader(),first=await reader.read();if(!first.done)return new ReadableStream({start(controller){controller.enqueue(first.value)},async pull(controller){let{done,value}=await reader.read();done?controller.close():controller.enqueue(value)},cancel(reason){return reader.cancel(reason)}})}function createLoopback(handle){let started,secret=randomBytes(24).toString(`hex`),server=()=>(started??=new Promise((resolve,reject)=>{let instance=http.createServer((req,res)=>{let host=req.headers[LOOPBACK_HOST];req.headers[LOOPBACK_SECRET]===secret&&typeof host==`string`&&(req.headers.host=host),delete req.headers[LOOPBACK_HOST],delete req.headers[LOOPBACK_SECRET],handle(req,res)});instance.once(`error`,reject),instance.listen(0,`127.0.0.1`,()=>{instance.unref(),resolve({origin:`http://127.0.0.1:${instance.address().port}`,server:instance})})}).catch(err=>{throw started=void 0,err}),started);return{async fetch(request){let{origin}=await server(),url=new URL(request.url),headers=new Headers(request.headers);for(let name of HOP_BY_HOP_REQUEST)headers.delete(name);headers.set(`x-forwarded-host`,url.host),headers.set(`x-forwarded-proto`,url.protocol.slice(0,-1)),headers.set(LOOPBACK_HOST,url.host),headers.set(LOOPBACK_SECRET,secret);let body=await streamedBody(request),upstream=await fetch(origin+url.pathname+url.search,{method:request.method,headers,body,...body?{duplex:`half`}:{},redirect:`manual`,signal:request.signal}),responseHeaders=new Headers(upstream.headers),named=responseHeaders.get(`connection`)?.split(`,`)??[];for(let name of[...HOP_BY_HOP_RESPONSE,...named])responseHeaders.delete(name.trim());return new Response(upstream.body,{status:upstream.status,statusText:upstream.statusText,headers:responseHeaders})},async close(){let running=await started?.catch(()=>void 0);if(started=void 0,!running)return;let closed=new Promise(resolve=>running.server.close(()=>resolve()));running.server.closeAllConnections(),await closed}}}const log=createLogger(`Application`);function assertUsableTls(options){let{key,cert,pfx,SNICallback,pskCallback}=options;if(!pfx&&!SNICallback&&!pskCallback){if(!key&&!cert)throw tlsCredentialsError(`no key/cert, pfx, SNICallback, or pskCallback`);if(!key)throw tlsCredentialsError(`cert is set but key is missing`);if(!cert)throw tlsCredentialsError(`key is set but cert is missing`)}try{tls.createSecureContext(options)}catch(err){throw tlsCredentialsError(err.message)}}function isModuleFactory(entry){return typeof entry==`function`&&`definition`in entry&&typeof entry.scoped==`function`}function isConfigurable(factory){return factory.definition?.defaults!=null}function toAppModule(entry){if(typeof entry!=`function`)return entry;if(isModuleFactory(entry)){if(isConfigurable(entry)){let name=entry.definition?.name||entry.name||`<anonymous>`;throw TypeError(`bootstrap: module \`${name}\` takes configuration, so it must be invoked.\n Write \`${name}()\` for its defaults, or \`${name}({ … })\` to configure it.\n Passing it bare would have silently selected the defaults.`)}return entry()}let name=entry.name||`<anonymous>`;if(!isConstructible(entry))throw TypeError(`bootstrap: module entry \`${name}\` is a function, but not a module class and not a defineModule() factory (no \`definition\`).\n A defineModule() module is passed as \`${name}\` or \`${name}()\`; a class must implement AppModule.`);let mod=new entry;if(typeof mod?.routes!=`function`)throw TypeError(`bootstrap: module entry \`${name}\` constructed, but the result does not implement AppModule — \`routes()\` is missing.\n A module class needs \`routes()\`; a defineModule() module is passed as \`${name}\` or \`${name}()\`.`);return mod}function isConstructible(fn){try{return Reflect.construct(String,[],fn),!0}catch{return!1}}var Application=class{options;app;runtime;container;httpServer=null;handlerMode=!1;observers=[];http2Sessions=new Set;loopback;adapters;routePolicy=new RoutePolicyTable;plugins;_moduleContributors=[];_inFlightRequests=0;_draining=!1;_shutdownInitiated=!1;_drainResolvers=[];constructor(options){this.options=options,this.runtime=options.runtime??expressRuntime(),this.app=this.runtime.createApp({trustProxy:options.trustProxy}),this.container=Container.getInstance(),this.plugins=mountSort(options.plugins??[],`plugin`);let allAdapters=[...this.plugins.flatMap(p=>p.adapters?.()??[]),...options.adapters??[]],anonAdapterCount=0,namedAdapters=allAdapters.map(adapter=>{if(!adapter.name){let ctorName=adapter.constructor?.name,fallback=ctorName&&ctorName!==`Object`?ctorName:`AnonymousAdapter#${anonAdapterCount++}`;return Object.assign(adapter,{name:fallback})}return adapter});this.adapters=mountSort(namedAdapters,`adapter`),this.observers=setObservers([...this.adapters,...this.plugins]),installChannelPublisher(),Container._requestStoreProvider=()=>requestStore.getStore()??null}get isDraining(){return this._draining}get inFlightRequests(){return this._inFlightRequests}getContainer(){return this.container}fetch(request){let runtimeApp=this.app;return typeof runtimeApp?.fetch==`function`?Promise.resolve(runtimeApp.fetch(request)):(this.loopback??=createLoopback((req,res)=>this.handle(req,res)),this.loopback.fetch(request))}handle(req,res,next){let handler=this.runtime.nodeHandler(this.app);next?handler(req,res,next):handler(req,res)}adapterCtx(server){let env=process.env.NODE_ENV??`development`;return{http:this.adapterHttp(),app:this.app,fetch:request=>this.fetch(request),container:this.container,server,env,isProduction:env===`production`}}adapterHttp(){return{route:(method,path,handler)=>{let entry={method,path,middlewares:[],contributorRunner:null,handler,meta:{}};this.runtime.mountRoutes(this.app,[{mountPath:`/`,routes:[entry]}])},mount:(prefix,routes)=>{this.runtime.mountRoutes(this.app,[{mountPath:prefix,routes}])},serveStatic:(prefix,dir)=>{this.runtime.serveStatic(this.app,prefix,dir)},use:(mw,opts)=>{this.runtime.useConnect(this.app,mw,opts)}}}async callHook(hook,ctx,adapterName=`adapter`,phase=`setup`){if(hook)try{await hook(ctx)}catch(err){if(phase===`setup`)throw err;log.error(err,`${adapterName} afterStart hook failed`)}}async setup(){log.debug(`Bootstrapping application...`),this._moduleContributors=[];let adapterMw=this.collectAdapterMiddleware();this.app.__kickApp=this;let ctx=this.adapterCtx();for(let adapter of this.adapters)await this.callHook(adapter.beforeMount?.bind(adapter),ctx,adapter.name);this.handlerMode&&this.mountCronTrigger(),this.runtime.useConnect(this.app,this.requestTrackingMiddleware()),this.shouldAutoMountRequestScope()&&this.runtime.useConnect(this.app,requestScopeMiddleware()),this.mountMiddlewareList(adapterMw.beforeGlobal);for(let plugin of this.plugins)plugin.register?.(this.container);for(let plugin of this.plugins)try{let mw=plugin.middleware?.()??[];for(let handler of mw)this.runtime.useConnect(this.app,handler)}catch(err){log.error(err,`Plugin middleware failed: ${plugin.name??`unknown`}`)}let declaredHelmet=(this.options.middlewares??[]).some(entry=>typeof entry==`function`&&Symbol.for(`kick/http/helmet`)in entry);if(this.options.security?.helmet!==!1&&!declaredHelmet)try{let{helmet:helmetFn}=await import(`./http/middleware/helmet.mjs`);this.runtime.useConnect(this.app,helmetFn())}catch{}let userMiddlewares=this.options.middlewares;if(userMiddlewares)for(let entry of userMiddlewares)this.mountMiddlewareEntry(entry);else if(this.runtime.useConnect(this.app,requestId()),!this.runtime.capabilities.nativeBodyParsing){let limit=this.options.jsonLimit??`1mb`;this.runtime.useConnect(this.app,express.json({limit,type:[...EXPRESS_JSON_TYPES]})),this.runtime.useConnect(this.app,express.urlencoded({extended:!0,limit})),this.runtime.useConnect(this.app,express.text({limit,type:`text/*`})),this.runtime.useConnect(this.app,rejectUnsupportedBody())}this.mountMiddlewareList(adapterMw.afterGlobal);let moduleRegistry=new MutableModuleRegistry;for(let plugin of this.plugins){for(let m of plugin.modules?.()??[])moduleRegistry.mount(m);plugin.setup?.(moduleRegistry)}for(let m of this.options.modules)moduleRegistry.mount(m);this.options.setup?.(moduleRegistry),this.options.health!==!1&&(this.container.registerInstance(HEALTH_PROBE,{isDraining:()=>this._draining,checks:async()=>{let withCheck=this.adapters.filter(a=>a.onHealthCheck);return(await Promise.allSettled(withCheck.map(a=>a.onHealthCheck()))).map((c,i)=>c.status===`fulfilled`?c.value:{name:withCheck[i].name??`unknown`,status:`down`})}}),moduleRegistry.mount(healthModule(this.options.health===!0?{}:this.options.health)));let modules=moduleRegistry.entries.map(entry=>{let mod=toAppModule(entry);return mod.register?.(this.container),mod});this.container.bootstrap(),this.container.has(Logger)||this.container.registerInstance(Logger,new Logger),this.mountMiddlewareList(adapterMw.beforeRoutes);let apiPrefix=this.options.apiPrefix??`/api`,defaultVersion=this.options.defaultVersion??1,shouldLogRoutes=this.options.logRouteTable??this.options.logRoutesTable??!1,mountedRoutes=[],adapterSources=[];for(let adapter of this.adapters){let adapterContribs=adapter.contributors?.()??[],adapterLabel=adapter.name??adapter.constructor.name??`adapter`;for(let registration of adapterContribs)adapterSources.push({source:`adapter`,registration,label:adapterLabel})}for(let plugin of this.plugins){let pluginContribs=plugin.contributors?.()??[],pluginLabel=plugin.name??plugin.constructor?.name??`plugin`;for(let registration of pluginContribs)adapterSources.push({source:`adapter`,registration,label:pluginLabel})}let globalSources=(this.options.contributors??[]).map(registration=>({source:`global`,registration,label:`bootstrap`}));this._moduleContributors=[];let seenRoutes=new Map,mountedPaths=[];for(let mod of modules){let declaredName=mod.name,ctorName=mod.constructor?.name,moduleLabel=typeof declaredName==`string`&&declaredName.length>0?declaredName:ctorName&&ctorName!==`Object`?ctorName:`module`,moduleSources=(mod.contributors?.()??[]).map(registration=>({source:`module`,registration,label:moduleLabel}));for(let src of moduleSources)this._moduleContributors.push({registration:src.registration,label:moduleLabel});_setExternalContributorSources([...moduleSources,...adapterSources,...globalSources]);try{let result=mod.routes();if(!result)continue;let routeSets=Array.isArray(result)?result:[result];for(let route of routeSets){let version=route.version??defaultVersion,mountPath=buildMountPath(route.prefix===!1?``:apiPrefix,version,route.path);if(route.router)this.runtime.useConnect(this.app,route.router,{path:mountPath});else if(route.controller){let routeTable=buildRouteTable(route.controller,{mountFlags:route.flags}),ctrl=route.controller.name??`controller`;for(let entry of routeTable){let owner=`${ctrl}.${String(entry.meta.handlerName??`?`)}`,fullPath=joinPaths(mountPath,entry.path);assertRouteUnique(seenRoutes,entry.method,fullPath,owner),mountedPaths.push({method:entry.method,path:fullPath}),this.routePolicy.add(entry.method,fullPath,entry.meta.flags)}let traced=routeTable.map(entry=>{let pattern=joinPaths(mountPath,entry.path);return{...entry,handler:traceHandler(entry.handler,{method:entry.method,route:pattern,controller:route.controller?.name,handler:entry.meta.handlerName}),meta:{...entry.meta,pattern}}});this.runtime.mountRoutes(this.app,[{mountPath,routes:traced}])}else throw moduleRouteMissingControllerError(mountPath);if(route.controller){for(let adapter of this.adapters)try{adapter.onRouteMount?.(route.controller,mountPath)}catch(err){log.error(err,`adapter.onRouteMount() failed for ${mountPath}`)}shouldLogRoutes&&mountedRoutes.push({controller:route.controller,mountPath})}}}finally{_setExternalContributorSources([])}}if(shouldLogRoutes&&mountedRoutes.length>0){let methodOrder=[`GET`,`POST`,`PUT`,`PATCH`,`DELETE`,`HEAD`,`OPTIONS`],methodRank=m=>{let i=methodOrder.indexOf(m);return i===-1?99:i},totalRoutes=0;log.info(`Routes:`);for(let{controller,mountPath}of mountedRoutes){let defs=getClassMeta(METADATA.ROUTES,controller,[]);if(defs.length===0)continue;let counts={};for(let def of defs){let m=def.method.toUpperCase();counts[m]=(counts[m]||0)+1}totalRoutes+=defs.length;let methods=Object.entries(counts).toSorted(([a],[b])=>methodRank(a)-methodRank(b)).map(([m,n])=>`${n} ${m}`).join(`, `),name=controller.name||`Controller`;log.info(` ${name.padEnd(30)} ${mountPath.padEnd(25)} ${defs.length} routes (${methods})`)}log.info(` Total: ${totalRoutes} routes`)}this.mountMiddlewareList(adapterMw.afterRoutes);for(let adapter of this.adapters)await this.callHook(adapter.beforeStart?.bind(adapter),ctx,adapter.name);for(let entry of this.options.middlewares??[])offerRoutePolicy(typeof entry==`function`?entry:entry.handler,this.routePolicy);for(let adapter of this.adapters)for(let mw of adapter.middleware?.()??[])offerRoutePolicy(mw.handler,this.routePolicy);this.runtime.setNotFound(this.app,this.options.onNotFound??notFoundHandler(mountedPaths)),this.runtime.setErrorHandler(this.app,observeErrors(this.options.onError??errorHandler()))}async registerOnly(){await this.setup()}async startWithoutServer(){this.handlerMode=!0,await this.setup();let needServer=this.adapters.filter(adapter=>adapter.afterStart);needServer.length>0&&log.warn(`No HTTP server in handler mode, so afterStart does not run for: ${needServer.map(adapter=>adapter.name).join(`, `)}. Anything they attach to the server (WebSockets, upgrade handlers) is unavailable.`);for(let plugin of this.plugins)await plugin.onReady?.(this.container)}async start(){let g=globalThis,serverOptions=this.options.server;if(serverOptions&&g.__kickjs_httpServer)log.warn(`server options (tls / http2) are ignored in dev mode — Vite owns the server`);else if(serverOptions){if(serverOptions.http2&&!serverOptions.tls)throw http2RequiresTlsError();if(serverOptions.http2&&!this.runtime.capabilities.http2)throw http2UnsupportedRuntimeError(this.runtime.name);serverOptions.tls&&assertUsableTls(serverOptions.tls)}if(await this.setup(),g.__kickjs_httpServer){this.httpServer=g.__kickjs_httpServer,log.debug(`Attached to Vite dev server`),g.__kickjs_app_shutdown=()=>this.shutdown();for(let adapter of this.adapters){let ctx=this.adapterCtx(this.httpServer);await this.callHook(adapter.afterStart?.bind(adapter),ctx,adapter.name,`afterStart`)}for(let plugin of this.plugins)await plugin.onReady?.(this.container);return}let port=this.options.port??parseInt(process.env.PORT||`3000`,10);this.httpServer=this.createNodeServer(),this.httpServer.on(`error`,err=>{throw err.code===`EADDRINUSE`&&(log.error(`Port ${port} is already in use. Kill the existing process or use a different port:\n PORT=${port+1} kick dev\n lsof -i :${port} # find what's using it\n kill <PID> # stop it`),process.exit(1)),err}),await new Promise((resolve,reject)=>{this.httpServer.listen(port,async()=>{try{let scheme=serverOptions?.tls?`https`:`http`,protocol=serverOptions?.http2?` (HTTP/2)`:``;log.info(`Server running on ${scheme}://localhost:${port}${protocol}`);for(let adapter of this.adapters){let afterCtx=this.adapterCtx(this.httpServer);await this.callHook(adapter.afterStart?.bind(adapter),afterCtx,adapter.name,`afterStart`)}for(let plugin of this.plugins)await plugin.onReady?.(this.container);resolve()}catch(err){reject(err)}})})}createNodeServer(){let handler=this.runtime.nodeHandler(this.app),{tls,http2:useHttp2}=this.options.server??{};if(useHttp2){let server=http2.createSecureServer({...tls,allowHTTP1:!0},handler);return server.on(`session`,session=>{this.http2Sessions.add(session),session.once(`close`,()=>this.http2Sessions.delete(session))}),server}return tls?https.createServer(tls,handler):http.createServer(handler)}async rebuild(){let prevApp=this.app,prevContainer=this.container,staleDisposables=drainDisposables();try{Container.reset(),this.container=Container.getInstance(),this.app=this.runtime.createApp({trustProxy:this.options.trustProxy}),await this.setup()}catch(err){log.error(err,`HMR rebuild failed, keeping previous app`),await runDisposables(drainDisposables());for(let d of staleDisposables)registerDisposable(d);this.app=prevApp,this.container=prevContainer;return}await runDisposables(staleDisposables),this.httpServer&&(this.httpServer.removeAllListeners(`request`),this.httpServer.on(`request`,this.runtime.nodeHandler(this.app)),log.debug(`HMR: app rebuilt and swapped`))}async shutdown(options={}){let closeServer=options.closeServer??!0;if(this._shutdownInitiated){log.debug(`Shutdown already in progress, skipping duplicate call`);return}this._shutdownInitiated=!0,this._draining=!0,log.debug(`Shutting down — draining in-flight requests...`);let timeoutMs=this.options.shutdownTimeout??3e4,timer,forceExitPromise=timeoutMs>0?new Promise(resolve=>{timer=setTimeout(()=>resolve(`timeout`),timeoutMs),timer.unref()}):new Promise(()=>{});try{if(closeServer&&this.httpServer){this.httpServer.close(()=>{});for(let session of this.http2Sessions)session.close()}if(closeServer&&this._inFlightRequests>0){log.debug(`Waiting for ${this._inFlightRequests} in-flight request(s) to complete...`);let drainPromise=new Promise(resolve=>{this._drainResolvers.push(()=>resolve(`drained`))});if(await Promise.race([drainPromise,forceExitPromise])===`timeout`){log.warn(`Shutdown timeout (${timeoutMs}ms) reached with ${this._inFlightRequests} request(s) still in-flight, forcing shutdown`);for(let session of this.http2Sessions)session.destroy()}else log.debug(`All in-flight requests completed`)}closeServer&&pendingBackgroundWork()>0&&(log.debug(`Waiting for ${pendingBackgroundWork()} waitUntil() task(s) to settle...`),await Promise.race([settleBackgroundWork().then(()=>`settled`),forceExitPromise])===`timeout`&&log.warn(`Shutdown timeout (${timeoutMs}ms) reached with ${pendingBackgroundWork()} waitUntil() task(s) still running`));let hookTimeoutMs=closeServer?timeoutMs:Math.min(timeoutMs,5e3),timeBoxed=(label,run)=>{let hook=(async()=>run())();return hookTimeoutMs<=0?hook:new Promise((resolve,reject)=>{let t=setTimeout(()=>{log.warn(`${label} did not finish shutting down within ${hookTimeoutMs}ms — continuing without it. Its resources may still be held.`),resolve(void 0)},hookTimeoutMs);t.unref(),hook.then(value=>{clearTimeout(t),resolve(value)},err=>{clearTimeout(t),reject(err)})})},wasListening=this.httpServer?.listening===!0,results=await Promise.allSettled([...this.plugins.map(plugin=>timeBoxed(`Plugin '${plugin.name}'`,()=>plugin.shutdown?.())),...this.adapters.map(adapter=>timeBoxed(`Adapter '${adapter.name}'`,()=>adapter.shutdown?.()))]);!closeServer&&wasListening&&this.httpServer?.listening===!1&&log.error("An adapter or plugin closed the shared HTTP server during an HMR reload. The dev server is now unreachable and will not rebind until you restart it. Close only what you own: socket.io's `io.close()` closes the HTTP server passed to its constructor — on a reload, disconnect the sockets instead.");for(let result of results)result.status===`rejected`&&log.error({err:result.reason},`Adapter shutdown failed`);await disposeAll(),this.container.flushChanges(),await this.loopback?.close(),this.loopback=void 0,closeServer&&releaseObservers(this.observers)}finally{timer&&clearTimeout(timer)}}getRuntimeApp(){return this.app}getActiveRuntime(){return{name:this.runtime.name,capabilities:this.runtime.capabilities}}getExpressApp(){return this.app}getAdapters(){return this.adapters}getPlugins(){return this.plugins}getContributors(){let out=[],ingest=(list,source,label)=>{if(list)for(let reg of list){let r=reg;if(r.key===void 0||r.key===null)continue;let key=typeof r.key==`string`?r.key:tokenName(r.key);out.push({key,source,label,dependsOn:Array.isArray(r.dependsOn)?r.dependsOn:[]})}};for(let adapter of this.adapters)ingest(adapter.contributors?.(),`adapter`,adapter.name??`adapter`);for(let plugin of this.plugins)ingest(plugin.contributors?.(),`plugin`,plugin.name??`plugin`);for(let{registration,label}of this._moduleContributors)ingest([registration],`module`,label);return ingest(this.options.contributors,`global`,`bootstrap`),out}getHttpServer(){return this.httpServer}shouldAutoMountRequestScope(){if(this.options.contextStore===`manual`)return!1;let userEntries=this.options.middlewares??[];for(let entry of userEntries){let handler=typeof entry==`function`?entry:entry.handler;if(isRequestScopeMiddleware(handler))return!1}return!0}collectAdapterMiddleware(){let result={beforeGlobal:[],afterGlobal:[],beforeRoutes:[],afterRoutes:[]};for(let adapter of this.adapters){let entries=adapter.middleware?.()??[];for(let entry of entries)result[entry.phase??`afterGlobal`].push(entry)}return result}mountMiddlewareList(entries){for(let entry of entries){if(entry.path===void 0){this.runtime.useConnect(this.app,entry.handler);continue}this.runtime.useConnect(this.app,entry.handler,{path:entry.path})}}mountMiddlewareEntry(entry){typeof entry==`function`?this.runtime.useConnect(this.app,entry):this.runtime.useConnect(this.app,entry.handler,{path:entry.path})}mountCronTrigger(){let secret=process.env.CRON_SECRET;secret&&this.adapterHttp().route(`GET`,`/_kick/cron/:scheduleId`,async ctx=>{if(!matchesBearer(ctx.headers.authorization,secret))throw new HttpException(401,`Missing or invalid CRON_SECRET bearer token`);let result=await runCronJobs(this.container,{scheduleId:ctx.params.scheduleId},`http`);ctx.json(result,result.failed>0?500:200)})}requestTrackingMiddleware(){return(req,res,next)=>{this._inFlightRequests++;let started=hasResponseObservers()?performance.now():void 0,onFinish=()=>settle(!0),onClose=()=>settle(!1),settle=finished=>{if(res.removeListener(`finish`,onFinish),res.removeListener(`close`,onClose),finished&&started!==void 0){let matched=req[ROUTE_SLOT];reportResponse({method:req.method,path:requestPath(req),route:matched?.pattern,status:res.statusCode,durationMs:performance.now()-started,requestId:requestIdOf(req)})}if(this._inFlightRequests--,this._draining&&this._inFlightRequests===0){for(let resolve of this._drainResolvers)resolve();this._drainResolvers=[]}};res.on(`finish`,onFinish),res.on(`close`,onClose),next()}}};function requestPath(req){return(req.originalUrl??req.url??``).split(`?`)[0]}function requestIdOf(req){let header=req.headers?.[`x-request-id`];return req.requestId??(typeof header==`string`?header:void 0)}function errorStatus(err){return typeof err?.problem?.status==`number`?err.problem.status:typeof err?.status==`number`?err.status:typeof err?.statusCode==`number`?err.statusCode:err?.name===`ZodError`?422:500}function observeErrors(handler){return((err,req,res,next)=>{let matched=req?.[ROUTE_SLOT];return reportError(err,{source:`request`,requestId:requestIdOf(req??{}),method:req?.method,path:requestPath(req??{}),route:matched?.pattern,status:errorStatus(err)}),handler(err,req,res,next)})}function matchesBearer(header,secret){if(typeof header!=`string`)return!1;let given=Buffer.from(header),expected=Buffer.from(`Bearer ${secret}`);return given.length===expected.length&&timingSafeEqual(given,expected)}export{HEALTH_PROBE as a,ERROR_CHANNEL as c,DuplicateMountNameError as d,MissingMountDepError as f,defineModule as h,materializeRouter as i,HANDLER_CHANNEL as l,mountSort as m,buildRoutes as n,HealthController as o,MountCycleError as p,expressRuntime as r,healthModule as s,Application as t,RESPONSE_CHANNEL as u};
|
|
12
|
+
//# sourceMappingURL=application-5IWcGDdu.mjs.map
|