@c9up/ream 0.2.16 → 0.2.18
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/dist/Application.d.ts +54 -8
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +77 -7
- package/dist/Application.js.map +1 -1
- package/dist/HttpKernel.d.ts +10 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +61 -5
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +76 -18
- package/dist/Ignitor.js.map +1 -1
- package/dist/commands/ScheduleList.d.ts.map +1 -1
- package/dist/commands/ScheduleList.js +29 -0
- package/dist/commands/ScheduleList.js.map +1 -1
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +174 -45
- package/dist/container/Container.js.map +1 -1
- package/dist/container/types.d.ts +7 -0
- package/dist/container/types.d.ts.map +1 -1
- package/dist/dev/hmr.d.ts +37 -0
- package/dist/dev/hmr.d.ts.map +1 -0
- package/dist/dev/hmr.js +72 -0
- package/dist/dev/hmr.js.map +1 -0
- package/dist/graphql/GraphQLProvider.d.ts +14 -3
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
- package/dist/graphql/GraphQLProvider.js +14 -3
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/hot.d.ts +29 -0
- package/dist/hot.d.ts.map +1 -0
- package/dist/hot.js +135 -0
- package/dist/hot.js.map +1 -0
- package/dist/http/Exception.d.ts +2 -2
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +3 -3
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/Request.d.ts +11 -0
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +40 -0
- package/dist/http/Request.js.map +1 -1
- package/dist/http/RequestValidator.d.ts +80 -0
- package/dist/http/RequestValidator.d.ts.map +1 -0
- package/dist/http/RequestValidator.js +93 -0
- package/dist/http/RequestValidator.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts +2 -2
- package/dist/middleware/Pipeline.js +2 -2
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/migrations/MigrationRegistry.d.ts +16 -0
- package/dist/migrations/MigrationRegistry.d.ts.map +1 -1
- package/dist/migrations/MigrationRegistry.js +24 -0
- package/dist/migrations/MigrationRegistry.js.map +1 -1
- package/dist/openapi/OpenApiGenerator.d.ts +16 -0
- package/dist/openapi/OpenApiGenerator.d.ts.map +1 -1
- package/dist/openapi/OpenApiGenerator.js +76 -4
- package/dist/openapi/OpenApiGenerator.js.map +1 -1
- package/dist/openapi/OpenApiProvider.d.ts +16 -5
- package/dist/openapi/OpenApiProvider.d.ts.map +1 -1
- package/dist/openapi/OpenApiProvider.js +21 -6
- package/dist/openapi/OpenApiProvider.js.map +1 -1
- package/dist/rpc/RpcProvider.d.ts +13 -2
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +13 -2
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/storage/StaticProvider.d.ts +12 -2
- package/dist/storage/StaticProvider.d.ts.map +1 -1
- package/dist/storage/StaticProvider.js +12 -2
- package/dist/storage/StaticProvider.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +14 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +89 -8
- package/src/HttpKernel.ts +87 -5
- package/src/Ignitor.ts +82 -19
- package/src/commands/ScheduleList.ts +30 -0
- package/src/container/Container.ts +178 -48
- package/src/container/types.ts +11 -0
- package/src/dev/hmr.ts +83 -0
- package/src/graphql/GraphQLProvider.ts +14 -3
- package/src/hot.ts +165 -0
- package/src/http/Exception.ts +4 -4
- package/src/http/Request.ts +54 -0
- package/src/http/RequestValidator.ts +143 -0
- package/src/index.ts +8 -1
- package/src/middleware/Pipeline.ts +4 -4
- package/src/migrations/MigrationRegistry.ts +23 -0
- package/src/openapi/OpenApiGenerator.ts +84 -4
- package/src/openapi/OpenApiProvider.ts +21 -6
- package/src/rpc/RpcProvider.ts +13 -2
- package/src/storage/StaticProvider.ts +12 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `OpenApiProvider` — serves an auto-generated OpenAPI 3.1 spec + Swagger UI.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `start()` mounts a global middleware that, on the first GET to `/api-docs`
|
|
5
5
|
* (JSON) or `/docs` (Swagger UI), lazily generates the spec from the router's
|
|
6
|
-
* registered routes — so every route registered
|
|
7
|
-
* (the spec must NOT be generated eagerly
|
|
8
|
-
* registered their routes). Opt-out via
|
|
6
|
+
* registered routes — so every route registered while the application starts
|
|
7
|
+
* up is included (the spec must NOT be generated eagerly, before the other
|
|
8
|
+
* providers and the preloads have registered their routes). Opt-out via
|
|
9
|
+
* `config.openapi.enabled = false`.
|
|
9
10
|
*
|
|
10
11
|
* @implements Story 24.4
|
|
11
12
|
*/
|
|
@@ -31,7 +32,17 @@ export interface OpenApiProviderOptions {
|
|
|
31
32
|
export declare class OpenApiProvider extends Provider {
|
|
32
33
|
#private;
|
|
33
34
|
constructor(app: AppContext, options?: OpenApiProviderOptions);
|
|
34
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
37
|
+
*
|
|
38
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
39
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
40
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
41
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
42
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
43
|
+
* the app that meant to override it.
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
35
46
|
}
|
|
36
47
|
export default OpenApiProvider;
|
|
37
48
|
//# sourceMappingURL=OpenApiProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenApiProvider.d.ts","sourceRoot":"","sources":["../../src/openapi/OpenApiProvider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"OpenApiProvider.d.ts","sourceRoot":"","sources":["../../src/openapi/OpenApiProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAGxD,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,gBAAgB,CAAA;CAC7B;AAED,qBAAa,eAAgB,SAAQ,QAAQ;;gBAG/B,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,sBAA2B;IAKjE;;;;;;;;;OASG;IACY,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBtC;AAGD,eAAe,eAAe,CAAA"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `OpenApiProvider` — serves an auto-generated OpenAPI 3.1 spec + Swagger UI.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `start()` mounts a global middleware that, on the first GET to `/api-docs`
|
|
5
5
|
* (JSON) or `/docs` (Swagger UI), lazily generates the spec from the router's
|
|
6
|
-
* registered routes — so every route registered
|
|
7
|
-
* (the spec must NOT be generated eagerly
|
|
8
|
-
* registered their routes). Opt-out via
|
|
6
|
+
* registered routes — so every route registered while the application starts
|
|
7
|
+
* up is included (the spec must NOT be generated eagerly, before the other
|
|
8
|
+
* providers and the preloads have registered their routes). Opt-out via
|
|
9
|
+
* `config.openapi.enabled = false`.
|
|
9
10
|
*
|
|
10
11
|
* @implements Story 24.4
|
|
11
12
|
*/
|
|
@@ -18,7 +19,17 @@ export class OpenApiProvider extends Provider {
|
|
|
18
19
|
super(app);
|
|
19
20
|
this.#override = options.generator;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
24
|
+
*
|
|
25
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
26
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
27
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
28
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
29
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
30
|
+
* the app that meant to override it.
|
|
31
|
+
*/
|
|
32
|
+
async start() {
|
|
22
33
|
const config = this.app.config.get('openapi') ?? {};
|
|
23
34
|
if (config.enabled === false)
|
|
24
35
|
return;
|
|
@@ -33,10 +44,14 @@ export class OpenApiProvider extends Provider {
|
|
|
33
44
|
}
|
|
34
45
|
async #buildGenerator(config) {
|
|
35
46
|
const router = await this.app.container.make('router');
|
|
36
|
-
|
|
47
|
+
const generator = new OpenApiGenerator(router, {
|
|
37
48
|
title: config.title ?? 'API',
|
|
38
49
|
version: config.version ?? '1.0.0',
|
|
39
50
|
});
|
|
51
|
+
// The routes already name their validators; ask each one to describe
|
|
52
|
+
// itself instead of making the app repeat the shape by hand.
|
|
53
|
+
await generator.hydrateSchemas((token) => this.app.container.make(token));
|
|
54
|
+
return generator;
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
57
|
// Default export so reamrc's provider loader can `() => import('@c9up/ream/<feature>/provider')` (resolves to { default }), matching events/rpc. Named export above stays.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenApiProvider.js","sourceRoot":"","sources":["../../src/openapi/OpenApiProvider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"OpenApiProvider.js","sourceRoot":"","sources":["../../src/openapi/OpenApiProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAoB1D,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAClC,SAAS,CAAmB;IAErC,YAAY,GAAe,EAAE,UAAkC,EAAE;QAC/D,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;IACpC,CAAC;IAED;;;;;;;;;OASG;IACM,KAAK,CAAC,KAAK;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAoB,SAAS,CAAC,IAAI,EAAE,CAAA;QACtE,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;YAAE,OAAM;QAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAA;QACxE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAS,QAAQ,CAAC,CAAA;QAC9D,MAAM,UAAU,GAAG,IAAI,iBAAiB,CAAC;YACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE;SACjC,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3D,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,MAAyB;QAC7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAS,QAAQ,CAAC,CAAA;QAC9D,MAAM,SAAS,GAAG,IAAI,gBAAgB,CAAC,MAAM,EAAE;YAC7C,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK;YAC5B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO;SACnC,CAAC,CAAA;QACF,qEAAqE;QACrE,6DAA6D;QAC7D,MAAM,SAAS,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAU,KAAK,CAAC,CAAC,CAAA;QAClF,OAAO,SAAS,CAAA;IAClB,CAAC;CACF;AAED,2KAA2K;AAC3K,eAAe,eAAe,CAAA"}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* rpc.method('task.validate', handler).guard('jwt')
|
|
10
10
|
* rpc.namespace('user', UserController)
|
|
11
11
|
*
|
|
12
|
-
* - `
|
|
12
|
+
* - `start()` — mount `POST <config.rpc.path ?? '/rpc'>` on the core router,
|
|
13
13
|
* dispatching every request body (single or batch) to `RpcRouter.handle()`.
|
|
14
|
+
* In `start`, which is the phase upstream documents for routes.
|
|
14
15
|
*
|
|
15
16
|
* @implements Story 24.1, 24.2
|
|
16
17
|
*/
|
|
@@ -38,7 +39,17 @@ export declare class RpcProvider extends Provider {
|
|
|
38
39
|
readonly rpc: RpcRouter;
|
|
39
40
|
constructor(app: AppContext, options?: RpcProviderOptions);
|
|
40
41
|
register(): void;
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
44
|
+
*
|
|
45
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
46
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
47
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
48
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
49
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
50
|
+
* the app that meant to override it.
|
|
51
|
+
*/
|
|
52
|
+
start(): Promise<void>;
|
|
42
53
|
}
|
|
43
54
|
export default RpcProvider;
|
|
44
55
|
//# sourceMappingURL=RpcProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RpcProvider.d.ts","sourceRoot":"","sources":["../../src/rpc/RpcProvider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"RpcProvider.d.ts","sourceRoot":"","sources":["../../src/rpc/RpcProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,qBAAa,WAAY,SAAQ,QAAQ;;IACvC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;gBAGX,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB;IAKpD,QAAQ,IAAI,IAAI;IAmBzB;;;;;;;;;OASG;IACY,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAatC;AAMD,eAAe,WAAW,CAAA"}
|
package/dist/rpc/RpcProvider.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* rpc.method('task.validate', handler).guard('jwt')
|
|
10
10
|
* rpc.namespace('user', UserController)
|
|
11
11
|
*
|
|
12
|
-
* - `
|
|
12
|
+
* - `start()` — mount `POST <config.rpc.path ?? '/rpc'>` on the core router,
|
|
13
13
|
* dispatching every request body (single or batch) to `RpcRouter.handle()`.
|
|
14
|
+
* In `start`, which is the phase upstream documents for routes.
|
|
14
15
|
*
|
|
15
16
|
* @implements Story 24.1, 24.2
|
|
16
17
|
*/
|
|
@@ -39,7 +40,17 @@ export class RpcProvider extends Provider {
|
|
|
39
40
|
container.singleton('rpc', () => this.rpc);
|
|
40
41
|
this.#registered = true;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
45
|
+
*
|
|
46
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
47
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
48
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
49
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
50
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
51
|
+
* the app that meant to override it.
|
|
52
|
+
*/
|
|
53
|
+
async start() {
|
|
43
54
|
const config = this.app.config.get('rpc');
|
|
44
55
|
const path = config?.path ?? '/rpc';
|
|
45
56
|
const router = await this.app.container.make('router');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RpcProvider.js","sourceRoot":"","sources":["../../src/rpc/RpcProvider.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"RpcProvider.js","sourceRoot":"","sources":["../../src/rpc/RpcProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAoB1C,MAAM,OAAO,WAAY,SAAQ,QAAQ;IAC9B,GAAG,CAAW;IACvB,WAAW,GAAG,KAAK,CAAA;IAEnB,YAAY,GAAe,EAAE,UAA8B,EAAE;QAC3D,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,SAAS,EAAE,CAAA;IAC9C,CAAC;IAEQ,QAAQ;QACf,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAA;QACpC,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,0DAA0D;YAC1D,8DAA8D;YAC9D,IAAI,IAAI,CAAC,WAAW;gBAAE,OAAM;YAC5B,MAAM,IAAI,SAAS,CACjB,mCAAmC,EACnC,gEAAgE,EAChE;gBACE,IAAI,EAAE,kGAAkG;aACzG,CACF,CAAA;QACH,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;QAChC,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;IACzB,CAAC;IAED;;;;;;;;;OASG;IACM,KAAK,CAAC,KAAK;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAY,KAAK,CAAC,CAAA;QACpD,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,MAAM,CAAA;QACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAS,QAAQ,CAAC,CAAA;QAC9D,0EAA0E;QAC1E,2DAA2D;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3E,sEAAsE;QACtE,wEAAwE;QACxE,gCAAgC;QAChC,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,EAAE,CAAA;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAA;IAC/C,CAAC;CACF;AAED,uEAAuE;AACvE,+EAA+E;AAC/E,gFAAgF;AAChF,4BAA4B;AAC5B,eAAe,WAAW,CAAA"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `StaticProvider` — serves static files from a directory.
|
|
3
3
|
*
|
|
4
4
|
* Opt-in: does nothing unless `config.static.root` is set (or a middleware is
|
|
5
|
-
* injected for tests). When configured, `
|
|
5
|
+
* injected for tests). When configured, `start()` mounts a global
|
|
6
6
|
* `StaticMiddleware` (path-traversal + symlink-escape guarded) that serves
|
|
7
7
|
* matching requests and falls through (`next()`) otherwise.
|
|
8
8
|
*
|
|
@@ -21,7 +21,17 @@ export interface StaticProviderOptions {
|
|
|
21
21
|
export declare class StaticProvider extends Provider {
|
|
22
22
|
#private;
|
|
23
23
|
constructor(app: AppContext, options?: StaticProviderOptions);
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
26
|
+
*
|
|
27
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
28
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
29
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
30
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
31
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
32
|
+
* the app that meant to override it.
|
|
33
|
+
*/
|
|
34
|
+
start(): Promise<void>;
|
|
25
35
|
}
|
|
26
36
|
export default StaticProvider;
|
|
27
37
|
//# sourceMappingURL=StaticProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StaticProvider.d.ts","sourceRoot":"","sources":["../../src/storage/StaticProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAqB,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE3E,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAC9B;AAED,qBAAa,cAAe,SAAQ,QAAQ;;gBAG9B,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,qBAA0B;
|
|
1
|
+
{"version":3,"file":"StaticProvider.d.ts","sourceRoot":"","sources":["../../src/storage/StaticProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAqB,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAE3E,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAC9B;AAED,qBAAa,cAAe,SAAQ,QAAQ;;gBAG9B,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,qBAA0B;IAKhE;;;;;;;;;OASG;IACY,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAOtC;AAGD,eAAe,cAAc,CAAA"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `StaticProvider` — serves static files from a directory.
|
|
3
3
|
*
|
|
4
4
|
* Opt-in: does nothing unless `config.static.root` is set (or a middleware is
|
|
5
|
-
* injected for tests). When configured, `
|
|
5
|
+
* injected for tests). When configured, `start()` mounts a global
|
|
6
6
|
* `StaticMiddleware` (path-traversal + symlink-escape guarded) that serves
|
|
7
7
|
* matching requests and falls through (`next()`) otherwise.
|
|
8
8
|
*
|
|
@@ -19,7 +19,17 @@ export class StaticProvider extends Provider {
|
|
|
19
19
|
super(app);
|
|
20
20
|
this.#override = options.middleware;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Routes and server middleware go in `start`, not `boot`.
|
|
24
|
+
*
|
|
25
|
+
* Upstream documents that phase for exactly this, and the order is what makes
|
|
26
|
+
* it matter: providers boot, then providers START, then the preloads run —
|
|
27
|
+
* and the preloads are where an application writes its own routes and kernel.
|
|
28
|
+
* Mounted in `boot`, a framework route landed ahead of every application
|
|
29
|
+
* route, so an overlapping path was answered by the framework rather than by
|
|
30
|
+
* the app that meant to override it.
|
|
31
|
+
*/
|
|
32
|
+
async start() {
|
|
23
33
|
const config = this.app.config.get('static');
|
|
24
34
|
const middleware = this.#override ?? (config?.root ? new StaticMiddleware(config) : undefined);
|
|
25
35
|
if (!middleware)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StaticProvider.js","sourceRoot":"","sources":["../../src/storage/StaticProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAqB,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAO3E,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACjC,SAAS,CAAmB;IAErC,YAAY,GAAe,EAAE,UAAiC,EAAE;QAC9D,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,UAAU,CAAA;IACrC,CAAC;
|
|
1
|
+
{"version":3,"file":"StaticProvider.js","sourceRoot":"","sources":["../../src/storage/StaticProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAqB,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAO3E,MAAM,OAAO,cAAe,SAAQ,QAAQ;IACjC,SAAS,CAAmB;IAErC,YAAY,GAAe,EAAE,UAAiC,EAAE;QAC9D,KAAK,CAAC,GAAG,CAAC,CAAA;QACV,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,UAAU,CAAA;IACrC,CAAC;IAED;;;;;;;;;OASG;IACM,KAAK,CAAC,KAAK;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAe,QAAQ,CAAC,CAAA;QAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QAC9F,IAAI,CAAC,UAAU;YAAE,OAAM,CAAC,2BAA2B;QACnD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAS,QAAQ,CAAC,CAAA;QAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3D,CAAC;CACF;AAED,yKAAyK;AACzK,eAAe,cAAc,CAAA"}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/ream",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "Ream — Rust-powered Node.js application framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -202,6 +202,14 @@
|
|
|
202
202
|
"./bodyparser_middleware": {
|
|
203
203
|
"types": "./dist/bodyparser/BodyParserMiddleware.d.ts",
|
|
204
204
|
"import": "./dist/bodyparser/BodyParserMiddleware.js"
|
|
205
|
+
},
|
|
206
|
+
"./hot": {
|
|
207
|
+
"types": "./dist/hot.d.ts",
|
|
208
|
+
"import": "./dist/hot.js"
|
|
209
|
+
},
|
|
210
|
+
"./dev/hmr": {
|
|
211
|
+
"types": "./dist/dev/hmr.d.ts",
|
|
212
|
+
"import": "./dist/dev/hmr.js"
|
|
205
213
|
}
|
|
206
214
|
},
|
|
207
215
|
"files": [
|
|
@@ -225,7 +233,8 @@
|
|
|
225
233
|
},
|
|
226
234
|
"peerDependencies": {
|
|
227
235
|
"@c9up/comet": "^0.1.0",
|
|
228
|
-
"@c9up/quasar": "^0.1.0"
|
|
236
|
+
"@c9up/quasar": "^0.1.0",
|
|
237
|
+
"hot-hook": "^1.0.0"
|
|
229
238
|
},
|
|
230
239
|
"peerDependenciesMeta": {
|
|
231
240
|
"@c9up/comet": {
|
|
@@ -233,6 +242,9 @@
|
|
|
233
242
|
},
|
|
234
243
|
"@c9up/quasar": {
|
|
235
244
|
"optional": true
|
|
245
|
+
},
|
|
246
|
+
"hot-hook": {
|
|
247
|
+
"optional": true
|
|
236
248
|
}
|
|
237
249
|
},
|
|
238
250
|
"devDependencies": {
|
|
Binary file
|
package/src/Application.ts
CHANGED
|
@@ -56,11 +56,26 @@ export type ApplicationState =
|
|
|
56
56
|
| 'terminating'
|
|
57
57
|
| 'terminated'
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* What the application keeps of the rc file.
|
|
61
|
+
*
|
|
62
|
+
* Structurally a subset of `ReamrcConfig`, declared here rather than imported:
|
|
63
|
+
* that type lives in `Ignitor.ts`, which imports this module, and naming it
|
|
64
|
+
* from here would close the cycle. Only the keys something reads back through
|
|
65
|
+
* `app.rcFile` are spelt out; the rest rides along untouched.
|
|
66
|
+
*/
|
|
67
|
+
export interface ReamrcContents {
|
|
68
|
+
directories?: Partial<DirectoriesNode>
|
|
69
|
+
/** `app/modules/**` auto-loading — opt-in, and scoped to named files. */
|
|
70
|
+
modules?: { path?: string; autoload?: string[] }
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
export class Application implements AppContext {
|
|
60
74
|
readonly container: Container
|
|
61
75
|
readonly config: ConfigStore
|
|
62
76
|
#appRoot?: URL
|
|
63
77
|
#directories: DirectoriesNode = { ...defaultDirectories }
|
|
78
|
+
#rcFile: ReamrcContents = {}
|
|
64
79
|
#providers: ProviderContract[] = []
|
|
65
80
|
#booted = false
|
|
66
81
|
#environment: AppEnvironment = 'unknown'
|
|
@@ -68,6 +83,10 @@ export class Application implements AppContext {
|
|
|
68
83
|
#packageJsonRead = false
|
|
69
84
|
#packageJsonCache: { name?: string; version?: string } | undefined
|
|
70
85
|
#mode: ApplicationMode = 'run'
|
|
86
|
+
/** Hooks that run — in reverse — when `terminate()` begins. */
|
|
87
|
+
#terminatingHooks: Array<() => Promise<void> | void> = []
|
|
88
|
+
/** Re-entrancy guard: two signals must terminate the application once. */
|
|
89
|
+
#terminating = false
|
|
71
90
|
#bootingHooks: Array<() => Promise<void> | void> = []
|
|
72
91
|
#bootedHooks: Array<() => Promise<void> | void> = []
|
|
73
92
|
#shutdownHooks: Array<() => Promise<void> | void> = []
|
|
@@ -161,14 +180,27 @@ export class Application implements AppContext {
|
|
|
161
180
|
return this.#directories
|
|
162
181
|
}
|
|
163
182
|
|
|
183
|
+
/**
|
|
184
|
+
* The parsed rc file (AdonisJS `app.rcFile`).
|
|
185
|
+
*
|
|
186
|
+
* Kept whole, not just the keys this class acts on. A console command has the
|
|
187
|
+
* application and nothing else, so anything it needs to explain itself — why
|
|
188
|
+
* `app/modules/**` loaded nothing, say — has to be reachable from here. Empty
|
|
189
|
+
* until `rcContents()` has run.
|
|
190
|
+
*/
|
|
191
|
+
get rcFile(): ReamrcContents {
|
|
192
|
+
return this.#rcFile
|
|
193
|
+
}
|
|
194
|
+
|
|
164
195
|
/**
|
|
165
196
|
* Feed the loaded rc file to the application (AdonisJS `rcContents`).
|
|
166
197
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
198
|
+
* `directories` is merged over the defaults; the rest is stored as-is and
|
|
199
|
+
* read back through {@link rcFile}. Providers, preloads, commands and test
|
|
200
|
+
* suites are still consumed by the Ignitor and the CLI, which own them.
|
|
170
201
|
*/
|
|
171
|
-
rcContents(value:
|
|
202
|
+
rcContents(value: ReamrcContents): this {
|
|
203
|
+
this.#rcFile = value
|
|
172
204
|
if (value.directories) {
|
|
173
205
|
// Skip the undefined entries a `Partial` may carry: spreading them would
|
|
174
206
|
// blank a default rather than leave it alone.
|
|
@@ -576,12 +608,56 @@ export class Application implements AppContext {
|
|
|
576
608
|
}
|
|
577
609
|
|
|
578
610
|
/**
|
|
579
|
-
*
|
|
580
|
-
*
|
|
611
|
+
* Register a hook that runs when the application begins to terminate
|
|
612
|
+
* (AdonisJS `app.terminating`).
|
|
613
|
+
*
|
|
614
|
+
* This is how the HTTP server closes on a signal: whoever opened the socket
|
|
615
|
+
* registers a hook that closes it, and `terminate()` runs them in reverse —
|
|
616
|
+
* last opened, first closed. Upstream's HTTP process does exactly this.
|
|
617
|
+
*/
|
|
618
|
+
terminating(callback: () => Promise<void> | void): void {
|
|
619
|
+
this.#terminatingHooks.push(callback)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Graceful shutdown (AdonisJS `app.terminate()`).
|
|
624
|
+
*
|
|
625
|
+
* Runs the `terminating` hooks in reverse, then shuts the providers down.
|
|
626
|
+
* Two things it deliberately does NOT do:
|
|
627
|
+
*
|
|
628
|
+
* - It does not close the HTTP server itself. Whoever opened the socket
|
|
629
|
+
* registers a `terminating` hook for it; the application does not know
|
|
630
|
+
* what a server is. Terminating without one used to skip the close, the
|
|
631
|
+
* error boundary and the service locators entirely.
|
|
632
|
+
* - It does not call `process.exit()`. Exiting from here cut the shutdown
|
|
633
|
+
* short whenever a second path was already draining the server — and the
|
|
634
|
+
* documented signal wiring set up exactly that race. The process ends when
|
|
635
|
+
* nothing is left holding the loop, which is what "graceful" means.
|
|
636
|
+
*
|
|
637
|
+
* Re-entrant calls are ignored, so a SIGTERM handler and a crash handler
|
|
638
|
+
* firing together terminate once.
|
|
581
639
|
*/
|
|
582
640
|
async terminate(): Promise<void> {
|
|
583
|
-
|
|
584
|
-
|
|
641
|
+
if (this.#terminating) return
|
|
642
|
+
this.#terminating = true
|
|
643
|
+
const errors: unknown[] = []
|
|
644
|
+
for (const hook of [...this.#terminatingHooks].reverse()) {
|
|
645
|
+
try {
|
|
646
|
+
await hook()
|
|
647
|
+
} catch (err) {
|
|
648
|
+
errors.push(err)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
this.#terminatingHooks = []
|
|
652
|
+
try {
|
|
653
|
+
await this.shutdown()
|
|
654
|
+
} catch (err) {
|
|
655
|
+
errors.push(err)
|
|
656
|
+
}
|
|
657
|
+
if (errors.length === 1) throw errors[0]
|
|
658
|
+
if (errors.length > 1) {
|
|
659
|
+
throw new AggregateError(errors, 'Application.terminate() completed with errors')
|
|
660
|
+
}
|
|
585
661
|
}
|
|
586
662
|
|
|
587
663
|
// ─── Provider lifecycle ───────────────────────────────────
|
|
@@ -640,6 +716,11 @@ export class Application implements AppContext {
|
|
|
640
716
|
* from closing their resources (DB pools, queues). Errors are aggregated.
|
|
641
717
|
*/
|
|
642
718
|
async shutdown(): Promise<void> {
|
|
719
|
+
// Idempotent: `terminate()` runs the `terminating` hooks and one of them is
|
|
720
|
+
// the Ignitor's `stop()`, which shuts the application down on its way. The
|
|
721
|
+
// second pass must not call every provider's `shutdown()` again — a pool
|
|
722
|
+
// closed twice, a socket destroyed twice.
|
|
723
|
+
if (this.#state === 'terminated') return
|
|
643
724
|
const errors: unknown[] = []
|
|
644
725
|
this.#state = 'terminating'
|
|
645
726
|
|
package/src/HttpKernel.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface HttpKernelConfig {
|
|
|
32
32
|
container?: Container
|
|
33
33
|
exceptionHandler?: ExceptionHandler
|
|
34
34
|
serverMiddleware?: MiddlewareFunction[]
|
|
35
|
+
/**
|
|
36
|
+
* A `<script>` appended to every HTML response, dev only.
|
|
37
|
+
*
|
|
38
|
+
* Set by the Ignitor when the application runs in development; `undefined`
|
|
39
|
+
* everywhere else, so a production response is byte-for-byte what the
|
|
40
|
+
* handler produced. It lives here rather than in a middleware because this
|
|
41
|
+
* is the one place every response passes through on its way out, whichever
|
|
42
|
+
* route or error handler produced it.
|
|
43
|
+
*/
|
|
44
|
+
devReloadScript?: (nonce?: string) => string
|
|
35
45
|
routerMiddleware?: MiddlewareFunction[]
|
|
36
46
|
onError?: (error: unknown, ctx: HttpContext) => void
|
|
37
47
|
debug?: boolean
|
|
@@ -345,7 +355,7 @@ export function createHttpKernel(
|
|
|
345
355
|
correlationId,
|
|
346
356
|
)
|
|
347
357
|
}
|
|
348
|
-
const serialized = await serializeResponse(ctx)
|
|
358
|
+
const serialized = await serializeResponse(ctx, config.devReloadScript)
|
|
349
359
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
350
360
|
// now — after the answer is ready, before we hand it back.
|
|
351
361
|
ctx.response.runFinishCallbacks()
|
|
@@ -401,7 +411,7 @@ export function createHttpKernel(
|
|
|
401
411
|
correlationId,
|
|
402
412
|
)
|
|
403
413
|
}
|
|
404
|
-
const serialized = await serializeResponse(ctx)
|
|
414
|
+
const serialized = await serializeResponse(ctx, config.devReloadScript)
|
|
405
415
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
406
416
|
// now — after the answer is ready, before we hand it back.
|
|
407
417
|
ctx.response.runFinishCallbacks()
|
|
@@ -452,15 +462,87 @@ function readControllerGuardMetadata(
|
|
|
452
462
|
}
|
|
453
463
|
}
|
|
454
464
|
|
|
455
|
-
async function serializeResponse(
|
|
465
|
+
async function serializeResponse(
|
|
466
|
+
ctx: HttpContext,
|
|
467
|
+
devReloadScript?: (nonce?: string) => string,
|
|
468
|
+
): Promise<HttpKernelResponse> {
|
|
456
469
|
// The terminal step, as AdonisJS' server calls `response.finish()`: it
|
|
457
470
|
// stamps the request id and waits for a body still being produced.
|
|
458
471
|
await ctx.response.finish()
|
|
459
472
|
const streamId = ctx.response.getStreamId()
|
|
473
|
+
const headers = ctx.response.getHeaders()
|
|
474
|
+
const body = withDevReloadScript(
|
|
475
|
+
ctx.response.getBody(),
|
|
476
|
+
headers,
|
|
477
|
+
streamId,
|
|
478
|
+
devReloadScript === undefined ? undefined : devReloadScript(ctx.response.nonce),
|
|
479
|
+
)
|
|
460
480
|
return {
|
|
461
481
|
status: ctx.response.getStatus(),
|
|
462
|
-
headers: ctx.response.
|
|
463
|
-
body
|
|
482
|
+
headers: reconcileRepresentation(headers, ctx.response.getBody(), body),
|
|
483
|
+
body,
|
|
464
484
|
...(streamId !== undefined ? { streamId } : {}),
|
|
465
485
|
}
|
|
466
486
|
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Keep the representation headers describing the body that is actually sent.
|
|
490
|
+
*
|
|
491
|
+
* `content-length`, `etag` and the digest headers are computed from the body
|
|
492
|
+
* BEFORE the dev reload script is appended. Left alone they describe the
|
|
493
|
+
* previous one: a `content-length` short by the length of the script truncates
|
|
494
|
+
* it, and an `etag` computed before the change caches modified content under an
|
|
495
|
+
* unchanged validator.
|
|
496
|
+
*
|
|
497
|
+
* `content-length` is recomputed because it is cheap and exact; the validators
|
|
498
|
+
* are DROPPED rather than recomputed, because a validator this layer invents is
|
|
499
|
+
* not the one the handler meant.
|
|
500
|
+
*/
|
|
501
|
+
function reconcileRepresentation(
|
|
502
|
+
headers: Record<string, string>,
|
|
503
|
+
original: string,
|
|
504
|
+
sent: string,
|
|
505
|
+
): Record<string, string> {
|
|
506
|
+
if (sent === original) return headers
|
|
507
|
+
const reconciled = { ...headers }
|
|
508
|
+
if (reconciled['content-length'] !== undefined) {
|
|
509
|
+
reconciled['content-length'] = String(Buffer.byteLength(sent, 'utf8'))
|
|
510
|
+
}
|
|
511
|
+
// Every validator, not the two that came to mind. `Content-Digest` and
|
|
512
|
+
// `Repr-Digest` (RFC 9530) are what replaced `Digest`, and leaving them
|
|
513
|
+
// describing the old body is the same defect one header along — a client
|
|
514
|
+
// that checks them rejects a response that is perfectly fine.
|
|
515
|
+
for (const name of [
|
|
516
|
+
'etag',
|
|
517
|
+
'content-md5',
|
|
518
|
+
'digest',
|
|
519
|
+
'content-digest',
|
|
520
|
+
'repr-digest',
|
|
521
|
+
'want-digest',
|
|
522
|
+
'want-content-digest',
|
|
523
|
+
'want-repr-digest',
|
|
524
|
+
]) {
|
|
525
|
+
delete reconciled[name]
|
|
526
|
+
}
|
|
527
|
+
return reconciled
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Append the dev reload script to an HTML body.
|
|
532
|
+
*
|
|
533
|
+
* Only HTML, and never a stream: appending to an SSE or a download would
|
|
534
|
+
* corrupt it, and a page is the only thing that can act on the script anyway.
|
|
535
|
+
* Before `</body>` when there is one, so the script is inside the document
|
|
536
|
+
* rather than trailing after it.
|
|
537
|
+
*/
|
|
538
|
+
function withDevReloadScript(
|
|
539
|
+
body: string,
|
|
540
|
+
headers: Record<string, string>,
|
|
541
|
+
streamId: string | undefined,
|
|
542
|
+
script?: string,
|
|
543
|
+
): string {
|
|
544
|
+
if (script === undefined || streamId !== undefined || body === '') return body
|
|
545
|
+
if (!(headers['content-type'] ?? '').includes('text/html')) return body
|
|
546
|
+
const closing = body.lastIndexOf('</body>')
|
|
547
|
+
return closing === -1 ? body + script : body.slice(0, closing) + script + body.slice(closing)
|
|
548
|
+
}
|