@btravstack/testing 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,8 +126,7 @@ pair, a `node:http` listener answering its public key as a JWKS, and a
126
126
  `sign(claims?, options?)` closing over the private key — a real fetch against
127
127
  a real JWKS document, for a test that wants the verifying library's own
128
128
  behaviour rather than a double's. `sign`'s `expiresIn: false` mints a token
129
- with no `exp` claim; `localIssuer`'s `algorithm` picks the asymmetric algorithm
130
- (default `"RS256"`). The subpath needs Node ≥22.12 under CommonJS — `jose` is
129
+ with no `exp` claim, and the key is RS256. The subpath needs Node ≥22.12 under CommonJS — `jose` is
131
130
  ESM-only, so the CJS build's `require("jose")` depends on `require(esm)`;
132
131
  ESM consumers, and anyone who never imports the subpath, are unaffected.
133
132
 
package/dist/index.cjs CHANGED
@@ -161,21 +161,11 @@ const testRuntime = (name = "test", options = {}) => {
161
161
  let accepting = false;
162
162
  let serving;
163
163
  let submitted = 0;
164
- let onStarted;
165
- const started = new Promise((resolve) => {
166
- onStarted = resolve;
167
- });
168
- const make = () => ({
169
- info: { name },
170
- drain: (signal) => {
171
- accepting = false;
172
- return (0, unthrown.OkAsync)();
173
- },
174
- stop: () => {
175
- accepting = false;
176
- return (0, unthrown.OkAsync)();
177
- }
178
- });
164
+ const started = Promise.withResolvers();
165
+ const stopAccepting = () => {
166
+ accepting = false;
167
+ return (0, unthrown.OkAsync)();
168
+ };
179
169
  const runtime = {
180
170
  name,
181
171
  module: (0, _btravstack_di.Module)("TestRuntime")({
@@ -189,12 +179,16 @@ const testRuntime = (name = "test", options = {}) => {
189
179
  start: (h) => {
190
180
  host = h;
191
181
  accepting = true;
192
- serving = make();
193
- onStarted();
182
+ serving = {
183
+ info: { name },
184
+ drain: stopAccepting,
185
+ stop: stopAccepting
186
+ };
187
+ started.resolve();
194
188
  return (0, unthrown.OkAsync)(serving);
195
189
  },
196
190
  started: () => serving !== void 0,
197
- untilStarted: () => (0, unthrown.fromSafePromise)(started),
191
+ untilStarted: () => (0, unthrown.fromSafePromise)(started.promise),
198
192
  accepting: () => accepting,
199
193
  serving: () => {
200
194
  if (serving === void 0) throw new Error("[test-runtime] not started");
@@ -207,10 +201,7 @@ const testRuntime = (name = "test", options = {}) => {
207
201
  submit: () => {
208
202
  if (host === void 0 || !accepting) throw new Error("[test-runtime] not accepting work");
209
203
  submitted += 1;
210
- let settle;
211
- const held = new Promise((resolve) => {
212
- settle = resolve;
213
- });
204
+ const held = Promise.withResolvers();
214
205
  const forwarded = new AbortController();
215
206
  const unit = options.unit;
216
207
  const result = host.run({
@@ -219,10 +210,10 @@ const testRuntime = (name = "test", options = {}) => {
219
210
  }, (unitHost, signal) => {
220
211
  if (signal.aborted) forwarded.abort(signal.reason);
221
212
  else signal.addEventListener("abort", () => forwarded.abort(signal.reason), { once: true });
222
- return unit === void 0 ? held : unitHost.fork(unit, []).flatMap(() => (0, unthrown.fromSafePromise)(held).flatMap((r) => r));
213
+ return unit === void 0 ? held.promise : unitHost.fork(unit, []).flatMap(() => (0, unthrown.fromSafePromise)(held.promise).flatMap((r) => r));
223
214
  });
224
215
  return {
225
- settle,
216
+ settle: held.resolve,
226
217
  result,
227
218
  signal: forwarded.signal
228
219
  };
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { Clock, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, RuntimePort, Serving, StartGate, StartOptions } from "@btravstack/core";
2
- import { AnyPort, Module, Provider, Scope, ServiceOf } from "@btravstack/di";
1
+ import { AnyUnitModule, Clock, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, RuntimePort, Serving, StartGate, StartOptions, UnitNeedsOf } from "@btravstack/core";
2
+ import { AnyPort, Module, Provider, ServiceOf } from "@btravstack/di";
3
3
  import { AsyncResult, Result } from "unthrown";
4
4
  //#region src/boot-fixture.d.ts
5
5
  /**
@@ -143,21 +143,6 @@ type TestRuntimeInfo = {
143
143
  };
144
144
  /** The in-memory runtime's port: what `TestRuntime.module` provides, and what a test composition exports. */
145
145
  export declare class TestRuntimePort extends RuntimePort<Runtime<never, TestRuntimeInfo>> {}
146
- /**
147
- * A module `options.unit` may bind, as the upper bound `testRuntime`
148
- * constrains its own `Unit` type parameter to. `Module`'s `_exports` channel is
149
- * contravariant, so `Exports = never` — never `unknown` — is what makes a REAL
150
- * module's own (necessarily narrower) export type assignable to this bound.
151
- * `@btravstack/http-server`'s `AnyUnitModule` carries the same bound for the
152
- * same reason.
153
- */
154
- type AnyUnitModule = Module<never, never, unknown>;
155
- /**
156
- * The needs a bound `unit` module still owes, or `never` when none is bound.
157
- * `Scope` is excluded, since nothing can ever provide it — the same exemption
158
- * `NeedsGate` itself carries.
159
- */
160
- type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope> : never;
161
146
  type TestRuntimeOptions<Unit extends AnyUnitModule | undefined = undefined> = {
162
147
  /** A module every submitted unit forks, with no seed, before its work runs. */
163
148
  readonly unit?: Unit;
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Clock, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, RuntimePort, Serving, StartGate, StartOptions } from "@btravstack/core";
1
+ import { AnyUnitModule, Clock, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, RuntimePort, Serving, StartGate, StartOptions, UnitNeedsOf } from "@btravstack/core";
2
2
  import { AsyncResult, Result } from "unthrown";
3
- import { AnyPort, Module, Provider, Scope, ServiceOf } from "@btravstack/di";
3
+ import { AnyPort, Module, Provider, ServiceOf } from "@btravstack/di";
4
4
  //#region src/boot-fixture.d.ts
5
5
  /**
6
6
  * `start`, as a test hands it out: the same signature and the same phantom
@@ -143,21 +143,6 @@ type TestRuntimeInfo = {
143
143
  };
144
144
  /** The in-memory runtime's port: what `TestRuntime.module` provides, and what a test composition exports. */
145
145
  export declare class TestRuntimePort extends RuntimePort<Runtime<never, TestRuntimeInfo>> {}
146
- /**
147
- * A module `options.unit` may bind, as the upper bound `testRuntime`
148
- * constrains its own `Unit` type parameter to. `Module`'s `_exports` channel is
149
- * contravariant, so `Exports = never` — never `unknown` — is what makes a REAL
150
- * module's own (necessarily narrower) export type assignable to this bound.
151
- * `@btravstack/http-server`'s `AnyUnitModule` carries the same bound for the
152
- * same reason.
153
- */
154
- type AnyUnitModule = Module<never, never, unknown>;
155
- /**
156
- * The needs a bound `unit` module still owes, or `never` when none is bound.
157
- * `Scope` is excluded, since nothing can ever provide it — the same exemption
158
- * `NeedsGate` itself carries.
159
- */
160
- type UnitNeedsOf<Unit> = Unit extends Module<never, never, infer N> ? Exclude<N, Scope> : never;
161
146
  type TestRuntimeOptions<Unit extends AnyUnitModule | undefined = undefined> = {
162
147
  /** A module every submitted unit forks, with no seed, before its work runs. */
163
148
  readonly unit?: Unit;
package/dist/index.mjs CHANGED
@@ -160,21 +160,11 @@ const testRuntime = (name = "test", options = {}) => {
160
160
  let accepting = false;
161
161
  let serving;
162
162
  let submitted = 0;
163
- let onStarted;
164
- const started = new Promise((resolve) => {
165
- onStarted = resolve;
166
- });
167
- const make = () => ({
168
- info: { name },
169
- drain: (signal) => {
170
- accepting = false;
171
- return OkAsync();
172
- },
173
- stop: () => {
174
- accepting = false;
175
- return OkAsync();
176
- }
177
- });
163
+ const started = Promise.withResolvers();
164
+ const stopAccepting = () => {
165
+ accepting = false;
166
+ return OkAsync();
167
+ };
178
168
  const runtime = {
179
169
  name,
180
170
  module: Module("TestRuntime")({
@@ -188,12 +178,16 @@ const testRuntime = (name = "test", options = {}) => {
188
178
  start: (h) => {
189
179
  host = h;
190
180
  accepting = true;
191
- serving = make();
192
- onStarted();
181
+ serving = {
182
+ info: { name },
183
+ drain: stopAccepting,
184
+ stop: stopAccepting
185
+ };
186
+ started.resolve();
193
187
  return OkAsync(serving);
194
188
  },
195
189
  started: () => serving !== void 0,
196
- untilStarted: () => fromSafePromise(started),
190
+ untilStarted: () => fromSafePromise(started.promise),
197
191
  accepting: () => accepting,
198
192
  serving: () => {
199
193
  if (serving === void 0) throw new Error("[test-runtime] not started");
@@ -206,10 +200,7 @@ const testRuntime = (name = "test", options = {}) => {
206
200
  submit: () => {
207
201
  if (host === void 0 || !accepting) throw new Error("[test-runtime] not accepting work");
208
202
  submitted += 1;
209
- let settle;
210
- const held = new Promise((resolve) => {
211
- settle = resolve;
212
- });
203
+ const held = Promise.withResolvers();
213
204
  const forwarded = new AbortController();
214
205
  const unit = options.unit;
215
206
  const result = host.run({
@@ -218,10 +209,10 @@ const testRuntime = (name = "test", options = {}) => {
218
209
  }, (unitHost, signal) => {
219
210
  if (signal.aborted) forwarded.abort(signal.reason);
220
211
  else signal.addEventListener("abort", () => forwarded.abort(signal.reason), { once: true });
221
- return unit === void 0 ? held : unitHost.fork(unit, []).flatMap(() => fromSafePromise(held).flatMap((r) => r));
212
+ return unit === void 0 ? held.promise : unitHost.fork(unit, []).flatMap(() => fromSafePromise(held.promise).flatMap((r) => r));
222
213
  });
223
214
  return {
224
- settle,
215
+ settle: held.resolve,
225
216
  result,
226
217
  signal: forwarded.signal
227
218
  };
package/dist/jwt.cjs CHANGED
@@ -4,6 +4,7 @@ let node_http = require("node:http");
4
4
  let jose = require("jose");
5
5
  //#region src/jwt.ts
6
6
  const KID = "k1";
7
+ const ALG = "RS256";
7
8
  /**
8
9
  * A real JWKS endpoint and a matching signer, for a test that wants a genuine
9
10
  * fetch and a genuine verify rather than a double for either.
@@ -12,38 +13,35 @@ const KID = "k1";
12
13
  * listener answering the JWKS on any path, and `sign` closing over the
13
14
  * private key. `close()` stops the listener; nothing else needs tearing down.
14
15
  */
15
- const localIssuer = (options) => {
16
- const algorithm = options.algorithm ?? "RS256";
17
- return (0, unthrown.fromSafePromise)((async () => {
18
- const { publicKey, privateKey } = await (0, jose.generateKeyPair)(algorithm, { extractable: true });
19
- const jwk = {
20
- ...await (0, jose.exportJWK)(publicKey),
21
- kid: KID,
22
- alg: algorithm,
23
- use: "sig"
24
- };
25
- const server = (0, node_http.createServer)((_request, response) => {
26
- response.setHeader("content-type", "application/json");
27
- response.end(JSON.stringify({ keys: [jwk] }));
28
- });
29
- await new Promise((done) => server.listen(0, "127.0.0.1", done));
30
- const { port } = server.address();
31
- return {
32
- jwks: `http://127.0.0.1:${port}/jwks.json`,
33
- jwk,
34
- issuer: options.issuer,
35
- audience: options.audience,
36
- sign: (claims = {}, signOptions = {}) => (0, unthrown.fromSafePromise)((async () => {
37
- const token = new jose.SignJWT(claims).setProtectedHeader({
38
- alg: algorithm,
39
- kid: KID
40
- }).setIssuer(signOptions.issuer ?? options.issuer).setAudience(signOptions.audience ?? options.audience).setIssuedAt();
41
- if (signOptions.expiresIn !== false) token.setExpirationTime(signOptions.expiresIn ?? "5m");
42
- return token.sign(privateKey);
43
- })()),
44
- close: () => (0, unthrown.fromSafePromise)(new Promise((done) => server.close(() => done())))
45
- };
46
- })());
47
- };
16
+ const localIssuer = (options) => (0, unthrown.fromSafePromise)((async () => {
17
+ const { publicKey, privateKey } = await (0, jose.generateKeyPair)(ALG, { extractable: true });
18
+ const jwk = {
19
+ ...await (0, jose.exportJWK)(publicKey),
20
+ kid: KID,
21
+ alg: ALG,
22
+ use: "sig"
23
+ };
24
+ const server = (0, node_http.createServer)((_request, response) => {
25
+ response.setHeader("content-type", "application/json");
26
+ response.end(JSON.stringify({ keys: [jwk] }));
27
+ });
28
+ await new Promise((done) => server.listen(0, "127.0.0.1", done));
29
+ const { port } = server.address();
30
+ return {
31
+ jwks: `http://127.0.0.1:${port}/jwks.json`,
32
+ jwk,
33
+ issuer: options.issuer,
34
+ audience: options.audience,
35
+ sign: (claims = {}, signOptions = {}) => (0, unthrown.fromSafePromise)((async () => {
36
+ const token = new jose.SignJWT(claims).setProtectedHeader({
37
+ alg: ALG,
38
+ kid: KID
39
+ }).setIssuer(signOptions.issuer ?? options.issuer).setAudience(signOptions.audience ?? options.audience).setIssuedAt();
40
+ if (signOptions.expiresIn !== false) token.setExpirationTime(signOptions.expiresIn ?? "5m");
41
+ return token.sign(privateKey);
42
+ })()),
43
+ close: () => (0, unthrown.fromSafePromise)(new Promise((done) => server.close(() => done())))
44
+ };
45
+ })());
48
46
  //#endregion
49
47
  exports.localIssuer = localIssuer;
package/dist/jwt.d.cts CHANGED
@@ -4,8 +4,6 @@ import { JWK, JWTPayload } from "jose";
4
4
  export type LocalIssuerOptions = {
5
5
  readonly issuer: string;
6
6
  readonly audience: string;
7
- /** Asymmetric only, the same list `jwtAuthenticator` accepts; default `"RS256"`. */
8
- readonly algorithm?: "RS256" | "RS384" | "RS512" | "ES256" | "ES384";
9
7
  };
10
8
  export type SignOptions = {
11
9
  readonly issuer?: string;
package/dist/jwt.d.mts CHANGED
@@ -4,8 +4,6 @@ import { JWK, JWTPayload } from "jose";
4
4
  export type LocalIssuerOptions = {
5
5
  readonly issuer: string;
6
6
  readonly audience: string;
7
- /** Asymmetric only, the same list `jwtAuthenticator` accepts; default `"RS256"`. */
8
- readonly algorithm?: "RS256" | "RS384" | "RS512" | "ES256" | "ES384";
9
7
  };
10
8
  export type SignOptions = {
11
9
  readonly issuer?: string;
package/dist/jwt.mjs CHANGED
@@ -3,6 +3,7 @@ import { createServer } from "node:http";
3
3
  import { SignJWT, exportJWK, generateKeyPair } from "jose";
4
4
  //#region src/jwt.ts
5
5
  const KID = "k1";
6
+ const ALG = "RS256";
6
7
  /**
7
8
  * A real JWKS endpoint and a matching signer, for a test that wants a genuine
8
9
  * fetch and a genuine verify rather than a double for either.
@@ -11,38 +12,35 @@ const KID = "k1";
11
12
  * listener answering the JWKS on any path, and `sign` closing over the
12
13
  * private key. `close()` stops the listener; nothing else needs tearing down.
13
14
  */
14
- const localIssuer = (options) => {
15
- const algorithm = options.algorithm ?? "RS256";
16
- return fromSafePromise((async () => {
17
- const { publicKey, privateKey } = await generateKeyPair(algorithm, { extractable: true });
18
- const jwk = {
19
- ...await exportJWK(publicKey),
20
- kid: KID,
21
- alg: algorithm,
22
- use: "sig"
23
- };
24
- const server = createServer((_request, response) => {
25
- response.setHeader("content-type", "application/json");
26
- response.end(JSON.stringify({ keys: [jwk] }));
27
- });
28
- await new Promise((done) => server.listen(0, "127.0.0.1", done));
29
- const { port } = server.address();
30
- return {
31
- jwks: `http://127.0.0.1:${port}/jwks.json`,
32
- jwk,
33
- issuer: options.issuer,
34
- audience: options.audience,
35
- sign: (claims = {}, signOptions = {}) => fromSafePromise((async () => {
36
- const token = new SignJWT(claims).setProtectedHeader({
37
- alg: algorithm,
38
- kid: KID
39
- }).setIssuer(signOptions.issuer ?? options.issuer).setAudience(signOptions.audience ?? options.audience).setIssuedAt();
40
- if (signOptions.expiresIn !== false) token.setExpirationTime(signOptions.expiresIn ?? "5m");
41
- return token.sign(privateKey);
42
- })()),
43
- close: () => fromSafePromise(new Promise((done) => server.close(() => done())))
44
- };
45
- })());
46
- };
15
+ const localIssuer = (options) => fromSafePromise((async () => {
16
+ const { publicKey, privateKey } = await generateKeyPair(ALG, { extractable: true });
17
+ const jwk = {
18
+ ...await exportJWK(publicKey),
19
+ kid: KID,
20
+ alg: ALG,
21
+ use: "sig"
22
+ };
23
+ const server = createServer((_request, response) => {
24
+ response.setHeader("content-type", "application/json");
25
+ response.end(JSON.stringify({ keys: [jwk] }));
26
+ });
27
+ await new Promise((done) => server.listen(0, "127.0.0.1", done));
28
+ const { port } = server.address();
29
+ return {
30
+ jwks: `http://127.0.0.1:${port}/jwks.json`,
31
+ jwk,
32
+ issuer: options.issuer,
33
+ audience: options.audience,
34
+ sign: (claims = {}, signOptions = {}) => fromSafePromise((async () => {
35
+ const token = new SignJWT(claims).setProtectedHeader({
36
+ alg: ALG,
37
+ kid: KID
38
+ }).setIssuer(signOptions.issuer ?? options.issuer).setAudience(signOptions.audience ?? options.audience).setIssuedAt();
39
+ if (signOptions.expiresIn !== false) token.setExpirationTime(signOptions.expiresIn ?? "5m");
40
+ return token.sign(privateKey);
41
+ })()),
42
+ close: () => fromSafePromise(new Promise((done) => server.close(() => done())))
43
+ };
44
+ })());
47
45
  //#endregion
48
46
  export { localIssuer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/testing",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Test the applications @btravstack/core boots: an in-memory runtime, a fake clock, a boot fixture that stops what it started, and a tap into a running graph",
5
5
  "keywords": [
6
6
  "dependency-injection",
@@ -54,23 +54,23 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@btravstack/config": "0.16.0",
58
- "@btravstack/core": "0.16.0",
59
- "@btravstack/di": "0.16.0",
57
+ "@btravstack/config": "0.17.0",
58
+ "@btravstack/core": "0.17.0",
59
+ "@btravstack/di": "0.17.0",
60
60
  "@btravstack/tsconfig": "0.4.0",
61
- "@types/node": "26.4.1",
62
- "@unthrown/vitest": "5.8.0",
63
- "@vitest/coverage-v8": "4.1.11",
61
+ "@types/node": "26.6.4",
62
+ "@unthrown/vitest": "5.12.0",
63
+ "@vitest/coverage-v8": "5.0.3",
64
64
  "jose": "6.2.12",
65
65
  "tsdown": "0.23.0",
66
66
  "typescript": "7.0.2",
67
- "unthrown": "5.8.0",
68
- "vitest": "4.1.11"
67
+ "unthrown": "5.12.0",
68
+ "vitest": "5.0.3"
69
69
  },
70
70
  "peerDependencies": {
71
- "@btravstack/config": "^0.16.0",
72
- "@btravstack/core": "^0.16.0",
73
- "@btravstack/di": "^0.16.0",
71
+ "@btravstack/config": "^0.17.0",
72
+ "@btravstack/core": "^0.17.0",
73
+ "@btravstack/di": "^0.17.0",
74
74
  "jose": "^6",
75
75
  "unthrown": "^5.0.0"
76
76
  },