sashiko 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -27
- package/dist/core/app.d.ts +30 -6
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +73 -18
- package/dist/core/app.js.map +1 -1
- package/dist/core/compiler.d.ts +34 -10
- package/dist/core/compiler.d.ts.map +1 -1
- package/dist/core/compiler.js +60 -36
- package/dist/core/compiler.js.map +1 -1
- package/dist/core/context.d.ts +43 -7
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +41 -11
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/infer.d.ts +9 -1
- package/dist/core/infer.d.ts.map +1 -1
- package/dist/core/metadata.d.ts +21 -5
- package/dist/core/metadata.d.ts.map +1 -1
- package/dist/core/metadata.js +6 -0
- package/dist/core/metadata.js.map +1 -1
- package/dist/core/mounted-group.d.ts +41 -0
- package/dist/core/mounted-group.d.ts.map +1 -0
- package/dist/core/mounted-group.js +43 -0
- package/dist/core/mounted-group.js.map +1 -0
- package/dist/core/response.d.ts +22 -8
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +93 -15
- package/dist/core/response.js.map +1 -1
- package/dist/core/shutdown.d.ts +36 -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/decorators/index.d.ts +1 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +1 -1
- package/dist/decorators/index.js.map +1 -1
- 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/middlewares/index.d.ts +1 -1
- package/dist/middlewares/index.d.ts.map +1 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/middlewares/security-headers.d.ts +4 -2
- package/dist/middlewares/security-headers.d.ts.map +1 -1
- package/dist/middlewares/security-headers.js +10 -2
- package/dist/middlewares/security-headers.js.map +1 -1
- package/dist/schema/validation.d.ts +11 -0
- package/dist/schema/validation.d.ts.map +1 -1
- package/dist/schema/validation.js +18 -0
- package/dist/schema/validation.js.map +1 -1
- package/dist/utils/content-type.d.ts +8 -0
- package/dist/utils/content-type.d.ts.map +1 -1
- package/dist/utils/content-type.js +42 -0
- package/dist/utils/content-type.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/decorators/use.d.ts +0 -26
- package/dist/decorators/use.d.ts.map +0 -1
- package/dist/decorators/use.js +0 -38
- package/dist/decorators/use.js.map +0 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retorno de `app.mount()` (F-30): permite mapear manualmente métodos do grupo e continuar
|
|
3
|
+
* encadeando chamadas do app.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* app
|
|
7
|
+
* .mount(new UserRoutes(repo))
|
|
8
|
+
* .get('/legacy/:id', (r) => r.getUser, { guards: [isAdmin] })
|
|
9
|
+
* .mount(HealthRoutes);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export class MountedGroup {
|
|
13
|
+
app;
|
|
14
|
+
#group;
|
|
15
|
+
#register;
|
|
16
|
+
/** @internal Criado por `app.mount()`. */
|
|
17
|
+
constructor(app, group, register) {
|
|
18
|
+
this.app = app;
|
|
19
|
+
this.#group = group;
|
|
20
|
+
this.#register = register;
|
|
21
|
+
}
|
|
22
|
+
#map(method, path, select, opts) {
|
|
23
|
+
this.#register(this.#group, method, path, select, opts);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
/** Mapeia `GET path` (relativo ao prefixo do grupo) para o método selecionado. */
|
|
27
|
+
get(path, select, opts) { return this.#map('GET', path, select, opts); }
|
|
28
|
+
post(path, select, opts) { return this.#map('POST', path, select, opts); }
|
|
29
|
+
put(path, select, opts) { return this.#map('PUT', path, select, opts); }
|
|
30
|
+
patch(path, select, opts) { return this.#map('PATCH', path, select, opts); }
|
|
31
|
+
delete(path, select, opts) { return this.#map('DELETE', path, select, opts); }
|
|
32
|
+
mount(ref, options) {
|
|
33
|
+
return this.app.mount(ref, options);
|
|
34
|
+
}
|
|
35
|
+
use(mw) { return this.app.use(mw); }
|
|
36
|
+
onError(h) { return this.app.onError(h); }
|
|
37
|
+
listen(options) { return this.app.listen(options); }
|
|
38
|
+
close(graceMs) { return this.app.close(graceMs); }
|
|
39
|
+
callback() { return this.app.callback(); }
|
|
40
|
+
inject(opts) { return this.app.inject(opts); }
|
|
41
|
+
get log() { return this.app.log; }
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=mounted-group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mounted-group.js","sourceRoot":"","sources":["../../src/core/mounted-group.ts"],"names":[],"mappings":"AASA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IACd,GAAG,CAAa;IAChB,MAAM,CAAa;IACnB,SAAS,CAAuH;IAEzI,0CAA0C;IAC1C,YACE,GAAe,EACf,KAAiB,EACjB,QAA8H;QAE9H,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,MAAc,EAAE,IAAY,EAAE,MAAyB,EAAE,IAAyB;QACrF,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAgC,EAAE,IAAI,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAClF,GAAG,CAAC,IAAY,EAAE,MAAyB,EAAE,IAAyB,IAAU,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9H,IAAI,CAAC,IAAY,EAAE,MAAyB,EAAE,IAAyB,IAAU,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAChI,GAAG,CAAC,IAAY,EAAE,MAAyB,EAAE,IAAyB,IAAU,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9H,KAAK,CAAC,IAAY,EAAE,MAAyB,EAAE,IAAyB,IAAU,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAClI,MAAM,CAAC,IAAY,EAAE,MAAyB,EAAE,IAAyB,IAAU,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAKpI,KAAK,CAAC,GAAW,EAAE,OAA8B;QAC/C,OAAQ,IAAI,CAAC,GAAG,CAAC,KAAuE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACzG,CAAC;IACD,GAAG,CAAC,EAAc,IAAgB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,CAAuD,IAAgB,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,OAAgC,IAA0B,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACnG,KAAK,CAAC,OAAgB,IAAmB,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1E,QAAQ,KAAoE,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzG,MAAM,CAAC,IAA4B,IAA6B,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/F,IAAI,GAAG,KAAa,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;CAC3C"}
|
package/dist/core/response.d.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
1
|
+
import { type Context } from './context.ts';
|
|
2
|
+
import { type RouteSchemas } from './metadata.ts';
|
|
3
|
+
/** Converte dados num corpo de resposta para um content type (F-31). */
|
|
4
|
+
export type Serializer = (data: unknown) => string | Uint8Array;
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - Com schemas de resposta declarados, um status sem schema correspondente **não** cai no
|
|
7
|
-
* `JSON.stringify`, que mandaria o objeto inteiro (S-07): vira 500 e o motivo vai para o log.
|
|
8
|
-
* - Respostas sem corpo (`undefined`) dispensam schema.
|
|
6
|
+
* Como o corpo chegou ao pipeline (F-32): `auto` (`return` e `ctx.send()`), `json` (`ctx.json()`,
|
|
7
|
+
* sempre JSON) ou `text` (`ctx.text()`, sempre texto).
|
|
9
8
|
*/
|
|
10
|
-
export
|
|
9
|
+
export type SendMode = 'auto' | 'json' | 'text';
|
|
10
|
+
/**
|
|
11
|
+
* Envia o retorno do handler.
|
|
12
|
+
*
|
|
13
|
+
* Status (B-05): sem `ctx.status()` e com um único 2xx declarado, esse 2xx vira o padrão. Com schemas
|
|
14
|
+
* de resposta declarados, um status sem schema correspondente vira 500 em vez de enviar o objeto
|
|
15
|
+
* sem filtro (S-07). Respostas sem corpo (`undefined`) dispensam schema.
|
|
16
|
+
*
|
|
17
|
+
* Content type (F-31), em ordem de prioridade:
|
|
18
|
+
* 1. o `Content-Type` definido pelo handler;
|
|
19
|
+
* 2. o `contentType` declarado no schema de resposta (com negociação pelo `Accept` se houver vários);
|
|
20
|
+
* 3. schema de resposta sem `contentType` → JSON;
|
|
21
|
+
* 4. sem schema, o tipo do valor: string → `text/plain`, Buffer/Uint8Array/stream → `application/octet-stream`,
|
|
22
|
+
* o resto → JSON.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sendResult(ctx: Context, response: RouteSchemas['response'], result: unknown, serializers?: ReadonlyMap<string, Serializer>, mode?: SendMode): void;
|
|
11
25
|
/**
|
|
12
26
|
* Resposta de erro padrão. Só mensagens marcadas como `expose` chegam ao cliente;
|
|
13
27
|
* o resto vira 500 genérico e o erro original é logado no servidor (S-05).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
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,EAAqB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAQrE,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;AAyDD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAc1D"}
|
package/dist/core/response.js
CHANGED
|
@@ -1,31 +1,107 @@
|
|
|
1
|
+
import { kRawSend } from './context.js';
|
|
1
2
|
import { HttpError } from './errors.js';
|
|
3
|
+
import { isResponseContent } from './metadata.js';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
2
5
|
import { serialize } from '../schema/serialization.js';
|
|
6
|
+
import { stripToSchema } from '../schema/validation.js';
|
|
7
|
+
import { isJsonContentType, mediaType, negotiate, withCharset } from '../utils/content-type.js';
|
|
3
8
|
const GENERIC_500 = 'Internal Server Error';
|
|
9
|
+
const JSON_TYPE = 'application/json';
|
|
4
10
|
/**
|
|
5
|
-
* Envia o retorno do handler
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
* - Respostas sem corpo (`undefined`) dispensam schema.
|
|
11
|
+
* Envia o retorno do handler.
|
|
12
|
+
*
|
|
13
|
+
* Status (B-05): sem `ctx.status()` e com um único 2xx declarado, esse 2xx vira o padrão. Com schemas
|
|
14
|
+
* de resposta declarados, um status sem schema correspondente vira 500 em vez de enviar o objeto
|
|
15
|
+
* sem filtro (S-07). Respostas sem corpo (`undefined`) dispensam schema.
|
|
16
|
+
*
|
|
17
|
+
* Content type (F-31), em ordem de prioridade:
|
|
18
|
+
* 1. o `Content-Type` definido pelo handler;
|
|
19
|
+
* 2. o `contentType` declarado no schema de resposta (com negociação pelo `Accept` se houver vários);
|
|
20
|
+
* 3. schema de resposta sem `contentType` → JSON;
|
|
21
|
+
* 4. sem schema, o tipo do valor: string → `text/plain`, Buffer/Uint8Array/stream → `application/octet-stream`,
|
|
22
|
+
* o resto → JSON.
|
|
10
23
|
*/
|
|
11
|
-
export function sendResult(ctx, response, result) {
|
|
24
|
+
export function sendResult(ctx, response, result, serializers = new Map(), mode = 'auto') {
|
|
12
25
|
if (response && !ctx.statusExplicit) {
|
|
13
|
-
const success = Object.keys(response).map(Number).filter((
|
|
26
|
+
const success = Object.keys(response).map(Number).filter((st) => st >= 200 && st < 300);
|
|
14
27
|
if (success.length === 1 && response[200] === undefined)
|
|
15
28
|
ctx.res.statusCode = success[0];
|
|
16
29
|
}
|
|
17
30
|
if (result === undefined)
|
|
18
31
|
return void ctx.res.end();
|
|
19
32
|
const status = ctx.res.statusCode;
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
ctx.res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
23
|
-
return void ctx.res.end(serialize(schema, result));
|
|
24
|
-
}
|
|
25
|
-
if (response && Object.keys(response).length > 0) {
|
|
33
|
+
const entry = response?.[status];
|
|
34
|
+
if (!entry && response && Object.keys(response).length > 0) {
|
|
26
35
|
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: no response schema for status ${status}`);
|
|
27
36
|
}
|
|
28
|
-
ctx.
|
|
37
|
+
const headerType = ctx.res.getHeader('Content-Type');
|
|
38
|
+
const explicit = mode === 'json' ? JSON_TYPE
|
|
39
|
+
: mode === 'text' ? 'text/plain'
|
|
40
|
+
: typeof headerType === 'string' ? headerType : undefined;
|
|
41
|
+
let type;
|
|
42
|
+
let schema;
|
|
43
|
+
let jsonSchemaDeclared = false;
|
|
44
|
+
if (entry && isResponseContent(entry)) {
|
|
45
|
+
schema = entry.schema;
|
|
46
|
+
const offered = typeof entry.contentType === 'string' ? [entry.contentType] : [...entry.contentType];
|
|
47
|
+
if (offered.length > 1)
|
|
48
|
+
ctx.res.appendHeader('Vary', 'Accept');
|
|
49
|
+
const chosen = explicit ?? negotiate(ctx.get('accept'), offered);
|
|
50
|
+
if (!chosen) {
|
|
51
|
+
throw new HttpError(406, 'Not Acceptable', 'NOT_ACCEPTABLE', { details: { available: offered } });
|
|
52
|
+
}
|
|
53
|
+
type = chosen;
|
|
54
|
+
}
|
|
55
|
+
else if (entry) {
|
|
56
|
+
schema = entry;
|
|
57
|
+
jsonSchemaDeclared = true;
|
|
58
|
+
type = explicit ?? JSON_TYPE;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
type = explicit ?? inferType(result);
|
|
62
|
+
}
|
|
63
|
+
write(ctx, type, result, schema, mode === 'json' || (jsonSchemaDeclared && !explicit), serializers);
|
|
64
|
+
}
|
|
65
|
+
function inferType(value) {
|
|
66
|
+
if (typeof value === 'string')
|
|
67
|
+
return 'text/plain';
|
|
68
|
+
if (value instanceof Uint8Array || value instanceof Readable)
|
|
69
|
+
return 'application/octet-stream';
|
|
70
|
+
return JSON_TYPE;
|
|
71
|
+
}
|
|
72
|
+
function write(ctx, type, value, schema, forceJson, serializers) {
|
|
73
|
+
const header = withCharset(type);
|
|
74
|
+
const base = mediaType(type);
|
|
75
|
+
const json = isJsonContentType(base);
|
|
76
|
+
if (value instanceof Readable)
|
|
77
|
+
return void ctx[kRawSend](value, header);
|
|
78
|
+
// Valores crus (string/Buffer) vão como estão, exceto quando a rota declarou um schema JSON:
|
|
79
|
+
// nesse caso `"olá"` continua sendo serializado como JSON, como sempre.
|
|
80
|
+
if ((typeof value === 'string' || value instanceof Uint8Array) && !forceJson) {
|
|
81
|
+
if (schema && typeof value === 'string')
|
|
82
|
+
ensureValid(ctx, schema, value);
|
|
83
|
+
return void ctx[kRawSend](value, header);
|
|
84
|
+
}
|
|
85
|
+
if (json) {
|
|
86
|
+
ctx.res.setHeader('Content-Type', header);
|
|
87
|
+
return void ctx.res.end(schema ? serialize(schema, value) : JSON.stringify(value));
|
|
88
|
+
}
|
|
89
|
+
const serializer = serializers.get(base);
|
|
90
|
+
if (!serializer) {
|
|
91
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: no serializer registered for "${base}". ` +
|
|
92
|
+
`Add it to createApp({ serializers: { '${base}': (data) => ... } }).`);
|
|
93
|
+
}
|
|
94
|
+
const data = schema ? ensureValid(ctx, schema, value) : value;
|
|
95
|
+
ctx[kRawSend](Buffer.from(serializer(data)), header);
|
|
96
|
+
}
|
|
97
|
+
/** Valida a saída e devolve uma cópia sem campos não declarados (S-07 em qualquer formato). */
|
|
98
|
+
function ensureValid(ctx, schema, value) {
|
|
99
|
+
const checked = stripToSchema(schema, value);
|
|
100
|
+
if (!checked.ok) {
|
|
101
|
+
throw new Error(`[sashiko] ${ctx.method} ${ctx.path}: response does not match its schema: ` +
|
|
102
|
+
checked.issues.map((i) => `${i.path} ${i.message}`).join('; '));
|
|
103
|
+
}
|
|
104
|
+
return checked.value;
|
|
29
105
|
}
|
|
30
106
|
/**
|
|
31
107
|
* Resposta de erro padrão. Só mensagens marcadas como `expose` chegam ao cliente;
|
|
@@ -44,6 +120,8 @@ export function sendError(ctx, err) {
|
|
|
44
120
|
const body = { error: exposed ? httpErr.message : GENERIC_500 };
|
|
45
121
|
if (exposed && httpErr.details !== undefined)
|
|
46
122
|
body.details = httpErr.details;
|
|
47
|
-
|
|
123
|
+
// Erros nunca passam pelo schema da rota (F-32): envio cru.
|
|
124
|
+
ctx.res.statusCode = status;
|
|
125
|
+
ctx[kRawSend](JSON.stringify(body), 'application/json; charset=utf-8');
|
|
48
126
|
}
|
|
49
127
|
//# sourceMappingURL=response.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Logger } from './logger.ts';
|
|
2
|
+
export interface ShutdownOptions {
|
|
3
|
+
/** Sinais tratados. Padrão: `['SIGINT', 'SIGTERM']`. */
|
|
4
|
+
signals?: readonly NodeJS.Signals[];
|
|
5
|
+
/** Prazo para as requisições em andamento terminarem. Padrão: 10s. */
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
/** Executado depois que o servidor fecha (banco, filas…). Se lançar, o processo sai com código 1. */
|
|
8
|
+
onShutdown?: (signal: NodeJS.Signals) => void | Promise<void>;
|
|
9
|
+
/** Chama `process.exit` ao final. Padrão: `true`. Use `false` se outro código controla o processo. */
|
|
10
|
+
exit?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** O que o coordenador precisa de cada app que está ouvindo. */
|
|
13
|
+
export interface ShutdownTarget {
|
|
14
|
+
readonly log: Logger;
|
|
15
|
+
/** Fecha o app e roda o `onShutdown`; resolve com `false` se algo falhou. */
|
|
16
|
+
shutdown(signal: NodeJS.Signals): Promise<boolean>;
|
|
17
|
+
/** Se este app quer `process.exit` ao final. */
|
|
18
|
+
readonly exitOnShutdown: boolean;
|
|
19
|
+
readonly signals: readonly NodeJS.Signals[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Coordena o encerramento de todos os apps ouvindo no processo (F-07):
|
|
23
|
+
* um único listener por sinal, compartilhado, removido quando o último app fecha.
|
|
24
|
+
*/
|
|
25
|
+
declare class ShutdownCoordinator {
|
|
26
|
+
private readonly targets;
|
|
27
|
+
private readonly listeners;
|
|
28
|
+
private shuttingDown;
|
|
29
|
+
register(target: ShutdownTarget): void;
|
|
30
|
+
unregister(target: ShutdownTarget): void;
|
|
31
|
+
private prune;
|
|
32
|
+
private onSignal;
|
|
33
|
+
}
|
|
34
|
+
export declare const shutdownCoordinator: ShutdownCoordinator;
|
|
35
|
+
export {};
|
|
36
|
+
//# 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,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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"}
|
|
@@ -2,5 +2,5 @@ export { Delete, Get, Patch, Post, Put } from './http.ts';
|
|
|
2
2
|
export { timer, type TimerOptions } from './log.ts';
|
|
3
3
|
export { Router } from './router.ts';
|
|
4
4
|
export { Schema } from './schema.ts';
|
|
5
|
-
export {
|
|
5
|
+
export { Guards, Middlewares } from './middlewares.ts';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/decorators/index.js
CHANGED
|
@@ -2,5 +2,5 @@ export { Delete, Get, Patch, Post, Put } from './http.js';
|
|
|
2
2
|
export { timer } from './log.js';
|
|
3
3
|
export { Router } from './router.js';
|
|
4
4
|
export { Schema } from './schema.js';
|
|
5
|
-
export {
|
|
5
|
+
export { Guards, Middlewares } from './middlewares.js';
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAqB,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAqB,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Middleware } from '../core/compose.ts';
|
|
2
|
+
import type { Guard } from '../core/guard.ts';
|
|
3
|
+
type ClassOrMethodContext = ClassDecoratorContext | ClassMethodDecoratorContext;
|
|
4
|
+
/**
|
|
5
|
+
* Middlewares para todas as rotas da classe ou para um método (F-01, F-30). Rodam depois dos globais
|
|
6
|
+
* e dos `middlewares` do `mount`, e antes dos guards. É o mesmo nome da opção `middlewares`.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* @Middlewares(rateLimit({ max: 10 })) // todas as rotas da classe
|
|
10
|
+
* @Router('/auth')
|
|
11
|
+
* class AuthRoutes {
|
|
12
|
+
* @Middlewares(audit) @Post('/login') login() {}
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function Middlewares(...middlewares: Middleware[]): (_value: unknown, context: ClassOrMethodContext) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Guards para todas as rotas da classe ou para um método (F-01, F-30). Rodam antes da validação;
|
|
19
|
+
* `false` responde 403. É o mesmo nome da opção `guards`.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* const isAdmin: Guard = (ctx) => ctx.state.user?.role === 'admin';
|
|
23
|
+
* @Guards(isAdmin) @Delete('/:id') remove() {}
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function Guards(...guards: Guard[]): (_value: unknown, context: ClassOrMethodContext) => void;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=middlewares.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middlewares.d.ts","sourceRoot":"","sources":["../../src/decorators/middlewares.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,KAAK,oBAAoB,GAAG,qBAAqB,GAAG,2BAA2B,CAAC;AAchF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,WAAW,EAAE,UAAU,EAAE,YAnB7B,OAAO,WAAW,oBAAoB,KAAG,IAAI,CAqBvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,YAhCd,OAAO,WAAW,oBAAoB,KAAG,IAAI,CAkCvE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getOrCreateMethod, metadataFor } from '../core/metadata.js';
|
|
2
|
+
/**
|
|
3
|
+
* Aplica `items` na classe (todas as rotas) ou no método. Decorators são avaliados de baixo para
|
|
4
|
+
* cima; inserimos no início para que a ordem de execução siga a ordem de leitura (de cima para baixo).
|
|
5
|
+
*/
|
|
6
|
+
function register(decorator, pick, items) {
|
|
7
|
+
return function (_value, context) {
|
|
8
|
+
const meta = metadataFor(context, decorator);
|
|
9
|
+
const target = context.kind === 'class' ? meta : getOrCreateMethod(meta, context.name);
|
|
10
|
+
target[pick].unshift(...items);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Middlewares para todas as rotas da classe ou para um método (F-01, F-30). Rodam depois dos globais
|
|
15
|
+
* e dos `middlewares` do `mount`, e antes dos guards. É o mesmo nome da opção `middlewares`.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* @Middlewares(rateLimit({ max: 10 })) // todas as rotas da classe
|
|
19
|
+
* @Router('/auth')
|
|
20
|
+
* class AuthRoutes {
|
|
21
|
+
* @Middlewares(audit) @Post('/login') login() {}
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function Middlewares(...middlewares) {
|
|
26
|
+
return register('Middlewares', 'middlewares', middlewares);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Guards para todas as rotas da classe ou para um método (F-01, F-30). Rodam antes da validação;
|
|
30
|
+
* `false` responde 403. É o mesmo nome da opção `guards`.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* const isAdmin: Guard = (ctx) => ctx.state.user?.role === 'admin';
|
|
34
|
+
* @Guards(isAdmin) @Delete('/:id') remove() {}
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function Guards(...guards) {
|
|
38
|
+
return register('Guards', 'guards', guards);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=middlewares.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middlewares.js","sourceRoot":"","sources":["../../src/decorators/middlewares.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIrE;;;GAGG;AACH,SAAS,QAAQ,CAAC,SAAiB,EAAE,IAA8B,EAAE,KAAyB;IAC5F,OAAO,UAAU,MAAe,EAAE,OAA6B;QAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACtF,MAAM,CAAC,IAAI,CAAe,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,GAAG,WAAyB;IACtD,OAAO,QAAQ,CAAC,aAAa,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,GAAG,MAAe;IACvC,OAAO,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { cors, type CorsOptions, type CorsOrigin } from './cors.ts';
|
|
2
2
|
export { MemoryRateLimitStore, rateLimit, type RateLimitOptions, type RateLimitStore } from './rate-limit.ts';
|
|
3
|
-
export { buildSecurityHeaders, type SecurityHeadersOptions } from './security-headers.ts';
|
|
3
|
+
export { buildSecurityHeaders, type SecurityHeadersOptions, type SecurityPreset } from './security-headers.ts';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAqC,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAA8C,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAqC,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAA8C,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,oBAAoB,EAAoD,MAAM,uBAAuB,CAAC"}
|
|
@@ -18,9 +18,11 @@ export interface SecurityHeadersOptions {
|
|
|
18
18
|
preload?: boolean;
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/** `'api'`: restritivo, para APIs (padrão). `'web'`: permite recursos do próprio site (F-31). */
|
|
22
|
+
export type SecurityPreset = 'api' | 'web';
|
|
21
23
|
/**
|
|
22
|
-
* Headers de segurança padrão (S-04) e proteções contra XSS
|
|
24
|
+
* Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
|
|
23
25
|
* Retorna uma função que aplica os headers pré-calculados na resposta.
|
|
24
26
|
*/
|
|
25
|
-
export declare function buildSecurityHeaders(
|
|
27
|
+
export declare function buildSecurityHeaders(input?: SecurityHeadersOptions | SecurityPreset): (res: ServerResponse) => void;
|
|
26
28
|
//# sourceMappingURL=security-headers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvC,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,KAAK,CAAC;IAC7C,6BAA6B;IAC7B,yBAAyB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,KAAK,CAAC;IACjF;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvC,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAChC,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,KAAK,CAAC;IAC7C,6BAA6B;IAC7B,yBAAyB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,KAAK,CAAC;IACjF;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxG;AAED,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,CAAC;AAU3C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,GAAE,sBAAsB,GAAG,cAAmB,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CA4BvH"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
const PRESETS = {
|
|
2
|
+
api: {},
|
|
3
|
+
web: {
|
|
4
|
+
contentSecurityPolicy: "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'",
|
|
5
|
+
frameOptions: 'SAMEORIGIN',
|
|
6
|
+
},
|
|
7
|
+
};
|
|
1
8
|
/**
|
|
2
|
-
* Headers de segurança padrão (S-04) e proteções contra XSS
|
|
9
|
+
* Headers de segurança padrão (S-04) e proteções contra XSS (S-02).
|
|
3
10
|
* Retorna uma função que aplica os headers pré-calculados na resposta.
|
|
4
11
|
*/
|
|
5
|
-
export function buildSecurityHeaders(
|
|
12
|
+
export function buildSecurityHeaders(input = {}) {
|
|
13
|
+
const opts = typeof input === 'string' ? PRESETS[input] : input;
|
|
6
14
|
const headers = [['X-Content-Type-Options', 'nosniff']];
|
|
7
15
|
const csp = opts.contentSecurityPolicy ?? "default-src 'none'; frame-ancestors 'none'";
|
|
8
16
|
if (csp)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/middlewares/security-headers.ts"],"names":[],"mappings":"AAqBA,MAAM,OAAO,GAAmD;IAC9D,GAAG,EAAE,EAAE;IACP,GAAG,EAAE;QACH,qBAAqB,EAAE,iFAAiF;QACxG,YAAY,EAAE,YAAY;KAC3B;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAK,GAA4C,EAAE;IACtF,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,MAAM,OAAO,GAAuB,CAAC,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC;IAE5E,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,IAAI,4CAA4C,CAAC;IACvF,IAAI,GAAG;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC,CAAC;IAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,aAAa,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1D,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IAC1C,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAC;IAEpD,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,IAAI,aAAa,CAAC;IAC7D,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,IAAI,CAAC,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACjF,IAAI,KAAK,GAAG,WAAW,GAAG,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,iBAAiB;YAAE,KAAK,IAAI,qBAAqB,CAAC;QAC1D,IAAI,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO;YAAE,KAAK,IAAI,WAAW,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO;YAAE,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -5,6 +5,17 @@ export interface ValidationIssue {
|
|
|
5
5
|
path: string;
|
|
6
6
|
message: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Valida `data` contra `schema` sobre uma cópia, removendo campos não declarados.
|
|
10
|
+
* Retorna a cópia filtrada, ou os problemas encontrados.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stripToSchema(schema: object, data: unknown): {
|
|
13
|
+
ok: true;
|
|
14
|
+
value: unknown;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
issues: ValidationIssue[];
|
|
18
|
+
};
|
|
8
19
|
/** Valida `data` contra `schema`. Retorna `null` se válido, ou a lista de problemas (B-02). */
|
|
9
20
|
export declare function validate(schema: object, data: unknown, target: ValidationTarget): ValidationIssue[] | null;
|
|
10
21
|
export declare function formatErrors(errors: ErrorObject[]): ValidationIssue[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,WAAW,EAAyB,MAAM,KAAK,CAAC;AAYzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,WAAW,EAAyB,MAAM,KAAK,CAAC;AAYzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAyBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAQpI;AAkBD,+FAA+F;AAC/F,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE,GAAG,IAAI,CAG1G;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAKrE"}
|
|
@@ -20,6 +20,24 @@ function createAjv(coerceTypes) {
|
|
|
20
20
|
// 'array' também converte valor único em array quando o schema pede array (`?tag=a` → `['a']`, B-07)
|
|
21
21
|
const coercing = createAjv('array');
|
|
22
22
|
const strict = createAjv(false);
|
|
23
|
+
// F-31: para respostas em formatos não JSON, remove TODOS os campos não declarados (como o
|
|
24
|
+
// fast-json-stringify faz no JSON) antes do serializador, sem alterar o objeto do handler.
|
|
25
|
+
const stripper = new Ajv({ allErrors: true, coerceTypes: false, useDefaults: false, removeAdditional: 'all' });
|
|
26
|
+
addFormats(stripper);
|
|
27
|
+
const stripCache = new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* Valida `data` contra `schema` sobre uma cópia, removendo campos não declarados.
|
|
30
|
+
* Retorna a cópia filtrada, ou os problemas encontrados.
|
|
31
|
+
*/
|
|
32
|
+
export function stripToSchema(schema, data) {
|
|
33
|
+
let fn = stripCache.get(schema);
|
|
34
|
+
if (!fn) {
|
|
35
|
+
fn = stripper.compile(schema);
|
|
36
|
+
stripCache.set(schema, fn);
|
|
37
|
+
}
|
|
38
|
+
const copy = data === undefined ? undefined : JSON.parse(JSON.stringify(data));
|
|
39
|
+
return fn(copy) ? { ok: true, value: copy } : { ok: false, issues: formatErrors(fn.errors ?? []) };
|
|
40
|
+
}
|
|
23
41
|
const caches = new Map([
|
|
24
42
|
[coercing, new WeakMap()],
|
|
25
43
|
[strict, new WeakMap()],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAA2C,MAAM,KAAK,CAAC;AACzE,OAAO,gBAAgB,MAAM,aAAa,CAAC;AAE3C,0EAA0E;AAC1E,0EAA0E;AAC1E,qEAAqE;AACrE,MAAM,GAAG,GAAG,CAAE,SAAiB,CAAC,OAAO,IAAI,SAAS,CAAiC,CAAC;AACtF,MAAM,UAAU,GAAG,CAAE,gBAAwB,CAAC,OAAO,IAAI,gBAAgB,CAGhE,CAAC;AAUV,SAAS,SAAS,CAAC,WAA8B;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,UAAU,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,qGAAqG;AACrG,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;AACpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;AAEhC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC;IACrB,CAAC,QAAQ,EAAE,IAAI,OAAO,EAA4B,CAAC;IACnD,CAAC,MAAM,EAAE,IAAI,OAAO,EAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,SAAS,OAAO,CAAC,MAAc,EAAE,MAAwB;IACvD,MAAM,GAAG,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAC/B,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,IAAa,EAAE,MAAwB;IAC9E,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,IAAK,CAAC,CAAC,MAAsC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChH,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,YAAY,GAAG,OAAO,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/schema/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
/** Media type sem parâmetros e em minúsculas: `"Application/JSON; charset=utf-8"` → `"application/json"`. */
|
|
2
2
|
export declare function mediaType(header: string | undefined): string;
|
|
3
|
+
/**
|
|
4
|
+
* Escolhe, entre os tipos `available` (em ordem de preferência do servidor), o melhor para o
|
|
5
|
+
* header `Accept` (F-31). Respeita q-values e curingas (`text/*`, `*\/*`). Sem `Accept`,
|
|
6
|
+
* devolve o primeiro. Retorna `undefined` quando nenhum é aceitável (→ 406).
|
|
7
|
+
*/
|
|
8
|
+
export declare function negotiate(accept: string | undefined, available: readonly string[]): string | undefined;
|
|
9
|
+
/** Acrescenta `charset=utf-8` a tipos textuais sem charset. */
|
|
10
|
+
export declare function withCharset(type: string): string;
|
|
3
11
|
/** `application/json` ou `application/*+json`, com parâmetros opcionais (B-11). */
|
|
4
12
|
export declare function isJsonContentType(header: string | undefined): boolean;
|
|
5
13
|
//# sourceMappingURL=content-type.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE5D;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGrE"}
|
|
1
|
+
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../../src/utils/content-type.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAiBtG;AAYD,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGrE"}
|