flowingts 0.0.0 → 0.19.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/LICENSE +15 -0
- package/README.md +74 -3
- package/dist/core/abort.d.ts +23 -0
- package/dist/core/abort.d.ts.map +1 -0
- package/dist/core/abort.js +23 -0
- package/dist/core/abort.js.map +1 -0
- package/dist/core/app/core.d.ts +35 -0
- package/dist/core/app/core.d.ts.map +1 -0
- package/dist/core/app/core.js +2 -0
- package/dist/core/app/core.js.map +1 -0
- package/dist/core/app/options.d.ts +178 -0
- package/dist/core/app/options.d.ts.map +1 -0
- package/dist/core/app/options.js +2 -0
- package/dist/core/app/options.js.map +1 -0
- package/dist/core/app.d.ts +143 -0
- package/dist/core/app.d.ts.map +1 -0
- package/dist/core/app.js +283 -0
- package/dist/core/app.js.map +1 -0
- package/dist/core/body-parser.d.ts +14 -0
- package/dist/core/body-parser.d.ts.map +1 -0
- package/dist/core/body-parser.js +76 -0
- package/dist/core/body-parser.js.map +1 -0
- package/dist/core/compiler.d.ts +104 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +151 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/compose.d.ts +16 -0
- package/dist/core/compose.d.ts.map +1 -0
- package/dist/core/compose.js +41 -0
- package/dist/core/compose.js.map +1 -0
- package/dist/core/context.d.ts +290 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +460 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/contracts.d.ts +76 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +87 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/errors.d.ts +50 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +25 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/guard.d.ts +10 -0
- package/dist/core/guard.d.ts.map +1 -0
- package/dist/core/guard.js +14 -0
- package/dist/core/guard.js.map +1 -0
- package/dist/core/health.d.ts +37 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +68 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/hooks.d.ts +34 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +27 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/index.d.ts +30 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/infer.d.ts +68 -0
- package/dist/core/infer.d.ts.map +1 -0
- package/dist/core/infer.js +2 -0
- package/dist/core/infer.js.map +1 -0
- package/dist/core/inject.d.ts +27 -0
- package/dist/core/inject.d.ts.map +1 -0
- package/dist/core/inject.js +85 -0
- package/dist/core/inject.js.map +1 -0
- package/dist/core/logger.d.ts +57 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +134 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/metadata.d.ts +83 -0
- package/dist/core/metadata.d.ts.map +1 -0
- package/dist/core/metadata.js +91 -0
- package/dist/core/metadata.js.map +1 -0
- package/dist/core/mounted-group.d.ts +77 -0
- package/dist/core/mounted-group.d.ts.map +1 -0
- package/dist/core/mounted-group.js +131 -0
- package/dist/core/mounted-group.js.map +1 -0
- package/dist/core/registry/framework-routes.d.ts +28 -0
- package/dist/core/registry/framework-routes.d.ts.map +1 -0
- package/dist/core/registry/framework-routes.js +83 -0
- package/dist/core/registry/framework-routes.js.map +1 -0
- package/dist/core/registry/registry.d.ts +58 -0
- package/dist/core/registry/registry.d.ts.map +1 -0
- package/dist/core/registry/registry.js +208 -0
- package/dist/core/registry/registry.js.map +1 -0
- package/dist/core/registry/route-program.d.ts +16 -0
- package/dist/core/registry/route-program.d.ts.map +1 -0
- package/dist/core/registry/route-program.js +62 -0
- package/dist/core/registry/route-program.js.map +1 -0
- package/dist/core/request-validation.d.ts +15 -0
- package/dist/core/request-validation.d.ts.map +1 -0
- package/dist/core/request-validation.js +67 -0
- package/dist/core/request-validation.js.map +1 -0
- package/dist/core/response/pipeline.d.ts +51 -0
- package/dist/core/response/pipeline.d.ts.map +1 -0
- package/dist/core/response/pipeline.js +169 -0
- package/dist/core/response/pipeline.js.map +1 -0
- package/dist/core/route-table.d.ts +58 -0
- package/dist/core/route-table.d.ts.map +1 -0
- package/dist/core/route-table.js +108 -0
- package/dist/core/route-table.js.map +1 -0
- package/dist/core/runtime/observer.d.ts +47 -0
- package/dist/core/runtime/observer.d.ts.map +1 -0
- package/dist/core/runtime/observer.js +125 -0
- package/dist/core/runtime/observer.js.map +1 -0
- package/dist/core/runtime/request-handler.d.ts +62 -0
- package/dist/core/runtime/request-handler.d.ts.map +1 -0
- package/dist/core/runtime/request-handler.js +256 -0
- package/dist/core/runtime/request-handler.js.map +1 -0
- package/dist/core/runtime/server.d.ts +59 -0
- package/dist/core/runtime/server.d.ts.map +1 -0
- package/dist/core/runtime/server.js +180 -0
- package/dist/core/runtime/server.js.map +1 -0
- package/dist/core/shutdown.d.ts +41 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +53 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/streaming/sse.d.ts +35 -0
- package/dist/core/streaming/sse.d.ts.map +1 -0
- package/dist/core/streaming/sse.js +121 -0
- package/dist/core/streaming/sse.js.map +1 -0
- package/dist/core/symbols.d.ts +28 -0
- package/dist/core/symbols.d.ts.map +1 -0
- package/dist/core/symbols.js +28 -0
- package/dist/core/symbols.js.map +1 -0
- package/dist/core/telemetry.d.ts +83 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +96 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/uploads/receive.d.ts +63 -0
- package/dist/core/uploads/receive.d.ts.map +1 -0
- package/dist/core/uploads/receive.js +342 -0
- package/dist/core/uploads/receive.js.map +1 -0
- package/dist/core/uploads/types.d.ts +79 -0
- package/dist/core/uploads/types.d.ts.map +1 -0
- package/dist/core/uploads/types.js +2 -0
- package/dist/core/uploads/types.js.map +1 -0
- package/dist/decorators/hooks.d.ts +25 -0
- package/dist/decorators/hooks.d.ts.map +1 -0
- package/dist/decorators/hooks.js +36 -0
- package/dist/decorators/hooks.js.map +1 -0
- package/dist/decorators/http.d.ts +21 -0
- package/dist/decorators/http.d.ts.map +1 -0
- package/dist/decorators/http.js +21 -0
- package/dist/decorators/http.js.map +1 -0
- package/dist/decorators/index.d.ts +7 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +7 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/log.d.ts +10 -0
- package/dist/decorators/log.d.ts.map +1 -0
- package/dist/decorators/log.js +32 -0
- package/dist/decorators/log.js.map +1 -0
- package/dist/decorators/middlewares.d.ts +28 -0
- package/dist/decorators/middlewares.d.ts.map +1 -0
- package/dist/decorators/middlewares.js +40 -0
- package/dist/decorators/middlewares.js.map +1 -0
- package/dist/decorators/router.d.ts +12 -0
- package/dist/decorators/router.d.ts.map +1 -0
- package/dist/decorators/router.js +20 -0
- package/dist/decorators/router.js.map +1 -0
- package/dist/decorators/schema.d.ts +8 -0
- package/dist/decorators/schema.d.ts.map +1 -0
- package/dist/decorators/schema.js +14 -0
- package/dist/decorators/schema.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/auth.d.ts +41 -0
- package/dist/middlewares/auth.d.ts.map +1 -0
- package/dist/middlewares/auth.js +74 -0
- package/dist/middlewares/auth.js.map +1 -0
- package/dist/middlewares/cors.d.ts +30 -0
- package/dist/middlewares/cors.d.ts.map +1 -0
- package/dist/middlewares/cors.js +69 -0
- package/dist/middlewares/cors.js.map +1 -0
- package/dist/middlewares/csrf.d.ts +42 -0
- package/dist/middlewares/csrf.d.ts.map +1 -0
- package/dist/middlewares/csrf.js +70 -0
- package/dist/middlewares/csrf.js.map +1 -0
- package/dist/middlewares/index.d.ts +6 -0
- package/dist/middlewares/index.d.ts.map +1 -0
- package/dist/middlewares/index.js +6 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/middlewares/rate-limit.d.ts +35 -0
- package/dist/middlewares/rate-limit.d.ts.map +1 -0
- package/dist/middlewares/rate-limit.js +45 -0
- package/dist/middlewares/rate-limit.js.map +1 -0
- package/dist/middlewares/security-headers.d.ts +28 -0
- package/dist/middlewares/security-headers.d.ts.map +1 -0
- package/dist/middlewares/security-headers.js +42 -0
- package/dist/middlewares/security-headers.js.map +1 -0
- package/dist/openapi/generate.d.ts +51 -0
- package/dist/openapi/generate.d.ts.map +1 -0
- package/dist/openapi/generate.js +175 -0
- package/dist/openapi/generate.js.map +1 -0
- package/dist/schema/serialization.d.ts +2 -0
- package/dist/schema/serialization.d.ts.map +1 -0
- package/dist/schema/serialization.js +11 -0
- package/dist/schema/serialization.js.map +1 -0
- package/dist/schema/validation.d.ts +22 -0
- package/dist/schema/validation.d.ts.map +1 -0
- package/dist/schema/validation.js +68 -0
- package/dist/schema/validation.js.map +1 -0
- package/dist/server/static.d.ts +45 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +204 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/tls.d.ts +48 -0
- package/dist/server/tls.d.ts.map +1 -0
- package/dist/server/tls.js +60 -0
- package/dist/server/tls.js.map +1 -0
- package/dist/shims/symbol-metadata.d.ts +2 -0
- package/dist/shims/symbol-metadata.d.ts.map +1 -0
- package/dist/shims/symbol-metadata.js +8 -0
- package/dist/shims/symbol-metadata.js.map +1 -0
- package/dist/utils/content-type.d.ts +13 -0
- package/dist/utils/content-type.d.ts.map +1 -0
- package/dist/utils/content-type.js +75 -0
- package/dist/utils/content-type.js.map +1 -0
- package/dist/utils/cookie.d.ts +34 -0
- package/dist/utils/cookie.d.ts.map +1 -0
- package/dist/utils/cookie.js +94 -0
- package/dist/utils/cookie.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/mime.d.ts +2 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +52 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/origin-match.d.ts +58 -0
- package/dist/utils/origin-match.d.ts.map +1 -0
- package/dist/utils/origin-match.js +194 -0
- package/dist/utils/origin-match.js.map +1 -0
- package/dist/utils/path.d.ts +10 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +60 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/query.d.ts +28 -0
- package/dist/utils/query.d.ts.map +1 -0
- package/dist/utils/query.js +67 -0
- package/dist/utils/query.js.map +1 -0
- package/dist/utils/request-target.d.ts +31 -0
- package/dist/utils/request-target.d.ts.map +1 -0
- package/dist/utils/request-target.js +80 -0
- package/dist/utils/request-target.js.map +1 -0
- package/dist/utils/safe-json.d.ts +24 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/safe-json.js +83 -0
- package/dist/utils/safe-json.js.map +1 -0
- package/dist/utils/trace-context.d.ts +17 -0
- package/dist/utils/trace-context.d.ts.map +1 -0
- package/dist/utils/trace-context.js +30 -0
- package/dist/utils/trace-context.js.map +1 -0
- package/package.json +85 -6
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { compileChain } from '../compose.js';
|
|
4
|
+
import { bodyReader } from '../body-parser.js';
|
|
5
|
+
import { isJsonContentType, mediaType } from '../../utils/content-type.js';
|
|
6
|
+
import { buildRouteProgram } from '../registry/route-program.js';
|
|
7
|
+
import { kHooks, kPhase, kRoute, kRouted } from '../symbols.js';
|
|
8
|
+
import { parseTraceparent } from '../../utils/trace-context.js';
|
|
9
|
+
import { Context } from '../context.js';
|
|
10
|
+
import { problem, sendError, sendProblem } from '../response/pipeline.js';
|
|
11
|
+
const SAFE_REQUEST_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
12
|
+
/**
|
|
13
|
+
* Normaliza os `parsers` do app (F-12) pelo tipo de mídia, na criação: um tipo inválido falha aqui, e não na primeira
|
|
14
|
+
* requisição. O JSON é nativo (com a proteção do S-08 e o erro `INVALID_JSON`) e não pode ser substituído.
|
|
15
|
+
*/
|
|
16
|
+
function compileParsers(parsers) {
|
|
17
|
+
const out = new Map();
|
|
18
|
+
for (const [type, parser] of Object.entries(parsers ?? {})) {
|
|
19
|
+
const media = mediaType(type);
|
|
20
|
+
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(media)) {
|
|
21
|
+
throw new Error(`[flowing] parsers: "${type}" is not a media type (use something like "application/yaml").`);
|
|
22
|
+
}
|
|
23
|
+
if (isJsonContentType(media)) {
|
|
24
|
+
throw new Error(`[flowing] parsers: "${type}" is JSON, which the framework already parses; remove it from parsers.`);
|
|
25
|
+
}
|
|
26
|
+
if (typeof parser !== 'function')
|
|
27
|
+
throw new Error(`[flowing] parsers: the parser for "${type}" must be a function.`);
|
|
28
|
+
out.set(media, parser);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Pipeline da requisição: rastreamento, request id, validação do `Host`, headers de segurança, healthchecks,
|
|
34
|
+
* middlewares globais, OpenAPI, roteamento, pipeline da rota e erros.
|
|
35
|
+
*/
|
|
36
|
+
export class RequestHandler {
|
|
37
|
+
core;
|
|
38
|
+
registry;
|
|
39
|
+
framework;
|
|
40
|
+
server;
|
|
41
|
+
observer;
|
|
42
|
+
constructor(core, registry, framework, server, observer) {
|
|
43
|
+
this.core = core;
|
|
44
|
+
this.registry = registry;
|
|
45
|
+
this.framework = framework;
|
|
46
|
+
this.server = server;
|
|
47
|
+
this.observer = observer;
|
|
48
|
+
this.readBody = bodyReader(core.opts.bodyLimit ?? 1024 * 1024, core.opts.protoPoisoning, compileParsers(core.opts.parsers));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Listener `(req, res)` do app, para usar com qualquer servidor do Node:
|
|
52
|
+
* `https.createServer(opts, app.callback())`, `http2` em modo compat, testes etc. (F-09).
|
|
53
|
+
*/
|
|
54
|
+
callback() {
|
|
55
|
+
const rid = this.core.opts.requestId ?? {};
|
|
56
|
+
const header = rid.header === false ? undefined : (rid.header ?? 'x-request-id').toLowerCase();
|
|
57
|
+
const trust = rid.trustIncoming ?? true;
|
|
58
|
+
const generate = rid.generate ?? (() => randomUUID());
|
|
59
|
+
// Factory + Flyweight (F-39): a mesma configuração para todos os contextos deste listener, em vez de
|
|
60
|
+
// um objeto de opções novo a cada requisição.
|
|
61
|
+
const contextOptions = {
|
|
62
|
+
queryMaxKeys: this.core.opts.queryMaxKeys,
|
|
63
|
+
queryParser: this.core.opts.queryParser,
|
|
64
|
+
contracts: this.core.contracts,
|
|
65
|
+
logger: this.core.log,
|
|
66
|
+
trustProxy: this.core.opts.trustProxy,
|
|
67
|
+
onSend: this.core.onSendHooks,
|
|
68
|
+
...(this.core.telemetry ? { spanIds: this.core.telemetry.spanIds } : {}),
|
|
69
|
+
onStream: (ctx) => {
|
|
70
|
+
server.streams.add(ctx);
|
|
71
|
+
ctx.res.once('close', () => server.streams.delete(ctx));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const server = this.server;
|
|
75
|
+
const observer = this.observer;
|
|
76
|
+
// Listener compartilhado por todas as respostas (antes, uma closure por requisição).
|
|
77
|
+
const onClose = () => {
|
|
78
|
+
// Durante o close(), cada resposta concluída libera as conexões que ficaram ociosas.
|
|
79
|
+
server.closing?.closeIdleConnections();
|
|
80
|
+
};
|
|
81
|
+
return (req, res) => {
|
|
82
|
+
const started = observer.watching ? performance.now() : -1;
|
|
83
|
+
if (server.closing)
|
|
84
|
+
res.setHeader('Connection', 'close');
|
|
85
|
+
res.on('close', onClose);
|
|
86
|
+
const incoming = header ? req.headers[header] : undefined;
|
|
87
|
+
// Sem X-Request-Id aceito, o traceId de um `traceparent` válido vira o id: logs e traces com a mesma chave.
|
|
88
|
+
const id = trust && typeof incoming === 'string' && SAFE_REQUEST_ID.test(incoming)
|
|
89
|
+
? incoming
|
|
90
|
+
: req.headers.traceparent
|
|
91
|
+
? (parseTraceparent(req.headers.traceparent)?.traceId ?? generate())
|
|
92
|
+
: generate();
|
|
93
|
+
if (header)
|
|
94
|
+
res.setHeader(header, id);
|
|
95
|
+
try {
|
|
96
|
+
const ctx = new Context(req, res, contextOptions, id);
|
|
97
|
+
// Antes da validação do Host: toda requisição observada no início também é observada no fim.
|
|
98
|
+
observer.start(ctx, started);
|
|
99
|
+
// S-09: Host ausente, malformado ou fora da lista permitida → 400, antes de qualquer middleware.
|
|
100
|
+
// Exceção: healthchecks aceitam qualquer host válido, porque sondas chegam com o IP do pod.
|
|
101
|
+
const healthProbe = this.framework.health?.bypassesHostCheck(ctx.method, ctx.path) ?? false;
|
|
102
|
+
if (!ctx.host || (this.core.allowedHosts && !healthProbe && !this.core.allowedHosts(ctx.hostname, ctx.host))) {
|
|
103
|
+
this.core.applySecurityHeaders?.(res);
|
|
104
|
+
sendProblem(ctx, problem(400, 'Invalid Host header', { code: 'INVALID_HOST' }), this.core.opts.formatError);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Síncrono no caso comum: só uma requisição que de fato ficou assíncrona recebe o handler da barreira.
|
|
108
|
+
const step = observer.run(ctx, () => this.run(ctx));
|
|
109
|
+
if (step)
|
|
110
|
+
step.then(undefined, (err) => this.fail(err, req, res, id));
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
this.fail(err, req, res, id);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Última barreira (B-08): `fail` recebe o que escapar, de forma síncrona (try/catch) ou assíncrona (.then).
|
|
118
|
+
fail(err, req, res, id) {
|
|
119
|
+
this.core.log.error({ err, reqId: id, method: req.method, url: req.url }, 'unhandled error while handling request');
|
|
120
|
+
if (!res.headersSent)
|
|
121
|
+
res.statusCode = 500;
|
|
122
|
+
if (!res.writableEnded)
|
|
123
|
+
res.end();
|
|
124
|
+
}
|
|
125
|
+
/** Cadeia global compilada; refeita só quando `app.use()` acrescenta um middleware. */
|
|
126
|
+
globalChain;
|
|
127
|
+
readBody;
|
|
128
|
+
/** O pipeline completo, como uma Promise (API pública: `app.handle()`). */
|
|
129
|
+
async handle(ctx) {
|
|
130
|
+
await this.run(ctx);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* O pipeline completo. Síncrono do começo ao fim quando nada é assíncrono (sem healthcheck no path, sem
|
|
134
|
+
* body a ler, sem middlewares, handler síncrono); vira assíncrono só a partir da primeira Promise de verdade
|
|
135
|
+
* (F-41). Erros síncronos e assíncronos terminam no mesmo `runErrorHandlers`.
|
|
136
|
+
*/
|
|
137
|
+
run(ctx) {
|
|
138
|
+
// Antes de tudo, para valer inclusive em erros, 404 e preflight (S-04).
|
|
139
|
+
this.core.applySecurityHeaders?.(ctx.res);
|
|
140
|
+
try {
|
|
141
|
+
// Healthchecks antes dos middlewares globais: sondas do load balancer não têm credenciais.
|
|
142
|
+
const health = this.framework.health;
|
|
143
|
+
if (health?.handles(ctx.method, ctx.path))
|
|
144
|
+
return this.catching(ctx, this.serveHealth(ctx));
|
|
145
|
+
return this.catching(ctx, this.afterHealth(ctx));
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return this.runErrorHandlers(err, ctx);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Liga a parte assíncrona (se houver) ao tratamento de erros. */
|
|
152
|
+
catching(ctx, step) {
|
|
153
|
+
return step ? step.then(undefined, (err) => this.runErrorHandlers(err, ctx)) : undefined;
|
|
154
|
+
}
|
|
155
|
+
async serveHealth(ctx) {
|
|
156
|
+
if (await this.framework.health.serve(ctx, this.server.draining || this.server.closing !== undefined))
|
|
157
|
+
return;
|
|
158
|
+
await this.afterHealth(ctx);
|
|
159
|
+
}
|
|
160
|
+
afterHealth(ctx) {
|
|
161
|
+
// O body chega lido aos middlewares globais (invariante 5), sem uma camada de middleware para isso.
|
|
162
|
+
ctx[kPhase] = 'validation';
|
|
163
|
+
const reading = this.readBody(ctx);
|
|
164
|
+
if (reading)
|
|
165
|
+
return reading.then(() => this.afterBody(ctx));
|
|
166
|
+
return this.afterBody(ctx);
|
|
167
|
+
}
|
|
168
|
+
afterBody(ctx) {
|
|
169
|
+
ctx[kPhase] = 'middleware';
|
|
170
|
+
const step = this.chain()(ctx);
|
|
171
|
+
if (step)
|
|
172
|
+
return step.then(() => this.checkRouted(ctx));
|
|
173
|
+
this.checkRouted(ctx);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Um middleware global que não chama next() e não responde: a rota não roda (antes, rodava mesmo assim).
|
|
178
|
+
* Para a requisição não ficar sem resposta, 404, como no Koa.
|
|
179
|
+
*/
|
|
180
|
+
checkRouted(ctx) {
|
|
181
|
+
if (!ctx[kRouted] && !ctx.sent) {
|
|
182
|
+
ctx.log.warn({ method: ctx.method, path: ctx.path }, 'a global middleware neither called next() nor sent a response');
|
|
183
|
+
sendProblem(ctx, problem(404, undefined, { code: 'NOT_FOUND' }), this.core.opts.formatError);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Cadeia global (Chain of Responsibility pré-compilada): os middlewares de `app.use()` envolvem o
|
|
188
|
+
* roteamento e a rota, como no Koa. Um `await next()` num middleware global inclui o handler.
|
|
189
|
+
*/
|
|
190
|
+
chain() {
|
|
191
|
+
const mws = this.core.middlewares;
|
|
192
|
+
if (this.globalChain?.size !== mws.length) {
|
|
193
|
+
this.globalChain = { size: mws.length, run: compileChain([...mws], (ctx) => this.route(ctx)) };
|
|
194
|
+
}
|
|
195
|
+
return this.globalChain.run;
|
|
196
|
+
}
|
|
197
|
+
/** Fim da cadeia global: OpenAPI, roteamento, 404/405 e o programa da rota. */
|
|
198
|
+
route(ctx) {
|
|
199
|
+
ctx[kRouted] = true;
|
|
200
|
+
if (ctx.sent)
|
|
201
|
+
return undefined;
|
|
202
|
+
// Só cria a Promise do OpenAPI se ele estiver configurado (F-41).
|
|
203
|
+
if (this.framework.openapi) {
|
|
204
|
+
return this.framework.serveOpenAPI(ctx).then((served) => (served ? undefined : this.dispatch(ctx)));
|
|
205
|
+
}
|
|
206
|
+
return this.dispatch(ctx);
|
|
207
|
+
}
|
|
208
|
+
/** Roteamento, 404/405 e o programa da rota. */
|
|
209
|
+
dispatch(ctx) {
|
|
210
|
+
// F-35: hosts não diferenciam maiúsculas; as regras já são compiladas em minúsculas.
|
|
211
|
+
const hostname = ctx.hostname.toLowerCase();
|
|
212
|
+
const match = this.registry.router.find(ctx.method, ctx.path, hostname);
|
|
213
|
+
if (!match) {
|
|
214
|
+
const allowed = this.registry.router.allowedMethods(ctx.path, hostname);
|
|
215
|
+
if (allowed.length > 0) {
|
|
216
|
+
ctx.res.setHeader('Allow', allowed.join(', '));
|
|
217
|
+
// F-08: OPTIONS automático para qualquer path com rotas.
|
|
218
|
+
if (ctx.method === 'OPTIONS')
|
|
219
|
+
return void ctx.status(204).res.end();
|
|
220
|
+
return void sendProblem(ctx, problem(405, undefined, { code: 'METHOD_NOT_ALLOWED', details: { allowed } }), this.core.opts.formatError);
|
|
221
|
+
}
|
|
222
|
+
return void sendProblem(ctx, problem(404, undefined, { code: 'NOT_FOUND' }), this.core.opts.formatError);
|
|
223
|
+
}
|
|
224
|
+
ctx.params = match.params;
|
|
225
|
+
const info = match.route.info;
|
|
226
|
+
if (info) {
|
|
227
|
+
ctx[kRoute] = info;
|
|
228
|
+
this.observer.routed(ctx, info);
|
|
229
|
+
}
|
|
230
|
+
// O programa é montado no commit do registro; o fallback cobre rotas registradas por fora dele.
|
|
231
|
+
const run = (match.route.run ??= buildRouteProgram(match.route, this.core.serializers, this.core.uploads));
|
|
232
|
+
return run(ctx);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Executa os `onError` do usuário. Se um deles lançar, o erro é logado e seguimos
|
|
236
|
+
* para a resposta padrão com o erro **original** — nunca deixamos a Promise rejeitar (B-08).
|
|
237
|
+
*/
|
|
238
|
+
async runErrorHandlers(err, ctx) {
|
|
239
|
+
this.observer.failed(ctx, err);
|
|
240
|
+
// Os da rota primeiro, do mais específico para o mais externo (F-43); depois os do app.
|
|
241
|
+
const scoped = ctx.route?.[kHooks]?.onError;
|
|
242
|
+
for (const h of scoped ? [...scoped, ...this.core.errorHandlers] : this.core.errorHandlers) {
|
|
243
|
+
try {
|
|
244
|
+
await h(err, ctx, ctx[kPhase]);
|
|
245
|
+
}
|
|
246
|
+
catch (handlerErr) {
|
|
247
|
+
ctx.log.error({ err: handlerErr, method: ctx.method, path: ctx.path }, 'onError handler failed');
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
if (ctx.res.writableEnded)
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
sendError(ctx, err, this.core.opts.formatError);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=request-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-handler.js","sourceRoot":"","sources":["../../../src/core/runtime/request-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import type { AppCore } from '../app/core.ts';
|
|
4
|
+
import type { ListenOptions } from '../app/options.ts';
|
|
5
|
+
import type { Context } from '../context.ts';
|
|
6
|
+
import { type ShutdownTarget } from '../shutdown.ts';
|
|
7
|
+
import { type RequestObserver } from './observer.ts';
|
|
8
|
+
import { type TlsOptions } from '../../server/tls.ts';
|
|
9
|
+
/** Servidor HTTP/HTTPS do app: `listen`, `close`, troca de certificados, drain e shutdown por sinal (F-07, F-33). */
|
|
10
|
+
export declare class ServerLifecycle {
|
|
11
|
+
private readonly core;
|
|
12
|
+
/** Listener `(req, res)` do app, criado a cada `listen()`. */
|
|
13
|
+
private readonly listener;
|
|
14
|
+
/** Diz se há quem observe o fim da requisição (o `ttfbMs` só é medido nesse caso). */
|
|
15
|
+
private readonly observer;
|
|
16
|
+
server: http.Server | https.Server | undefined;
|
|
17
|
+
private tlsState;
|
|
18
|
+
/**
|
|
19
|
+
* Respostas de streaming abertas (SSE, NDJSON, `Readable`), que o `close()` cancela para não esperar por
|
|
20
|
+
* elas. Só streams entram aqui: um conjunto com todas as respostas em andamento fazia cada uma sobreviver
|
|
21
|
+
* às coletas rápidas do V8 e ser promovida, junto com `req` e o parser, e dobrava o RSS de pico (F-41).
|
|
22
|
+
*/
|
|
23
|
+
readonly streams: Set<Context<import("../metadata.ts").RouteSchemas>>;
|
|
24
|
+
closing: http.Server | https.Server | undefined;
|
|
25
|
+
draining: boolean;
|
|
26
|
+
private closeHooksPending;
|
|
27
|
+
/**
|
|
28
|
+
* Respostas dos servidores criados pelo `listen()`. Durante o `close()`, as que ainda não enviaram headers
|
|
29
|
+
* saem com `Connection: close`, para o cliente não reaproveitar a conexão. Assim o app não precisa guardar
|
|
30
|
+
* as respostas em andamento só para isso (F-41). Quando há quem observe o fim da requisição (`onResponse`,
|
|
31
|
+
* `logRequests`, `telemetry`), também guarda o instante dos headers, para o `ttfbMs` (F-38). No protótipo,
|
|
32
|
+
* e não um wrapper por resposta: um wrapper por instância fazia cada resposta ser promovida e dobrava o RSS.
|
|
33
|
+
*/
|
|
34
|
+
private readonly responseClass;
|
|
35
|
+
constructor(core: AppCore,
|
|
36
|
+
/** Listener `(req, res)` do app, criado a cada `listen()`. */
|
|
37
|
+
listener: () => http.RequestListener,
|
|
38
|
+
/** Diz se há quem observe o fim da requisição (o `ttfbMs` só é medido nesse caso). */
|
|
39
|
+
observer: RequestObserver);
|
|
40
|
+
listen(options: ListenOptions & {
|
|
41
|
+
tls: TlsOptions;
|
|
42
|
+
}): Promise<https.Server>;
|
|
43
|
+
listen(options?: number | ListenOptions): Promise<http.Server>;
|
|
44
|
+
/**
|
|
45
|
+
* Troca os certificados sem reiniciar nem derrubar conexões (renovação, ACME externo). Conexões novas
|
|
46
|
+
* usam os novos; as abertas seguem com os antigos. Um certificado inválido lança erro e mantém os atuais.
|
|
47
|
+
*/
|
|
48
|
+
reloadTLS(tls: TlsOptions): void;
|
|
49
|
+
/** Ponte entre este app e o coordenador de sinais (F-07). */
|
|
50
|
+
readonly shutdownTarget: ShutdownTarget;
|
|
51
|
+
/**
|
|
52
|
+
* Para de aceitar conexões, encerra as ociosas e espera as requisições em andamento terminarem.
|
|
53
|
+
* Depois de `graceMs`, força o fechamento das restantes.
|
|
54
|
+
*/
|
|
55
|
+
close(graceMs?: number): Promise<void>;
|
|
56
|
+
/** `onClose` uma vez por ciclo (até o próximo `listen`); todos rodam mesmo se algum falhar (F-04). */
|
|
57
|
+
runCloseHooks(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/runtime/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE1E,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjF,qHAAqH;AACrH,qBAAa,eAAe;IAkCxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,QAAQ;IArC3B,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,QAAQ,CAA6C;IAC7D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,sDAAsB;IACtC,OAAO,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,UAAS;IACjB,OAAO,CAAC,iBAAiB,CAAQ;IAEjC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAWzB;IAEL,YACmB,IAAI,EAAE,OAAO;IAC9B,8DAA8D;IAC7C,QAAQ,EAAE,MAAM,IAAI,CAAC,eAAe;IACrD,sFAAsF;IACrE,QAAQ,EAAE,eAAe,EAQ3C;IAEK,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG;QAAE,GAAG,EAAE,UAAU,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAkCrE;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAS/B;IAED,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAoBrC;IAEF;;;OAGG;IACG,KAAK,CAAC,OAAO,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB3C;IAED,sGAAsG;IAChG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAanC;CACF"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { shutdownCoordinator } from '../shutdown.js';
|
|
4
|
+
import { kAbort } from '../symbols.js';
|
|
5
|
+
import { markHeadersSent } from './observer.js';
|
|
6
|
+
import { compileTls } from '../../server/tls.js';
|
|
7
|
+
/** Servidor HTTP/HTTPS do app: `listen`, `close`, troca de certificados, drain e shutdown por sinal (F-07, F-33). */
|
|
8
|
+
export class ServerLifecycle {
|
|
9
|
+
core;
|
|
10
|
+
listener;
|
|
11
|
+
observer;
|
|
12
|
+
server;
|
|
13
|
+
tlsState;
|
|
14
|
+
/**
|
|
15
|
+
* Respostas de streaming abertas (SSE, NDJSON, `Readable`), que o `close()` cancela para não esperar por
|
|
16
|
+
* elas. Só streams entram aqui: um conjunto com todas as respostas em andamento fazia cada uma sobreviver
|
|
17
|
+
* às coletas rápidas do V8 e ser promovida, junto com `req` e o parser, e dobrava o RSS de pico (F-41).
|
|
18
|
+
*/
|
|
19
|
+
streams = new Set();
|
|
20
|
+
closing;
|
|
21
|
+
draining = false;
|
|
22
|
+
closeHooksPending = true;
|
|
23
|
+
/**
|
|
24
|
+
* Respostas dos servidores criados pelo `listen()`. Durante o `close()`, as que ainda não enviaram headers
|
|
25
|
+
* saem com `Connection: close`, para o cliente não reaproveitar a conexão. Assim o app não precisa guardar
|
|
26
|
+
* as respostas em andamento só para isso (F-41). Quando há quem observe o fim da requisição (`onResponse`,
|
|
27
|
+
* `logRequests`, `telemetry`), também guarda o instante dos headers, para o `ttfbMs` (F-38). No protótipo,
|
|
28
|
+
* e não um wrapper por resposta: um wrapper por instância fazia cada resposta ser promovida e dobrava o RSS.
|
|
29
|
+
*/
|
|
30
|
+
responseClass = (() => {
|
|
31
|
+
const lifecycle = this;
|
|
32
|
+
class FlowingResponse extends http.ServerResponse {
|
|
33
|
+
}
|
|
34
|
+
// Atribuição no protótipo: o `writeHead` do Node tem várias sobrecargas, e só repassamos os argumentos.
|
|
35
|
+
const base = http.ServerResponse.prototype.writeHead;
|
|
36
|
+
FlowingResponse.prototype.writeHead = function (...args) {
|
|
37
|
+
if (lifecycle.closing)
|
|
38
|
+
this.shouldKeepAlive = false;
|
|
39
|
+
if (lifecycle.observer.watching)
|
|
40
|
+
markHeadersSent(this);
|
|
41
|
+
return base.apply(this, args);
|
|
42
|
+
};
|
|
43
|
+
return FlowingResponse;
|
|
44
|
+
})();
|
|
45
|
+
constructor(core,
|
|
46
|
+
/** Listener `(req, res)` do app, criado a cada `listen()`. */
|
|
47
|
+
listener,
|
|
48
|
+
/** Diz se há quem observe o fim da requisição (o `ttfbMs` só é medido nesse caso). */
|
|
49
|
+
observer) {
|
|
50
|
+
this.core = core;
|
|
51
|
+
this.listener = listener;
|
|
52
|
+
this.observer = observer;
|
|
53
|
+
const sd = core.opts.shutdown || {};
|
|
54
|
+
Object.assign(this.shutdownTarget, {
|
|
55
|
+
log: core.log,
|
|
56
|
+
signals: sd.signals ?? ['SIGINT', 'SIGTERM'],
|
|
57
|
+
exitOnShutdown: sd.exit ?? true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async listen(options = {}) {
|
|
61
|
+
if (this.server)
|
|
62
|
+
throw new Error('[flowing] app is already listening; call app.close() first.');
|
|
63
|
+
const { port = 3000, host = '127.0.0.1', tls } = typeof options === 'number' ? { port: options } : options;
|
|
64
|
+
let server;
|
|
65
|
+
if (tls) {
|
|
66
|
+
const sni = { current: { find: () => undefined } };
|
|
67
|
+
const compiled = compileTls(tls, sni);
|
|
68
|
+
server = https.createServer({ ...compiled.options, ServerResponse: this.responseClass }, this.listener());
|
|
69
|
+
this.tlsState = { sni };
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
server = http.createServer({ ServerResponse: this.responseClass }, this.listener());
|
|
73
|
+
this.tlsState = undefined;
|
|
74
|
+
}
|
|
75
|
+
const t = this.core.opts.timeouts ?? {};
|
|
76
|
+
server.requestTimeout = t.requestTimeout ?? 30_000;
|
|
77
|
+
server.headersTimeout = Math.min(t.headersTimeout ?? 10_000, server.requestTimeout || Infinity);
|
|
78
|
+
server.keepAliveTimeout = t.keepAliveTimeout ?? 5_000;
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
server.once('error', reject);
|
|
81
|
+
server.listen(port, host, () => {
|
|
82
|
+
server.off('error', reject);
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
this.server = server;
|
|
87
|
+
this.closeHooksPending = true;
|
|
88
|
+
this.draining = false;
|
|
89
|
+
if (this.core.opts.shutdown !== false)
|
|
90
|
+
shutdownCoordinator.register(this.shutdownTarget);
|
|
91
|
+
return server;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Troca os certificados sem reiniciar nem derrubar conexões (renovação, ACME externo). Conexões novas
|
|
95
|
+
* usam os novos; as abertas seguem com os antigos. Um certificado inválido lança erro e mantém os atuais.
|
|
96
|
+
*/
|
|
97
|
+
reloadTLS(tls) {
|
|
98
|
+
const server = this.server;
|
|
99
|
+
if (!server || !this.tlsState || !(server instanceof https.Server)) {
|
|
100
|
+
throw new Error('[flowing] reloadTLS(): the app is not listening with { tls }.');
|
|
101
|
+
}
|
|
102
|
+
const next = { current: this.tlsState.sni.current };
|
|
103
|
+
const compiled = compileTls(tls, next); // valida tudo antes de trocar
|
|
104
|
+
if (compiled.main)
|
|
105
|
+
server.setSecureContext(compiled.main);
|
|
106
|
+
this.tlsState.sni.current = next.current;
|
|
107
|
+
}
|
|
108
|
+
/** Ponte entre este app e o coordenador de sinais (F-07). */
|
|
109
|
+
shutdownTarget = {
|
|
110
|
+
log: undefined, // definido no construtor
|
|
111
|
+
signals: [],
|
|
112
|
+
exitOnShutdown: true,
|
|
113
|
+
shutdown: async (signal) => {
|
|
114
|
+
const opts = this.core.opts.shutdown || {};
|
|
115
|
+
try {
|
|
116
|
+
this.draining = true;
|
|
117
|
+
if (opts.drainDelayMs) {
|
|
118
|
+
this.core.log.info({ signal, drainDelayMs: opts.drainDelayMs }, 'draining before shutdown');
|
|
119
|
+
await new Promise((r) => setTimeout(r, opts.drainDelayMs));
|
|
120
|
+
}
|
|
121
|
+
await this.close(opts.timeoutMs ?? 10_000);
|
|
122
|
+
await opts.onShutdown?.(signal);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
this.core.log.error({ err, signal }, 'error during graceful shutdown');
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Para de aceitar conexões, encerra as ociosas e espera as requisições em andamento terminarem.
|
|
133
|
+
* Depois de `graceMs`, força o fechamento das restantes.
|
|
134
|
+
*/
|
|
135
|
+
async close(graceMs = 10_000) {
|
|
136
|
+
const server = this.server;
|
|
137
|
+
if (!server)
|
|
138
|
+
return this.runCloseHooks();
|
|
139
|
+
this.server = undefined;
|
|
140
|
+
this.closing = server;
|
|
141
|
+
// Streams longos (SSE, NDJSON) não terminam sozinhos: o `ctx.signal` é cancelado para eles fecharem
|
|
142
|
+
// já, em vez de segurar o encerramento até o prazo. Requisições comuns seguem até terminar, e cada
|
|
143
|
+
// conexão é fechada assim que fica ociosa (listener `close` das respostas, no request-handler).
|
|
144
|
+
for (const ctx of this.streams)
|
|
145
|
+
ctx[kAbort]('SHUTDOWN');
|
|
146
|
+
const force = setTimeout(() => server.closeAllConnections(), graceMs);
|
|
147
|
+
force.unref();
|
|
148
|
+
try {
|
|
149
|
+
await new Promise((resolve, reject) => {
|
|
150
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
151
|
+
server.closeIdleConnections();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
clearTimeout(force);
|
|
156
|
+
this.closing = undefined;
|
|
157
|
+
shutdownCoordinator.unregister(this.shutdownTarget);
|
|
158
|
+
}
|
|
159
|
+
await this.runCloseHooks();
|
|
160
|
+
}
|
|
161
|
+
/** `onClose` uma vez por ciclo (até o próximo `listen`); todos rodam mesmo se algum falhar (F-04). */
|
|
162
|
+
async runCloseHooks() {
|
|
163
|
+
if (!this.closeHooksPending)
|
|
164
|
+
return;
|
|
165
|
+
this.closeHooksPending = false;
|
|
166
|
+
const errors = [];
|
|
167
|
+
for (const hook of this.core.onCloseHooks) {
|
|
168
|
+
try {
|
|
169
|
+
await hook();
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
errors.push(err);
|
|
173
|
+
this.core.log.error({ err }, 'onClose hook failed');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (errors.length > 0)
|
|
177
|
+
throw new AggregateError(errors, `[flowing] ${errors.length} onClose hook(s) failed`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/core/runtime/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Logger } from './logger.ts';
|
|
2
|
+
export interface ShutdownOptions {
|
|
3
|
+
/** Sinais tratados. Padrão: `['SIGINT', 'SIGTERM']`. */
|
|
4
|
+
signals?: readonly NodeJS.Signals[];
|
|
5
|
+
/**
|
|
6
|
+
* Espera antes de parar de aceitar conexões, com o `/readyz` já em 503, para o load balancer tirar
|
|
7
|
+
* a instância de rotação (Kubernetes, ECS…). Padrão: 0.
|
|
8
|
+
*/
|
|
9
|
+
drainDelayMs?: number;
|
|
10
|
+
/** Prazo para as requisições em andamento terminarem. Padrão: 10s. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/** Executado depois que o servidor fecha (banco, filas…). Se lançar, o processo sai com código 1. */
|
|
13
|
+
onShutdown?: (signal: NodeJS.Signals) => void | Promise<void>;
|
|
14
|
+
/** Chama `process.exit` ao final. Padrão: `true`. Use `false` se outro código controla o processo. */
|
|
15
|
+
exit?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** O que o coordenador precisa de cada app que está ouvindo. */
|
|
18
|
+
export interface ShutdownTarget {
|
|
19
|
+
readonly log: Logger;
|
|
20
|
+
/** Fecha o app e roda o `onShutdown`; resolve com `false` se algo falhou. */
|
|
21
|
+
shutdown(signal: NodeJS.Signals): Promise<boolean>;
|
|
22
|
+
/** Se este app quer `process.exit` ao final. */
|
|
23
|
+
readonly exitOnShutdown: boolean;
|
|
24
|
+
readonly signals: readonly NodeJS.Signals[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Coordena o encerramento de todos os apps ouvindo no processo (F-07):
|
|
28
|
+
* um único listener por sinal, compartilhado, removido quando o último app fecha.
|
|
29
|
+
*/
|
|
30
|
+
declare class ShutdownCoordinator {
|
|
31
|
+
private readonly targets;
|
|
32
|
+
private readonly listeners;
|
|
33
|
+
private shuttingDown;
|
|
34
|
+
register(target: ShutdownTarget): void;
|
|
35
|
+
unregister(target: ShutdownTarget): void;
|
|
36
|
+
private prune;
|
|
37
|
+
private onSignal;
|
|
38
|
+
}
|
|
39
|
+
export declare const shutdownCoordinator: ShutdownCoordinator;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,OAAO,EAAE,CAAC;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qGAAqG;IACrG,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,sGAAsG;IACtG,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,gEAAgE;AAChE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,CAAC,OAAO,EAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,cAAM,mBAAmB;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,OAAO,CAAC,YAAY,CAAS;IAE7B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAQrC;IAED,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAKvC;IAED,OAAO,CAAC,KAAK;YASC,QAAQ;CAgBvB;AAED,eAAO,MAAM,mBAAmB,qBAA4B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordena o encerramento de todos os apps ouvindo no processo (F-07):
|
|
3
|
+
* um único listener por sinal, compartilhado, removido quando o último app fecha.
|
|
4
|
+
*/
|
|
5
|
+
class ShutdownCoordinator {
|
|
6
|
+
targets = new Set();
|
|
7
|
+
listeners = new Map();
|
|
8
|
+
shuttingDown = false;
|
|
9
|
+
register(target) {
|
|
10
|
+
this.targets.add(target);
|
|
11
|
+
for (const signal of target.signals) {
|
|
12
|
+
if (this.listeners.has(signal))
|
|
13
|
+
continue;
|
|
14
|
+
const listener = () => void this.onSignal(signal);
|
|
15
|
+
this.listeners.set(signal, listener);
|
|
16
|
+
process.on(signal, listener);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
unregister(target) {
|
|
20
|
+
this.targets.delete(target);
|
|
21
|
+
// Durante um encerramento, os listeners ficam até o fim: o segundo sinal precisa ser ouvido
|
|
22
|
+
// enquanto o onShutdown ainda roda (ex.: fechando o banco).
|
|
23
|
+
if (!this.shuttingDown)
|
|
24
|
+
this.prune();
|
|
25
|
+
}
|
|
26
|
+
prune() {
|
|
27
|
+
const stillUsed = new Set([...this.targets].flatMap((t) => t.signals));
|
|
28
|
+
for (const [signal, listener] of this.listeners) {
|
|
29
|
+
if (stillUsed.has(signal))
|
|
30
|
+
continue;
|
|
31
|
+
process.off(signal, listener);
|
|
32
|
+
this.listeners.delete(signal);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async onSignal(signal) {
|
|
36
|
+
const targets = [...this.targets].filter((t) => t.signals.includes(signal));
|
|
37
|
+
if (this.shuttingDown) {
|
|
38
|
+
// Segundo sinal durante o encerramento: o usuário quer sair já (Ctrl+C duplo).
|
|
39
|
+
targets[0]?.log.warn({ signal }, 'second signal received, forcing exit');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
this.shuttingDown = true;
|
|
43
|
+
targets[0]?.log.info({ signal }, 'shutting down gracefully');
|
|
44
|
+
const results = await Promise.all(targets.map((t) => t.shutdown(signal)));
|
|
45
|
+
const ok = results.every(Boolean);
|
|
46
|
+
this.shuttingDown = false;
|
|
47
|
+
this.prune();
|
|
48
|
+
if (targets.some((t) => t.exitOnShutdown))
|
|
49
|
+
process.exit(ok ? 0 : 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export const shutdownCoordinator = new ShutdownCoordinator();
|
|
53
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"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"}
|