@c9up/ream 0.2.6 → 0.2.7
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/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +17 -0
- package/dist/Ignitor.js.map +1 -1
- package/dist/bootstrap/hyper-server.js.map +1 -1
- package/dist/helpers/is.d.ts.map +1 -1
- package/dist/helpers/is.js +1 -4
- package/dist/helpers/is.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/scheduler/Scheduler.d.ts.map +1 -1
- package/dist/scheduler/Scheduler.js +12 -4
- package/dist/scheduler/Scheduler.js.map +1 -1
- package/dist/services/logger.d.ts +29 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +40 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/session/Session.js.map +1 -1
- package/dist/testing/TestClient.d.ts +18 -3
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +45 -4
- package/dist/testing/TestClient.js.map +1 -1
- package/dist/testing/testUtils.d.ts +62 -0
- package/dist/testing/testUtils.d.ts.map +1 -0
- package/dist/testing/testUtils.js +77 -0
- package/dist/testing/testUtils.js.map +1 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +9 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Ignitor.ts +17 -1
- package/src/bootstrap/hyper-server.ts +1 -1
- package/src/helpers/is.ts +1 -6
- package/src/index.ts +1 -1
- package/src/scheduler/Scheduler.ts +10 -4
- package/src/services/logger.ts +49 -0
- package/src/session/Session.ts +2 -2
- package/src/testing/TestClient.ts +47 -5
- package/src/testing/testUtils.ts +90 -0
|
@@ -34,23 +34,59 @@ export class TestClient {
|
|
|
34
34
|
this.#sessionSeeder = options.sessionSeeder ?? null;
|
|
35
35
|
}
|
|
36
36
|
#server = null;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* In flight or settled, so concurrent callers share one boot.
|
|
39
|
+
*
|
|
40
|
+
* Also what makes `boot()` optional: the first request awaits this, starting
|
|
41
|
+
* it if nobody has. A test file that never issues a request never starts a
|
|
42
|
+
* server — which is the difference between a unit file paying for one and
|
|
43
|
+
* not, and at ~400ms a file that is most of a campaign's time.
|
|
44
|
+
*/
|
|
45
|
+
#booting = null;
|
|
46
|
+
/**
|
|
47
|
+
* Boot the application on a random port. Idempotent: a second call awaits the
|
|
48
|
+
* first rather than starting a second server.
|
|
49
|
+
*
|
|
50
|
+
* Optional — a request boots on its own. Call it when you need `port` before
|
|
51
|
+
* issuing one (an SSE or WebSocket connection opened with `fetch`).
|
|
52
|
+
*/
|
|
38
53
|
async boot() {
|
|
54
|
+
this.#booting ??= this.#startServer();
|
|
55
|
+
return this.#booting;
|
|
56
|
+
}
|
|
57
|
+
async #startServer() {
|
|
39
58
|
this.#server = await this.#bootFn(0);
|
|
40
59
|
this.#port = this.#server.port;
|
|
41
60
|
}
|
|
42
|
-
/**
|
|
61
|
+
/** Whether a server is currently running for this client. */
|
|
62
|
+
get booted() {
|
|
63
|
+
return this.#server !== null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Close the server. A no-op when nothing was ever started, so a teardown can
|
|
67
|
+
* call it unconditionally against a client whose file issued no request.
|
|
68
|
+
*/
|
|
43
69
|
async close() {
|
|
70
|
+
// Awaited first: closing while a boot is in flight would otherwise leave
|
|
71
|
+
// the server that boot is about to assign running with nobody holding it.
|
|
72
|
+
if (this.#booting)
|
|
73
|
+
await this.#booting.catch(() => { });
|
|
74
|
+
this.#booting = null;
|
|
44
75
|
if (this.#server) {
|
|
45
76
|
await this.#server.close();
|
|
46
77
|
this.#server = null;
|
|
78
|
+
this.#port = 0;
|
|
47
79
|
}
|
|
48
80
|
}
|
|
49
81
|
/**
|
|
50
82
|
* Ephemeral port the booted server bound to. Useful for long-lived
|
|
51
83
|
* connections (SSE / WebSocket) that the fluent buffered request
|
|
52
84
|
* surface can't model — open them with `fetch` against
|
|
53
|
-
* `http://127.0.0.1:${client.port}/...`.
|
|
85
|
+
* `http://127.0.0.1:${client.port}/...`.
|
|
86
|
+
*
|
|
87
|
+
* Returns 0 while no server is running. Requests boot on their own, so a
|
|
88
|
+
* client that has only been constructed has not bound a port yet — `await
|
|
89
|
+
* client.boot()` first when you need this before issuing one.
|
|
54
90
|
*/
|
|
55
91
|
get port() {
|
|
56
92
|
return this.#port;
|
|
@@ -102,7 +138,12 @@ export class TestClient {
|
|
|
102
138
|
* `form()`. The lower-level `request()` (below) stays for raw control.
|
|
103
139
|
*/
|
|
104
140
|
fluent(method, path) {
|
|
105
|
-
const sender = (m, p, init) =>
|
|
141
|
+
const sender = async (m, p, init) => {
|
|
142
|
+
// Boots on the first request and no earlier. `#port` is read here rather
|
|
143
|
+
// than captured, so it is the port this boot bound to.
|
|
144
|
+
await this.boot();
|
|
145
|
+
return sendRequest(this.#port, m, p, { ...this.#headers, ...init.headers }, init.body.toString('utf8'), init.timeoutMs);
|
|
146
|
+
};
|
|
106
147
|
return new RequestBuilder(sender, method, path, this.#auth, this.#sessionSeeder);
|
|
107
148
|
}
|
|
108
149
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestClient.js","sourceRoot":"","sources":["../../src/testing/TestClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,EAIL,cAAc,GAGf,MAAM,qBAAqB,CAAA;AAE5B,4EAA4E;AAC5E,6EAA6E;AAC7E,OAAO,EAAE,WAAW,EAAqB,MAAM,qBAAqB,CAAA;AAcpE,MAAM,OAAO,UAAU;IACrB,KAAK,GAAG,CAAC,CAAA;IACT,QAAQ,GAAS,EAAE,CAAA;IACnB,OAAO,CAAgF;IACvF,KAAK,CAAqB;IAC1B,cAAc,CAAsB;IACpC,OAAO,CAAsB;IAE7B,YACE,MAAsF,EACtF,UAKI,EAAE;QAEN,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAA;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAA;QACrC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAA;IACrD,CAAC;IAED,OAAO,GAA+D,IAAI,CAAA;IAE1E,
|
|
1
|
+
{"version":3,"file":"TestClient.js","sourceRoot":"","sources":["../../src/testing/TestClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities, in the shape AdonisJS gives them: `testUtils.httpServer()`
|
|
3
|
+
* starts the application's HTTP server for a test run and hands back the
|
|
4
|
+
* function that closes it.
|
|
5
|
+
*
|
|
6
|
+
* // tests/bootstrap.ts
|
|
7
|
+
* configureSuite(suite) {
|
|
8
|
+
* if (['functional', 'e2e'].includes(suite.name)) {
|
|
9
|
+
* return suite.setup(() => testUtils.httpServer().start())
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The point of the shape is WHERE the server starts. A suite that does not
|
|
14
|
+
* declare the hook never starts one, so a unit suite pays nothing — which is
|
|
15
|
+
* the same saving a lazily-booted client buys, obtained by scoping instead of
|
|
16
|
+
* by deferring, and obtained per SUITE rather than per file.
|
|
17
|
+
*
|
|
18
|
+
* `start()` returning its own teardown is what makes it a one-liner in a hook:
|
|
19
|
+
* a setup hook may return its undo, so there is no matching `teardown` to
|
|
20
|
+
* write and no way to forget it.
|
|
21
|
+
*/
|
|
22
|
+
import { TestClient } from './TestClient.js';
|
|
23
|
+
/** Boots the app on `port` and reports the port it bound plus how to stop it. */
|
|
24
|
+
export type BootServer = (port: number) => Promise<{
|
|
25
|
+
port: number;
|
|
26
|
+
close: () => Promise<void> | void;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* What `testUtils.httpServer()` returns — AdonisJS `HttpServerUtils`.
|
|
30
|
+
*/
|
|
31
|
+
export declare class HttpServerUtils {
|
|
32
|
+
#private;
|
|
33
|
+
constructor(boot: BootServer, onStarted: (client: TestClient) => void);
|
|
34
|
+
/**
|
|
35
|
+
* Start the server. Returns the function that closes it, so a setup hook can
|
|
36
|
+
* return this call directly.
|
|
37
|
+
*/
|
|
38
|
+
start(): Promise<() => Promise<void>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The utilities themselves — AdonisJS `TestUtils`.
|
|
42
|
+
*
|
|
43
|
+
* Constructed with how to boot the app, because ream has no ambient
|
|
44
|
+
* application to reach for: the app is whatever the test bootstrap hands over.
|
|
45
|
+
* That is the one place this differs from AdonisJS, where `testUtils` is a
|
|
46
|
+
* service resolved from the container and already knows its app.
|
|
47
|
+
*/
|
|
48
|
+
export declare class TestUtils {
|
|
49
|
+
#private;
|
|
50
|
+
constructor(boot: BootServer);
|
|
51
|
+
/** Start/stop the HTTP server for a suite. */
|
|
52
|
+
httpServer(): HttpServerUtils;
|
|
53
|
+
/**
|
|
54
|
+
* The client for the server this run started, or `undefined` before
|
|
55
|
+
* `httpServer().start()`. A plugin reads it to put a client on the test
|
|
56
|
+
* context without booting a second server.
|
|
57
|
+
*/
|
|
58
|
+
client(): TestClient | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Build the utilities for an app booted by `boot`. */
|
|
61
|
+
export declare function createTestUtils(boot: BootServer): TestUtils;
|
|
62
|
+
//# sourceMappingURL=testUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../../src/testing/testUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG,CACvB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,CAAC,CAAA;AAEjE;;GAEG;AACH,qBAAa,eAAe;;gBAId,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI;IAKrE;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAM5C;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;;gBAIR,IAAI,EAAE,UAAU;IAI5B,8CAA8C;IAC9C,UAAU,IAAI,eAAe;IAM7B;;;;OAIG;IACH,MAAM,IAAI,UAAU,GAAG,SAAS;CAGjC;AAED,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAE3D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities, in the shape AdonisJS gives them: `testUtils.httpServer()`
|
|
3
|
+
* starts the application's HTTP server for a test run and hands back the
|
|
4
|
+
* function that closes it.
|
|
5
|
+
*
|
|
6
|
+
* // tests/bootstrap.ts
|
|
7
|
+
* configureSuite(suite) {
|
|
8
|
+
* if (['functional', 'e2e'].includes(suite.name)) {
|
|
9
|
+
* return suite.setup(() => testUtils.httpServer().start())
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The point of the shape is WHERE the server starts. A suite that does not
|
|
14
|
+
* declare the hook never starts one, so a unit suite pays nothing — which is
|
|
15
|
+
* the same saving a lazily-booted client buys, obtained by scoping instead of
|
|
16
|
+
* by deferring, and obtained per SUITE rather than per file.
|
|
17
|
+
*
|
|
18
|
+
* `start()` returning its own teardown is what makes it a one-liner in a hook:
|
|
19
|
+
* a setup hook may return its undo, so there is no matching `teardown` to
|
|
20
|
+
* write and no way to forget it.
|
|
21
|
+
*/
|
|
22
|
+
import { TestClient } from './TestClient.js';
|
|
23
|
+
/**
|
|
24
|
+
* What `testUtils.httpServer()` returns — AdonisJS `HttpServerUtils`.
|
|
25
|
+
*/
|
|
26
|
+
export class HttpServerUtils {
|
|
27
|
+
#boot;
|
|
28
|
+
#onStarted;
|
|
29
|
+
constructor(boot, onStarted) {
|
|
30
|
+
this.#boot = boot;
|
|
31
|
+
this.#onStarted = onStarted;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start the server. Returns the function that closes it, so a setup hook can
|
|
35
|
+
* return this call directly.
|
|
36
|
+
*/
|
|
37
|
+
async start() {
|
|
38
|
+
const client = new TestClient(this.#boot);
|
|
39
|
+
await client.boot();
|
|
40
|
+
this.#onStarted(client);
|
|
41
|
+
return () => client.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The utilities themselves — AdonisJS `TestUtils`.
|
|
46
|
+
*
|
|
47
|
+
* Constructed with how to boot the app, because ream has no ambient
|
|
48
|
+
* application to reach for: the app is whatever the test bootstrap hands over.
|
|
49
|
+
* That is the one place this differs from AdonisJS, where `testUtils` is a
|
|
50
|
+
* service resolved from the container and already knows its app.
|
|
51
|
+
*/
|
|
52
|
+
export class TestUtils {
|
|
53
|
+
#boot;
|
|
54
|
+
#client;
|
|
55
|
+
constructor(boot) {
|
|
56
|
+
this.#boot = boot;
|
|
57
|
+
}
|
|
58
|
+
/** Start/stop the HTTP server for a suite. */
|
|
59
|
+
httpServer() {
|
|
60
|
+
return new HttpServerUtils(this.#boot, (client) => {
|
|
61
|
+
this.#client = client;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The client for the server this run started, or `undefined` before
|
|
66
|
+
* `httpServer().start()`. A plugin reads it to put a client on the test
|
|
67
|
+
* context without booting a second server.
|
|
68
|
+
*/
|
|
69
|
+
client() {
|
|
70
|
+
return this.#client;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Build the utilities for an app booted by `boot`. */
|
|
74
|
+
export function createTestUtils(boot) {
|
|
75
|
+
return new TestUtils(boot);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=testUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.js","sourceRoot":"","sources":["../../src/testing/testUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAO5C;;GAEG;AACH,MAAM,OAAO,eAAe;IACjB,KAAK,CAAY;IACjB,UAAU,CAA8B;IAEjD,YAAY,IAAgB,EAAE,SAAuC;QACnE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACzC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACnB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QACvB,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IAC7B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,SAAS;IACX,KAAK,CAAY;IAC1B,OAAO,CAAwB;IAE/B,YAAY,IAAgB;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,8CAA8C;IAC9C,UAAU;QACR,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE;YAChD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACvB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;CACF;AAED,uDAAuD;AACvD,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC"}
|
|
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.7",
|
|
4
4
|
"description": "Ream — Rust-powered Node.js application framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"types": "./dist/services/encryption.d.ts",
|
|
44
44
|
"import": "./dist/services/encryption.js"
|
|
45
45
|
},
|
|
46
|
+
"./services/logger": {
|
|
47
|
+
"types": "./dist/services/logger.d.ts",
|
|
48
|
+
"import": "./dist/services/logger.js"
|
|
49
|
+
},
|
|
46
50
|
"./services/urlBuilder": {
|
|
47
51
|
"types": "./dist/services/urlBuilder.d.ts",
|
|
48
52
|
"import": "./dist/services/urlBuilder.js"
|
|
@@ -91,6 +95,10 @@
|
|
|
91
95
|
"types": "./dist/testing/TestClient.d.ts",
|
|
92
96
|
"import": "./dist/testing/TestClient.js"
|
|
93
97
|
},
|
|
98
|
+
"./testing/utils": {
|
|
99
|
+
"types": "./dist/testing/testUtils.d.ts",
|
|
100
|
+
"import": "./dist/testing/testUtils.js"
|
|
101
|
+
},
|
|
94
102
|
"./bootstrap": {
|
|
95
103
|
"types": "./dist/bootstrap/hyper-server.d.ts",
|
|
96
104
|
"import": "./dist/bootstrap/hyper-server.js"
|
|
Binary file
|
package/src/Ignitor.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { startHotReload } from './HotReload.js'
|
|
|
30
30
|
import type { HttpKernelRequest, HttpKernelResponse } from './HttpKernel.js'
|
|
31
31
|
import { createHttpKernel } from './HttpKernel.js'
|
|
32
32
|
import { ExceptionHandler } from './http/Exception.js'
|
|
33
|
-
import { HttpContext } from './http/HttpContext.js'
|
|
33
|
+
import { type ChildLoggerSource, HttpContext } from './http/HttpContext.js'
|
|
34
34
|
import type { MiddlewareFunction } from './middleware/Pipeline.js'
|
|
35
35
|
import { MiddlewareRegistry } from './middleware/Pipeline.js'
|
|
36
36
|
import { MigrationRegistry } from './migrations/MigrationRegistry.js'
|
|
@@ -42,6 +42,7 @@ import { SignedUrl } from './security/SignedUrl.js'
|
|
|
42
42
|
import { Server } from './server/Server.js'
|
|
43
43
|
import { clearApp, setApp } from './services/app.js'
|
|
44
44
|
import { clearEncryption, setEncryption } from './services/encryption.js'
|
|
45
|
+
import { clearLogger, setLogger } from './services/logger.js'
|
|
45
46
|
import { clearRouter, setRouter } from './services/router.js'
|
|
46
47
|
import { clearServer, setServer } from './services/server.js'
|
|
47
48
|
import { registerSessionTemplateTags } from './session/templateTags.js'
|
|
@@ -250,6 +251,11 @@ export class Ignitor {
|
|
|
250
251
|
* — an unguarded clear would tear down a newer Ignitor's binding.
|
|
251
252
|
*/
|
|
252
253
|
#signer?: CookieSigner
|
|
254
|
+
/**
|
|
255
|
+
* The bound logger, when a provider supplied one. Held so `stop()` releases
|
|
256
|
+
* the locator against the exact instance it installed.
|
|
257
|
+
*/
|
|
258
|
+
#logger?: ChildLoggerSource
|
|
253
259
|
#_httpServer?: HyperServerLike
|
|
254
260
|
#config: IgnitorConfig
|
|
255
261
|
#appRoot?: URL
|
|
@@ -557,6 +563,15 @@ export class Ignitor {
|
|
|
557
563
|
|
|
558
564
|
async #phaseBoot(): Promise<void> {
|
|
559
565
|
await this.#app.boot()
|
|
566
|
+
// A provider is what binds `'logger'` — ream declares the contract and
|
|
567
|
+
// implements none — so the locator can only be filled once they have all
|
|
568
|
+
// booted. Absent is a normal state: `ctx.logger` falls back to console and
|
|
569
|
+
// `services/logger` says which package to install.
|
|
570
|
+
const container = this.#app.container
|
|
571
|
+
if (container.has('logger')) {
|
|
572
|
+
this.#logger = await container.resolve<ChildLoggerSource>('logger')
|
|
573
|
+
setLogger(this.#logger)
|
|
574
|
+
}
|
|
560
575
|
this.#phase = 'booted'
|
|
561
576
|
}
|
|
562
577
|
|
|
@@ -885,6 +900,7 @@ export class Ignitor {
|
|
|
885
900
|
// `services/encryption` is the same object, so releasing it here keeps the
|
|
886
901
|
// locator from outliving the app that built it.
|
|
887
902
|
if (this.#signer) clearEncryption(this.#signer)
|
|
903
|
+
if (this.#logger) clearLogger(this.#logger)
|
|
888
904
|
|
|
889
905
|
this.#phase = 'shutdown'
|
|
890
906
|
if (errors.length === 1) throw errors[0]
|
|
@@ -38,7 +38,7 @@ function isHyperServerNapi(value: unknown): value is HyperServerNapiModule {
|
|
|
38
38
|
typeof value === 'object' &&
|
|
39
39
|
value !== null &&
|
|
40
40
|
'HyperServer' in value &&
|
|
41
|
-
typeof
|
|
41
|
+
typeof value.HyperServer === 'function'
|
|
42
42
|
)
|
|
43
43
|
}
|
|
44
44
|
|
package/src/helpers/is.ts
CHANGED
|
@@ -62,12 +62,7 @@ export function isArray<T = unknown>(v: unknown): v is T[] {
|
|
|
62
62
|
* @example isPromise(Promise.resolve()) // true
|
|
63
63
|
*/
|
|
64
64
|
export function isPromise<T = unknown>(v: unknown): v is Promise<T> {
|
|
65
|
-
return
|
|
66
|
-
v !== null &&
|
|
67
|
-
typeof v === 'object' &&
|
|
68
|
-
'then' in v &&
|
|
69
|
-
typeof (v as Record<string, unknown>).then === 'function'
|
|
70
|
-
)
|
|
65
|
+
return v !== null && typeof v === 'object' && 'then' in v && typeof v.then === 'function'
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
/**
|
package/src/index.ts
CHANGED
|
@@ -252,9 +252,12 @@ export class Scheduler {
|
|
|
252
252
|
// `.then`-based detection + `Promise.resolve` wrap so non-native
|
|
253
253
|
// thenables (e.g. RxJS-style) are handled uniformly.
|
|
254
254
|
if (
|
|
255
|
+
// Object OR function: a thenable function is exotic but legal, and the
|
|
256
|
+
// guard this replaced accepted one. `in` narrows either.
|
|
257
|
+
(typeof result === 'object' || typeof result === 'function') &&
|
|
255
258
|
result !== null &&
|
|
256
|
-
|
|
257
|
-
typeof
|
|
259
|
+
'then' in result &&
|
|
260
|
+
typeof result.then === 'function'
|
|
258
261
|
) {
|
|
259
262
|
Promise.resolve(result).catch(() => {})
|
|
260
263
|
}
|
|
@@ -334,9 +337,12 @@ export class Scheduler {
|
|
|
334
337
|
// reporter never crashes the ticker.
|
|
335
338
|
const result = this.#errorReporter(err, { taskName }) as unknown
|
|
336
339
|
if (
|
|
340
|
+
// Object OR function: a thenable function is exotic but legal, and the
|
|
341
|
+
// guard this replaced accepted one. `in` narrows either.
|
|
342
|
+
(typeof result === 'object' || typeof result === 'function') &&
|
|
337
343
|
result !== null &&
|
|
338
|
-
|
|
339
|
-
typeof
|
|
344
|
+
'then' in result &&
|
|
345
|
+
typeof result.then === 'function'
|
|
340
346
|
) {
|
|
341
347
|
Promise.resolve(result).catch(() => {})
|
|
342
348
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application logger singleton service.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import logger from '@c9up/ream/services/logger'
|
|
6
|
+
* logger.info('user signed in', { userId })
|
|
7
|
+
*
|
|
8
|
+
* ream does not implement the logger — it declares the contract
|
|
9
|
+
* ({@link ContextLogger}) and reads whatever a provider bound as `'logger'`.
|
|
10
|
+
* `@c9up/spectrum` is the implementation in this universe, the way `hash` is
|
|
11
|
+
* sigil's and validation is rune's.
|
|
12
|
+
*
|
|
13
|
+
* `ctx.logger` stays the right thing to use inside a request: it is
|
|
14
|
+
* correlation-scoped. This is for everywhere else — a provider, a scheduled
|
|
15
|
+
* task, a console command — where there is no request to scope to.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ChildLoggerSource } from '../http/HttpContext.js'
|
|
19
|
+
import { createServiceProxy } from './createServiceProxy.js'
|
|
20
|
+
|
|
21
|
+
let instance: ChildLoggerSource | undefined
|
|
22
|
+
|
|
23
|
+
/** @internal Set the logger (called by Ignitor once providers have booted). */
|
|
24
|
+
export function setLogger(logger: ChildLoggerSource): void {
|
|
25
|
+
instance = logger
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @internal Unset the locator IF it still points at `logger` (called by
|
|
30
|
+
* Ignitor.stop()). Ownership-guarded — see services/app.ts.
|
|
31
|
+
*/
|
|
32
|
+
export function clearLogger(logger: ChildLoggerSource): void {
|
|
33
|
+
if (instance === logger) instance = undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @internal Get the logger directly. */
|
|
37
|
+
export function getLogger(): ChildLoggerSource | undefined {
|
|
38
|
+
return instance
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const logger: ChildLoggerSource = createServiceProxy<ChildLoggerSource>(
|
|
42
|
+
() => instance,
|
|
43
|
+
'Logger accessed before initialization. ' +
|
|
44
|
+
'ream declares the contract but implements no logger — install one (`@c9up/spectrum`) ' +
|
|
45
|
+
"and register its provider, which binds it as 'logger'. Inside a request, prefer " +
|
|
46
|
+
'`ctx.logger`, which is scoped to the correlation id.',
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
export default logger
|
package/src/session/Session.ts
CHANGED
|
@@ -56,9 +56,9 @@ export interface SessionDriverWithTagging extends SessionDriver {
|
|
|
56
56
|
export function supportsTagging(driver: SessionDriver): driver is SessionDriverWithTagging {
|
|
57
57
|
return (
|
|
58
58
|
'tag' in driver &&
|
|
59
|
-
typeof
|
|
59
|
+
typeof driver.tag === 'function' &&
|
|
60
60
|
'tagged' in driver &&
|
|
61
|
-
typeof
|
|
61
|
+
typeof driver.tagged === 'function'
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,17 +68,51 @@ export class TestClient {
|
|
|
68
68
|
|
|
69
69
|
#server: { port: number; close: () => Promise<void> | void } | null = null
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* In flight or settled, so concurrent callers share one boot.
|
|
73
|
+
*
|
|
74
|
+
* Also what makes `boot()` optional: the first request awaits this, starting
|
|
75
|
+
* it if nobody has. A test file that never issues a request never starts a
|
|
76
|
+
* server — which is the difference between a unit file paying for one and
|
|
77
|
+
* not, and at ~400ms a file that is most of a campaign's time.
|
|
78
|
+
*/
|
|
79
|
+
#booting: Promise<void> | null = null
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Boot the application on a random port. Idempotent: a second call awaits the
|
|
83
|
+
* first rather than starting a second server.
|
|
84
|
+
*
|
|
85
|
+
* Optional — a request boots on its own. Call it when you need `port` before
|
|
86
|
+
* issuing one (an SSE or WebSocket connection opened with `fetch`).
|
|
87
|
+
*/
|
|
72
88
|
async boot(): Promise<void> {
|
|
89
|
+
this.#booting ??= this.#startServer()
|
|
90
|
+
return this.#booting
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async #startServer(): Promise<void> {
|
|
73
94
|
this.#server = await this.#bootFn(0)
|
|
74
95
|
this.#port = this.#server.port
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
/**
|
|
98
|
+
/** Whether a server is currently running for this client. */
|
|
99
|
+
get booted(): boolean {
|
|
100
|
+
return this.#server !== null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Close the server. A no-op when nothing was ever started, so a teardown can
|
|
105
|
+
* call it unconditionally against a client whose file issued no request.
|
|
106
|
+
*/
|
|
78
107
|
async close(): Promise<void> {
|
|
108
|
+
// Awaited first: closing while a boot is in flight would otherwise leave
|
|
109
|
+
// the server that boot is about to assign running with nobody holding it.
|
|
110
|
+
if (this.#booting) await this.#booting.catch(() => {})
|
|
111
|
+
this.#booting = null
|
|
79
112
|
if (this.#server) {
|
|
80
113
|
await this.#server.close()
|
|
81
114
|
this.#server = null
|
|
115
|
+
this.#port = 0
|
|
82
116
|
}
|
|
83
117
|
}
|
|
84
118
|
|
|
@@ -86,7 +120,11 @@ export class TestClient {
|
|
|
86
120
|
* Ephemeral port the booted server bound to. Useful for long-lived
|
|
87
121
|
* connections (SSE / WebSocket) that the fluent buffered request
|
|
88
122
|
* surface can't model — open them with `fetch` against
|
|
89
|
-
* `http://127.0.0.1:${client.port}/...`.
|
|
123
|
+
* `http://127.0.0.1:${client.port}/...`.
|
|
124
|
+
*
|
|
125
|
+
* Returns 0 while no server is running. Requests boot on their own, so a
|
|
126
|
+
* client that has only been constructed has not bound a port yet — `await
|
|
127
|
+
* client.boot()` first when you need this before issuing one.
|
|
90
128
|
*/
|
|
91
129
|
get port(): number {
|
|
92
130
|
return this.#port
|
|
@@ -148,8 +186,11 @@ export class TestClient {
|
|
|
148
186
|
* `form()`. The lower-level `request()` (below) stays for raw control.
|
|
149
187
|
*/
|
|
150
188
|
fluent(method: HttpMethod, path: string): RequestBuilder {
|
|
151
|
-
const sender: HttpSender = (m, p, init) =>
|
|
152
|
-
|
|
189
|
+
const sender: HttpSender = async (m, p, init) => {
|
|
190
|
+
// Boots on the first request and no earlier. `#port` is read here rather
|
|
191
|
+
// than captured, so it is the port this boot bound to.
|
|
192
|
+
await this.boot()
|
|
193
|
+
return sendRequest(
|
|
153
194
|
this.#port,
|
|
154
195
|
m,
|
|
155
196
|
p,
|
|
@@ -157,6 +198,7 @@ export class TestClient {
|
|
|
157
198
|
init.body.toString('utf8'),
|
|
158
199
|
init.timeoutMs,
|
|
159
200
|
)
|
|
201
|
+
}
|
|
160
202
|
return new RequestBuilder(sender, method, path, this.#auth, this.#sessionSeeder)
|
|
161
203
|
}
|
|
162
204
|
|