@znight/http 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +28 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/plugin-routes.ts +34 -1
- package/src/plugin.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@znight/http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Znight HTTP plugin: controllers, routing, guards, interceptors, validation with Fastify, Express or Hono",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ugho STEPHAN",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"access": "public"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@znight/core": "0.
|
|
82
|
+
"@znight/core": "0.3.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"@hono/node-server": "^2.1.5",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"fastify": "^5.12.7",
|
|
88
88
|
"hono": "^4.13.14",
|
|
89
89
|
"light-my-request": "^6.8.0",
|
|
90
|
-
"@znight/compiler": "^0.
|
|
90
|
+
"@znight/compiler": "^0.3.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
93
93
|
"@hono/node-server": {
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"valibot": "1.5.0",
|
|
119
119
|
"zod": "4.6.7",
|
|
120
120
|
"light-my-request": "6.8.0",
|
|
121
|
-
"@znight/compiler": "0.
|
|
121
|
+
"@znight/compiler": "0.3.0"
|
|
122
122
|
},
|
|
123
123
|
"znight": {
|
|
124
124
|
"apiVersion": 1,
|
package/src/plugin-routes.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* health or metrics endpoints), registered on every adapter in the same pipeline as the compiled
|
|
4
4
|
* controllers.
|
|
5
5
|
*/
|
|
6
|
-
import type { Container, Plugin } from '@znight/core'
|
|
6
|
+
import type { Container, CoreLogger, Plugin } from '@znight/core'
|
|
7
7
|
import type { HttpContext } from './context.ts'
|
|
8
8
|
import type { RouteMetaEntry } from './decorators.ts'
|
|
9
9
|
import type { HttpMethod } from './metadata.ts'
|
|
@@ -71,6 +71,14 @@ export interface PluginHttpRoutesContext {
|
|
|
71
71
|
*/
|
|
72
72
|
export interface PluginHttpRoutes {
|
|
73
73
|
httpRoutes(context: PluginHttpRoutesContext): readonly PluginRoute[]
|
|
74
|
+
/**
|
|
75
|
+
* Called once when `HttpPlugin` starts draining (its `onStop`, whatever the plugin order), after
|
|
76
|
+
* it stopped accepting connections and started closing its event streams: end the long-lived
|
|
77
|
+
* responses `HttpPlugin` does not know of (a stream another server produced), so that the drain
|
|
78
|
+
* does not wait for them until `drainTimeoutMs`. Requests still in flight are served. The drain
|
|
79
|
+
* waits for a returned promise too (within `drainTimeoutMs`); a failure is logged.
|
|
80
|
+
*/
|
|
81
|
+
onHttpDrain?(): void | Promise<void>
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
/** A validated plugin route (internal). */
|
|
@@ -122,6 +130,31 @@ export function collectPluginRoutes(
|
|
|
122
130
|
return collected
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Calls `onHttpDrain()` on every other plugin implementing it, in plugin order: the promises the
|
|
135
|
+
* drain waits for (they never reject: a failure is logged).
|
|
136
|
+
*/
|
|
137
|
+
export function drainPlugins(plugins: readonly Plugin[], self: Plugin, logger: CoreLogger): Promise<void>[] {
|
|
138
|
+
const pending: Promise<void>[] = []
|
|
139
|
+
for (const plugin of plugins) {
|
|
140
|
+
if (plugin === self) continue
|
|
141
|
+
const drain = (plugin as Plugin & Partial<PluginHttpRoutes>).onHttpDrain
|
|
142
|
+
if (typeof drain !== 'function') continue
|
|
143
|
+
const failed = (error: unknown): void => {
|
|
144
|
+
logger.error(`HttpPlugin: onHttpDrain() of the plugin "${String(plugin.name)}" failed`, { err: error })
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const result: unknown = drain.call(plugin)
|
|
148
|
+
if (typeof (result as PromiseLike<void> | undefined)?.then === 'function') {
|
|
149
|
+
pending.push(Promise.resolve(result as PromiseLike<void>).then(undefined, failed))
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
failed(error)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return pending
|
|
156
|
+
}
|
|
157
|
+
|
|
125
158
|
function validateRoute(instance: object, plugin: string, value: unknown, prefix: string): CollectedPluginRoute {
|
|
126
159
|
if (value === null || typeof value !== 'object') throw invalid(plugin, `invalid route ${describe(value)}`)
|
|
127
160
|
const route = value as Partial<PluginRoute>
|
package/src/plugin.ts
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
randomRequestId,
|
|
57
57
|
type SseSettings,
|
|
58
58
|
} from './pipeline.ts'
|
|
59
|
-
import { collectPluginRoutes } from './plugin-routes.ts'
|
|
59
|
+
import { collectPluginRoutes, drainPlugins } from './plugin-routes.ts'
|
|
60
60
|
import { resolveTrustProxy } from './proxy.ts'
|
|
61
61
|
import { RouteTable } from './router.ts'
|
|
62
62
|
import { resolveSecurityHeaders } from './security-headers.ts'
|
|
@@ -552,6 +552,8 @@ export class HttpPlugin implements Plugin {
|
|
|
552
552
|
runtime.pipeline.draining = true
|
|
553
553
|
// event streams never end by themselves: let their sources finish, then send them a jittered retry
|
|
554
554
|
runtime.pipeline.closeStreams('shutdown')
|
|
555
|
+
// the streams other plugins serve on their routes (a GraphQL driver's subscriptions)
|
|
556
|
+
const drains = drainPlugins(ctx.app.plugins, this, ctx.logger)
|
|
555
557
|
const timeoutMs = runtime.options.drainTimeoutMs
|
|
556
558
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
557
559
|
// no timer for Infinity; larger delays than setTimeout supports are clamped (they would fire at once)
|
|
@@ -561,7 +563,9 @@ export class HttpPlugin implements Plugin {
|
|
|
561
563
|
}
|
|
562
564
|
})
|
|
563
565
|
try {
|
|
564
|
-
const drained = Promise.all([runtime.adapter.close(), runtime.pipeline.whenIdle()]).then(
|
|
566
|
+
const drained = Promise.all([runtime.adapter.close(), runtime.pipeline.whenIdle(), ...drains]).then(
|
|
567
|
+
() => 'drained' as const,
|
|
568
|
+
)
|
|
565
569
|
if ((await Promise.race([drained, timeout])) === 'timeout') {
|
|
566
570
|
ctx.logger.warn(`HTTP server: closing ${runtime.pipeline.inflight} in-flight request(s) after ${timeoutMs} ms`)
|
|
567
571
|
await runtime.adapter.close({ force: true })
|