@c9up/ream 0.2.17 → 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/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 +65 -15
- package/dist/Ignitor.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/HttpKernel.ts +87 -5
- package/src/Ignitor.ts +70 -16
- 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/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
|
+
}
|
package/src/Ignitor.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { Application } from './Application.js'
|
|
|
17
17
|
import type { Console } from './console/Console.js'
|
|
18
18
|
import type { CommandLoader, Kernel as ConsoleKernelInstance } from './console/Kernel.js'
|
|
19
19
|
import { type CommandClass, isCommandClass } from './console/types.js'
|
|
20
|
+
import { HMR_PATH, hmrClientScript, hmrToken } from './dev/hmr.js'
|
|
20
21
|
import type { DirectoriesNode } from './directories.js'
|
|
21
22
|
import type { ErrorEvent } from './ErrorBoundary.js'
|
|
22
23
|
import { ErrorBoundary } from './ErrorBoundary.js'
|
|
@@ -31,6 +32,7 @@ import type { HttpKernelRequest, HttpKernelResponse } from './HttpKernel.js'
|
|
|
31
32
|
import { createHttpKernel } from './HttpKernel.js'
|
|
32
33
|
import { ExceptionHandler } from './http/Exception.js'
|
|
33
34
|
import { type ChildLoggerSource, HttpContext } from './http/HttpContext.js'
|
|
35
|
+
import { RequestValidator } from './http/RequestValidator.js'
|
|
34
36
|
import type { MiddlewareFunction } from './middleware/Pipeline.js'
|
|
35
37
|
import { MiddlewareRegistry } from './middleware/Pipeline.js'
|
|
36
38
|
import { MigrationRegistry } from './migrations/MigrationRegistry.js'
|
|
@@ -325,6 +327,11 @@ export class Ignitor {
|
|
|
325
327
|
// must stay agnostic reaches it through the container instead of importing
|
|
326
328
|
// ream at runtime.
|
|
327
329
|
this.#app.container.singleton('HttpContext', () => HttpContext)
|
|
330
|
+
// The seam a package installs its per-request messages provider or error
|
|
331
|
+
// reporter on. Bound rather than imported so an i18n provider can reach it
|
|
332
|
+
// without depending on ream — which is the only reason it is a container
|
|
333
|
+
// token and not just an import.
|
|
334
|
+
this.#app.container.singleton('requestValidator', () => RequestValidator)
|
|
328
335
|
// `appRoot` is the URL passed to `new Ignitor(new URL('../', import.meta.url))`.
|
|
329
336
|
// Providers resolve it through the container so they can interpret
|
|
330
337
|
// relative paths in config files (e.g. `pages.root: './resources/pages'`)
|
|
@@ -659,6 +666,12 @@ export class Ignitor {
|
|
|
659
666
|
}
|
|
660
667
|
|
|
661
668
|
async #phaseStart(): Promise<void> {
|
|
669
|
+
// What the injected reload script polls, mounted BEFORE the socket accepts
|
|
670
|
+
// anything. Registered in `ready()` it came up after the server was already
|
|
671
|
+
// serving, so a page loaded in that window polled a route that did not
|
|
672
|
+
// exist yet.
|
|
673
|
+
this.#registerDevReloadRoute()
|
|
674
|
+
|
|
662
675
|
// Providers start BEFORE the preload files, matching upstream's warm-up
|
|
663
676
|
// order (`providers.start()` → `starting` hooks → preloads). A preload is
|
|
664
677
|
// where an app writes its routes and kernel, and those reach for services
|
|
@@ -668,18 +681,28 @@ export class Ignitor {
|
|
|
668
681
|
await callProviderPhase(provider, 'start')
|
|
669
682
|
}
|
|
670
683
|
|
|
671
|
-
// Import preload files (routes.ts, kernel.ts, etc.)
|
|
684
|
+
// Import preload files (routes.ts, kernel.ts, etc.).
|
|
685
|
+
//
|
|
686
|
+
// Filtered on the environment first, then imported TOGETHER — upstream's
|
|
687
|
+
// `PreloadsManager.import()` is a filter followed by one `Promise.all`, and
|
|
688
|
+
// this used to walk them one at a time.
|
|
689
|
+
//
|
|
690
|
+
// The consequence is worth knowing: evaluation order between two preload
|
|
691
|
+
// files is now whichever finishes loading first, so an application cannot
|
|
692
|
+
// rely on the array's order to decide which of two overlapping routes
|
|
693
|
+
// answers. Work that must be ordered belongs inside ONE preload, where the
|
|
694
|
+
// order is the file's own — which is how upstream applications are written.
|
|
672
695
|
if (this.#reamrc?.preloads) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const env =
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
696
|
+
const applicable = this.#reamrc.preloads.filter((preloadEntry) => {
|
|
697
|
+
if (typeof preloadEntry === 'function') return true
|
|
698
|
+
const env = (preloadEntry as { environment?: string[] }).environment
|
|
699
|
+
return !env || env.includes(this.#app.getEnvironment())
|
|
700
|
+
})
|
|
701
|
+
await Promise.all(
|
|
702
|
+
applicable.map((preloadEntry) =>
|
|
703
|
+
typeof preloadEntry === 'function' ? preloadEntry() : preloadEntry.file(),
|
|
704
|
+
),
|
|
705
|
+
)
|
|
683
706
|
}
|
|
684
707
|
|
|
685
708
|
// Auto-load module files (routes.ts, etc.) from modules directory
|
|
@@ -737,6 +760,7 @@ export class Ignitor {
|
|
|
737
760
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
738
761
|
serverMiddleware: this.#server.getServerMiddleware(),
|
|
739
762
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
763
|
+
devReloadScript: this.#devReloadScript(),
|
|
740
764
|
onError: (error, ctx) => {
|
|
741
765
|
this.#errorBoundary.serviceError('HttpKernel', error, ctx.id)
|
|
742
766
|
},
|
|
@@ -859,11 +883,13 @@ export class Ignitor {
|
|
|
859
883
|
// already-loaded ESM module returns the CACHED module without re-executing
|
|
860
884
|
// its body (the thunk's specifier is opaque, so it cannot be cache-busted).
|
|
861
885
|
// The "reload" therefore destroyed every preload-registered route on the
|
|
862
|
-
// first file save and never restored them.
|
|
863
|
-
//
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
886
|
+
// first file save and never restored them.
|
|
887
|
+
//
|
|
888
|
+
// Module-level reloading now happens OUTSIDE the Ignitor: `ream dev` loads
|
|
889
|
+
// `@c9up/ream/hot`, which registers hot-hook's loader and can invalidate
|
|
890
|
+
// the ESM cache — the loader hook this comment used to ask for. What is
|
|
891
|
+
// left here only surfaces an informational event for hosts that embed the
|
|
892
|
+
// Ignitor without a supervisor.
|
|
867
893
|
if (this.isDevMode()) {
|
|
868
894
|
const watchDirs = this.#config.watchDirs ?? ['app', 'start']
|
|
869
895
|
this.#hotReloadCleanup = startHotReload({
|
|
@@ -1125,6 +1151,7 @@ export class Ignitor {
|
|
|
1125
1151
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
1126
1152
|
serverMiddleware: this.#server.getServerMiddleware(),
|
|
1127
1153
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
1154
|
+
devReloadScript: this.#devReloadScript(),
|
|
1128
1155
|
})
|
|
1129
1156
|
return this.#kernel
|
|
1130
1157
|
}
|
|
@@ -1135,6 +1162,33 @@ export class Ignitor {
|
|
|
1135
1162
|
return this.#app.inDev
|
|
1136
1163
|
}
|
|
1137
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* The reload script, in development only.
|
|
1167
|
+
*
|
|
1168
|
+
* Gated on the environment rather than on `hot-hook` being present: the
|
|
1169
|
+
* script is just as useful without it, since a restart is exactly what it
|
|
1170
|
+
* detects. In production it is `undefined` and the response is untouched.
|
|
1171
|
+
*/
|
|
1172
|
+
#devReloadScript(): ((nonce?: string) => string) | undefined {
|
|
1173
|
+
return this.isDevMode() ? hmrClientScript : undefined
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Serve the token the injected script polls.
|
|
1178
|
+
*
|
|
1179
|
+
* Registered on the router in development, next to the application's own
|
|
1180
|
+
* routes, so it goes through the same server as everything else. Plain text
|
|
1181
|
+
* and uncached: it is read once a second and its whole content is the answer.
|
|
1182
|
+
*/
|
|
1183
|
+
#registerDevReloadRoute(): void {
|
|
1184
|
+
if (!this.isDevMode()) return
|
|
1185
|
+
this.#router.get(HMR_PATH, ({ response }) => {
|
|
1186
|
+
response.header('cache-control', 'no-store')
|
|
1187
|
+
response.header('content-type', 'text/plain; charset=utf-8')
|
|
1188
|
+
response.send(hmrToken())
|
|
1189
|
+
})
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1138
1192
|
getPhase(): string {
|
|
1139
1193
|
return this.#phase
|
|
1140
1194
|
}
|