@geekmidas/constructs 10.0.0-alpha.21 → 10.0.0-alpha.23

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.
Files changed (52) hide show
  1. package/README.md +29 -23
  2. package/dist/{Endpoint-DbYQJgb-.d.cts → Endpoint-CrhoafsT.d.cts} +2 -2
  3. package/dist/{Endpoint-BxkRWQ8G.d.mts.map → Endpoint-CrhoafsT.d.cts.map} +1 -1
  4. package/dist/{Endpoint-BxkRWQ8G.d.mts → Endpoint-pTZrMs_h.d.mts} +2 -2
  5. package/dist/{Endpoint-DbYQJgb-.d.cts.map → Endpoint-pTZrMs_h.d.mts.map} +1 -1
  6. package/dist/{EndpointFactory-T06kTFHg.d.mts → EndpointFactory-BSLH4hSU.d.mts} +2 -2
  7. package/dist/{EndpointFactory-DE6nKtFZ.d.cts.map → EndpointFactory-BSLH4hSU.d.mts.map} +1 -1
  8. package/dist/{EndpointFactory-DE6nKtFZ.d.cts → EndpointFactory-CiCzFdMx.d.cts} +2 -2
  9. package/dist/{EndpointFactory-T06kTFHg.d.mts.map → EndpointFactory-CiCzFdMx.d.cts.map} +1 -1
  10. package/dist/Queue-BB2-DgqJ.cjs +123 -0
  11. package/dist/Queue-BB2-DgqJ.cjs.map +1 -0
  12. package/dist/Queue-Dfumm1WR.mjs +118 -0
  13. package/dist/Queue-Dfumm1WR.mjs.map +1 -0
  14. package/dist/Topic-BaNCgP61.cjs +107 -0
  15. package/dist/Topic-BaNCgP61.cjs.map +1 -0
  16. package/dist/Topic-QqnO_6Dl.mjs +102 -0
  17. package/dist/Topic-QqnO_6Dl.mjs.map +1 -0
  18. package/dist/adaptors/aws.d.cts +1 -1
  19. package/dist/adaptors/aws.d.mts +1 -1
  20. package/dist/adaptors/hono.d.cts +1 -1
  21. package/dist/adaptors/hono.d.mts +1 -1
  22. package/dist/adaptors/testing.cjs +136 -32
  23. package/dist/adaptors/testing.cjs.map +1 -1
  24. package/dist/adaptors/testing.d.cts +101 -13
  25. package/dist/adaptors/testing.d.cts.map +1 -1
  26. package/dist/adaptors/testing.d.mts +101 -13
  27. package/dist/adaptors/testing.d.mts.map +1 -1
  28. package/dist/adaptors/testing.mjs +133 -33
  29. package/dist/adaptors/testing.mjs.map +1 -1
  30. package/dist/{auth-BlO8MUkR.d.cts → auth-BR2eYxIW.d.cts} +16 -1
  31. package/dist/{auth-BlO8MUkR.d.cts.map → auth-BR2eYxIW.d.cts.map} +1 -1
  32. package/dist/{auth-CHWIdcQO.d.mts → auth-DtprqoJP.d.mts} +16 -1
  33. package/dist/{auth-CHWIdcQO.d.mts.map → auth-DtprqoJP.d.mts.map} +1 -1
  34. package/dist/auth.cjs +28 -6
  35. package/dist/auth.cjs.map +1 -1
  36. package/dist/auth.d.cts +1 -1
  37. package/dist/auth.d.mts +1 -1
  38. package/dist/auth.mjs +28 -6
  39. package/dist/auth.mjs.map +1 -1
  40. package/dist/endpoints/index.d.cts +2 -2
  41. package/dist/endpoints/index.d.mts +2 -2
  42. package/dist/queue/index.cjs +3 -116
  43. package/dist/queue/index.cjs.map +1 -1
  44. package/dist/queue/index.mjs +1 -114
  45. package/dist/queue/index.mjs.map +1 -1
  46. package/dist/rest-api.d.cts +2 -2
  47. package/dist/rest-api.d.mts +2 -2
  48. package/dist/topic/index.cjs +3 -100
  49. package/dist/topic/index.cjs.map +1 -1
  50. package/dist/topic/index.mjs +1 -98
  51. package/dist/topic/index.mjs.map +1 -1
  52. package/package.json +31 -31
@@ -1,7 +1,7 @@
1
1
  import { t as StandardSchemaV1 } from "../index-CeEtxDmK.mjs";
2
2
  import { r as Function } from "../index-DU3Twmwh.mjs";
3
3
  import { t as HttpMethod } from "../types-DMuSQeLL.mjs";
4
- import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-BxkRWQ8G.mjs";
4
+ import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-pTZrMs_h.mjs";
5
5
  import { t as Queue } from "../Queue-BoUA-e83.mjs";
6
6
  import { t as Subscriber } from "../Subscriber-CIO3pwji.mjs";
7
7
  import { t as KyselyDatabase } from "../kysely-D3-G5b0j.mjs";
@@ -186,20 +186,77 @@ type TestSubscriberRequest<TEventPublisher extends EventPublisher<any> | undefin
186
186
  services?: ServiceRecord<TServices>;
187
187
  };
188
188
  //#endregion
189
+ //#region src/testing/manifest.d.ts
190
+ /**
191
+ * The test manifest: what `gkm test` knows about an app, written down for the
192
+ * suite it starts.
193
+ *
194
+ * `gkm test` discovers the constructs, reconciles the test stage and resolves
195
+ * its environment — then, until this existed, threw all of it away and left a
196
+ * test to declare it again by hand. The manifest keeps it: which construct and
197
+ * endpoint is exported where, and the environment the test stage resolved,
198
+ * under the keys the constructs themselves derive. A feature test is built from
199
+ * it, so nothing a test says can drift from what the app declares.
200
+ */
201
+ /** Where an export lives: what an import statement has to say. */
202
+ interface TestManifestSource {
203
+ /** Absolute path to the module. */
204
+ file: string;
205
+ /** The name it is exported as. */
206
+ export: string;
207
+ }
208
+ interface TestManifest {
209
+ /** The stage the environment was resolved for — `test`. */
210
+ stage: string;
211
+ /** Every construct the app declares, by id. */
212
+ constructs: Record<string, {
213
+ kind: string;
214
+ source: TestManifestSource;
215
+ }>;
216
+ /** Every endpoint, and the surface that serves it. */
217
+ endpoints: {
218
+ surface: string;
219
+ source: TestManifestSource;
220
+ }[];
221
+ /**
222
+ * The test stage's environment — reconcile's output, keyed as the
223
+ * constructs derive their keys.
224
+ */
225
+ env: Record<string, string>;
226
+ }
227
+ /** The environment variable `gkm test` names the manifest in. */
228
+ export declare const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
229
+ /**
230
+ * Read a test manifest: from a path, a `file:` URL, or — given nothing — the
231
+ * one `gkm test` named in `GKM_TEST_MANIFEST`.
232
+ */
233
+ export declare function loadTestManifest(from?: string | URL): TestManifest;
234
+ /** A feature test ran without a manifest to build itself from. */
235
+ export declare class NoTestManifest extends Error {
236
+ constructor();
237
+ }
238
+ //#endregion
189
239
  //#region src/testing/featureTest.d.ts
190
- interface FeatureTestOptions<TBrowser extends Browser, DB> {
240
+ interface FeatureTestOptions<TBrowser extends Browser> {
241
+ /**
242
+ * What the app declares and the environment its test stage resolved.
243
+ * Defaults to the manifest `gkm test` names in `GKM_TEST_MANIFEST`.
244
+ */
245
+ manifest?: TestManifest;
191
246
  /**
192
- * The modules an app's constructs and endpoints are exported from — what
193
- * `import.meta.glob(…, { eager: true })` returns, or a list of modules.
194
- * Discovery reads exports, as the build does.
247
+ * The app's modules, already imported, by the absolute path the manifest
248
+ * records for them. The generated harness imports them statically, from
249
+ * inside the app, so they resolve the way the app's own code does — its
250
+ * tsconfig paths included. A module not here is imported by path.
195
251
  */
196
- modules: Record<string, unknown> | readonly unknown[];
197
- /** The app's own database: what a test receives as `db`. */
198
- database?: KyselyDatabase<DB>;
199
- /** The browser each test gets — the app's subclass, with its clients. */
252
+ modules?: Readonly<Record<string, Record<string, unknown>>>;
253
+ /** The browser each test gets — the generated one, with the app's clients. */
200
254
  browser?: new () => TBrowser;
201
- /** The environment `gkm test` injected. Defaults to `process.env`. */
202
- env?: Record<string, string | undefined>;
255
+ /**
256
+ * The database a test receives as `db`, by construct id. Defaults to the
257
+ * one the app's endpoints name with `.database()`.
258
+ */
259
+ database?: string;
203
260
  }
204
261
  interface FeatureContext<TBrowser extends Browser, DB> {
205
262
  /** This test's browser, already the global `fetch`. */
@@ -208,6 +265,31 @@ interface FeatureContext<TBrowser extends Browser, DB> {
208
265
  db: Kysely<DB>;
209
266
  /** The mail sent to an address during this test, read from Mailpit. */
210
267
  mailbox: (address: string) => Mailbox;
268
+ /**
269
+ * What this test published to a topic or enqueued on a queue, in order.
270
+ * Nothing is delivered — a subscriber or a worker is tested on its own, by
271
+ * handing it events — so this is where publishing is asserted.
272
+ */
273
+ published: (channel: {
274
+ id: string;
275
+ }) => PublishedMessage[];
276
+ /**
277
+ * A subscriber, run on its own — handed events rather than delivered them —
278
+ * with this test's services: its databases resolve to this test's
279
+ * transactions, as an endpoint's do.
280
+ */
281
+ subscriber: <S extends Subscriber<any, any, any, any, any, any>>(subscriber: S) => SubscriberAdaptorOf<S>;
282
+ /** A queue's worker, run on its own with this test's services. */
283
+ queue: <Q extends Queue<any, any, any, any>>(queue: Q) => QueueAdaptorOf<Q>;
284
+ }
285
+ type SubscriberAdaptorOf<S> = S extends Subscriber<infer TServices, infer TLogger, infer OutSchema, infer TEventPublisher, infer TEventPublisherServiceName, infer TSubscribedEvents> ? TestSubscriberAdaptor<TServices, TLogger, OutSchema, TEventPublisher, TEventPublisherServiceName, TSubscribedEvents> : never;
286
+ type QueueAdaptorOf<Q> = Q extends Queue<infer TName, infer TMessage, infer TServices, infer TLogger> ? TestQueueAdaptor<TName, TMessage, TServices, TLogger> : never;
287
+ /** The schema a database construct was declared with — what `db` is typed by. */
288
+ type DatabaseOf<C> = C extends KyselyDatabase<infer DB, any> ? DB : unknown;
289
+ /** One message as it was published: its type and its payload. */
290
+ interface PublishedMessage {
291
+ type: string;
292
+ payload: unknown;
211
293
  }
212
294
  type FeatureFn<TBrowser extends Browser, DB> = (context: FeatureContext<TBrowser, DB>) => unknown;
213
295
  interface FeatureIt<TBrowser extends Browser, DB> {
@@ -215,7 +297,7 @@ interface FeatureIt<TBrowser extends Browser, DB> {
215
297
  only(name: string, fn: FeatureFn<TBrowser, DB>, timeout?: number): void;
216
298
  skip(name: string, fn: FeatureFn<TBrowser, DB>, timeout?: number): void;
217
299
  }
218
- export declare function featureTest<TBrowser extends Browser = Browser, DB = unknown>(options: FeatureTestOptions<TBrowser, DB>): FeatureIt<TBrowser, DB>;
300
+ export declare function featureTest<TBrowser extends Browser = Browser, DB = unknown>(options?: FeatureTestOptions<TBrowser>): FeatureIt<TBrowser, DB>;
219
301
  /** A request reached an in-process server without a test to belong to. */
220
302
  export declare class UnknownTestContext extends Error {
221
303
  readonly url: string;
@@ -226,6 +308,12 @@ export declare class UnknownTestContext extends Error {
226
308
  export declare class NoInbox extends Error {
227
309
  constructor();
228
310
  }
311
+ /** `featureTest({ database })` named a database the app does not declare. */
312
+ export declare class UnknownDatabase extends Error {
313
+ readonly id: string;
314
+ readonly declared: string[];
315
+ constructor(id: string, declared: string[]);
316
+ }
229
317
  //#endregion
230
- export { type CreateMswHandlersOptions, type CreateMswHandlersResult, type FeatureContext, type FeatureIt, type FeatureTestOptions, type RegisterContextOptions, TEST_CONTEXT_HEADER };
318
+ export { type CreateMswHandlersOptions, type CreateMswHandlersResult, type DatabaseOf, type FeatureContext, type FeatureIt, type FeatureTestOptions, type RegisterContextOptions, TEST_CONTEXT_HEADER, type TestManifest, type TestManifestSource };
231
319
  //# sourceMappingURL=testing.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.mts","names":["TestBrowser"],"sources":["../../src/endpoints/MswEndpointAdaptor.ts","../../src/endpoints/TestEndpointAdaptor.ts","../../src/functions/TestFunctionAdaptor.ts","../../src/queue/TestQueueAdaptor.ts","../../src/subscribers/TestSubscriberAdaptor.ts","../../src/testing/featureTest.ts"],"mappings":"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"}
1
+ {"version":3,"file":"testing.d.mts","names":["TestBrowser"],"sources":["../../src/endpoints/MswEndpointAdaptor.ts","../../src/endpoints/TestEndpointAdaptor.ts","../../src/functions/TestFunctionAdaptor.ts","../../src/queue/TestQueueAdaptor.ts","../../src/subscribers/TestSubscriberAdaptor.ts","../../src/testing/manifest.ts","../../src/testing/featureTest.ts"],"mappings":"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"}
@@ -4,6 +4,8 @@ import { n as ResponseBuilder, t as Endpoint } from "../Endpoint-DCr5tlbJ.mjs";
4
4
  import { Email } from "../email.mjs";
5
5
  import { KyselyDatabase } from "../database/kysely.mjs";
6
6
  import { c as createObjectHeaders, l as publishConstructEvents, r as createCookieHeaderAccessor, u as publishEvents } from "../lazyAccessors-BHtcqgz2.mjs";
7
+ import { t as Queue } from "../Queue-Dfumm1WR.mjs";
8
+ import { t as Topic } from "../Topic-QqnO_6Dl.mjs";
7
9
  import { t as HonoEndpoint } from "../HonoEndpointAdaptor-Be6UdK0r.mjs";
8
10
  import { n as executeWithAuditTransaction } from "../processAudits-BpRlPQMW.mjs";
9
11
  import { provideKey } from "@geekmidas/manifest";
@@ -20,6 +22,8 @@ import { createMailbox } from "@geekmidas/testkit/mailbox";
20
22
  import { TransactionRegistry } from "@geekmidas/testkit/transactions";
21
23
  import { setupServer } from "msw/node";
22
24
  import { afterAll, beforeAll, test } from "vitest";
25
+ import { readFileSync } from "node:fs";
26
+ import { fileURLToPath } from "node:url";
23
27
  //#region src/endpoints/MswEndpointAdaptor.ts
24
28
  const MSW_HTTP_METHODS = [
25
29
  "get",
@@ -415,6 +419,38 @@ var TestSubscriberAdaptor = class TestSubscriberAdaptor {
415
419
  }
416
420
  };
417
421
  //#endregion
422
+ //#region src/testing/manifest.ts
423
+ /**
424
+ * The test manifest: what `gkm test` knows about an app, written down for the
425
+ * suite it starts.
426
+ *
427
+ * `gkm test` discovers the constructs, reconciles the test stage and resolves
428
+ * its environment — then, until this existed, threw all of it away and left a
429
+ * test to declare it again by hand. The manifest keeps it: which construct and
430
+ * endpoint is exported where, and the environment the test stage resolved,
431
+ * under the keys the constructs themselves derive. A feature test is built from
432
+ * it, so nothing a test says can drift from what the app declares.
433
+ */
434
+ /** The environment variable `gkm test` names the manifest in. */
435
+ const TEST_MANIFEST_ENV = "GKM_TEST_MANIFEST";
436
+ /**
437
+ * Read a test manifest: from a path, a `file:` URL, or — given nothing — the
438
+ * one `gkm test` named in `GKM_TEST_MANIFEST`.
439
+ */
440
+ function loadTestManifest(from) {
441
+ const location = from ?? process.env["GKM_TEST_MANIFEST"];
442
+ if (!location) throw new NoTestManifest();
443
+ const path = location instanceof URL || location.startsWith("file:") ? fileURLToPath(location) : location;
444
+ return JSON.parse(readFileSync(path, "utf-8"));
445
+ }
446
+ /** A feature test ran without a manifest to build itself from. */
447
+ var NoTestManifest = class extends Error {
448
+ constructor() {
449
+ super(`No test manifest: run the suite with \`gkm test\`, which writes one and names it in ${TEST_MANIFEST_ENV}.`);
450
+ this.name = "NoTestManifest";
451
+ }
452
+ };
453
+ //#endregion
418
454
  //#region src/testing/featureTest.ts
419
455
  /**
420
456
  * `featureTest`: an app, driven the way it runs deployed.
@@ -425,15 +461,14 @@ var TestSubscriberAdaptor = class TestSubscriberAdaptor {
425
461
  * from the real handler, for the test that made the request. Everything else
426
462
  * with a real local equivalent — Postgres, Mailpit, MinIO — is used directly.
427
463
  *
464
+ * It is built from the test manifest `gkm test` writes — the constructs and
465
+ * endpoints the app exports, and the environment the test stage resolved — so
466
+ * a test declares none of it. `gkm test` also generates the configured `it`
467
+ * and a `Browser` with a typed client per surface:
468
+ *
428
469
  * ```ts
429
- * // test/config.ts
430
- * export const it = featureTest({
431
- * modules: import.meta.glob(['../../constructs/*.ts', '../src/endpoints/**\/*.ts'], { eager: true }),
432
- * database,
433
- * browser: Browser,
434
- * });
470
+ * import { it } from '#test';
435
471
  *
436
- * // a test
437
472
  * it('shows the signed-in user their profile', async ({ browser, mailbox }) => {
438
473
  * await browser.auth.signIn.magicLink({ email: 'ada@shop.test' });
439
474
  * await browser.visit((await mailbox('ada@shop.test').last()).link!);
@@ -445,17 +480,16 @@ var TestSubscriberAdaptor = class TestSubscriberAdaptor {
445
480
  const contexts = /* @__PURE__ */ new Map();
446
481
  /** Databases already resolving to test transactions — patched once each. */
447
482
  const bound = /* @__PURE__ */ new WeakSet();
448
- function featureTest(options) {
449
- const env = options.env ?? process.env;
450
- const envParser = new EnvironmentParser({ ...env });
451
- const found = discover(options.modules);
483
+ function featureTest(options = {}) {
484
+ const manifest = options.manifest ?? loadTestManifest();
452
485
  const BrowserClass = options.browser ?? Browser;
453
- for (const database of found.databases) bindToTests(database);
454
- const network = setupServer(...surfaceHandlers(found.endpoints, env), ...found.auths.map((auth) => authHandler(auth, env, envParser)));
455
- const inbox = found.emails.map((email) => env[provideKey(email.id, "inboxUrl")]).find(Boolean);
456
- const readMail = inbox ? createMailbox({ inbox }) : void 0;
486
+ let app;
487
+ let network;
457
488
  let restoreFetch = () => {};
458
- beforeAll(() => {
489
+ beforeAll(async () => {
490
+ app = await load(manifest, options.database, options.modules ?? {});
491
+ for (const database of app.databases) bindToTests(database);
492
+ network = setupServer(...surfaceHandlers(app.endpoints, manifest.env), ...app.auths.map((auth) => authHandler(auth, manifest.env, app.envParser)));
459
493
  network.listen({ onUnhandledRequest(request, print) {
460
494
  if (isLocal(new URL(request.url))) return;
461
495
  print.error();
@@ -464,18 +498,20 @@ function featureTest(options) {
464
498
  });
465
499
  afterAll(() => {
466
500
  restoreFetch();
467
- network.close();
501
+ network?.close();
468
502
  });
469
503
  const run = (fn) => async () => {
470
504
  const id = randomUUID();
471
505
  const state = {
472
506
  transactions: new TransactionRegistry(),
473
- discovery: new ServiceDiscovery(envParser),
507
+ discovery: new ServiceDiscovery(app.envParser),
474
508
  surfaces: /* @__PURE__ */ new Map(),
475
509
  auth: /* @__PURE__ */ new Map(),
476
- addresses: /* @__PURE__ */ new Set()
510
+ addresses: /* @__PURE__ */ new Set(),
511
+ published: /* @__PURE__ */ new Map()
477
512
  };
478
513
  contexts.set(id, state);
514
+ await state.discovery.register(recorders(app.channels, state));
479
515
  try {
480
516
  await runInTestContext(id, async () => {
481
517
  const browser = new BrowserClass();
@@ -483,12 +519,15 @@ function featureTest(options) {
483
519
  try {
484
520
  await fn({
485
521
  browser,
486
- db: options.database ? await state.transactions.get(options.database.id, urlOf(options.database, envParser)) : void 0,
522
+ db: app.database ? await state.transactions.get(app.database.id, urlOf(app.database, app.envParser)) : void 0,
487
523
  mailbox: (address) => {
488
- if (!readMail) throw new NoInbox();
524
+ if (!app.readMail) throw new NoInbox();
489
525
  state.addresses.add(address);
490
- return readMail(address);
491
- }
526
+ return app.readMail(address);
527
+ },
528
+ published: (channel) => [...state.published.get(channel.id) ?? []],
529
+ subscriber: (subscriber) => new TestSubscriberAdaptor(subscriber, state.discovery),
530
+ queue: (queue) => new TestQueueAdaptor(queue, state.discovery)
492
531
  });
493
532
  } finally {
494
533
  restore();
@@ -496,7 +535,7 @@ function featureTest(options) {
496
535
  });
497
536
  } finally {
498
537
  contexts.delete(id);
499
- await Promise.all([state.transactions.rollbackAll(), ...[...state.addresses].map((address) => readMail?.(address).clear())]);
538
+ await Promise.all([state.transactions.rollbackAll(), ...[...state.addresses].map((address) => app.readMail?.(address).clear())]);
500
539
  }
501
540
  };
502
541
  const it = ((name, fn, timeout) => test(name, run(fn), timeout));
@@ -504,17 +543,67 @@ function featureTest(options) {
504
543
  it.skip = (name, fn, timeout) => test.skip(name, run(fn), timeout);
505
544
  return it;
506
545
  }
507
- /** What an app's modules export, sorted by what a test does with it. */
508
- function discover(modules) {
509
- const values = (Array.isArray(modules) ? modules : Object.values(modules)).flatMap((module) => module && typeof module === "object" ? Object.values(module) : []);
546
+ /**
547
+ * Import what the manifest points at.
548
+ *
549
+ * The same modules the app runs, so a construct here *is* the app's construct —
550
+ * the one its endpoints were built from — never a copy declared again.
551
+ */
552
+ async function load(manifest, database, modules) {
553
+ const envParser = new EnvironmentParser({ ...manifest.env });
554
+ const imported = async ({ file, export: name }) => (modules[file] ?? await import(file))[name];
555
+ const constructs = await Promise.all(Object.values(manifest.constructs).map(({ source }) => imported(source)));
556
+ const endpoints = (await Promise.all(manifest.endpoints.map(({ source }) => imported(source)))).filter((value) => Endpoint.isEndpoint(value));
557
+ const unique = (values) => [...new Set(values)];
558
+ const databases = unique(constructs.filter((value) => value instanceof KyselyDatabase));
559
+ const auths = unique(constructs.filter((value) => value instanceof BetterAuth));
560
+ const emails = unique(constructs.filter((value) => value instanceof Email));
561
+ const channels = unique(constructs.filter((value) => value instanceof Topic || value instanceof Queue));
562
+ const inbox = emails.map((email) => manifest.env[provideKey(email.id, "inboxUrl")]).find(Boolean);
510
563
  return {
511
- endpoints: values.filter((value) => Endpoint.isEndpoint(value)),
512
- databases: values.filter((value) => value instanceof KyselyDatabase),
513
- auths: values.filter((value) => value instanceof BetterAuth),
514
- emails: values.filter((value) => value instanceof Email)
564
+ envParser,
565
+ endpoints,
566
+ databases,
567
+ auths,
568
+ emails,
569
+ channels,
570
+ database: databaseOf(databases, endpoints, database),
571
+ ...inbox ? { readMail: createMailbox({ inbox }) } : {}
515
572
  };
516
573
  }
517
574
  /**
575
+ * A publisher that records rather than sends, under every name a topic or
576
+ * queue is injected by — `users`, and the older `usersPublisher`.
577
+ */
578
+ function recorders(channels, state) {
579
+ return channels.flatMap((channel) => {
580
+ const publisher = { async publish(messages) {
581
+ state.published.set(channel.id, [...state.published.get(channel.id) ?? [], ...messages.map(({ type, payload }) => ({
582
+ type,
583
+ payload
584
+ }))]);
585
+ } };
586
+ return [channel.service.serviceName, channel.publisher.serviceName].map((serviceName) => ({
587
+ serviceName,
588
+ register: () => publisher
589
+ }));
590
+ });
591
+ }
592
+ /**
593
+ * The database a test sees as `db`: the one named, or else the one the
594
+ * endpoints were given with `.database()` — found by the service they hold,
595
+ * which is the construct's own.
596
+ */
597
+ function databaseOf(databases, endpoints, named) {
598
+ if (named) {
599
+ const found = databases.find(({ id }) => id === named);
600
+ if (!found) throw new UnknownDatabase(named, databases.map(({ id }) => id));
601
+ return found;
602
+ }
603
+ const services = new Set(endpoints.map((e) => e.databaseService).filter(Boolean));
604
+ return databases.find((database) => services.has(database.service));
605
+ }
606
+ /**
518
607
  * Make a database resolve, inside a test, to that test's transaction.
519
608
  *
520
609
  * Everything reaches a database through its service — an endpoint through
@@ -565,7 +654,7 @@ function surfaceHandlers(endpoints, env) {
565
654
  /** An auth server, served at its URL, on the transaction of the test that asked. */
566
655
  function authHandler(auth, env, envParser) {
567
656
  const base = trim(env[provideKey(auth.id, "url")] ?? "");
568
- return http.all(`${base}${auth.basePath}/*`, ({ request }) => serve(request, async (state) => {
657
+ return http.all(`${base}/*`, ({ request }) => serve(request, async (state) => {
569
658
  let app = state.auth.get(auth);
570
659
  if (!app) {
571
660
  app = auth.server({
@@ -608,7 +697,18 @@ var NoInbox = class extends Error {
608
697
  this.name = "NoInbox";
609
698
  }
610
699
  };
700
+ /** `featureTest({ database })` named a database the app does not declare. */
701
+ var UnknownDatabase = class extends Error {
702
+ id;
703
+ declared;
704
+ constructor(id, declared) {
705
+ super(`featureTest was asked for the database '${id}', and the app declares ${declared.length ? declared.map((d) => `'${d}'`).join(", ") : "none"}.`);
706
+ this.id = id;
707
+ this.declared = declared;
708
+ this.name = "UnknownDatabase";
709
+ }
710
+ };
611
711
  //#endregion
612
- export { NoInbox, TEST_CONTEXT_HEADER, TestEndpointAdaptor, TestFunctionAdaptor, TestQueueAdaptor, TestSubscriberAdaptor, UnknownTestContext, createMswHandlers, featureTest };
712
+ export { NoInbox, NoTestManifest, TEST_CONTEXT_HEADER, TEST_MANIFEST_ENV, TestEndpointAdaptor, TestFunctionAdaptor, TestQueueAdaptor, TestSubscriberAdaptor, UnknownDatabase, UnknownTestContext, createMswHandlers, featureTest, loadTestManifest };
613
713
 
614
714
  //# sourceMappingURL=testing.mjs.map