sashiko 0.11.4 → 0.13.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 +74 -1
- package/dist/core/app/core.d.ts +23 -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 +109 -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 +34 -175
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +101 -496
- package/dist/core/app.js.map +1 -1
- package/dist/core/body-parser.d.ts +6 -3
- package/dist/core/body-parser.d.ts.map +1 -1
- package/dist/core/body-parser.js +8 -6
- package/dist/core/body-parser.js.map +1 -1
- package/dist/core/compiler.d.ts +2 -0
- package/dist/core/compiler.d.ts.map +1 -1
- package/dist/core/compiler.js.map +1 -1
- package/dist/core/compose.d.ts +7 -0
- package/dist/core/compose.d.ts.map +1 -1
- package/dist/core/compose.js +25 -0
- package/dist/core/compose.js.map +1 -1
- package/dist/core/context.d.ts +61 -14
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +162 -39
- package/dist/core/context.js.map +1 -1
- package/dist/core/health.js +1 -1
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/inject.d.ts.map +1 -1
- package/dist/core/inject.js +6 -0
- package/dist/core/inject.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +41 -14
- package/dist/core/logger.js.map +1 -1
- package/dist/core/mounted-group.d.ts +5 -0
- package/dist/core/mounted-group.d.ts.map +1 -1
- package/dist/core/mounted-group.js +10 -0
- package/dist/core/mounted-group.js.map +1 -1
- 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 +53 -0
- package/dist/core/registry/registry.d.ts.map +1 -0
- package/dist/core/registry/registry.js +156 -0
- package/dist/core/registry/registry.js.map +1 -0
- package/dist/core/registry/route-program.d.ts +14 -0
- package/dist/core/registry/route-program.d.ts.map +1 -0
- package/dist/core/registry/route-program.js +29 -0
- package/dist/core/registry/route-program.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 +171 -0
- package/dist/core/response/pipeline.js.map +1 -0
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +25 -0
- package/dist/core/response.js.map +1 -1
- package/dist/core/route-table.d.ts +5 -1
- package/dist/core/route-table.d.ts.map +1 -1
- package/dist/core/route-table.js +1 -1
- package/dist/core/route-table.js.map +1 -1
- package/dist/core/runtime/request-handler.d.ts +42 -0
- package/dist/core/runtime/request-handler.d.ts.map +1 -0
- package/dist/core/runtime/request-handler.js +187 -0
- package/dist/core/runtime/request-handler.js.map +1 -0
- package/dist/core/runtime/server.d.ts +41 -0
- package/dist/core/runtime/server.d.ts.map +1 -0
- package/dist/core/runtime/server.js +155 -0
- package/dist/core/runtime/server.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 +14 -0
- package/dist/core/symbols.d.ts.map +1 -0
- package/dist/core/symbols.js +14 -0
- package/dist/core/symbols.js.map +1 -0
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +2 -0
- package/dist/openapi/generate.js.map +1 -1
- 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/utils/content-type.d.ts.map +1 -1
- package/dist/utils/content-type.js +26 -6
- package/dist/utils/content-type.js.map +1 -1
- 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/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-routes.d.ts","sourceRoot":"","sources":["../../../src/core/registry/framework-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF,oFAAoF;AACpF,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAEnC,YACmB,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,aAAa,EACtC;IAEJ,0GAA0G;IAC1G,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAShE;IAED,2GAA2G;IAC3G,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAiBzF;IAED,qGAAqG;IAC/F,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BjD;CACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { compose } from '../compose.js';
|
|
3
|
+
import { kRawSend } from '../symbols.js';
|
|
4
|
+
import { docsPage, generateOpenAPI } from '../../openapi/generate.js';
|
|
5
|
+
import { canonicalPath } from '../../utils/path.js';
|
|
6
|
+
/** Rotas servidas pelo próprio app: healthchecks e OpenAPI (documento e página). */
|
|
7
|
+
export class FrameworkRoutes {
|
|
8
|
+
core;
|
|
9
|
+
registry;
|
|
10
|
+
health;
|
|
11
|
+
openapi;
|
|
12
|
+
constructor(core, registry) {
|
|
13
|
+
this.core = core;
|
|
14
|
+
this.registry = registry;
|
|
15
|
+
}
|
|
16
|
+
/** Paths servidos pelo próprio app (`health()`, `openapi()`), que encobririam uma rota GET do usuário. */
|
|
17
|
+
reservedPaths(except) {
|
|
18
|
+
const out = new Map();
|
|
19
|
+
if (except !== 'health')
|
|
20
|
+
for (const p of this.health?.paths ?? [])
|
|
21
|
+
out.set(p, 'app.health()');
|
|
22
|
+
const cfg = this.openapi;
|
|
23
|
+
if (cfg && except !== 'openapi') {
|
|
24
|
+
out.set(canonicalPath(cfg.path), 'app.openapi()');
|
|
25
|
+
if (cfg.docs)
|
|
26
|
+
out.set(canonicalPath(cfg.docs), 'app.openapi({ docs })');
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Um path do framework não pode coincidir com uma rota GET/HEAD registrada nem com outro do framework. */
|
|
31
|
+
assertFreePaths(where, paths, kind) {
|
|
32
|
+
// `health()`/`openapi()` configuram o app, não o grupo: dentro de um `group()` em andamento, a
|
|
33
|
+
// configuração sobreviveria a um grupo desfeito e não enxergaria as rotas ainda pendentes.
|
|
34
|
+
if (this.registry.pending !== undefined) {
|
|
35
|
+
throw new Error(`[sashiko] ${where}: cannot be called inside an app.group() callback; call it on the app, outside the group.`);
|
|
36
|
+
}
|
|
37
|
+
const reserved = this.reservedPaths(kind);
|
|
38
|
+
for (const path of paths) {
|
|
39
|
+
const other = reserved.get(path);
|
|
40
|
+
if (other)
|
|
41
|
+
throw new Error(`[sashiko] ${where}: path "${path}" is already used by ${other}.`);
|
|
42
|
+
const route = this.registry.routes.find((r) => (r.method === 'GET' || r.method === 'HEAD') && canonicalPath(r.path) === path);
|
|
43
|
+
if (route) {
|
|
44
|
+
throw new Error(`[sashiko] ${where}: path "${path}" would shadow ${route.owner}.${route.handlerName} (GET ${route.path}); choose another path.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Se a requisição é para `/openapi.json` ou `/docs` (quando ligados), responde e devolve `true`. */
|
|
49
|
+
async serveOpenAPI(ctx) {
|
|
50
|
+
const cfg = this.openapi;
|
|
51
|
+
if (!cfg || (ctx.method !== 'GET' && ctx.method !== 'HEAD'))
|
|
52
|
+
return false;
|
|
53
|
+
const path = canonicalPath(ctx.path);
|
|
54
|
+
const isSpec = path === canonicalPath(cfg.path);
|
|
55
|
+
if (!isSpec && !(cfg.docs && path === canonicalPath(cfg.docs)))
|
|
56
|
+
return false;
|
|
57
|
+
await compose(ctx, [
|
|
58
|
+
...cfg.pipeline,
|
|
59
|
+
() => {
|
|
60
|
+
if (isSpec) {
|
|
61
|
+
// Um documento por conjunto de rotas visíveis no host da requisição (F-35); a chave não usa o
|
|
62
|
+
// hostname em si, então hosts arbitrários não fazem o cache crescer.
|
|
63
|
+
const visible = this.registry.routesFor(ctx.hostname.toLowerCase());
|
|
64
|
+
const key = visible.map((r) => this.registry.routes.indexOf(r)).join(',');
|
|
65
|
+
let doc = cfg.cache.get(key);
|
|
66
|
+
if (doc === undefined) {
|
|
67
|
+
doc = JSON.stringify(generateOpenAPI(visible, cfg.opts, !this.core.opts.formatError));
|
|
68
|
+
cfg.cache.set(key, doc);
|
|
69
|
+
}
|
|
70
|
+
ctx[kRawSend](doc, 'application/json; charset=utf-8');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const nonce = randomBytes(16).toString('base64');
|
|
74
|
+
const { html, csp } = docsPage(cfg.path, cfg.opts.info?.title ?? 'API', nonce);
|
|
75
|
+
ctx.res.setHeader('Content-Security-Policy', csp);
|
|
76
|
+
ctx.res.setHeader('Cache-Control', 'no-store');
|
|
77
|
+
ctx[kRawSend](html, 'text/html; charset=utf-8');
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=framework-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-routes.js","sourceRoot":"","sources":["../../../src/core/registry/framework-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AppCore } from '../app/core.ts';
|
|
2
|
+
import type { CompiledRoute } from '../compiler.ts';
|
|
3
|
+
import { RouteTable } from '../route-table.ts';
|
|
4
|
+
/** Uma rota validada e ainda não registrada (registro em duas fases). */
|
|
5
|
+
export interface PendingRoute {
|
|
6
|
+
owner: string;
|
|
7
|
+
route: CompiledRoute;
|
|
8
|
+
hosts: (string | undefined)[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Registro de rotas em duas fases (F-35): `stage()` valida tudo contra o roteador e contra o próprio lote,
|
|
12
|
+
* `commit()` registra e desfaz se algo falhar. Também sabe quais rotas responderiam num host (`routesFor`).
|
|
13
|
+
*/
|
|
14
|
+
export declare class RouteRegistry {
|
|
15
|
+
private readonly core;
|
|
16
|
+
/** Paths servidos pelo próprio app (health, openapi), que uma rota GET não pode encobrir. */
|
|
17
|
+
private readonly reservedPaths;
|
|
18
|
+
/** Chamado depois de cada commit com rotas (invalida o cache do OpenAPI). */
|
|
19
|
+
private readonly onCommit;
|
|
20
|
+
readonly router: RouteTable;
|
|
21
|
+
readonly routes: CompiledRoute[];
|
|
22
|
+
/** Registros pendentes de um `group()` em andamento. */
|
|
23
|
+
pending: PendingRoute[] | undefined;
|
|
24
|
+
private readonly warnedHosts;
|
|
25
|
+
constructor(core: AppCore,
|
|
26
|
+
/** Paths servidos pelo próprio app (health, openapi), que uma rota GET não pode encobrir. */
|
|
27
|
+
reservedPaths: () => Map<string, string>,
|
|
28
|
+
/** Chamado depois de cada commit com rotas (invalida o cache do OpenAPI). */
|
|
29
|
+
onCommit: () => void);
|
|
30
|
+
/**
|
|
31
|
+
* Primeira fase: valida as rotas (schemas obrigatórios, hosts alcançáveis, conflitos com o roteador e
|
|
32
|
+
* dentro do próprio lote). Dentro de um `group()`, acumula; fora, efetiva na hora.
|
|
33
|
+
*/
|
|
34
|
+
stage(owner: string, routes: readonly CompiledRoute[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Segunda fase: registra o lote no roteador. Se algo ainda falhar (um conflito que o `has()` não prevê),
|
|
37
|
+
* desfaz o que já entrou, e nenhuma rota do lote fica registrada.
|
|
38
|
+
*/
|
|
39
|
+
commit(batch: readonly PendingRoute[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Um host de rota sem interseção com o `allowedHosts` nunca seria alcançado: o app responde 400 antes do
|
|
42
|
+
* roteamento (F-35). Comprovado: erro (ou aviso, com `unreachableHosts: 'warn'`). Sem como provar
|
|
43
|
+
* (RegExp em algum dos lados): aviso.
|
|
44
|
+
*/
|
|
45
|
+
private checkReachableHosts;
|
|
46
|
+
/**
|
|
47
|
+
* Rotas que o roteador **executaria** num host (F-35): para cada método e path, só a vencedora pela mesma
|
|
48
|
+
* prioridade do roteador (exato → curinga mais específico → RegExp → sem host). Assim o OpenAPI documenta
|
|
49
|
+
* exatamente o handler que responde. Sem `hostname`, só as rotas sem host.
|
|
50
|
+
*/
|
|
51
|
+
routesFor(hostname: string | undefined): CompiledRoute[];
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/core/registry/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAK/C,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,aAAa;IAQtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,QAAQ,CAAC,MAAM,aAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAM;IACtC,wDAAwD;IACxD,OAAO,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAEjD,YACmB,IAAI,EAAE,OAAO;IAC9B,6FAA6F;IAC5E,aAAa,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IACzD,6EAA6E;IAC5D,QAAQ,EAAE,MAAM,IAAI,EACnC;IAEJ;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAuC3D;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,CAsB3C;IAED;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,EAAE,CAkBvD;CACF"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { assertSchemas } from '../request-validation.js';
|
|
2
|
+
import { RouteTable } from '../route-table.js';
|
|
3
|
+
import { buildRouteProgram } from './route-program.js';
|
|
4
|
+
import { hostRank, hostReachability, NO_HOST_RANK } from '../../utils/origin-match.js';
|
|
5
|
+
import { canonicalPath } from '../../utils/path.js';
|
|
6
|
+
/**
|
|
7
|
+
* Registro de rotas em duas fases (F-35): `stage()` valida tudo contra o roteador e contra o próprio lote,
|
|
8
|
+
* `commit()` registra e desfaz se algo falhar. Também sabe quais rotas responderiam num host (`routesFor`).
|
|
9
|
+
*/
|
|
10
|
+
export class RouteRegistry {
|
|
11
|
+
core;
|
|
12
|
+
reservedPaths;
|
|
13
|
+
onCommit;
|
|
14
|
+
router = new RouteTable();
|
|
15
|
+
routes = [];
|
|
16
|
+
/** Registros pendentes de um `group()` em andamento. */
|
|
17
|
+
pending;
|
|
18
|
+
warnedHosts = new Set();
|
|
19
|
+
constructor(core,
|
|
20
|
+
/** Paths servidos pelo próprio app (health, openapi), que uma rota GET não pode encobrir. */
|
|
21
|
+
reservedPaths,
|
|
22
|
+
/** Chamado depois de cada commit com rotas (invalida o cache do OpenAPI). */
|
|
23
|
+
onCommit) {
|
|
24
|
+
this.core = core;
|
|
25
|
+
this.reservedPaths = reservedPaths;
|
|
26
|
+
this.onCommit = onCommit;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Primeira fase: valida as rotas (schemas obrigatórios, hosts alcançáveis, conflitos com o roteador e
|
|
30
|
+
* dentro do próprio lote). Dentro de um `group()`, acumula; fora, efetiva na hora.
|
|
31
|
+
*/
|
|
32
|
+
stage(owner, routes) {
|
|
33
|
+
const batch = this.pending ?? [];
|
|
34
|
+
// Checkpoint: dentro de um group(), um mount() que falha no meio não deixa parte dele no lote,
|
|
35
|
+
// mesmo que o callback capture o erro e siga em frente.
|
|
36
|
+
const mark = batch.length;
|
|
37
|
+
try {
|
|
38
|
+
for (const route of routes) {
|
|
39
|
+
// Rotas internas (static) não têm schema por natureza: o requireSchemas vale para as da aplicação.
|
|
40
|
+
if (this.core.opts.requireSchemas && !route.internal)
|
|
41
|
+
assertSchemas(owner, route);
|
|
42
|
+
if (route.method === 'GET' || route.method === 'HEAD') {
|
|
43
|
+
const reservedBy = this.reservedPaths().get(canonicalPath(route.path));
|
|
44
|
+
if (reservedBy) {
|
|
45
|
+
throw new Error(`[sashiko] app.mount(${owner}): GET ${route.path} is served by ${reservedBy} and would never be reached; choose another path.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.checkReachableHosts(owner, route);
|
|
49
|
+
for (const host of route.hosts.length > 0 ? route.hosts.map((h) => h.key) : [undefined]) {
|
|
50
|
+
const taken = this.router.has(route.method, route.path, host) ||
|
|
51
|
+
batch.some((p) => p.route.method === route.method && canonicalPath(p.route.path) === canonicalPath(route.path) && p.hosts.includes(host));
|
|
52
|
+
if (taken) {
|
|
53
|
+
const where = host ? ` on host ${host}` : '';
|
|
54
|
+
throw new Error(`[sashiko] app.mount(${owner}): ${route.method} ${route.path}${where} is already registered. ` +
|
|
55
|
+
`Use a different "prefix" or "host" to mount the same class twice.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
batch.push({ owner, route, hosts: route.hosts.length > 0 ? route.hosts.map((h) => h.key) : [undefined] });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
batch.length = mark;
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
if (this.pending === undefined)
|
|
66
|
+
this.commit(batch);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Segunda fase: registra o lote no roteador. Se algo ainda falhar (um conflito que o `has()` não prevê),
|
|
70
|
+
* desfaz o que já entrou, e nenhuma rota do lote fica registrada.
|
|
71
|
+
*/
|
|
72
|
+
commit(batch) {
|
|
73
|
+
const done = [];
|
|
74
|
+
try {
|
|
75
|
+
for (const { owner, route, hosts } of batch) {
|
|
76
|
+
route.run ??= buildRouteProgram(route, this.core.serializers);
|
|
77
|
+
for (const host of hosts) {
|
|
78
|
+
try {
|
|
79
|
+
this.router.on(route.method, route.path, route, host);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
throw new Error(`[sashiko] app.mount(${owner}): ${route.method} ${route.path} conflicts with a registered route.`, {
|
|
83
|
+
cause: err,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
done.push({ method: route.method, path: route.path, host });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
for (const r of done.reverse())
|
|
92
|
+
this.router.off(r.method, r.path, r.host);
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
for (const { route } of batch)
|
|
96
|
+
this.routes.push(route);
|
|
97
|
+
if (batch.length > 0)
|
|
98
|
+
this.onCommit();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Um host de rota sem interseção com o `allowedHosts` nunca seria alcançado: o app responde 400 antes do
|
|
102
|
+
* roteamento (F-35). Comprovado: erro (ou aviso, com `unreachableHosts: 'warn'`). Sem como provar
|
|
103
|
+
* (RegExp em algum dos lados): aviso.
|
|
104
|
+
*/
|
|
105
|
+
checkReachableHosts(owner, route) {
|
|
106
|
+
const mode = this.core.opts.unreachableHosts ?? 'error';
|
|
107
|
+
const allowed = this.core.opts.allowedHosts;
|
|
108
|
+
if (mode === 'off' || !allowed)
|
|
109
|
+
return;
|
|
110
|
+
for (const host of route.hosts) {
|
|
111
|
+
const verdict = hostReachability(host, allowed);
|
|
112
|
+
if (verdict === 'yes')
|
|
113
|
+
continue;
|
|
114
|
+
const where = `app.mount(${owner}): ${route.method} ${route.path} uses host ${host.label}`;
|
|
115
|
+
if (verdict === 'no' && mode === 'error') {
|
|
116
|
+
throw new Error(`[sashiko] ${where}, which no allowedHosts rule accepts; the route is unreachable. Add it to allowedHosts, or set unreachableHosts: 'warn'.`);
|
|
117
|
+
}
|
|
118
|
+
const key = `${owner} ${host.key}`;
|
|
119
|
+
if (this.warnedHosts.has(key))
|
|
120
|
+
continue;
|
|
121
|
+
this.warnedHosts.add(key);
|
|
122
|
+
this.core.log.warn({ host: host.label }, verdict === 'no'
|
|
123
|
+
? `[sashiko] ${where}, which no allowedHosts rule accepts; the route is unreachable.`
|
|
124
|
+
: `[sashiko] ${where}; could not verify it against allowedHosts (RegExp).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Rotas que o roteador **executaria** num host (F-35): para cada método e path, só a vencedora pela mesma
|
|
129
|
+
* prioridade do roteador (exato → curinga mais específico → RegExp → sem host). Assim o OpenAPI documenta
|
|
130
|
+
* exatamente o handler que responde. Sem `hostname`, só as rotas sem host.
|
|
131
|
+
*/
|
|
132
|
+
routesFor(hostname) {
|
|
133
|
+
const best = new Map();
|
|
134
|
+
for (const [seq, route] of this.routes.entries()) {
|
|
135
|
+
let rank = route.hosts.length === 0 ? NO_HOST_RANK : -1;
|
|
136
|
+
if (rank === -1 && hostname !== undefined) {
|
|
137
|
+
for (const h of route.hosts) {
|
|
138
|
+
if (!h.test(hostname))
|
|
139
|
+
continue;
|
|
140
|
+
// Desempate pela ordem de registro, a mesma que o roteador usa em cada path.
|
|
141
|
+
const r = hostRank(h, seq);
|
|
142
|
+
if (rank === -1 || r < rank)
|
|
143
|
+
rank = r;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (rank === -1)
|
|
147
|
+
continue;
|
|
148
|
+
const key = `${route.method} ${canonicalPath(route.path)}`;
|
|
149
|
+
const current = best.get(key);
|
|
150
|
+
if (!current || rank < current.rank)
|
|
151
|
+
best.set(key, { route, rank });
|
|
152
|
+
}
|
|
153
|
+
return this.routes.filter((r) => best.get(`${r.method} ${canonicalPath(r.path)}`)?.route === r);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/registry/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Context } from '../context.ts';
|
|
2
|
+
import { type Serializer } from '../response/pipeline.ts';
|
|
3
|
+
import type { RouteDefinition } from '../route-table.ts';
|
|
4
|
+
/** A função completa de uma rota, montada uma vez no registro. */
|
|
5
|
+
export type RouteProgram = (ctx: Context) => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Builder (F-39): monta, no registro, o programa de cada rota. Antes, a cada requisição, o app criava o
|
|
8
|
+
* array do pipeline, o middleware de guards, a closure final e o objeto de resposta.
|
|
9
|
+
*
|
|
10
|
+
* Ordem (F-01): middlewares da rota → guards → validação → handler → resposta. Os guards vêm antes da
|
|
11
|
+
* validação, para quem não tem acesso receber 401/403, e não um 400 que revele o formato esperado.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildRouteProgram(route: RouteDefinition, serializers: ReadonlyMap<string, Serializer>): RouteProgram;
|
|
14
|
+
//# sourceMappingURL=route-program.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-program.d.ts","sourceRoot":"","sources":["../../../src/core/registry/route-program.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EAA4B,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzD,kEAAkE;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,YAAY,CAapH"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { compileChain } from '../compose.js';
|
|
2
|
+
import { guardsMiddleware } from '../guard.js';
|
|
3
|
+
import { validateRequest } from '../request-validation.js';
|
|
4
|
+
import { responsePlan, sendResult } from '../response/pipeline.js';
|
|
5
|
+
import { kBindResponse, kPendingSend } from '../symbols.js';
|
|
6
|
+
/**
|
|
7
|
+
* Builder (F-39): monta, no registro, o programa de cada rota. Antes, a cada requisição, o app criava o
|
|
8
|
+
* array do pipeline, o middleware de guards, a closure final e o objeto de resposta.
|
|
9
|
+
*
|
|
10
|
+
* Ordem (F-01): middlewares da rota → guards → validação → handler → resposta. Os guards vêm antes da
|
|
11
|
+
* validação, para quem não tem acesso receber 401/403, e não um 400 que revele o formato esperado.
|
|
12
|
+
*/
|
|
13
|
+
export function buildRouteProgram(route, serializers) {
|
|
14
|
+
const { schemas, handler } = route;
|
|
15
|
+
const plan = responsePlan(schemas.response, serializers);
|
|
16
|
+
const terminal = async (ctx) => {
|
|
17
|
+
validateRequest(ctx, schemas);
|
|
18
|
+
// F-32: a partir daqui, ctx.json/send/text passam pelo mesmo pipeline do `return`.
|
|
19
|
+
ctx[kBindResponse](plan);
|
|
20
|
+
const result = await handler(ctx);
|
|
21
|
+
if (ctx[kPendingSend])
|
|
22
|
+
await ctx[kPendingSend];
|
|
23
|
+
if (!ctx.sent)
|
|
24
|
+
sendResult(ctx, plan.response, result, plan.serializers, 'auto', plan.defaultStatus);
|
|
25
|
+
};
|
|
26
|
+
const layers = route.guards.length > 0 ? [...route.middlewares, guardsMiddleware(route.guards)] : route.middlewares;
|
|
27
|
+
return layers.length === 0 ? terminal : compileChain(layers, terminal);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=route-program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-program.js","sourceRoot":"","sources":["../../../src/core/registry/route-program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAmB,MAAM,yBAAyB,CAAC;AAEpF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAK5D;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB,EAAE,WAA4C;IACpG,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IACnC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAY,EAAiB,EAAE;QACrD,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC9B,mFAAmF;QACnF,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,GAAG,CAAC,YAAY,CAAC;YAAE,MAAM,GAAG,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,GAAG,CAAC,IAAI;YAAE,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IACtG,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;IACpH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Context } from '../context.ts';
|
|
2
|
+
import { type ErrorFormatter, type ProblemDetails } from '../errors.ts';
|
|
3
|
+
import { type RouteSchemas } from '../metadata.ts';
|
|
4
|
+
/** Converte dados num corpo de resposta para um content type (F-31). */
|
|
5
|
+
export type Serializer = (data: unknown) => string | Uint8Array;
|
|
6
|
+
/**
|
|
7
|
+
* Como o corpo chegou ao pipeline (F-32): `auto` (`return` e `ctx.send()`), `json` (`ctx.json()`,
|
|
8
|
+
* sempre JSON) ou `text` (`ctx.text()`, sempre texto).
|
|
9
|
+
*/
|
|
10
|
+
export type SendMode = 'auto' | 'json' | 'text';
|
|
11
|
+
/**
|
|
12
|
+
* Envia o retorno do handler.
|
|
13
|
+
*
|
|
14
|
+
* Status (B-05): sem `ctx.status()` e com um único 2xx declarado, esse 2xx vira o padrão. Com schemas
|
|
15
|
+
* de resposta declarados, um status sem schema correspondente vira 500 em vez de enviar o objeto
|
|
16
|
+
* sem filtro (S-07). Respostas sem corpo (`undefined`) dispensam schema.
|
|
17
|
+
*
|
|
18
|
+
* Content type (F-31), em ordem de prioridade:
|
|
19
|
+
* 1. o `Content-Type` definido pelo handler;
|
|
20
|
+
* 2. o `contentType` declarado no schema de resposta (com negociação pelo `Accept` se houver vários);
|
|
21
|
+
* 3. schema de resposta sem `contentType` → JSON;
|
|
22
|
+
* 4. sem schema, o tipo do valor: string → `text/plain`, Buffer/Uint8Array/stream → `application/octet-stream`,
|
|
23
|
+
* o resto → JSON.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* O que cada rota sabe sobre a resposta, decidido no registro (Strategy, F-39): schemas, serializadores e o
|
|
27
|
+
* status padrão (o único 2xx declarado, quando não há 200). O mesmo objeto serve a todas as requisições.
|
|
28
|
+
*/
|
|
29
|
+
export interface ResponsePlan {
|
|
30
|
+
readonly response: RouteSchemas['response'];
|
|
31
|
+
readonly serializers: ReadonlyMap<string, Serializer>;
|
|
32
|
+
readonly defaultStatus: number | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Status padrão de uma rota (B-05): com um único 2xx declarado e sem 200, é esse 2xx. */
|
|
35
|
+
export declare function defaultStatusFor(response: RouteSchemas['response']): number | undefined;
|
|
36
|
+
export declare function responsePlan(response: RouteSchemas['response'], serializers: ReadonlyMap<string, Serializer>): ResponsePlan;
|
|
37
|
+
export declare function sendResult(ctx: Context, response: RouteSchemas['response'], result: unknown, serializers?: ReadonlyMap<string, Serializer>, mode?: SendMode, defaultStatus?: number | undefined): void;
|
|
38
|
+
/** Monta um Problem Details (RFC 9457). Sem `type`, usa `about:blank` e a frase do status como título. */
|
|
39
|
+
export declare function problem(status: number, detail?: string, extra?: {
|
|
40
|
+
code?: string | undefined;
|
|
41
|
+
details?: unknown;
|
|
42
|
+
type?: string | undefined;
|
|
43
|
+
}): ProblemDetails;
|
|
44
|
+
/** Escreve um problema (status + corpo), sem passar pelo schema da rota (F-32). */
|
|
45
|
+
export declare function sendProblem(ctx: Context, body: ProblemDetails, format?: ErrorFormatter): void;
|
|
46
|
+
/**
|
|
47
|
+
* Resposta de erro padrão, no formato Problem Details (F-15). Só mensagens marcadas como `expose`
|
|
48
|
+
* chegam ao cliente; o resto vira 500 genérico e o erro original é logado no servidor (S-05).
|
|
49
|
+
*/
|
|
50
|
+
export declare function sendError(ctx: Context, err: unknown, format?: ErrorFormatter): void;
|
|
51
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../src/core/response/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EAAa,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAMtE,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,UAAU,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAIhD;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACtD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CAMvF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,YAAY,CAE3H;AAID,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,EAClC,MAAM,EAAE,OAAO,EACf,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAqB,EAChE,IAAI,GAAE,QAAiB,EACvB,aAAa,GAAE,MAAM,GAAG,SAAsC,GAC7D,IAAI,CAmCN;AAwFD,0GAA0G;AAC1G,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GACtF,cAAc,CAMhB;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAa7F;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAanF"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { kRawSend } from '../symbols.js';
|
|
2
|
+
import { STATUS_CODES } from 'node:http';
|
|
3
|
+
import { HttpError } from '../errors.js';
|
|
4
|
+
import { isResponseContent } from '../metadata.js';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
|
+
import { serialize } from '../../schema/serialization.js';
|
|
7
|
+
import { stripToSchema } from '../../schema/validation.js';
|
|
8
|
+
import { isJsonContentType, mediaType, negotiate, withCharset } from '../../utils/content-type.js';
|
|
9
|
+
const JSON_TYPE = 'application/json';
|
|
10
|
+
/** Status padrão de uma rota (B-05): com um único 2xx declarado e sem 200, é esse 2xx. */
|
|
11
|
+
export function defaultStatusFor(response) {
|
|
12
|
+
if (!response)
|
|
13
|
+
return undefined;
|
|
14
|
+
const success = Object.keys(response)
|
|
15
|
+
.map(Number)
|
|
16
|
+
.filter((st) => st >= 200 && st < 300);
|
|
17
|
+
return success.length === 1 && response[200] === undefined ? success[0] : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function responsePlan(response, serializers) {
|
|
20
|
+
return { response, serializers, defaultStatus: defaultStatusFor(response) };
|
|
21
|
+
}
|
|
22
|
+
const EMPTY_SERIALIZERS = new Map();
|
|
23
|
+
export function sendResult(ctx, response, result, serializers = EMPTY_SERIALIZERS, mode = 'auto', defaultStatus = defaultStatusFor(response)) {
|
|
24
|
+
if (defaultStatus !== undefined && !ctx.statusExplicit)
|
|
25
|
+
ctx.res.statusCode = defaultStatus;
|
|
26
|
+
if (result === undefined)
|
|
27
|
+
return void ctx.res.end();
|
|
28
|
+
const status = ctx.res.statusCode;
|
|
29
|
+
const entry = response?.[status];
|
|
30
|
+
if (!entry && response && Object.keys(response).length > 0) {
|
|
31
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: no response schema for status ${status}`);
|
|
32
|
+
}
|
|
33
|
+
const headerType = ctx.res.getHeader('Content-Type');
|
|
34
|
+
const explicit = mode === 'json' ? JSON_TYPE : mode === 'text' ? 'text/plain' : typeof headerType === 'string' ? headerType : undefined;
|
|
35
|
+
let type;
|
|
36
|
+
let schema;
|
|
37
|
+
let jsonSchemaDeclared = false;
|
|
38
|
+
if (entry && isResponseContent(entry)) {
|
|
39
|
+
schema = entry.schema;
|
|
40
|
+
const offered = typeof entry.contentType === 'string' ? [entry.contentType] : [...entry.contentType];
|
|
41
|
+
if (offered.length > 1)
|
|
42
|
+
ctx.res.appendHeader('Vary', 'Accept');
|
|
43
|
+
const chosen = explicit ?? negotiate(ctx.get('accept'), offered);
|
|
44
|
+
if (!chosen) {
|
|
45
|
+
throw new HttpError(406, 'Not Acceptable', 'NOT_ACCEPTABLE', { details: { available: offered } });
|
|
46
|
+
}
|
|
47
|
+
type = chosen;
|
|
48
|
+
}
|
|
49
|
+
else if (entry) {
|
|
50
|
+
schema = entry;
|
|
51
|
+
jsonSchemaDeclared = true;
|
|
52
|
+
type = explicit ?? JSON_TYPE;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
type = explicit ?? inferType(result);
|
|
56
|
+
}
|
|
57
|
+
write(ctx, type, result, schema, mode === 'json' || (jsonSchemaDeclared && !explicit), serializers);
|
|
58
|
+
}
|
|
59
|
+
const NDJSON = 'application/x-ndjson';
|
|
60
|
+
/** Async iterable que não é um stream (async generator, cursor de banco…) (F-13). */
|
|
61
|
+
function isAsyncIterable(value) {
|
|
62
|
+
return typeof value === 'object' && value !== null && !(value instanceof Readable) && Symbol.asyncIterator in value;
|
|
63
|
+
}
|
|
64
|
+
function inferType(value) {
|
|
65
|
+
if (typeof value === 'string')
|
|
66
|
+
return 'text/plain';
|
|
67
|
+
if (value instanceof Uint8Array || value instanceof Readable)
|
|
68
|
+
return 'application/octet-stream';
|
|
69
|
+
if (isAsyncIterable(value))
|
|
70
|
+
return NDJSON;
|
|
71
|
+
return JSON_TYPE;
|
|
72
|
+
}
|
|
73
|
+
/** Cada item vira uma linha JSON (NDJSON). Com schema, cada item é validado e filtrado como uma resposta. */
|
|
74
|
+
function ndjson(ctx, items, schema) {
|
|
75
|
+
const signal = ctx.signal;
|
|
76
|
+
return Readable.from((async function* () {
|
|
77
|
+
for await (const item of items) {
|
|
78
|
+
if (signal.aborted)
|
|
79
|
+
return; // cliente desconectou: para de consumir a fonte
|
|
80
|
+
yield `${schema ? serialize(schema, item) : JSON.stringify(item)}\n`;
|
|
81
|
+
}
|
|
82
|
+
})());
|
|
83
|
+
}
|
|
84
|
+
function write(ctx, type, value, schema, forceJson, serializers) {
|
|
85
|
+
const header = withCharset(type);
|
|
86
|
+
const base = mediaType(type);
|
|
87
|
+
const json = isJsonContentType(base);
|
|
88
|
+
if (value instanceof Readable)
|
|
89
|
+
return void ctx[kRawSend](value, header);
|
|
90
|
+
if (isAsyncIterable(value)) {
|
|
91
|
+
if (base !== NDJSON) {
|
|
92
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: an async iterable is streamed as NDJSON; declare ` +
|
|
93
|
+
`response: { 200: { contentType: '${NDJSON}', schema: Item } } (the schema of each item), or return a Readable.`);
|
|
94
|
+
}
|
|
95
|
+
return void ctx[kRawSend](ndjson(ctx, value, schema), header);
|
|
96
|
+
}
|
|
97
|
+
// Valores crus (string/Buffer) vão como estão, exceto quando a rota declarou um schema JSON:
|
|
98
|
+
// nesse caso `"olá"` continua sendo serializado como JSON, como sempre.
|
|
99
|
+
if ((typeof value === 'string' || value instanceof Uint8Array) && !forceJson) {
|
|
100
|
+
if (schema && typeof value === 'string')
|
|
101
|
+
ensureValid(ctx, schema, value);
|
|
102
|
+
return void ctx[kRawSend](value, header);
|
|
103
|
+
}
|
|
104
|
+
if (json) {
|
|
105
|
+
// Pelo ponto único de escrita (F-04): onSend e sentBody valem também para JSON.
|
|
106
|
+
return void ctx[kRawSend](schema ? serialize(schema, value) : JSON.stringify(value), header);
|
|
107
|
+
}
|
|
108
|
+
const serializer = serializers.get(base);
|
|
109
|
+
if (!serializer) {
|
|
110
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: no serializer registered for "${base}". ` +
|
|
111
|
+
`Add it to createApp({ serializers: { '${base}': (data) => ... } }).`);
|
|
112
|
+
}
|
|
113
|
+
const data = schema ? ensureValid(ctx, schema, value) : value;
|
|
114
|
+
ctx[kRawSend](Buffer.from(serializer(data)), header);
|
|
115
|
+
}
|
|
116
|
+
/** Valida a saída e devolve uma cópia sem campos não declarados (S-07 em qualquer formato). */
|
|
117
|
+
function ensureValid(ctx, schema, value) {
|
|
118
|
+
const checked = stripToSchema(schema, value);
|
|
119
|
+
if (!checked.ok) {
|
|
120
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: response does not match its schema: ` +
|
|
121
|
+
checked.issues.map((i) => `${i.path} ${i.message}`).join('; '));
|
|
122
|
+
}
|
|
123
|
+
return checked.value;
|
|
124
|
+
}
|
|
125
|
+
/** Monta um Problem Details (RFC 9457). Sem `type`, usa `about:blank` e a frase do status como título. */
|
|
126
|
+
export function problem(status, detail, extra = {}) {
|
|
127
|
+
const out = { type: extra.type ?? 'about:blank', title: STATUS_CODES[status] ?? 'Error', status };
|
|
128
|
+
if (detail !== undefined && detail !== out.title)
|
|
129
|
+
out.detail = detail;
|
|
130
|
+
if (extra.code !== undefined)
|
|
131
|
+
out.code = extra.code;
|
|
132
|
+
if (extra.details !== undefined)
|
|
133
|
+
out.details = extra.details;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/** Escreve um problema (status + corpo), sem passar pelo schema da rota (F-32). */
|
|
137
|
+
export function sendProblem(ctx, body, format) {
|
|
138
|
+
ctx.res.statusCode = body.status;
|
|
139
|
+
if (format) {
|
|
140
|
+
let custom;
|
|
141
|
+
try {
|
|
142
|
+
custom = format(body, ctx);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
ctx.log.error({ err, method: ctx.method, path: ctx.path }, 'formatError failed; sending the default problem');
|
|
146
|
+
return void ctx[kRawSend](JSON.stringify(body), 'application/problem+json; charset=utf-8');
|
|
147
|
+
}
|
|
148
|
+
return void ctx[kRawSend](JSON.stringify(custom), 'application/json; charset=utf-8');
|
|
149
|
+
}
|
|
150
|
+
ctx[kRawSend](JSON.stringify(body), 'application/problem+json; charset=utf-8');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resposta de erro padrão, no formato Problem Details (F-15). Só mensagens marcadas como `expose`
|
|
154
|
+
* chegam ao cliente; o resto vira 500 genérico e o erro original é logado no servidor (S-05).
|
|
155
|
+
*/
|
|
156
|
+
export function sendError(ctx, err, format) {
|
|
157
|
+
const httpErr = err instanceof HttpError ? err : undefined;
|
|
158
|
+
const status = httpErr?.status ?? 500;
|
|
159
|
+
const exposed = httpErr?.expose === true;
|
|
160
|
+
if (!exposed)
|
|
161
|
+
ctx.log.error({ err, method: ctx.method, path: ctx.path, status }, 'request failed');
|
|
162
|
+
if (ctx.res.writableEnded)
|
|
163
|
+
return;
|
|
164
|
+
if (ctx.res.headersSent)
|
|
165
|
+
return void ctx.res.end();
|
|
166
|
+
const body = exposed
|
|
167
|
+
? problem(status, httpErr.message, { code: httpErr.code, details: httpErr.details, type: httpErr.type })
|
|
168
|
+
: problem(status);
|
|
169
|
+
sendProblem(ctx, body, format);
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/core/response/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAa,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAMrE,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,UAAU,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAIhD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,EAClC,MAAM,EAAE,OAAO,EACf,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAa,EACxD,IAAI,GAAE,QAAiB,GACtB,IAAI,CAwCN;
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAa,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAMrE,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,UAAU,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAIhD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,EAClC,MAAM,EAAE,OAAO,EACf,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAa,EACxD,IAAI,GAAE,QAAiB,GACtB,IAAI,CAwCN;AAwFD,0GAA0G;AAC1G,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GACtF,cAAc,CAMhB;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAa7F;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAanF"}
|