@znight/http 0.2.1 → 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 CHANGED
@@ -1622,6 +1622,13 @@ export class StatusPlugin implements Plugin, PluginHttpRoutes {
1622
1622
  needs a decision there too: without one the boot fails (ZNIGHT023), with a hint naming the plugin.
1623
1623
  - An invalid route (method, path grammar, name, handler, meta, body limit) or a collision with another route is
1624
1624
  ZNIGHT007 naming the plugin. `HttpPlugin.routes` lists the routes with `source: '<plugin name>'`.
1625
+ - `onHttpDrain()` (optional) is called once when `HttpPlugin` starts draining, in its `onStop`,
1626
+ whatever the plugin order: after it stopped accepting connections and started closing its event
1627
+ streams, before it waits for the requests in flight. A plugin whose routes answer long-lived
1628
+ streams `HttpPlugin` does not know of (a streamed `Response` produced by another server, such as a
1629
+ GraphQL driver's subscriptions) ends them there, so that the drain does not wait for them until
1630
+ `drainTimeoutMs`. The drain waits for a returned promise too (within `drainTimeoutMs`); a failure
1631
+ is logged, never thrown.
1625
1632
 
1626
1633
  ## Adapters
1627
1634
 
package/dist/index.d.mts CHANGED
@@ -1162,6 +1162,14 @@ interface PluginHttpRoutesContext {
1162
1162
  */
1163
1163
  interface PluginHttpRoutes {
1164
1164
  httpRoutes(context: PluginHttpRoutesContext): readonly PluginRoute[];
1165
+ /**
1166
+ * Called once when `HttpPlugin` starts draining (its `onStop`, whatever the plugin order), after
1167
+ * it stopped accepting connections and started closing its event streams: end the long-lived
1168
+ * responses `HttpPlugin` does not know of (a stream another server produced), so that the drain
1169
+ * does not wait for them until `drainTimeoutMs`. Requests still in flight are served. The drain
1170
+ * waits for a returned promise too (within `drainTimeoutMs`); a failure is logged.
1171
+ */
1172
+ onHttpDrain?(): void | Promise<void>;
1165
1173
  }
1166
1174
  /** A parsed `content-type` header. */
1167
1175
  interface MediaType {
package/dist/index.mjs CHANGED
@@ -5135,6 +5135,28 @@ function collectPluginRoutes(plugins, self, context) {
5135
5135
  }
5136
5136
  return collected;
5137
5137
  }
5138
+ /**
5139
+ * Calls `onHttpDrain()` on every other plugin implementing it, in plugin order: the promises the
5140
+ * drain waits for (they never reject: a failure is logged).
5141
+ */
5142
+ function drainPlugins(plugins, self, logger) {
5143
+ const pending = [];
5144
+ for (const plugin of plugins) {
5145
+ if (plugin === self) continue;
5146
+ const drain = plugin.onHttpDrain;
5147
+ if (typeof drain !== "function") continue;
5148
+ const failed = (error) => {
5149
+ logger.error(`HttpPlugin: onHttpDrain() of the plugin "${String(plugin.name)}" failed`, { err: error });
5150
+ };
5151
+ try {
5152
+ const result = drain.call(plugin);
5153
+ if (typeof result?.then === "function") pending.push(Promise.resolve(result).then(void 0, failed));
5154
+ } catch (error) {
5155
+ failed(error);
5156
+ }
5157
+ }
5158
+ return pending;
5159
+ }
5138
5160
  function validateRoute(instance, plugin, value, prefix) {
5139
5161
  if (value === null || typeof value !== "object") throw invalid(plugin, `invalid route ${describe(value)}`);
5140
5162
  const route = value;
@@ -5827,13 +5849,18 @@ var HttpPlugin = class HttpPlugin {
5827
5849
  if (runtime === void 0) return;
5828
5850
  runtime.pipeline.draining = true;
5829
5851
  runtime.pipeline.closeStreams("shutdown");
5852
+ const drains = drainPlugins(ctx.app.plugins, this, ctx.logger);
5830
5853
  const timeoutMs = runtime.options.drainTimeoutMs;
5831
5854
  let timer;
5832
5855
  const timeout = new Promise((resolve) => {
5833
5856
  if (timeoutMs !== Number.POSITIVE_INFINITY) timer = setTimeout(() => resolve("timeout"), Math.min(timeoutMs, MAX_TIMER_MS));
5834
5857
  });
5835
5858
  try {
5836
- const drained = Promise.all([runtime.adapter.close(), runtime.pipeline.whenIdle()]).then(() => "drained");
5859
+ const drained = Promise.all([
5860
+ runtime.adapter.close(),
5861
+ runtime.pipeline.whenIdle(),
5862
+ ...drains
5863
+ ]).then(() => "drained");
5837
5864
  if (await Promise.race([drained, timeout]) === "timeout") {
5838
5865
  ctx.logger.warn(`HTTP server: closing ${runtime.pipeline.inflight} in-flight request(s) after ${timeoutMs} ms`);
5839
5866
  await runtime.adapter.close({ force: true });