@znight/http 0.2.2 → 0.4.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 +51 -2
- package/dist/{adapter-J0UXFnNM.d.mts → adapter-DmhQGqAR.d.mts} +18 -5
- package/dist/compiler.d.mts +23 -1
- package/dist/compiler.mjs +126 -9
- package/dist/compiler.mjs.map +1 -1
- package/dist/express.d.mts +1 -1
- package/dist/fastify.d.mts +1 -1
- package/dist/hono.d.mts +1 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.mjs +54 -8
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +1 -1
- package/package.json +4 -4
- package/src/compiler/analyze.ts +43 -0
- package/src/compiler/generate.ts +122 -13
- package/src/compiler/model.ts +21 -0
- package/src/context.ts +7 -0
- package/src/metadata.ts +13 -4
- package/src/pipeline.ts +12 -7
- package/src/plugin-routes.ts +34 -1
- package/src/plugin.ts +39 -2
package/dist/express.d.mts
CHANGED
package/dist/fastify.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as HttpAdapter } from "./adapter-
|
|
1
|
+
import { s as HttpAdapter } from "./adapter-DmhQGqAR.mjs";
|
|
2
2
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
3
3
|
/** Options of {@link fastifyAdapter}. */
|
|
4
4
|
export interface FastifyAdapterOptions {
|
package/dist/hono.d.mts
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as PayloadTooLargeException, A as UploadedFileOptions, B as BadRequestException, C as HttpUploadDefinition, Ct as CookieOptions, D as RequestAbortedError, Dt as parseSetCookie, E as RequestAbortKind, Et as parseCookieHeader, F as formatByteSize, G as HeaderValue, H as ForbiddenException, I as parseByteSize, J as HttpExceptionOptions, K as HttpErrorBody, L as QueryRecord, M as Uploads, N as uploads, O as RequestId, Ot as serializeClearCookie, P as ByteSize, Q as NotImplementedException, R as parseQuery, S as HttpSseDefinition, St as CookieDefaults, T as HttpRouteInfo, Tt as SetCookieInfo, U as GatewayTimeoutException, V as ConflictException, W as GoneException, X as MethodNotAllowedException, Y as InternalServerErrorException, Z as NotFoundException, _ as HttpMethod, _t as StandardSchemaV1Result, a as AdapterRoute, at as ValidationException, b as HttpRouteDefinition, bt as isStandardSchema, c as HttpServerAddress, ct as toIssueDetail, d as InjectStreamOptions, dt as StandardSchemaV1, et as ServiceUnavailableException, f as InjectStreamRequest, ft as StandardSchemaV1FailureResult, g as HttpMetadata, gt as StandardSchemaV1Props, h as HttpControllerDefinition, ht as StandardSchemaV1PathSegment, i as AdapterRequest, it as UnsupportedMediaTypeException, j as UploadedFilesOptions, k as UploadLimits, kt as serializeCookie, l as InjectRequest, lt as InferSchemaInput, m as ReplyBody, mt as StandardSchemaV1Options, n as AdapterHandler, nt as UnauthorizedException, o as FallbackHandler, ot as ValidationIssueDetail, p as InjectStreamResponse, pt as StandardSchemaV1Issue, q as HttpException, r as AdapterReply, rt as UnprocessableEntityException, s as HttpAdapter, st as isHttpException, t as AdapterCloseOptions, tt as TooManyRequestsException, u as InjectResponse, ut as InferSchemaOutput, v as HttpParamDefinition, vt as StandardSchemaV1SuccessResult, w as HttpContext, wt as ResponseCookie, x as HttpRouteGroupDefinition, xt as ClearCookieOptions, y as HttpParamSource, yt as StandardSchemaV1Types, z as BadGatewayException } from "./adapter-
|
|
1
|
+
import { $ as PayloadTooLargeException, A as UploadedFileOptions, B as BadRequestException, C as HttpUploadDefinition, Ct as CookieOptions, D as RequestAbortedError, Dt as parseSetCookie, E as RequestAbortKind, Et as parseCookieHeader, F as formatByteSize, G as HeaderValue, H as ForbiddenException, I as parseByteSize, J as HttpExceptionOptions, K as HttpErrorBody, L as QueryRecord, M as Uploads, N as uploads, O as RequestId, Ot as serializeClearCookie, P as ByteSize, Q as NotImplementedException, R as parseQuery, S as HttpSseDefinition, St as CookieDefaults, T as HttpRouteInfo, Tt as SetCookieInfo, U as GatewayTimeoutException, V as ConflictException, W as GoneException, X as MethodNotAllowedException, Y as InternalServerErrorException, Z as NotFoundException, _ as HttpMethod, _t as StandardSchemaV1Result, a as AdapterRoute, at as ValidationException, b as HttpRouteDefinition, bt as isStandardSchema, c as HttpServerAddress, ct as toIssueDetail, d as InjectStreamOptions, dt as StandardSchemaV1, et as ServiceUnavailableException, f as InjectStreamRequest, ft as StandardSchemaV1FailureResult, g as HttpMetadata, gt as StandardSchemaV1Props, h as HttpControllerDefinition, ht as StandardSchemaV1PathSegment, i as AdapterRequest, it as UnsupportedMediaTypeException, j as UploadedFilesOptions, k as UploadLimits, kt as serializeCookie, l as InjectRequest, lt as InferSchemaInput, m as ReplyBody, mt as StandardSchemaV1Options, n as AdapterHandler, nt as UnauthorizedException, o as FallbackHandler, ot as ValidationIssueDetail, p as InjectStreamResponse, pt as StandardSchemaV1Issue, q as HttpException, r as AdapterReply, rt as UnprocessableEntityException, s as HttpAdapter, st as isHttpException, t as AdapterCloseOptions, tt as TooManyRequestsException, u as InjectResponse, ut as InferSchemaOutput, v as HttpParamDefinition, vt as StandardSchemaV1SuccessResult, w as HttpContext, wt as ResponseCookie, x as HttpRouteGroupDefinition, xt as ClearCookieOptions, y as HttpParamSource, yt as StandardSchemaV1Types, z as BadGatewayException } from "./adapter-DmhQGqAR.mjs";
|
|
2
2
|
import { NodeAdapter, NodeAdapterOptions, nodeAdapter } from "./node.mjs";
|
|
3
3
|
import { ClassMarker, Container, MethodMarker, ParameterMarker, Plugin, PluginContext, PluginOptions, ProviderKey, Scope, TypedToken } from "@znight/core";
|
|
4
4
|
/**
|
|
@@ -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
|
@@ -3967,7 +3967,7 @@ var Pipeline = class {
|
|
|
3967
3967
|
let result;
|
|
3968
3968
|
let reply;
|
|
3969
3969
|
try {
|
|
3970
|
-
result = plan.
|
|
3970
|
+
result = plan.invoke(this.#resolve(plan.controller), ctx);
|
|
3971
3971
|
if (isThenable(result)) return this.#fastLater(route, request, ctx, result, startTime);
|
|
3972
3972
|
reply = this.#fastReply(route, ctx, result);
|
|
3973
3973
|
} catch (error) {
|
|
@@ -4003,7 +4003,7 @@ var Pipeline = class {
|
|
|
4003
4003
|
if (result === NO_RESULT) {
|
|
4004
4004
|
const controller = this.#resolve(plan.controller);
|
|
4005
4005
|
await ctx.parseBody();
|
|
4006
|
-
result = plan.
|
|
4006
|
+
result = plan.invoke(controller, ctx);
|
|
4007
4007
|
}
|
|
4008
4008
|
if (isThenable(result)) result = await result;
|
|
4009
4009
|
reply = this.#fastReply(route, ctx, result);
|
|
@@ -4091,7 +4091,7 @@ var Pipeline = class {
|
|
|
4091
4091
|
if (plan.parseBody) return this.#fastOperationLater(x, route, NO_RESULT);
|
|
4092
4092
|
let result;
|
|
4093
4093
|
try {
|
|
4094
|
-
result = plan.
|
|
4094
|
+
result = plan.invoke(this.#resolve(plan.controller), ctx);
|
|
4095
4095
|
} catch (error) {
|
|
4096
4096
|
return this.#fastChecked(x, this.#failed(error, ctx, x.event));
|
|
4097
4097
|
}
|
|
@@ -4107,7 +4107,7 @@ var Pipeline = class {
|
|
|
4107
4107
|
if (result === NO_RESULT) {
|
|
4108
4108
|
const controller = this.#resolve(plan.controller);
|
|
4109
4109
|
await ctx.parseBody();
|
|
4110
|
-
result = plan.
|
|
4110
|
+
result = plan.invoke(controller, ctx);
|
|
4111
4111
|
}
|
|
4112
4112
|
if (isThenable(result)) result = await result;
|
|
4113
4113
|
} catch (error) {
|
|
@@ -4634,12 +4634,11 @@ var Pipeline = class {
|
|
|
4634
4634
|
const controller = this.#resolve(plan.controller);
|
|
4635
4635
|
for (const guard of plan.guards) if (await this.#resolve(guard).canActivate(ctx) !== true) throw new ForbiddenException("Forbidden resource");
|
|
4636
4636
|
if (plan.parseBody) await ctx.parseBody();
|
|
4637
|
-
const definition = plan.definition;
|
|
4638
4637
|
const interceptors = plan.interceptors;
|
|
4639
|
-
if (interceptors.length === 0) result =
|
|
4638
|
+
if (interceptors.length === 0) result = plan.invoke(controller, ctx);
|
|
4640
4639
|
else {
|
|
4641
4640
|
const chain = async (index) => {
|
|
4642
|
-
if (index === interceptors.length) return await
|
|
4641
|
+
if (index === interceptors.length) return await plan.invoke(controller, ctx);
|
|
4643
4642
|
return await this.#resolve(interceptors[index]).intercept(ctx, () => chain(index + 1));
|
|
4644
4643
|
};
|
|
4645
4644
|
result = chain(0);
|
|
@@ -5135,6 +5134,28 @@ function collectPluginRoutes(plugins, self, context) {
|
|
|
5135
5134
|
}
|
|
5136
5135
|
return collected;
|
|
5137
5136
|
}
|
|
5137
|
+
/**
|
|
5138
|
+
* Calls `onHttpDrain()` on every other plugin implementing it, in plugin order: the promises the
|
|
5139
|
+
* drain waits for (they never reject: a failure is logged).
|
|
5140
|
+
*/
|
|
5141
|
+
function drainPlugins(plugins, self, logger) {
|
|
5142
|
+
const pending = [];
|
|
5143
|
+
for (const plugin of plugins) {
|
|
5144
|
+
if (plugin === self) continue;
|
|
5145
|
+
const drain = plugin.onHttpDrain;
|
|
5146
|
+
if (typeof drain !== "function") continue;
|
|
5147
|
+
const failed = (error) => {
|
|
5148
|
+
logger.error(`HttpPlugin: onHttpDrain() of the plugin "${String(plugin.name)}" failed`, { err: error });
|
|
5149
|
+
};
|
|
5150
|
+
try {
|
|
5151
|
+
const result = drain.call(plugin);
|
|
5152
|
+
if (typeof result?.then === "function") pending.push(Promise.resolve(result).then(void 0, failed));
|
|
5153
|
+
} catch (error) {
|
|
5154
|
+
failed(error);
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
return pending;
|
|
5158
|
+
}
|
|
5138
5159
|
function validateRoute(instance, plugin, value, prefix) {
|
|
5139
5160
|
if (value === null || typeof value !== "object") throw invalid(plugin, `invalid route ${describe(value)}`);
|
|
5140
5161
|
const route = value;
|
|
@@ -5827,13 +5848,18 @@ var HttpPlugin = class HttpPlugin {
|
|
|
5827
5848
|
if (runtime === void 0) return;
|
|
5828
5849
|
runtime.pipeline.draining = true;
|
|
5829
5850
|
runtime.pipeline.closeStreams("shutdown");
|
|
5851
|
+
const drains = drainPlugins(ctx.app.plugins, this, ctx.logger);
|
|
5830
5852
|
const timeoutMs = runtime.options.drainTimeoutMs;
|
|
5831
5853
|
let timer;
|
|
5832
5854
|
const timeout = new Promise((resolve) => {
|
|
5833
5855
|
if (timeoutMs !== Number.POSITIVE_INFINITY) timer = setTimeout(() => resolve("timeout"), Math.min(timeoutMs, MAX_TIMER_MS));
|
|
5834
5856
|
});
|
|
5835
5857
|
try {
|
|
5836
|
-
const drained = Promise.all([
|
|
5858
|
+
const drained = Promise.all([
|
|
5859
|
+
runtime.adapter.close(),
|
|
5860
|
+
runtime.pipeline.whenIdle(),
|
|
5861
|
+
...drains
|
|
5862
|
+
]).then(() => "drained");
|
|
5837
5863
|
if (await Promise.race([drained, timeout]) === "timeout") {
|
|
5838
5864
|
ctx.logger.warn(`HTTP server: closing ${runtime.pipeline.inflight} in-flight request(s) after ${timeoutMs} ms`);
|
|
5839
5865
|
await runtime.adapter.close({ force: true });
|
|
@@ -6102,6 +6128,7 @@ function buildRegistrations(ctx, options, raw, pluginRoutes) {
|
|
|
6102
6128
|
const body = bodyPlan(route, options.bodyLimit, options.uploads);
|
|
6103
6129
|
const parseBody = body.upload || route.params.some((param) => param.source === "body");
|
|
6104
6130
|
const chain = guards.map((guard) => guard.resolvable);
|
|
6131
|
+
const invoke = routeInvoke(container, route, lookup, where);
|
|
6105
6132
|
let meta;
|
|
6106
6133
|
const effective = () => {
|
|
6107
6134
|
meta ??= effectiveMeta(controllerMeta, routeMeta, groupOf);
|
|
@@ -6113,6 +6140,7 @@ function buildRegistrations(ctx, options, raw, pluginRoutes) {
|
|
|
6113
6140
|
path,
|
|
6114
6141
|
plan: (method) => ({
|
|
6115
6142
|
definition: route,
|
|
6143
|
+
invoke,
|
|
6116
6144
|
path,
|
|
6117
6145
|
info: Object.freeze({
|
|
6118
6146
|
method,
|
|
@@ -6166,6 +6194,7 @@ function buildRegistrations(ctx, options, raw, pluginRoutes) {
|
|
|
6166
6194
|
path,
|
|
6167
6195
|
plan: (method) => ({
|
|
6168
6196
|
definition,
|
|
6197
|
+
invoke: definition.invoke,
|
|
6169
6198
|
path,
|
|
6170
6199
|
info: Object.freeze({
|
|
6171
6200
|
method,
|
|
@@ -6320,6 +6349,7 @@ function assertRoute(route, where) {
|
|
|
6320
6349
|
if (route.bodyLimit !== void 0 && !isSize(route.bodyLimit)) throw new ZnightError(ErrorCode.InvalidRoute, `Invalid route ${where}: bodyLimit must be a number of bytes (or Infinity), got ${describeValue(route.bodyLimit)}`);
|
|
6321
6350
|
if (route.uploads !== void 0) assertUploads(route, where);
|
|
6322
6351
|
if (route.sse !== void 0) assertSse(route, where);
|
|
6352
|
+
if (route.paramResolvers !== void 0 && !Array.isArray(route.paramResolvers)) throw new ZnightError(ErrorCode.InvalidRoute, `Invalid route ${where}: paramResolvers must be an array of tokens`);
|
|
6323
6353
|
}
|
|
6324
6354
|
function assertSse(route, where) {
|
|
6325
6355
|
const sse = route.sse;
|
|
@@ -6359,6 +6389,22 @@ function describeValue(value) {
|
|
|
6359
6389
|
}
|
|
6360
6390
|
}
|
|
6361
6391
|
/**
|
|
6392
|
+
* The `invoke` of a route plan: the generated `invoke` itself, unless the route has parameter
|
|
6393
|
+
* resolvers (RFC 0010), whose instances are passed as its third argument: one frozen array resolved
|
|
6394
|
+
* at bootstrap when they are all singletons, otherwise an array resolved in the request scope.
|
|
6395
|
+
*/
|
|
6396
|
+
function routeInvoke(container, route, lookup, where) {
|
|
6397
|
+
const invoke = route.invoke;
|
|
6398
|
+
const tokens = route.paramResolvers;
|
|
6399
|
+
if (tokens === void 0 || tokens.length === 0) return invoke;
|
|
6400
|
+
const resolvers = tokens.map((token) => lookup(token, `parameter resolver of ${where}`));
|
|
6401
|
+
if (resolvers.every((resolver) => "instance" in resolver)) {
|
|
6402
|
+
const instances = Object.freeze(resolvers.map((resolver) => resolver.instance));
|
|
6403
|
+
return (controller, ctx) => invoke(controller, ctx, instances);
|
|
6404
|
+
}
|
|
6405
|
+
return (controller, ctx) => invoke(controller, ctx, resolvers.map((resolver) => "instance" in resolver ? resolver.instance : container.get(resolver.token)));
|
|
6406
|
+
}
|
|
6407
|
+
/**
|
|
6362
6408
|
* Singletons (and registered values) are resolved once at bootstrap; request-scoped and transient
|
|
6363
6409
|
* providers on every request.
|
|
6364
6410
|
*/
|