@geekmidas/constructs 10.0.0-alpha.36 → 10.0.0-alpha.38

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 (107) hide show
  1. package/dist/{Cron-CA2oa2nC.d.cts → Cron-Brs_n_vQ.d.cts} +2 -2
  2. package/dist/{Cron-B_iuyNxr.d.mts.map → Cron-Brs_n_vQ.d.cts.map} +1 -1
  3. package/dist/{Cron-B_iuyNxr.d.mts → Cron-C18-2Lgb.d.mts} +2 -2
  4. package/dist/{Cron-CA2oa2nC.d.cts.map → Cron-C18-2Lgb.d.mts.map} +1 -1
  5. package/dist/{CronBuilder-B_KqaodP.d.cts → CronBuilder-4WnWKR40.d.cts} +4 -4
  6. package/dist/{CronBuilder-BIKCLWu7.d.mts.map → CronBuilder-4WnWKR40.d.cts.map} +1 -1
  7. package/dist/{CronBuilder-BIKCLWu7.d.mts → CronBuilder-CGv51EoE.d.mts} +4 -4
  8. package/dist/{CronBuilder-B_KqaodP.d.cts.map → CronBuilder-CGv51EoE.d.mts.map} +1 -1
  9. package/dist/{Endpoint-1-XYQSx0.d.cts → Endpoint-BkhPHCuX.d.cts} +4 -4
  10. package/dist/{Endpoint-1-XYQSx0.d.cts.map → Endpoint-BkhPHCuX.d.cts.map} +1 -1
  11. package/dist/{Endpoint-GFIxYb9L.d.mts → Endpoint-DkiD0GJs.d.mts} +4 -4
  12. package/dist/{Endpoint-GFIxYb9L.d.mts.map → Endpoint-DkiD0GJs.d.mts.map} +1 -1
  13. package/dist/{EndpointFactory-BCZdh_u3.d.mts → EndpointFactory-WJzZfkTW.d.mts} +4 -4
  14. package/dist/{EndpointFactory-BCZdh_u3.d.mts.map → EndpointFactory-WJzZfkTW.d.mts.map} +1 -1
  15. package/dist/{EndpointFactory-BnK_YkW1.d.cts → EndpointFactory-n96CT_Ys.d.cts} +4 -4
  16. package/dist/{EndpointFactory-BnK_YkW1.d.cts.map → EndpointFactory-n96CT_Ys.d.cts.map} +1 -1
  17. package/dist/{SubscriberBuilder-CjDGSTZc.d.cts → SubscriberBuilder-CIXTozBi.d.cts} +2 -2
  18. package/dist/{SubscriberBuilder-C2FRfd26.d.mts.map → SubscriberBuilder-CIXTozBi.d.cts.map} +1 -1
  19. package/dist/{SubscriberBuilder-C2FRfd26.d.mts → SubscriberBuilder-CMBzPdRX.d.mts} +2 -2
  20. package/dist/{SubscriberBuilder-CjDGSTZc.d.cts.map → SubscriberBuilder-CMBzPdRX.d.mts.map} +1 -1
  21. package/dist/adaptors/aws.d.cts +3 -3
  22. package/dist/adaptors/aws.d.mts +3 -3
  23. package/dist/adaptors/hono.d.cts +1 -1
  24. package/dist/adaptors/hono.d.mts +1 -1
  25. package/dist/adaptors/testing.cjs +44 -6
  26. package/dist/adaptors/testing.cjs.map +1 -1
  27. package/dist/adaptors/testing.d.cts +18 -4
  28. package/dist/adaptors/testing.d.cts.map +1 -1
  29. package/dist/adaptors/testing.d.mts +18 -4
  30. package/dist/adaptors/testing.d.mts.map +1 -1
  31. package/dist/adaptors/testing.mjs +44 -6
  32. package/dist/adaptors/testing.mjs.map +1 -1
  33. package/dist/{auth-c6nWnvD8.d.cts → auth-6Cdx5amw.d.cts} +2 -2
  34. package/dist/{auth-c6nWnvD8.d.cts.map → auth-6Cdx5amw.d.cts.map} +1 -1
  35. package/dist/{auth-pS_U1H_X.d.mts → auth-B_c9b54v.d.mts} +2 -2
  36. package/dist/{auth-pS_U1H_X.d.mts.map → auth-B_c9b54v.d.mts.map} +1 -1
  37. package/dist/auth.d.cts +1 -1
  38. package/dist/auth.d.mts +1 -1
  39. package/dist/{cache-B2s0b-ss.d.mts → cache-CSJHbgwS.d.cts} +3 -3
  40. package/dist/{cache-B2s0b-ss.d.mts.map → cache-CSJHbgwS.d.cts.map} +1 -1
  41. package/dist/{cache-CYe2Kzjc.d.cts → cache-HHg5No4L.d.mts} +3 -3
  42. package/dist/{cache-CYe2Kzjc.d.cts.map → cache-HHg5No4L.d.mts.map} +1 -1
  43. package/dist/cache.d.cts +1 -1
  44. package/dist/cache.d.mts +1 -1
  45. package/dist/{construct-interface-D2gesxos.d.mts → construct-interface-BLG_BEH5.d.mts} +3 -3
  46. package/dist/{construct-interface-D2gesxos.d.mts.map → construct-interface-BLG_BEH5.d.mts.map} +1 -1
  47. package/dist/{construct-interface-DPusCUSQ.d.cts → construct-interface-DcNaRKoC.d.cts} +3 -3
  48. package/dist/{construct-interface-DPusCUSQ.d.cts.map → construct-interface-DcNaRKoC.d.cts.map} +1 -1
  49. package/dist/construct-interface.cjs +3 -3
  50. package/dist/construct-interface.cjs.map +1 -1
  51. package/dist/construct-interface.d.cts +1 -1
  52. package/dist/construct-interface.d.mts +1 -1
  53. package/dist/construct-interface.mjs +3 -3
  54. package/dist/construct-interface.mjs.map +1 -1
  55. package/dist/credential.cjs +22 -9
  56. package/dist/credential.cjs.map +1 -1
  57. package/dist/credential.d.cts +12 -2
  58. package/dist/credential.d.cts.map +1 -1
  59. package/dist/credential.d.mts +12 -2
  60. package/dist/credential.d.mts.map +1 -1
  61. package/dist/credential.mjs +22 -10
  62. package/dist/credential.mjs.map +1 -1
  63. package/dist/crons/index.d.cts +2 -2
  64. package/dist/crons/index.d.mts +2 -2
  65. package/dist/database/kysely.d.cts +1 -1
  66. package/dist/database/kysely.d.mts +1 -1
  67. package/dist/email.d.cts +1 -1
  68. package/dist/email.d.mts +1 -1
  69. package/dist/endpoints/index.d.cts +2 -2
  70. package/dist/endpoints/index.d.mts +2 -2
  71. package/dist/external-api-BKOb2jSC.d.cts +86 -0
  72. package/dist/external-api-BKOb2jSC.d.cts.map +1 -0
  73. package/dist/external-api-CZHev1Oh.d.mts +86 -0
  74. package/dist/external-api-CZHev1Oh.d.mts.map +1 -0
  75. package/dist/external-api.cjs +124 -0
  76. package/dist/external-api.cjs.map +1 -0
  77. package/dist/external-api.d.cts +2 -0
  78. package/dist/external-api.d.mts +2 -0
  79. package/dist/external-api.mjs +121 -0
  80. package/dist/external-api.mjs.map +1 -0
  81. package/dist/file-server.d.cts +1 -1
  82. package/dist/file-server.d.mts +1 -1
  83. package/dist/functions/index.d.cts +1 -1
  84. package/dist/functions/index.d.mts +1 -1
  85. package/dist/{index-yb3fXNyH.d.mts → index-DwveWr3c.d.mts} +2 -2
  86. package/dist/{index-BI23K3wy.d.cts.map → index-DwveWr3c.d.mts.map} +1 -1
  87. package/dist/{index-BI23K3wy.d.cts → index-Dzkdm-IE.d.cts} +2 -2
  88. package/dist/{index-yb3fXNyH.d.mts.map → index-Dzkdm-IE.d.cts.map} +1 -1
  89. package/dist/{kysely-CEVXacPN.d.cts → kysely-Dy8nNgvk.d.cts} +3 -3
  90. package/dist/{kysely-CEVXacPN.d.cts.map → kysely-Dy8nNgvk.d.cts.map} +1 -1
  91. package/dist/{kysely-mcTdPNhl.d.mts → kysely-QLoPapbM.d.mts} +3 -3
  92. package/dist/{kysely-mcTdPNhl.d.mts.map → kysely-QLoPapbM.d.mts.map} +1 -1
  93. package/dist/mobile-app.d.cts +1 -1
  94. package/dist/mobile-app.d.mts +1 -1
  95. package/dist/object-storage.d.cts +1 -1
  96. package/dist/object-storage.d.mts +1 -1
  97. package/dist/queue/index.d.cts +1 -1
  98. package/dist/queue/index.d.mts +1 -1
  99. package/dist/rest-api.d.cts +3 -3
  100. package/dist/rest-api.d.mts +3 -3
  101. package/dist/site.d.cts +1 -1
  102. package/dist/site.d.mts +1 -1
  103. package/dist/subscribers/index.d.cts +1 -1
  104. package/dist/subscribers/index.d.mts +1 -1
  105. package/dist/worker.d.cts +5 -5
  106. package/dist/worker.d.mts +5 -5
  107. package/package.json +41 -31
@@ -18,6 +18,7 @@ import { TEST_CONTEXT_HEADER, currentTestContext, installContextFetch, runAsServ
18
18
  import { http } from "msw";
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { Browser } from "@geekmidas/testkit/browser";
21
+ import { faker } from "@geekmidas/testkit/faker";
21
22
  import { createMailbox } from "@geekmidas/testkit/mailbox";
22
23
  import { TransactionRegistry } from "@geekmidas/testkit/transactions";
23
24
  import { setupServer } from "msw/node";
@@ -512,7 +513,7 @@ function featureTest(options = {}) {
512
513
  owned = ownDatabases(app, manifest);
513
514
  factoryDatabases = databasesFor(options.factories ?? {}, owned);
514
515
  for (const database of app.databases) bindToTests(database);
515
- network = setupServer(...surfaceHandlers(app.endpoints, manifest.env), ...app.auths.map((auth) => authHandler(auth, manifest.env, app.envParser)));
516
+ network = setupServer(...surfaceHandlers(app.endpoints, manifest.env), ...app.auths.map((auth) => authHandler(auth, manifest.env, app.envParser)), ...Object.entries(options.fakes ?? {}).flatMap(([id, fake]) => fakeHandler(id, fake, manifest.env)));
516
517
  network.listen({ onUnhandledRequest(request, print) {
517
518
  if (isLocal(new URL(request.url))) return;
518
519
  print.error();
@@ -523,8 +524,9 @@ function featureTest(options = {}) {
523
524
  restoreFetch();
524
525
  network?.close();
525
526
  });
526
- const run = (fn) => async () => {
527
+ const run = (name, fn) => async () => {
527
528
  const id = randomUUID();
529
+ faker.seed(seedOf(name));
528
530
  const state = {
529
531
  transactions: new TransactionRegistry(),
530
532
  discovery: new ServiceDiscovery(app.envParser),
@@ -565,6 +567,7 @@ function featureTest(options = {}) {
565
567
  }
566
568
  return factory;
567
569
  } },
570
+ faker,
568
571
  mailbox: (address) => {
569
572
  if (!state.mailbox) throw new NoInbox();
570
573
  return state.mailbox(address);
@@ -582,12 +585,21 @@ function featureTest(options = {}) {
582
585
  await Promise.all([state.transactions.rollbackAll(), ...[...state.addresses].map((address) => app.readMail?.(address).clear())]);
583
586
  }
584
587
  };
585
- const it = ((name, fn, timeout) => test(name, run(fn), timeout));
586
- it.only = (name, fn, timeout) => test.only(name, run(fn), timeout);
587
- it.skip = (name, fn, timeout) => test.skip(name, run(fn), timeout);
588
+ const it = ((name, fn, timeout) => test(name, run(name, fn), timeout));
589
+ it.only = (name, fn, timeout) => test.only(name, run(name, fn), timeout);
590
+ it.skip = (name, fn, timeout) => test.skip(name, run(name, fn), timeout);
588
591
  return it;
589
592
  }
590
593
  /**
594
+ * A test's faker seed, from its name: the same test draws the same data on
595
+ * every run, and two tests draw different data.
596
+ */
597
+ function seedOf(name) {
598
+ let hash = 0;
599
+ for (const char of name) hash = Math.imul(hash, 31) + char.charCodeAt(0) | 0;
600
+ return hash >>> 0;
601
+ }
602
+ /**
591
603
  * Import what the manifest points at.
592
604
  *
593
605
  * The same modules the app runs, so a construct here *is* the app's construct —
@@ -698,6 +710,20 @@ function surfaceHandlers(endpoints, env) {
698
710
  }));
699
711
  });
700
712
  }
713
+ /**
714
+ * An external API's app fake, answering at the URL the test stage resolved for
715
+ * it — so a handler that calls Polar calls the fake, and the test sets up
716
+ * nothing.
717
+ *
718
+ * Not per test, unlike a surface: a fake stands in for somebody else's server,
719
+ * which no test's transaction reaches. An image fake has no handler here — its
720
+ * container answers on localhost, which is let through.
721
+ */
722
+ function fakeHandler(id, fake, env) {
723
+ const base = env[provideKey(id, "url")];
724
+ if (fake.kind !== "app" || !base) return [];
725
+ return [http.all(`${trim(base)}/*`, ({ request }) => fake.handler.fetch(request))];
726
+ }
701
727
  /** An auth server, served at its URL, on the transaction of the test that asked. */
702
728
  function authHandler(auth, env, envParser) {
703
729
  const base = trim(env[provideKey(auth.id, "url")] ?? "");
@@ -735,7 +761,7 @@ function isLocal(url) {
735
761
  * — and cleared again after the test, like any address a test reads. Returns
736
762
  * the session the auth server then reports, as better-auth hands it back.
737
763
  */
738
- async function signInWithMagicLink(browser, auth, email) {
764
+ async function signInWithMagicLink(browser, auth, email = freshAddress()) {
739
765
  const context = currentTestContext();
740
766
  const state = context && contexts.get(context.id);
741
767
  if (!state) throw new UnknownTestContext("signIn", context?.id);
@@ -751,6 +777,18 @@ async function signInWithMagicLink(browser, auth, email) {
751
777
  if (!data) throw new SignInFailed(email, "the auth server reported no session after the link was opened");
752
778
  return data;
753
779
  }
780
+ /**
781
+ * An address nobody else signs in as — what `browser.signIn()` uses when the
782
+ * test does not care who it is.
783
+ *
784
+ * Unique rather than only seeded: tests in other files run at the same time,
785
+ * against the same Mailpit and the same unique columns, and a seeded address
786
+ * repeats wherever two tests share a name. The readable half is the seeded
787
+ * faker's, so a failure still names somebody.
788
+ */
789
+ function freshAddress() {
790
+ return `${faker.internet.username().toLowerCase().replace(/[^a-z0-9]/g, "")}.${randomUUID().slice(0, 8)}@example.test`;
791
+ }
754
792
  /** `browser.signIn` finished without a session. */
755
793
  var SignInFailed = class extends Error {
756
794
  email;
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","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"],"sourcesContent":["import { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Service } from '@geekmidas/services';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport { TEST_CONTEXT_HEADER } from '@geekmidas/testkit/context';\nimport { Hono } from 'hono';\nimport { type HttpHandler, http } from 'msw';\nimport type { Endpoint } from './Endpoint';\nimport { HonoEndpoint } from './HonoEndpointAdaptor';\n\n/**\n * Header used to identify which test context a request belongs to — defined\n * once, in testkit, where the stamped `fetch` that writes it lives.\n */\nexport { TEST_CONTEXT_HEADER };\n\n/**\n * Per-test context containing an isolated ServiceDiscovery instance.\n * Registered in the context map and looked up per-request via the context ID header.\n */\nexport interface MswTestContext {\n\tserviceDiscovery: ServiceDiscovery<any>;\n}\n\n/**\n * Options for creating MSW handlers from endpoint constructs.\n *\n * @example\n * ```typescript\n * import { createMswHandlers, registerTestContext } from '@geekmidas/constructs/testing';\n * import { setupServer } from 'msw/node';\n *\n * // Create handlers once (global)\n * const { handlers, registerContext, removeContext } = createMswHandlers(\n * [getUsers, createUser],\n * { baseURL: 'http://localhost:3000' },\n * );\n * const server = setupServer(...handlers);\n *\n * // Per test: register an isolated context with its own services/transaction\n * it('should list users', async ({ db }) => {\n * const contextId = crypto.randomUUID();\n * registerContext(contextId, {\n * services: { database: db, auth: mockAuth },\n * });\n *\n * const api = createApi({\n * baseURL: 'http://localhost:3000',\n * headers: { [TEST_CONTEXT_HEADER]: contextId },\n * });\n *\n * const result = await api('GET /users');\n * expect(result).toBeDefined();\n *\n * removeContext(contextId);\n * });\n * ```\n */\nexport interface CreateMswHandlersOptions {\n\t/** Base URL the client fetches from (e.g., 'http://localhost:3000') */\n\tbaseURL: string;\n}\n\n/**\n * Options for registering a per-test context.\n * Follows the same pattern as TestRequestAdaptor — services, database,\n * publisher, and auditorStorage are provided explicitly.\n */\nexport interface RegisterContextOptions {\n\t/** Service instances keyed by serviceName */\n\tservices?: Record<string, unknown>;\n\t/** Database instance — required when endpoints use .database() */\n\tdatabase?: unknown;\n\t/** Event publisher service definition */\n\tpublisher?: Service;\n\t/** Audit storage instance — required when endpoints use .auditor() */\n\tauditorStorage?: unknown;\n}\n\nconst MSW_HTTP_METHODS = [\n\t'get',\n\t'post',\n\t'put',\n\t'patch',\n\t'delete',\n\t'options',\n] as const;\n\ntype MswHttpMethod = (typeof MSW_HTTP_METHODS)[number];\n\n/**\n * Result of creating MSW handlers from endpoint constructs.\n */\nexport interface CreateMswHandlersResult {\n\t/** MSW handlers to pass to setupServer() */\n\thandlers: HttpHandler[];\n\t/** Register an isolated context for a test */\n\tregisterContext: (id: string, options: RegisterContextOptions) => void;\n}\n\n/**\n * Creates MSW HTTP handlers from endpoint constructs.\n *\n * Mounts endpoints on a Hono app and creates MSW handlers that intercept\n * fetch requests and route them through `app.request()` — giving frontend\n * tests full endpoint behavior (validation, auth, sessions) without HTTP.\n *\n * Each test registers its own isolated context via `registerContext()`,\n * which is resolved per-request using the `x-test-context-id` header.\n * This allows concurrent tests with their own transactions and services.\n *\n * @param endpoints - Array of endpoint constructs to create handlers for\n * @param options - Configuration including baseURL\n * @returns MSW handlers, the Hono app, and context management functions\n */\nexport function createMswHandlers(\n\tendpoints: Endpoint<any, any, any, any, any, any>[],\n\toptions: CreateMswHandlersOptions,\n): CreateMswHandlersResult {\n\tconst { baseURL } = options;\n\tconst contexts = new Map<string, MswTestContext>();\n\n\t/**\n\t * Register an isolated context for a test.\n\t * Creates a fresh ServiceDiscovery and pre-registers all provided services.\n\t */\n\tfunction registerContext(id: string, ctxOptions: RegisterContextOptions) {\n\t\tconst envParser = new EnvironmentParser({});\n\t\tconst serviceDiscovery = new ServiceDiscovery(envParser);\n\n\t\tconst serviceDefs: Service[] = [];\n\n\t\t// Register explicit services\n\t\tif (ctxOptions.services) {\n\t\t\tfor (const [name, instance] of Object.entries(ctxOptions.services)) {\n\t\t\t\tserviceDefs.push({\n\t\t\t\t\tserviceName: name,\n\t\t\t\t\tregister: () => instance,\n\t\t\t\t} as Service);\n\t\t\t}\n\t\t}\n\n\t\t// Register database service from endpoint metadata\n\t\tif (ctxOptions.database !== undefined) {\n\t\t\tfor (const endpoint of endpoints) {\n\t\t\t\tif (endpoint.databaseService) {\n\t\t\t\t\tserviceDefs.push({\n\t\t\t\t\t\tserviceName: endpoint.databaseService.serviceName,\n\t\t\t\t\t\tregister: () => ctxOptions.database,\n\t\t\t\t\t} as Service);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Register publisher service\n\t\tif (ctxOptions.publisher) {\n\t\t\tserviceDefs.push(ctxOptions.publisher);\n\t\t}\n\n\t\t// Register auditor storage service from endpoint metadata\n\t\tif (ctxOptions.auditorStorage !== undefined) {\n\t\t\tfor (const endpoint of endpoints) {\n\t\t\t\tif (endpoint.auditorStorageService) {\n\t\t\t\t\tserviceDefs.push({\n\t\t\t\t\t\tserviceName: endpoint.auditorStorageService.serviceName,\n\t\t\t\t\t\tregister: () => ctxOptions.auditorStorage,\n\t\t\t\t\t} as Service);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (serviceDefs.length > 0) {\n\t\t\tserviceDiscovery.register(serviceDefs);\n\t\t}\n\n\t\tcontexts.set(id, { serviceDiscovery });\n\t}\n\n\t// Create a Hono app per-request that uses the correct ServiceDiscovery.\n\t// We use a wrapper app that resolves the context from the header,\n\t// then delegates to a context-specific Hono app.\n\tconst handlers: HttpHandler[] = [];\n\n\tfor (const endpoint of endpoints) {\n\t\tconst method = endpoint.method.toLowerCase() as MswHttpMethod;\n\t\tif (!MSW_HTTP_METHODS.includes(method)) continue;\n\n\t\tconst mswUrl = `${baseURL}${endpoint.route}`;\n\t\tconst mswMethod = http[method];\n\n\t\thandlers.push(\n\t\t\tmswMethod(mswUrl, async ({ request }) => {\n\t\t\t\tconst contextId = request.headers.get(TEST_CONTEXT_HEADER);\n\t\t\t\tconst ctx = contextId ? contexts.get(contextId) : undefined;\n\n\t\t\t\tif (!ctx) {\n\t\t\t\t\treturn new Response(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\terror: 'Missing or unknown test context ID',\n\t\t\t\t\t\t\thint: `Set the '${TEST_CONTEXT_HEADER}' header to a registered context ID`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t{ status: 500, headers: { 'content-type': 'application/json' } },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Build a fresh Hono app with this context's ServiceDiscovery\n\t\t\t\tconst app = new Hono();\n\t\t\t\tHonoEndpoint.addRoute(endpoint, ctx.serviceDiscovery as any, app);\n\n\t\t\t\tconst response = await app.request(request);\n\n\t\t\t\treturn new Response(response.body, {\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\theaders: response.headers,\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t}\n\n\treturn {\n\t\thandlers,\n\t\tregisterContext,\n\t};\n}\n","import type {\n\tAuditActor,\n\tAuditableAction,\n\tAuditStorage,\n} from '@geekmidas/audit';\nimport { DefaultAuditor } from '@geekmidas/audit';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport { ForbiddenError } from '@geekmidas/errors';\nimport type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type {\n\tInferComposableStandardSchema,\n\tInferStandardSchema,\n} from '@geekmidas/schema';\nimport {\n\trunWithRequestContext,\n\ttype Service,\n\tServiceDiscovery,\n\ttype ServiceRecord,\n} from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishConstructEvents } from '../publisher';\nimport type { HttpMethod } from '../types';\nimport type { MappedAudit } from './audit';\nimport {\n\ttype CookieOptions,\n\tEndpoint,\n\ttype EndpointSchemas,\n\tResponseBuilder,\n} from './Endpoint';\nimport {\n\tcreateCookieHeaderAccessor,\n\tcreateObjectHeaders,\n} from './lazyAccessors';\nimport {\n\ttype AuditExecutionContext,\n\texecuteWithAuditTransaction,\n} from './processAudits';\n\nexport type TestHttpResponse<TBody = any> = {\n\tbody: TBody;\n\tstatus: number;\n\theaders: Record<string, string | string[]>;\n};\n\n/**\n * Serializes a cookie into a Set-Cookie header string\n */\nfunction serializeCookie(\n\tname: string,\n\tvalue: string,\n\toptions?: CookieOptions,\n): string {\n\tlet cookieString = `${name}=${value}`;\n\n\tif (options) {\n\t\tif (options.maxAge !== undefined) {\n\t\t\tcookieString += `; Max-Age=${options.maxAge}`;\n\t\t}\n\t\tif (options.expires) {\n\t\t\tcookieString += `; Expires=${options.expires.toUTCString()}`;\n\t\t}\n\t\tif (options.domain) {\n\t\t\tcookieString += `; Domain=${options.domain}`;\n\t\t}\n\t\tif (options.path) {\n\t\t\tcookieString += `; Path=${options.path}`;\n\t\t}\n\t\tif (options.httpOnly) {\n\t\t\tcookieString += '; HttpOnly';\n\t\t}\n\t\tif (options.secure) {\n\t\t\tcookieString += '; Secure';\n\t\t}\n\t\tif (options.sameSite) {\n\t\t\tcookieString += `; SameSite=${options.sameSite}`;\n\t\t}\n\t}\n\n\treturn cookieString;\n}\n\nexport class TestEndpointAdaptor<\n\tTRoute extends string,\n\tTMethod extends HttpMethod,\n\tTInput extends EndpointSchemas = {},\n\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tTSession = unknown,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n> {\n\tstatic getDefaultServiceDiscover<\n\t\tTRoute extends string,\n\t\tTMethod extends HttpMethod,\n\t\tTInput extends EndpointSchemas = {},\n\t\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\t\tTServices extends Service[] = [],\n\t\tTLogger extends Logger = Logger,\n\t\tTSession = unknown,\n\t\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\t\tTEventPublisherServiceName extends string = string,\n\t\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t\tTAuditStorageServiceName extends string = string,\n\t\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t\tTDatabase = undefined,\n\t\tTDatabaseServiceName extends string = string,\n\t>(\n\t\t_endpoint: Endpoint<\n\t\t\tTRoute,\n\t\t\tTMethod,\n\t\t\tTInput,\n\t\t\tTOutSchema,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTSession,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditAction,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t) {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\tconstructor(\n\t\tprivate readonly endpoint: Endpoint<\n\t\t\tTRoute,\n\t\t\tTMethod,\n\t\t\tTInput,\n\t\t\tTOutSchema,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTSession,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditAction,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestEndpointAdaptor.getDefaultServiceDiscover(\n\t\t\tendpoint,\n\t\t),\n\t) {}\n\n\tasync fullRequest(\n\t\tctx: TestRequestAdaptor<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t): Promise<TestHttpResponse<InferStandardSchema<TOutSchema>>> {\n\t\tconst body = await this.endpoint.parseInput((ctx as any).body, 'body');\n\t\tconst query = await this.endpoint.parseInput((ctx as any).query, 'query');\n\t\tconst params = await this.endpoint.parseInput(\n\t\t\t(ctx as any).params,\n\t\t\t'params',\n\t\t);\n\n\t\t// Lazy accessors - defer parsing until needed\n\t\tconst header = createObjectHeaders(ctx.headers);\n\t\tconst cookie = createCookieHeaderAccessor(ctx.headers.cookie);\n\n\t\t// Request context setup\n\t\tconst startTime = Date.now();\n\t\tconst requestId =\n\t\t\t(ctx as any).requestId ??\n\t\t\tctx.headers['x-request-id'] ??\n\t\t\tcrypto.randomUUID();\n\n\t\tconst logger = this.endpoint.logger.child({\n\t\t\trequestId,\n\t\t\troute: this.endpoint.route,\n\t\t\thost: ctx.headers.host,\n\t\t\tmethod: this.endpoint.method,\n\t\t}) as TLogger;\n\n\t\t// Get database from context for session extraction\n\t\tconst rawDb = (ctx as any).database as TDatabase;\n\n\t\t// Wrap handler execution in request context\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\tconst session = await this.endpoint.getSession({\n\t\t\t\tlogger,\n\t\t\t\tservices: ctx.services,\n\t\t\t\theader,\n\t\t\t\tcookie,\n\t\t\t\t...(rawDb !== undefined && { db: rawDb }),\n\t\t\t} as any);\n\n\t\t\t// Check authorization\n\t\t\tconst isAuthorized = await this.endpoint.authorize({\n\t\t\t\theader,\n\t\t\t\tcookie,\n\t\t\t\tservices: ctx.services,\n\t\t\t\tlogger,\n\t\t\t\tsession,\n\t\t\t\t...(rawDb !== undefined && { db: rawDb }),\n\t\t\t\tbody,\n\t\t\t\tquery,\n\t\t\t\tparams,\n\t\t\t} as any);\n\n\t\t\tif (!isAuthorized) {\n\t\t\t\tlogger.warn('Forbidden access attempt');\n\t\t\t\tthrow new ForbiddenError(\n\t\t\t\t\t'Forbidden access to the endpoint',\n\t\t\t\t\t'You do not have permission to access this resource.',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Create audit context if audit storage is provided\n\t\t\t// The auditorStorage instance is required when endpoint uses .auditor()\n\t\t\tconst auditorStorage = (ctx as any).auditorStorage as TAuditStorage;\n\t\t\tlet auditContext: AuditExecutionContext<TAuditAction> | undefined;\n\n\t\t\tif (auditorStorage) {\n\t\t\t\t// Extract actor if configured\n\t\t\t\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\t\t\t\tif (this.endpoint.actorExtractor) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactor = await this.endpoint.actorExtractor({\n\t\t\t\t\t\t\tservices: ctx.services as any,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\theader,\n\t\t\t\t\t\t\tcookie,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst auditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\t\tactor,\n\t\t\t\t\tstorage: auditorStorage as AuditStorage,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tendpoint: this.endpoint.route,\n\t\t\t\t\t\tmethod: this.endpoint.method,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tauditContext = { auditor, storage: auditorStorage as AuditStorage };\n\t\t\t}\n\n\t\t\t// Warn if declarative audits are configured but no audit storage\n\t\t\tconst audits = this.endpoint.audits as MappedAudit<\n\t\t\t\tTAuditAction,\n\t\t\t\tTOutSchema\n\t\t\t>[];\n\t\t\tif (!auditContext && audits?.length) {\n\t\t\t\tlogger.warn('No auditor storage service available');\n\t\t\t}\n\n\t\t\t// Execute handler with automatic audit transaction support\n\t\t\tconst result = await executeWithAuditTransaction(\n\t\t\t\tauditContext,\n\t\t\t\tasync (auditor) => {\n\t\t\t\t\t// Use audit transaction as db if available (when storage has same database)\n\t\t\t\t\t// For testing, the tester controls whether to use transactional auditing\n\t\t\t\t\tconst trx = auditor?.getTransaction?.();\n\t\t\t\t\tconst db = trx ?? rawDb;\n\n\t\t\t\t\tconst responseBuilder = new ResponseBuilder();\n\t\t\t\t\tconst response = await this.endpoint.handler(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\tquery,\n\t\t\t\t\t\t\tparams,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\tservices: ctx.services,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\theader,\n\t\t\t\t\t\t\tcookie,\n\t\t\t\t\t\t\tauditor,\n\t\t\t\t\t\t\tdb,\n\t\t\t\t\t\t} as any,\n\t\t\t\t\t\tresponseBuilder,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check if response has metadata\n\t\t\t\t\tlet data = response;\n\t\t\t\t\tlet metadata = responseBuilder.getMetadata();\n\n\t\t\t\t\tif (Endpoint.hasMetadata(response)) {\n\t\t\t\t\t\tdata = response.data;\n\t\t\t\t\t\tmetadata = response.metadata;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst output = await this.endpoint.parseOutput(data);\n\n\t\t\t\t\treturn { output, metadata, responseBuilder };\n\t\t\t\t},\n\t\t\t\t// Process declarative audits after handler (inside transaction)\n\t\t\t\tasync (result, auditor) => {\n\t\t\t\t\tif (!audits?.length) return;\n\n\t\t\t\t\tfor (const audit of audits) {\n\t\t\t\t\t\tif (audit.when && !audit.when(result.output as any)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst payload = audit.payload(result.output as any);\n\t\t\t\t\t\tconst entityId = audit.entityId?.(result.output as any);\n\t\t\t\t\t\tauditor.audit(audit.type as any, payload as any, {\n\t\t\t\t\t\t\ttable: audit.table,\n\t\t\t\t\t\t\tentityId,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t// Pass rawDb so storage can reuse existing transactions\n\t\t\t\t{ db: rawDb },\n\t\t\t);\n\n\t\t\tconst { output, metadata } = result;\n\n\t\t\tctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));\n\t\t\tawait publishConstructEvents(\n\t\t\t\tthis.endpoint,\n\t\t\t\toutput,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t);\n\n\t\t\t// Convert cookies to Set-Cookie headers\n\t\t\tconst headers: Record<string, string | string[]> = {\n\t\t\t\t...(metadata.headers || {}),\n\t\t\t};\n\n\t\t\tif (metadata.cookies && metadata.cookies.size > 0) {\n\t\t\t\tconst setCookieValues: string[] = [];\n\t\t\t\tfor (const [name, cookie] of metadata.cookies.entries()) {\n\t\t\t\t\tsetCookieValues.push(\n\t\t\t\t\t\tserializeCookie(name, cookie.value, cookie.options),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\theaders['set-cookie'] = setCookieValues;\n\t\t\t}\n\n\t\t\t// Return HTTP response format\n\t\t\treturn {\n\t\t\t\tbody: output,\n\t\t\t\tstatus: metadata.status || 200,\n\t\t\t\theaders,\n\t\t\t};\n\t\t});\n\t}\n\n\tasync request(\n\t\tctx: TestRequestAdaptor<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t): Promise<InferStandardSchema<TOutSchema>> {\n\t\tconst response = await this.fullRequest(ctx);\n\t\treturn response.body;\n\t}\n}\n\n/**\n * Conditional audit storage requirement - required when TAuditStorage is configured\n */\ntype AuditStorageRequirement<\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n> = TAuditStorage extends undefined\n\t? {}\n\t: {\n\t\t\t/** Audit storage instance - required when endpoint uses .auditor() */\n\t\t\tauditorStorage: TAuditStorage;\n\t\t};\n\n/**\n * Conditional database requirement - required when TDatabase is configured\n */\ntype DatabaseRequirement<TDatabase = undefined> = TDatabase extends undefined\n\t? {}\n\t: {\n\t\t\t/** Database instance - required when endpoint uses .database() */\n\t\t\tdatabase: TDatabase;\n\t\t};\n\nexport type TestRequestAdaptor<\n\tTInput extends EndpointSchemas = {},\n\tTServices extends Service[] = [],\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t_TAuditStorageServiceName extends string = string,\n\tTDatabase = undefined,\n\t_TDatabaseServiceName extends string = string,\n> = {\n\tservices: ServiceRecord<TServices>;\n\theaders: Record<string, string>;\n\tpublisher?: Service<TEventPublisherServiceName, TEventPublisher>;\n} & InferComposableStandardSchema<TInput> &\n\tAuditStorageRequirement<TAuditStorage> &\n\tDatabaseRequirement<TDatabase>;\n","import type { AuditableAction, Auditor, AuditStorage } from '@geekmidas/audit';\nimport { DefaultAuditor } from '@geekmidas/audit';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type {\n\tComposableStandardSchema,\n\tInferComposableStandardSchema,\n\tInferStandardSchema,\n} from '@geekmidas/schema';\nimport {\n\trunWithRequestContext,\n\ttype Service,\n\tServiceDiscovery,\n\ttype ServiceRecord,\n} from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishEvents } from '../publisher';\nimport type { Function } from './Function';\nimport { FunctionBuilder } from './FunctionBuilder';\n\nexport class TestFunctionAdaptor<\n\tTInput extends ComposableStandardSchema | undefined = undefined,\n\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> {\n\tstatic getDefaultServiceDiscovery<\n\t\tTInput extends ComposableStandardSchema | undefined = undefined,\n\t\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\t\tTServices extends Service[] = [],\n\t\tTLogger extends Logger = Logger,\n\t\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\t\tTEventPublisherServiceName extends string = string,\n\t\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t\tTAuditStorageServiceName extends string = string,\n\t\tTDatabase = undefined,\n\t\tTDatabaseServiceName extends string = string,\n\t\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t>(\n\t\t_fn: Function<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName,\n\t\t\tTAuditAction,\n\t\t\tany\n\t\t>,\n\t) {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly fn: Function<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName,\n\t\t\tTAuditAction,\n\t\t\tany\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestFunctionAdaptor.getDefaultServiceDiscovery(\n\t\t\tfn,\n\t\t),\n\t) {}\n\n\tasync invoke(\n\t\tctx: TestFunctionRequest<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTAuditAction\n\t\t>,\n\t): Promise<InferStandardSchema<TOutSchema>> {\n\t\t// Parse input if schema is provided\n\n\t\tconst parsedInput = await FunctionBuilder.parseComposableStandardSchema(\n\t\t\tctx.input,\n\t\t\tthis.fn.input,\n\t\t);\n\n\t\t// Create logger with context\n\t\tconst logger = this.fn.logger.child({\n\t\t\ttest: true,\n\t\t}) as TLogger;\n\n\t\t// Register services (use provided services or register from function)\n\t\tlet services: ServiceRecord<TServices>;\n\t\tif (ctx.services) {\n\t\t\tservices = ctx.services;\n\t\t} else {\n\t\t\tservices = await this.serviceDiscovery.register(this.fn.services);\n\t\t}\n\n\t\t// Resolve database (use provided db or register from function)\n\t\tlet db: TDatabase | undefined;\n\t\tif ('db' in ctx && ctx.db !== undefined) {\n\t\t\tdb = ctx.db;\n\t\t} else if (this.fn.databaseService) {\n\t\t\tconst dbServices = await this.serviceDiscovery.register([\n\t\t\t\tthis.fn.databaseService,\n\t\t\t]);\n\t\t\tdb = dbServices[\n\t\t\t\tthis.fn.databaseService.serviceName as keyof typeof dbServices\n\t\t\t] as TDatabase;\n\t\t}\n\n\t\t// Resolve auditor (use provided auditor or create from function)\n\t\tlet auditor: Auditor<TAuditAction> | undefined;\n\t\tif ('auditor' in ctx && ctx.auditor !== undefined) {\n\t\t\tauditor = ctx.auditor;\n\t\t} else if (this.fn.auditorStorageService) {\n\t\t\tconst auditServices = await this.serviceDiscovery.register([\n\t\t\t\tthis.fn.auditorStorageService,\n\t\t\t]);\n\t\t\tconst storage = auditServices[\n\t\t\t\tthis.fn.auditorStorageService.serviceName as keyof typeof auditServices\n\t\t\t] as AuditStorage;\n\n\t\t\tauditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\tactor: { id: 'system', type: 'system' },\n\t\t\t\tstorage,\n\t\t\t\tmetadata: {\n\t\t\t\t\tfunction: this.fn.type,\n\t\t\t\t\ttest: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Wrap execution in request context for service access via context.getLogger()\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\t// Execute the function\n\t\t\tconst response = await this.fn.fn({\n\t\t\t\tinput: parsedInput,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t\tdb,\n\t\t\t\tauditor,\n\t\t\t} as any);\n\n\t\t\t// Parse output if schema is provided\n\t\t\tconst output = await this.fn.parseOutput(response);\n\n\t\t\t// Flush audits if any were recorded\n\t\t\tif (auditor) {\n\t\t\t\tconst records = auditor.getRecords();\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t{ auditCount: records.length },\n\t\t\t\t\t\t'Flushing function audits',\n\t\t\t\t\t);\n\t\t\t\t\tawait auditor.flush();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Register publisher service if provided in context\n\n\t\t\tawait publishEvents(\n\t\t\t\tlogger,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t\tthis.fn.events,\n\t\t\t\toutput,\n\t\t\t\tthis.fn.publisherService,\n\t\t\t);\n\n\t\t\treturn output;\n\t\t}) as Promise<InferStandardSchema<TOutSchema>>;\n\t}\n}\n\nexport type TestFunctionRequest<\n\tTInput extends ComposableStandardSchema | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTDatabase = undefined,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> = {\n\tinput: InferComposableStandardSchema<TInput>;\n\tservices: ServiceRecord<TServices>;\n\tpublisher?: Service<TEventPublisherServiceName, TEventPublisher>;\n\tdb?: TDatabase;\n\tauditor?: Auditor<TAuditAction>;\n} & InferComposableStandardSchema<{ input: TInput }>;\n","import { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport { runWithRequestContext, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Queue } from './Queue';\n\n/**\n * In-memory test driver for a {@link Queue} worker. Hand it a batch of messages\n * and it registers the queue's services, runs the handler in a request context,\n * and returns the handler result — no SQS/pg-boss required.\n *\n * @example\n * const adapter = new TestQueueAdaptor(ordersQueue);\n * await adapter.invoke({ messages: [{ orderId: '123' }] });\n */\nexport class TestQueueAdaptor<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tstatic getDefaultServiceDiscovery() {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly queue: Queue<TName, TMessage, TServices, TLogger>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestQueueAdaptor.getDefaultServiceDiscovery(),\n\t) {}\n\n\tasync invoke(\n\t\trequest: TestQueueRequest<TMessage, TServices>,\n\t): Promise<unknown> {\n\t\tconst logger = this.queue.logger.child({ test: true }) as TLogger;\n\n\t\tconst services =\n\t\t\trequest.services ??\n\t\t\t((await this.serviceDiscovery.register(\n\t\t\t\tthis.queue.services,\n\t\t\t)) as ServiceRecord<TServices>);\n\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, () =>\n\t\t\tthis.queue.handler({\n\t\t\t\tmessages: request.messages,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t}),\n\t\t);\n\t}\n}\n\nexport type TestQueueRequest<\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n> = {\n\tmessages: InferStandardSchema<TMessage>[];\n\tservices?: ServiceRecord<TServices>;\n};\n","import { EnvironmentParser } from '@geekmidas/envkit';\nimport type {\n\tEventPublisher,\n\tExtractPublisherMessage,\n} from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport { runWithRequestContext, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishEvents } from '../publisher';\nimport type { Subscriber } from './Subscriber';\n\n// Helper type to extract payload types for subscribed events\ntype ExtractEventPayloads<\n\tTPublisher extends EventPublisher<any> | undefined,\n\tTEventTypes extends any[],\n> = TPublisher extends EventPublisher<any>\n\t? Extract<ExtractPublisherMessage<TPublisher>, { type: TEventTypes[number] }>\n\t: never;\n\nexport class TestSubscriberAdaptor<\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTSubscribedEvents extends\n\t\tExtractPublisherMessage<TEventPublisher>['type'][] = ExtractPublisherMessage<TEventPublisher>['type'][],\n> {\n\tstatic getDefaultServiceDiscovery() {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly subscriber: Subscriber<\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTSubscribedEvents\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestSubscriberAdaptor.getDefaultServiceDiscovery(),\n\t) {}\n\n\tasync invoke(\n\t\trequest: TestSubscriberRequest<\n\t\t\tTEventPublisher,\n\t\t\tTSubscribedEvents,\n\t\t\tTServices\n\t\t>,\n\t): Promise<InferStandardSchema<OutSchema>> {\n\t\t// Create logger with test context\n\t\tconst logger = this.subscriber.logger.child({\n\t\t\ttest: true,\n\t\t}) as TLogger;\n\n\t\t// Resolve services (use provided or auto-register)\n\t\tlet services: ServiceRecord<TServices>;\n\t\tif (request.services) {\n\t\t\tservices = request.services;\n\t\t} else {\n\t\t\tservices = await this.serviceDiscovery.register(this.subscriber.services);\n\t\t}\n\n\t\t// Filter events to only subscribed types\n\t\tconst filteredEvents = this.filterEvents(request.events);\n\n\t\t// Return early if no events after filtering (mirrors AWSLambdaSubscriber)\n\t\tif (filteredEvents.length === 0) {\n\t\t\treturn { batchItemFailures: [] } as any;\n\t\t}\n\n\t\t// Wrap execution in request context for service access via context.getLogger()\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\t// Execute the subscriber handler\n\t\t\tconst result = await this.subscriber.handler({\n\t\t\t\tevents: filteredEvents,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t});\n\n\t\t\t// Validate output if schema is provided\n\t\t\tlet output: any = result;\n\t\t\tif (this.subscriber.outputSchema && result) {\n\t\t\t\tconst validationResult =\n\t\t\t\t\tawait this.subscriber.outputSchema['~standard'].validate(result);\n\n\t\t\t\tif (validationResult.issues) {\n\t\t\t\t\tthrow new Error('Subscriber output validation failed');\n\t\t\t\t}\n\n\t\t\t\toutput = validationResult.value;\n\t\t\t}\n\n\t\t\t// Publish events if configured\n\t\t\tawait publishEvents(\n\t\t\t\tlogger,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t\tthis.subscriber.events,\n\t\t\t\toutput,\n\t\t\t\tthis.subscriber.publisherService,\n\t\t\t);\n\n\t\t\treturn output;\n\t\t}) as Promise<InferStandardSchema<OutSchema>>;\n\t}\n\n\tprivate filterEvents(\n\t\tevents: ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[],\n\t): ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[] {\n\t\tif (!this.subscriber.subscribedEvents) {\n\t\t\treturn events;\n\t\t}\n\n\t\treturn events.filter((event: any) =>\n\t\t\tthis.subscriber.subscribedEvents!.includes(event.type),\n\t\t);\n\t}\n}\n\nexport type TestSubscriberRequest<\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTSubscribedEvents extends any[] = [],\n\tTServices extends Service[] = [],\n> = {\n\tevents: ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[];\n\tservices?: ServiceRecord<TServices>;\n};\n","/**\n * The test manifest: what `gkm test` knows about an app, written down for the\n * suite it starts.\n *\n * `gkm test` discovers the constructs, reconciles the test stage and resolves\n * its environment — then, until this existed, threw all of it away and left a\n * test to declare it again by hand. The manifest keeps it: which construct and\n * endpoint is exported where, and the environment the test stage resolved,\n * under the keys the constructs themselves derive. A feature test is built from\n * it, so nothing a test says can drift from what the app declares.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport tls from 'node:tls';\nimport { fileURLToPath } from 'node:url';\n\n/** Where an export lives: what an import statement has to say. */\nexport interface TestManifestSource {\n\t/** Absolute path to the module. */\n\tfile: string;\n\t/** The name it is exported as. */\n\texport: string;\n}\n\nexport interface TestManifest {\n\t/** The stage the environment was resolved for — `test`. */\n\tstage: string;\n\t/** Every construct the app declares, by id. */\n\tconstructs: Record<string, { kind: string; source: TestManifestSource }>;\n\t/** Every endpoint, and the surface that serves it. */\n\tendpoints: { surface: string; source: TestManifestSource }[];\n\t/**\n\t * The test stage's environment — reconcile's output, keyed as the\n\t * constructs derive their keys.\n\t */\n\tenv: Record<string, string>;\n}\n\n/** The environment variable `gkm test` names the manifest in. */\nexport const TEST_MANIFEST_ENV = 'GKM_TEST_MANIFEST';\n\n/**\n * Read a test manifest: from a path, a `file:` URL, or — given nothing — the\n * one `gkm test` named in `GKM_TEST_MANIFEST`.\n */\nexport function loadTestManifest(from?: string | URL): TestManifest {\n\tconst location = from ?? process.env[TEST_MANIFEST_ENV];\n\tif (!location) throw new NoTestManifest();\n\n\ttrustLocalEdge();\n\n\tconst path =\n\t\tlocation instanceof URL || location.startsWith('file:')\n\t\t\t? fileURLToPath(location)\n\t\t\t: location;\n\n\treturn JSON.parse(readFileSync(path, 'utf-8')) as TestManifest;\n}\n\nlet trusted = false;\n\n/**\n * Trust the local edge's certificate authority in this thread.\n *\n * `NODE_EXTRA_CA_CERTS` does it for a process started with it — which a\n * suite started by `gkm test` is, and one started by plain `vitest` or an\n * editor is not: its setup only learns the path once Vitest is running, and a\n * worker *thread* shares that process and keeps a TLS store of its own. Every\n * feature test loads the manifest, so this is the one place each worker passes\n * through.\n */\nfunction trustLocalEdge(): void {\n\tconst certificate = process.env.NODE_EXTRA_CA_CERTS;\n\tif (trusted || !certificate || !existsSync(certificate)) return;\n\ttrusted = true;\n\n\ttls.setDefaultCACertificates([\n\t\t...tls.getCACertificates('default'),\n\t\treadFileSync(certificate, 'utf-8'),\n\t]);\n}\n\n/** A feature test ran without a manifest to build itself from. */\nexport class NoTestManifest extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t`No test manifest: run the suite with \\`gkm test\\`, or add ` +\n\t\t\t\t`\\`globalSetup: ['@geekmidas/cli/vitest']\\` to the root vitest config — ` +\n\t\t\t\t`either writes one and names it in ${TEST_MANIFEST_ENV}.`,\n\t\t);\n\t\tthis.name = 'NoTestManifest';\n\t}\n}\n","/**\n * `featureTest`: an app, driven the way it runs deployed.\n *\n * A browser signs in and calls the API; the API calls the auth server; each\n * over its URL, each database in its own transaction. Nothing runs as a server:\n * every URL an app's constructs answer on is served in-process through MSW,\n * from the real handler, for the test that made the request. Everything else\n * with a real local equivalent — Postgres, Mailpit, MinIO — is used directly.\n *\n * It is built from the test manifest `gkm test` writes — the constructs and\n * endpoints the app exports, and the environment the test stage resolved — so\n * a test declares none of it. `gkm test` also generates the configured `it`\n * and a `Browser` with a typed client per surface:\n *\n * ```ts\n * import { it } from '#test';\n *\n * it('shows the signed-in user their profile', async ({ browser, mailbox }) => {\n * await browser.auth.signIn.magicLink({ email: 'ada@shop.test' });\n * await browser.visit((await mailbox('ada@shop.test').last()).link!);\n * expect(await browser.api.get('/profile')).toMatchObject({ email: 'ada@shop.test' });\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport { provideKey } from '@geekmidas/manifest';\nimport { ServiceDiscovery, serviceContext } from '@geekmidas/services';\nimport { Browser as TestBrowser } from '@geekmidas/testkit/browser';\nimport {\n\tcurrentTestContext,\n\tinstallContextFetch,\n\trunAsServer,\n\trunInTestContext,\n\ttestContextOf,\n} from '@geekmidas/testkit/context';\nimport { createMailbox, type Mailbox } from '@geekmidas/testkit/mailbox';\nimport { TransactionRegistry } from '@geekmidas/testkit/transactions';\nimport { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport { http } from 'msw';\nimport { type SetupServer, setupServer } from 'msw/node';\nimport { afterAll, beforeAll, test } from 'vitest';\nimport { BetterAuth } from '../auth';\nimport { KyselyDatabase } from '../database/kysely';\nimport { Email } from '../email';\nimport { Endpoint } from '../endpoints/Endpoint';\nimport { HonoEndpoint } from '../endpoints/HonoEndpointAdaptor';\nimport { Queue } from '../queue/Queue';\nimport { TestQueueAdaptor } from '../queue/TestQueueAdaptor';\nimport type { Subscriber } from '../subscribers/Subscriber';\nimport { TestSubscriberAdaptor } from '../subscribers/TestSubscriberAdaptor';\nimport { Topic } from '../topic/Topic';\nimport { loadTestManifest, type TestManifest } from './manifest';\n\n/** How each database's test factory is built, keyed by its service name. */\nexport type FactoryBuilders = Record<string, (db: Kysely<any>) => unknown>;\n\nexport interface FeatureTestOptions<\n\tTBrowser extends TestBrowser,\n\tTFactories extends FactoryBuilders = {},\n> {\n\t/**\n\t * What the app declares and the environment its test stage resolved.\n\t * Defaults to the manifest `gkm test` names in `GKM_TEST_MANIFEST`.\n\t */\n\tmanifest?: TestManifest;\n\t/**\n\t * The app's modules, already imported, by the absolute path the manifest\n\t * records for them. The generated harness imports them statically, from\n\t * inside the app, so they resolve the way the app's own code does — its\n\t * tsconfig paths included. A module not here is imported by path.\n\t */\n\tmodules?: Readonly<Record<string, Record<string, unknown>>>;\n\t/** The browser each test gets — the generated one, with the app's clients. */\n\tbrowser?: new () => TBrowser;\n\t/**\n\t * Each database's test factory — `createFactory` from\n\t * `test/factories/<construct>.ts`, which the generated harness imports —\n\t * keyed by the database's service name. Each is called once per test with\n\t * that database's transaction, so what a test inserts through it is rolled\n\t * back with everything else, and seen by the endpoints it calls.\n\t */\n\tfactories?: TFactories;\n}\n\n/** Each database's schema, keyed by its service name. */\nexport type DatabaseSchemas = Record<string, unknown>;\n\n/**\n * The app's databases for this test, by service name — `db.get('database')`.\n *\n * Each is this test's transaction on that database: opened by whatever reaches\n * it first — the test, a factory, an endpoint — and the same one for all of\n * them, rolled back after the test.\n */\nexport interface TestDatabases<TDatabases extends DatabaseSchemas> {\n\tget<K extends keyof TDatabases & string>(\n\t\tname: K,\n\t): Promise<Kysely<TDatabases[K]>>;\n}\n\n/**\n * Each of the app's databases' test factory, by service name —\n * `await factories.get('database')`. Built on first use, on that database's\n * transaction for this test — the one `db.get` returns — and the same factory\n * for the rest of the test.\n */\nexport interface TestFactories<TFactories extends FactoryBuilders> {\n\tget<K extends keyof TFactories & string>(\n\t\tname: K,\n\t): Promise<ReturnType<TFactories[K]>>;\n}\n\nexport interface FeatureContext<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n> {\n\t/** This test's browser, already the global `fetch`. */\n\tbrowser: TBrowser;\n\t/**\n\t * The app's own databases — not a tenant an auth server owns, not a\n\t * reader — by service name: `await db.get('database')`. The same\n\t * transaction the test's endpoints and factories use.\n\t */\n\tdb: TestDatabases<TDatabases>;\n\t/**\n\t * Each of the app's databases' test factory, by service name —\n\t * `await factories.get('database')` — on the same transaction `db.get`\n\t * returns.\n\t */\n\tfactories: TestFactories<TFactories>;\n\t/** The mail sent to an address during this test, read from Mailpit. */\n\tmailbox: (address: string) => Mailbox;\n\t/**\n\t * What this test published to a topic or enqueued on a queue, in order.\n\t * Nothing is delivered — a subscriber or a worker is tested on its own, by\n\t * handing it events — so this is where publishing is asserted.\n\t */\n\tpublished: (channel: { id: string }) => PublishedMessage[];\n\t/**\n\t * A subscriber, run on its own — handed events rather than delivered them —\n\t * with this test's services: its databases resolve to this test's\n\t * transactions, as an endpoint's do.\n\t */\n\tsubscriber: <S extends Subscriber<any, any, any, any, any, any>>(\n\t\tsubscriber: S,\n\t) => SubscriberAdaptorOf<S>;\n\t/** A queue's worker, run on its own with this test's services. */\n\tqueue: <Q extends Queue<any, any, any, any>>(queue: Q) => QueueAdaptorOf<Q>;\n}\n\ntype SubscriberAdaptorOf<S> =\n\tS extends Subscriber<\n\t\tinfer TServices,\n\t\tinfer TLogger,\n\t\tinfer OutSchema,\n\t\tinfer TEventPublisher,\n\t\tinfer TEventPublisherServiceName,\n\t\tinfer TSubscribedEvents\n\t>\n\t\t? TestSubscriberAdaptor<\n\t\t\t\tTServices,\n\t\t\t\tTLogger,\n\t\t\t\tOutSchema,\n\t\t\t\tTEventPublisher,\n\t\t\t\tTEventPublisherServiceName,\n\t\t\t\tTSubscribedEvents\n\t\t\t>\n\t\t: never;\n\ntype QueueAdaptorOf<Q> =\n\tQ extends Queue<infer TName, infer TMessage, infer TServices, infer TLogger>\n\t\t? TestQueueAdaptor<TName, TMessage, TServices, TLogger>\n\t\t: never;\n\n/** The schema a database construct was declared with — what `db` is typed by. */\nexport type DatabaseOf<C> =\n\tC extends KyselyDatabase<infer DB, any> ? DB : unknown;\n\n/** One message as it was published: its type and its payload. */\nexport interface PublishedMessage {\n\ttype: string;\n\tpayload: unknown;\n}\n\ntype FeatureFn<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas,\n\tTFactories extends FactoryBuilders,\n> = (context: FeatureContext<TBrowser, TDatabases, TFactories>) => unknown;\n\nexport interface FeatureIt<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n> {\n\t(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n\tonly(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n\tskip(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n}\n\n/** Everything one test owns, found again from its id. */\ninterface ContextState {\n\ttransactions: TransactionRegistry;\n\tdiscovery: ServiceDiscovery<any>;\n\t/** One app per surface, with this test's services. */\n\tsurfaces: Map<string, Promise<Hono>>;\n\t/** One auth server per construct, on this test's transaction. */\n\tauth: Map<BetterAuth, Promise<Hono>>;\n\t/** The addresses this test read mail for, cleared after it. */\n\taddresses: Set<string>;\n\t/** This test's inbox, when the app sends mail — what `browser.signIn` reads. */\n\tmailbox?: (address: string) => Mailbox;\n\t/** What this test published, by topic or queue id. */\n\tpublished: Map<string, PublishedMessage[]>;\n}\n\n/** Every test in flight in this process, by id. */\nconst contexts = new Map<string, ContextState>();\n\n/** Databases already resolving to test transactions — patched once each. */\nconst bound = new WeakSet<KyselyDatabase>();\n\n/** What the manifest points at, imported: the app's own instances. */\ninterface LoadedApp {\n\tenvParser: EnvironmentParser<{}>;\n\tendpoints: Endpoint<any, any, any, any>[];\n\tdatabases: KyselyDatabase[];\n\tauths: BetterAuth[];\n\temails: Email[];\n\t/** Every topic and queue — what a test's publishing is recorded for. */\n\tchannels: (Topic<any, any> | Queue<any, any>)[];\n\treadMail?: (address: string) => Mailbox;\n}\n\nexport function featureTest<\n\tTBrowser extends TestBrowser = TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n>(\n\toptions: FeatureTestOptions<TBrowser, TFactories> = {},\n): FeatureIt<TBrowser, TDatabases, TFactories> {\n\tconst manifest = options.manifest ?? loadTestManifest();\n\tconst BrowserClass = (options.browser ?? TestBrowser) as new () => TBrowser;\n\n\tlet app: LoadedApp;\n\tlet factoryDatabases: [string, KyselyDatabase][] = [];\n\tlet owned: KyselyDatabase[] = [];\n\tlet network: SetupServer;\n\tlet restoreFetch: () => void = () => {};\n\n\tbeforeAll(async () => {\n\t\tapp = await load(manifest, options.modules ?? {});\n\t\towned = ownDatabases(app, manifest);\n\t\tfactoryDatabases = databasesFor(options.factories ?? {}, owned);\n\t\tfor (const database of app.databases) bindToTests(database);\n\n\t\tnetwork = setupServer(\n\t\t\t...surfaceHandlers(app.endpoints, manifest.env),\n\t\t\t...app.auths.map((auth) =>\n\t\t\t\tauthHandler(auth, manifest.env, app.envParser),\n\t\t\t),\n\t\t);\n\t\tnetwork.listen({\n\t\t\tonUnhandledRequest(request, print) {\n\t\t\t\t// A real local service — Mailpit, MinIO — is used directly; only a\n\t\t\t\t// URL with nothing real behind it has to be answered here.\n\t\t\t\tif (isLocal(new URL(request.url))) return;\n\t\t\t\tprint.error();\n\t\t\t},\n\t\t});\n\t\t// After MSW's interceptor, so the stamp is on before a request is caught.\n\t\trestoreFetch = installContextFetch();\n\t});\n\tafterAll(() => {\n\t\trestoreFetch();\n\t\tnetwork?.close();\n\t});\n\n\tconst run =\n\t\t(fn: FeatureFn<TBrowser, TDatabases, TFactories>) =>\n\t\tasync (): Promise<void> => {\n\t\t\tconst id = randomUUID();\n\t\t\tconst state: ContextState = {\n\t\t\t\ttransactions: new TransactionRegistry(),\n\t\t\t\tdiscovery: new ServiceDiscovery(app.envParser),\n\t\t\t\tsurfaces: new Map(),\n\t\t\t\tauth: new Map(),\n\t\t\t\taddresses: new Set(),\n\t\t\t\tpublished: new Map(),\n\t\t\t};\n\t\t\tconst readMail = app.readMail;\n\t\t\tif (readMail) {\n\t\t\t\tstate.mailbox = (address) => {\n\t\t\t\t\tstate.addresses.add(address);\n\t\t\t\t\treturn readMail(address);\n\t\t\t\t};\n\t\t\t}\n\t\t\tcontexts.set(id, state);\n\t\t\t// Before anything resolves them: a publisher is a service, so the one this\n\t\t\t// test's endpoints get is whichever is registered under its name first.\n\t\t\tawait state.discovery.register(recorders(app.channels, state));\n\n\t\t\ttry {\n\t\t\t\tawait runInTestContext(id, async () => {\n\t\t\t\t\tconst browser = new BrowserClass();\n\t\t\t\t\tconst restore = browser.install();\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// A database's transaction for this test, opened on first use —\n\t\t\t\t\t\t// the registry hands everyone in the test the same one.\n\t\t\t\t\t\tconst transaction = (database: KyselyDatabase) =>\n\t\t\t\t\t\t\tstate.transactions.get(\n\t\t\t\t\t\t\t\tdatabase.id,\n\t\t\t\t\t\t\t\turlOf(database, app.envParser),\n\t\t\t\t\t\t\t\tdatabase.clientConfig,\n\t\t\t\t\t\t\t) as Promise<Kysely<any>>;\n\t\t\t\t\t\tconst db: TestDatabases<any> = {\n\t\t\t\t\t\t\tget: (name) => {\n\t\t\t\t\t\t\t\tconst database = owned.find(\n\t\t\t\t\t\t\t\t\t(candidate) => candidate.service.serviceName === name,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (!database) {\n\t\t\t\t\t\t\t\t\tthrow new UnknownDatabase(\n\t\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t\t\towned.map((candidate) => candidate.service.serviceName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn transaction(database);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst built = new Map<string, Promise<unknown>>();\n\t\t\t\t\t\tconst factories: TestFactories<any> = {\n\t\t\t\t\t\t\tget: (name) => {\n\t\t\t\t\t\t\t\tconst database = factoryDatabases.find(\n\t\t\t\t\t\t\t\t\t([candidate]) => candidate === name,\n\t\t\t\t\t\t\t\t)?.[1];\n\t\t\t\t\t\t\t\tif (!database) {\n\t\t\t\t\t\t\t\t\tthrow new UnknownFactory(\n\t\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t\t\tfactoryDatabases.map(([candidate]) => candidate),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet factory = built.get(name);\n\t\t\t\t\t\t\t\tif (!factory) {\n\t\t\t\t\t\t\t\t\tfactory = transaction(database).then(\n\t\t\t\t\t\t\t\t\t\toptions.factories![name]!,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tbuilt.set(name, factory);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn factory as Promise<any>;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\tawait fn({\n\t\t\t\t\t\t\tbrowser,\n\t\t\t\t\t\t\tdb: db as TestDatabases<TDatabases>,\n\t\t\t\t\t\t\tfactories: factories as TestFactories<TFactories>,\n\t\t\t\t\t\t\tmailbox: (address) => {\n\t\t\t\t\t\t\t\tif (!state.mailbox) throw new NoInbox();\n\t\t\t\t\t\t\t\treturn state.mailbox(address);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpublished: (channel) => [\n\t\t\t\t\t\t\t\t...(state.published.get(channel.id) ?? []),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tsubscriber: (subscriber) =>\n\t\t\t\t\t\t\t\tnew TestSubscriberAdaptor(\n\t\t\t\t\t\t\t\t\tsubscriber,\n\t\t\t\t\t\t\t\t\tstate.discovery,\n\t\t\t\t\t\t\t\t) as SubscriberAdaptorOf<typeof subscriber>,\n\t\t\t\t\t\t\tqueue: (queue) =>\n\t\t\t\t\t\t\t\tnew TestQueueAdaptor(queue, state.discovery) as QueueAdaptorOf<\n\t\t\t\t\t\t\t\t\ttypeof queue\n\t\t\t\t\t\t\t\t>,\n\t\t\t\t\t\t} as FeatureContext<TBrowser, TDatabases, TFactories>);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\trestore();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tcontexts.delete(id);\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tstate.transactions.rollbackAll(),\n\t\t\t\t\t...[...state.addresses].map((address) =>\n\t\t\t\t\t\tapp.readMail?.(address).clear(),\n\t\t\t\t\t),\n\t\t\t\t]);\n\t\t\t}\n\t\t};\n\n\tconst it = ((name, fn, timeout) => test(name, run(fn), timeout)) as FeatureIt<\n\t\tTBrowser,\n\t\tTDatabases,\n\t\tTFactories\n\t>;\n\tit.only = (name, fn, timeout) => test.only(name, run(fn), timeout);\n\tit.skip = (name, fn, timeout) => test.skip(name, run(fn), timeout);\n\treturn it;\n}\n\n/**\n * Import what the manifest points at.\n *\n * The same modules the app runs, so a construct here *is* the app's construct —\n * the one its endpoints were built from — never a copy declared again.\n */\nasync function load(\n\tmanifest: TestManifest,\n\tmodules: Readonly<Record<string, Record<string, unknown>>>,\n): Promise<LoadedApp> {\n\tconst envParser = new EnvironmentParser({ ...manifest.env });\n\tconst imported = async ({\n\t\tfile,\n\t\texport: name,\n\t}: {\n\t\tfile: string;\n\t\texport: string;\n\t}) =>\n\t\t// Imported by path only as a fallback: from inside `node_modules`, a\n\t\t// dynamic import is left to Node, which knows nothing of the app's\n\t\t// tsconfig paths — `~/router.ts` resolves in the app and not here.\n\t\t(modules[file] ?? ((await import(file)) as Record<string, unknown>))[name];\n\n\tconst constructs = await Promise.all(\n\t\tObject.values(manifest.constructs).map(({ source }) => imported(source)),\n\t);\n\tconst endpoints = (\n\t\tawait Promise.all(manifest.endpoints.map(({ source }) => imported(source)))\n\t).filter((value): value is Endpoint<any, any, any, any> =>\n\t\tEndpoint.isEndpoint(value),\n\t);\n\n\tconst unique = <T>(values: T[]) => [...new Set(values)];\n\tconst databases = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is KyselyDatabase => value instanceof KyselyDatabase,\n\t\t),\n\t);\n\tconst auths = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is BetterAuth => value instanceof BetterAuth,\n\t\t),\n\t);\n\tconst emails = unique(\n\t\tconstructs.filter((value): value is Email => value instanceof Email),\n\t);\n\tconst channels = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is Topic<any, any> | Queue<any, any> =>\n\t\t\t\tvalue instanceof Topic || value instanceof Queue,\n\t\t),\n\t);\n\n\tconst inbox = emails\n\t\t.map((email) => manifest.env[provideKey(email.id, 'inboxUrl')])\n\t\t.find(Boolean);\n\n\treturn {\n\t\tenvParser,\n\t\tendpoints,\n\t\tdatabases,\n\t\tauths,\n\t\temails,\n\t\tchannels,\n\t\t...(inbox ? { readMail: createMailbox({ inbox }) } : {}),\n\t};\n}\n\n/**\n * A publisher that records rather than sends, under every name a topic or\n * queue is injected by — `users`, and the older `usersPublisher`.\n */\nfunction recorders(\n\tchannels: LoadedApp['channels'],\n\tstate: ContextState,\n): { serviceName: string; register: () => unknown }[] {\n\treturn channels.flatMap((channel) => {\n\t\tconst publisher = {\n\t\t\tasync publish(messages: PublishedMessage[]) {\n\t\t\t\tstate.published.set(channel.id, [\n\t\t\t\t\t...(state.published.get(channel.id) ?? []),\n\t\t\t\t\t...messages.map(({ type, payload }) => ({ type, payload })),\n\t\t\t\t]);\n\t\t\t},\n\t\t};\n\t\treturn [channel.service.serviceName, channel.publisher.serviceName].map(\n\t\t\t(serviceName) => ({ serviceName, register: () => publisher }),\n\t\t);\n\t});\n}\n\n/**\n * The databases a test is handed: the app's own. A schema tenant an auth server\n * owns is reached through that server, as the app reaches it, and a reader is\n * the same database through a read-only role — neither is another database to\n * test against.\n */\nfunction ownDatabases(\n\tapp: LoadedApp,\n\tmanifest: TestManifest,\n): KyselyDatabase[] {\n\tconst owners = new Set(app.auths.map((auth) => auth.databaseId));\n\treturn app.databases.filter(\n\t\t(database) =>\n\t\t\t!owners.has(database.id) &&\n\t\t\tmanifest.constructs[database.id]?.kind !== 'database-reader',\n\t);\n}\n\n/** Each factory's database, found by the service name it is keyed by. */\nfunction databasesFor(\n\tfactories: FactoryBuilders,\n\tdatabases: KyselyDatabase[],\n): [string, KyselyDatabase][] {\n\treturn Object.keys(factories).map((name) => {\n\t\tconst database = databases.find(\n\t\t\t(candidate) => candidate.service.serviceName === name,\n\t\t);\n\t\tif (!database) {\n\t\t\tthrow new UnknownFactory(\n\t\t\t\tname,\n\t\t\t\tdatabases.map((candidate) => candidate.service.serviceName),\n\t\t\t);\n\t\t}\n\t\treturn [name, database];\n\t});\n}\n\n/**\n * Make a database resolve, inside a test, to that test's transaction.\n *\n * Everything reaches a database through its service — an endpoint through\n * service discovery, the auth server through its tenant directly — so this is\n * the one place a test's connection can be handed over. Outside a test it is\n * the database it always was.\n */\nfunction bindToTests(database: KyselyDatabase): void {\n\tif (bound.has(database)) return;\n\tbound.add(database);\n\n\tconst service = database.service as { register: (options: any) => unknown };\n\tconst register = service.register;\n\n\tservice.register = (options) => {\n\t\tconst context = currentTestContext();\n\t\tconst state = context && contexts.get(context.id);\n\t\tif (!state) return register(options);\n\n\t\treturn state.transactions.get(\n\t\t\tdatabase.id,\n\t\t\turlOf(database, options.envParser),\n\t\t\tdatabase.clientConfig,\n\t\t);\n\t};\n}\n\nfunction urlOf(database: { id: string }, envParser: EnvironmentParser<{}>) {\n\treturn envParser\n\t\t.create((get) => ({ url: get(provideKey(database.id, 'url')).string() }))\n\t\t.parse().url;\n}\n\n/** Each surface's endpoints, served at its URL for the test that asked. */\nfunction surfaceHandlers(\n\tendpoints: Endpoint<any, any, any, any>[],\n\tenv: Record<string, string | undefined>,\n) {\n\tconst bySurface = new Map<string, Endpoint<any, any, any, any>[]>();\n\tfor (const endpoint of endpoints) {\n\t\tconst surface = endpoint.surface?.id;\n\t\tif (!surface) continue;\n\t\tbySurface.set(surface, [...(bySurface.get(surface) ?? []), endpoint]);\n\t}\n\n\treturn [...bySurface].flatMap(([surface, served]) => {\n\t\tconst base = env[provideKey(surface, 'url')];\n\t\tif (!base) return [];\n\n\t\treturn http.all(`${trim(base)}/*`, ({ request }) =>\n\t\t\tserve(request, async (state) => {\n\t\t\t\tlet app = state.surfaces.get(surface);\n\t\t\t\tif (!app) {\n\t\t\t\t\tapp = Promise.resolve().then(() => {\n\t\t\t\t\t\tconst hono = new Hono();\n\t\t\t\t\t\tHonoEndpoint.addRoutes(served, state.discovery, hono);\n\t\t\t\t\t\treturn hono;\n\t\t\t\t\t});\n\t\t\t\t\tstate.surfaces.set(surface, app);\n\t\t\t\t}\n\t\t\t\treturn (await app).fetch(request);\n\t\t\t}),\n\t\t);\n\t});\n}\n\n/** An auth server, served at its URL, on the transaction of the test that asked. */\nfunction authHandler(\n\tauth: BetterAuth,\n\tenv: Record<string, string | undefined>,\n\tenvParser: EnvironmentParser<{}>,\n) {\n\tconst base = trim(env[provideKey(auth.id, 'url')] ?? '');\n\n\t// The whole origin, not only `basePath`: the auth server answers its own\n\t// host deployed, and a redirect it issues — to `/` after verifying a magic\n\t// link — lands on it. Paths it does not route get its own 404.\n\treturn http.all(`${base}/*`, ({ request }) =>\n\t\tserve(request, async (state) => {\n\t\t\tlet app = state.auth.get(auth);\n\t\t\tif (!app) {\n\t\t\t\tapp = auth\n\t\t\t\t\t.server({ envParser, context: serviceContext })\n\t\t\t\t\t.then(({ app }) => app);\n\t\t\t\tstate.auth.set(auth, app);\n\t\t\t}\n\t\t\treturn (await app).fetch(request);\n\t\t}),\n\t);\n}\n\n/** Handle a request as the server side of the test it was made for. */\nasync function serve(\n\trequest: Request,\n\thandle: (state: ContextState) => Promise<Response>,\n): Promise<Response> {\n\tconst id = testContextOf(request);\n\tconst state = id ? contexts.get(id) : undefined;\n\tif (!id || !state) throw new UnknownTestContext(request.url, id);\n\n\treturn runAsServer(id, () => handle(state));\n}\n\nfunction trim(url: string): string {\n\treturn url.replace(/\\/$/, '');\n}\n\nfunction isLocal(url: URL): boolean {\n\treturn (\n\t\turl.hostname === 'localhost' ||\n\t\turl.hostname === '127.0.0.1' ||\n\t\turl.hostname.endsWith('.localhost')\n\t);\n}\n\n/** What `signInWithMagicLink` uses of a better-auth client. */\nexport interface MagicLinkAuthClient<TSession> {\n\tsignIn: {\n\t\tmagicLink(input: {\n\t\t\temail: string;\n\t\t}): Promise<{ error?: { message?: string } | null }>;\n\t};\n\tgetSession(): Promise<{ data: TSession | null }>;\n}\n\n/**\n * Sign a test's browser in the way a person does: ask the auth server for a\n * magic link, open the email it sent, follow the link.\n *\n * What the generated `browser.signIn(email)` calls. The address's mail is\n * cleared first — so the link is this request's, not one an earlier test left\n * — and cleared again after the test, like any address a test reads. Returns\n * the session the auth server then reports, as better-auth hands it back.\n */\nexport async function signInWithMagicLink<TSession>(\n\tbrowser: TestBrowser,\n\tauth: MagicLinkAuthClient<TSession>,\n\temail: string,\n): Promise<TSession> {\n\tconst context = currentTestContext();\n\tconst state = context && contexts.get(context.id);\n\tif (!state) throw new UnknownTestContext('signIn', context?.id);\n\tif (!state.mailbox) throw new NoInbox();\n\n\tconst inbox = state.mailbox(email);\n\tawait inbox.clear();\n\n\tconst requested = await auth.signIn.magicLink({ email });\n\tif (requested.error) {\n\t\tthrow new SignInFailed(\n\t\t\temail,\n\t\t\t`the auth server refused the magic link: ${requested.error.message ?? 'no reason given'}`,\n\t\t);\n\t}\n\n\tconst mail = await inbox.last();\n\tif (!mail.link) throw new SignInFailed(email, 'its email held no link');\n\tawait browser.visit(mail.link);\n\n\tconst { data } = await auth.getSession();\n\tif (!data) {\n\t\tthrow new SignInFailed(\n\t\t\temail,\n\t\t\t'the auth server reported no session after the link was opened',\n\t\t);\n\t}\n\treturn data;\n}\n\n/** `browser.signIn` finished without a session. */\nexport class SignInFailed extends Error {\n\tconstructor(\n\t\treadonly email: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(`Signing ${email} in failed: ${reason}.`);\n\t\tthis.name = 'SignInFailed';\n\t}\n}\n\n/** A request reached an in-process server without a test to belong to. */\nexport class UnknownTestContext extends Error {\n\tconstructor(\n\t\treadonly url: string,\n\t\treadonly id: string | undefined,\n\t) {\n\t\tsuper(\n\t\t\tid\n\t\t\t\t? `${url} was requested for test ${id}, which is not running. A ` +\n\t\t\t\t\t\t`request that outlives its test — an un-awaited call — lands here.`\n\t\t\t\t: `${url} was requested outside any test. Make the request inside a ` +\n\t\t\t\t\t\t`featureTest, so it reaches that test's transactions.`,\n\t\t);\n\t\tthis.name = 'UnknownTestContext';\n\t}\n}\n\n/** `mailbox` was used in an app that declares no Email construct. */\nexport class NoInbox extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t`mailbox() reads the inbox of the app's Email construct, and none was ` +\n\t\t\t\t`found in featureTest's modules — or \\`gkm test\\` did not inject its ` +\n\t\t\t\t`<ID>_INBOX_URL.`,\n\t\t);\n\t\tthis.name = 'NoInbox';\n\t}\n}\n\n/** `db.get(name)` named no database the test is handed. */\nexport class UnknownDatabase extends Error {\n\tconstructor(\n\t\treadonly database: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`db.get('${database}') names no database of this app's. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `Its databases: ${known.join(', ')}. `\n\t\t\t\t\t: 'It declares no database of its own. ') +\n\t\t\t\t`A tenant an auth server owns is reached through that server, and a ` +\n\t\t\t\t`reader through the database it reads.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabase';\n\t}\n}\n\n/** A factory keyed by a service name no database the app declares has. */\nexport class UnknownFactory extends Error {\n\tconstructor(\n\t\treadonly factory: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`There is a test factory for '${factory}', and no database this app ` +\n\t\t\t\t`declares is called that. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `Its databases: ${known.join(', ')}. `\n\t\t\t\t\t: 'It declares no database. ') +\n\t\t\t\t`Name the file after the database construct: test/factories/<construct>.ts.`,\n\t\t);\n\t\tthis.name = 'UnknownFactory';\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","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"],"sourcesContent":["import { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Service } from '@geekmidas/services';\nimport { ServiceDiscovery } from '@geekmidas/services';\nimport { TEST_CONTEXT_HEADER } from '@geekmidas/testkit/context';\nimport { Hono } from 'hono';\nimport { type HttpHandler, http } from 'msw';\nimport type { Endpoint } from './Endpoint';\nimport { HonoEndpoint } from './HonoEndpointAdaptor';\n\n/**\n * Header used to identify which test context a request belongs to — defined\n * once, in testkit, where the stamped `fetch` that writes it lives.\n */\nexport { TEST_CONTEXT_HEADER };\n\n/**\n * Per-test context containing an isolated ServiceDiscovery instance.\n * Registered in the context map and looked up per-request via the context ID header.\n */\nexport interface MswTestContext {\n\tserviceDiscovery: ServiceDiscovery<any>;\n}\n\n/**\n * Options for creating MSW handlers from endpoint constructs.\n *\n * @example\n * ```typescript\n * import { createMswHandlers, registerTestContext } from '@geekmidas/constructs/testing';\n * import { setupServer } from 'msw/node';\n *\n * // Create handlers once (global)\n * const { handlers, registerContext, removeContext } = createMswHandlers(\n * [getUsers, createUser],\n * { baseURL: 'http://localhost:3000' },\n * );\n * const server = setupServer(...handlers);\n *\n * // Per test: register an isolated context with its own services/transaction\n * it('should list users', async ({ db }) => {\n * const contextId = crypto.randomUUID();\n * registerContext(contextId, {\n * services: { database: db, auth: mockAuth },\n * });\n *\n * const api = createApi({\n * baseURL: 'http://localhost:3000',\n * headers: { [TEST_CONTEXT_HEADER]: contextId },\n * });\n *\n * const result = await api('GET /users');\n * expect(result).toBeDefined();\n *\n * removeContext(contextId);\n * });\n * ```\n */\nexport interface CreateMswHandlersOptions {\n\t/** Base URL the client fetches from (e.g., 'http://localhost:3000') */\n\tbaseURL: string;\n}\n\n/**\n * Options for registering a per-test context.\n * Follows the same pattern as TestRequestAdaptor — services, database,\n * publisher, and auditorStorage are provided explicitly.\n */\nexport interface RegisterContextOptions {\n\t/** Service instances keyed by serviceName */\n\tservices?: Record<string, unknown>;\n\t/** Database instance — required when endpoints use .database() */\n\tdatabase?: unknown;\n\t/** Event publisher service definition */\n\tpublisher?: Service;\n\t/** Audit storage instance — required when endpoints use .auditor() */\n\tauditorStorage?: unknown;\n}\n\nconst MSW_HTTP_METHODS = [\n\t'get',\n\t'post',\n\t'put',\n\t'patch',\n\t'delete',\n\t'options',\n] as const;\n\ntype MswHttpMethod = (typeof MSW_HTTP_METHODS)[number];\n\n/**\n * Result of creating MSW handlers from endpoint constructs.\n */\nexport interface CreateMswHandlersResult {\n\t/** MSW handlers to pass to setupServer() */\n\thandlers: HttpHandler[];\n\t/** Register an isolated context for a test */\n\tregisterContext: (id: string, options: RegisterContextOptions) => void;\n}\n\n/**\n * Creates MSW HTTP handlers from endpoint constructs.\n *\n * Mounts endpoints on a Hono app and creates MSW handlers that intercept\n * fetch requests and route them through `app.request()` — giving frontend\n * tests full endpoint behavior (validation, auth, sessions) without HTTP.\n *\n * Each test registers its own isolated context via `registerContext()`,\n * which is resolved per-request using the `x-test-context-id` header.\n * This allows concurrent tests with their own transactions and services.\n *\n * @param endpoints - Array of endpoint constructs to create handlers for\n * @param options - Configuration including baseURL\n * @returns MSW handlers, the Hono app, and context management functions\n */\nexport function createMswHandlers(\n\tendpoints: Endpoint<any, any, any, any, any, any>[],\n\toptions: CreateMswHandlersOptions,\n): CreateMswHandlersResult {\n\tconst { baseURL } = options;\n\tconst contexts = new Map<string, MswTestContext>();\n\n\t/**\n\t * Register an isolated context for a test.\n\t * Creates a fresh ServiceDiscovery and pre-registers all provided services.\n\t */\n\tfunction registerContext(id: string, ctxOptions: RegisterContextOptions) {\n\t\tconst envParser = new EnvironmentParser({});\n\t\tconst serviceDiscovery = new ServiceDiscovery(envParser);\n\n\t\tconst serviceDefs: Service[] = [];\n\n\t\t// Register explicit services\n\t\tif (ctxOptions.services) {\n\t\t\tfor (const [name, instance] of Object.entries(ctxOptions.services)) {\n\t\t\t\tserviceDefs.push({\n\t\t\t\t\tserviceName: name,\n\t\t\t\t\tregister: () => instance,\n\t\t\t\t} as Service);\n\t\t\t}\n\t\t}\n\n\t\t// Register database service from endpoint metadata\n\t\tif (ctxOptions.database !== undefined) {\n\t\t\tfor (const endpoint of endpoints) {\n\t\t\t\tif (endpoint.databaseService) {\n\t\t\t\t\tserviceDefs.push({\n\t\t\t\t\t\tserviceName: endpoint.databaseService.serviceName,\n\t\t\t\t\t\tregister: () => ctxOptions.database,\n\t\t\t\t\t} as Service);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Register publisher service\n\t\tif (ctxOptions.publisher) {\n\t\t\tserviceDefs.push(ctxOptions.publisher);\n\t\t}\n\n\t\t// Register auditor storage service from endpoint metadata\n\t\tif (ctxOptions.auditorStorage !== undefined) {\n\t\t\tfor (const endpoint of endpoints) {\n\t\t\t\tif (endpoint.auditorStorageService) {\n\t\t\t\t\tserviceDefs.push({\n\t\t\t\t\t\tserviceName: endpoint.auditorStorageService.serviceName,\n\t\t\t\t\t\tregister: () => ctxOptions.auditorStorage,\n\t\t\t\t\t} as Service);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (serviceDefs.length > 0) {\n\t\t\tserviceDiscovery.register(serviceDefs);\n\t\t}\n\n\t\tcontexts.set(id, { serviceDiscovery });\n\t}\n\n\t// Create a Hono app per-request that uses the correct ServiceDiscovery.\n\t// We use a wrapper app that resolves the context from the header,\n\t// then delegates to a context-specific Hono app.\n\tconst handlers: HttpHandler[] = [];\n\n\tfor (const endpoint of endpoints) {\n\t\tconst method = endpoint.method.toLowerCase() as MswHttpMethod;\n\t\tif (!MSW_HTTP_METHODS.includes(method)) continue;\n\n\t\tconst mswUrl = `${baseURL}${endpoint.route}`;\n\t\tconst mswMethod = http[method];\n\n\t\thandlers.push(\n\t\t\tmswMethod(mswUrl, async ({ request }) => {\n\t\t\t\tconst contextId = request.headers.get(TEST_CONTEXT_HEADER);\n\t\t\t\tconst ctx = contextId ? contexts.get(contextId) : undefined;\n\n\t\t\t\tif (!ctx) {\n\t\t\t\t\treturn new Response(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\terror: 'Missing or unknown test context ID',\n\t\t\t\t\t\t\thint: `Set the '${TEST_CONTEXT_HEADER}' header to a registered context ID`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t{ status: 500, headers: { 'content-type': 'application/json' } },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Build a fresh Hono app with this context's ServiceDiscovery\n\t\t\t\tconst app = new Hono();\n\t\t\t\tHonoEndpoint.addRoute(endpoint, ctx.serviceDiscovery as any, app);\n\n\t\t\t\tconst response = await app.request(request);\n\n\t\t\t\treturn new Response(response.body, {\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\theaders: response.headers,\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t}\n\n\treturn {\n\t\thandlers,\n\t\tregisterContext,\n\t};\n}\n","import type {\n\tAuditActor,\n\tAuditableAction,\n\tAuditStorage,\n} from '@geekmidas/audit';\nimport { DefaultAuditor } from '@geekmidas/audit';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport { ForbiddenError } from '@geekmidas/errors';\nimport type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type {\n\tInferComposableStandardSchema,\n\tInferStandardSchema,\n} from '@geekmidas/schema';\nimport {\n\trunWithRequestContext,\n\ttype Service,\n\tServiceDiscovery,\n\ttype ServiceRecord,\n} from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishConstructEvents } from '../publisher';\nimport type { HttpMethod } from '../types';\nimport type { MappedAudit } from './audit';\nimport {\n\ttype CookieOptions,\n\tEndpoint,\n\ttype EndpointSchemas,\n\tResponseBuilder,\n} from './Endpoint';\nimport {\n\tcreateCookieHeaderAccessor,\n\tcreateObjectHeaders,\n} from './lazyAccessors';\nimport {\n\ttype AuditExecutionContext,\n\texecuteWithAuditTransaction,\n} from './processAudits';\n\nexport type TestHttpResponse<TBody = any> = {\n\tbody: TBody;\n\tstatus: number;\n\theaders: Record<string, string | string[]>;\n};\n\n/**\n * Serializes a cookie into a Set-Cookie header string\n */\nfunction serializeCookie(\n\tname: string,\n\tvalue: string,\n\toptions?: CookieOptions,\n): string {\n\tlet cookieString = `${name}=${value}`;\n\n\tif (options) {\n\t\tif (options.maxAge !== undefined) {\n\t\t\tcookieString += `; Max-Age=${options.maxAge}`;\n\t\t}\n\t\tif (options.expires) {\n\t\t\tcookieString += `; Expires=${options.expires.toUTCString()}`;\n\t\t}\n\t\tif (options.domain) {\n\t\t\tcookieString += `; Domain=${options.domain}`;\n\t\t}\n\t\tif (options.path) {\n\t\t\tcookieString += `; Path=${options.path}`;\n\t\t}\n\t\tif (options.httpOnly) {\n\t\t\tcookieString += '; HttpOnly';\n\t\t}\n\t\tif (options.secure) {\n\t\t\tcookieString += '; Secure';\n\t\t}\n\t\tif (options.sameSite) {\n\t\t\tcookieString += `; SameSite=${options.sameSite}`;\n\t\t}\n\t}\n\n\treturn cookieString;\n}\n\nexport class TestEndpointAdaptor<\n\tTRoute extends string,\n\tTMethod extends HttpMethod,\n\tTInput extends EndpointSchemas = {},\n\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tTSession = unknown,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n> {\n\tstatic getDefaultServiceDiscover<\n\t\tTRoute extends string,\n\t\tTMethod extends HttpMethod,\n\t\tTInput extends EndpointSchemas = {},\n\t\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\t\tTServices extends Service[] = [],\n\t\tTLogger extends Logger = Logger,\n\t\tTSession = unknown,\n\t\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\t\tTEventPublisherServiceName extends string = string,\n\t\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t\tTAuditStorageServiceName extends string = string,\n\t\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t\tTDatabase = undefined,\n\t\tTDatabaseServiceName extends string = string,\n\t>(\n\t\t_endpoint: Endpoint<\n\t\t\tTRoute,\n\t\t\tTMethod,\n\t\t\tTInput,\n\t\t\tTOutSchema,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTSession,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditAction,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t) {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\tconstructor(\n\t\tprivate readonly endpoint: Endpoint<\n\t\t\tTRoute,\n\t\t\tTMethod,\n\t\t\tTInput,\n\t\t\tTOutSchema,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTSession,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditAction,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestEndpointAdaptor.getDefaultServiceDiscover(\n\t\t\tendpoint,\n\t\t),\n\t) {}\n\n\tasync fullRequest(\n\t\tctx: TestRequestAdaptor<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t): Promise<TestHttpResponse<InferStandardSchema<TOutSchema>>> {\n\t\tconst body = await this.endpoint.parseInput((ctx as any).body, 'body');\n\t\tconst query = await this.endpoint.parseInput((ctx as any).query, 'query');\n\t\tconst params = await this.endpoint.parseInput(\n\t\t\t(ctx as any).params,\n\t\t\t'params',\n\t\t);\n\n\t\t// Lazy accessors - defer parsing until needed\n\t\tconst header = createObjectHeaders(ctx.headers);\n\t\tconst cookie = createCookieHeaderAccessor(ctx.headers.cookie);\n\n\t\t// Request context setup\n\t\tconst startTime = Date.now();\n\t\tconst requestId =\n\t\t\t(ctx as any).requestId ??\n\t\t\tctx.headers['x-request-id'] ??\n\t\t\tcrypto.randomUUID();\n\n\t\tconst logger = this.endpoint.logger.child({\n\t\t\trequestId,\n\t\t\troute: this.endpoint.route,\n\t\t\thost: ctx.headers.host,\n\t\t\tmethod: this.endpoint.method,\n\t\t}) as TLogger;\n\n\t\t// Get database from context for session extraction\n\t\tconst rawDb = (ctx as any).database as TDatabase;\n\n\t\t// Wrap handler execution in request context\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\tconst session = await this.endpoint.getSession({\n\t\t\t\tlogger,\n\t\t\t\tservices: ctx.services,\n\t\t\t\theader,\n\t\t\t\tcookie,\n\t\t\t\t...(rawDb !== undefined && { db: rawDb }),\n\t\t\t} as any);\n\n\t\t\t// Check authorization\n\t\t\tconst isAuthorized = await this.endpoint.authorize({\n\t\t\t\theader,\n\t\t\t\tcookie,\n\t\t\t\tservices: ctx.services,\n\t\t\t\tlogger,\n\t\t\t\tsession,\n\t\t\t\t...(rawDb !== undefined && { db: rawDb }),\n\t\t\t\tbody,\n\t\t\t\tquery,\n\t\t\t\tparams,\n\t\t\t} as any);\n\n\t\t\tif (!isAuthorized) {\n\t\t\t\tlogger.warn('Forbidden access attempt');\n\t\t\t\tthrow new ForbiddenError(\n\t\t\t\t\t'Forbidden access to the endpoint',\n\t\t\t\t\t'You do not have permission to access this resource.',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Create audit context if audit storage is provided\n\t\t\t// The auditorStorage instance is required when endpoint uses .auditor()\n\t\t\tconst auditorStorage = (ctx as any).auditorStorage as TAuditStorage;\n\t\t\tlet auditContext: AuditExecutionContext<TAuditAction> | undefined;\n\n\t\t\tif (auditorStorage) {\n\t\t\t\t// Extract actor if configured\n\t\t\t\tlet actor: AuditActor = { id: 'system', type: 'system' };\n\t\t\t\tif (this.endpoint.actorExtractor) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactor = await this.endpoint.actorExtractor({\n\t\t\t\t\t\t\tservices: ctx.services as any,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\theader,\n\t\t\t\t\t\t\tcookie,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger.error(error as Error, 'Failed to extract actor for audits');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst auditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\t\tactor,\n\t\t\t\t\tstorage: auditorStorage as AuditStorage,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tendpoint: this.endpoint.route,\n\t\t\t\t\t\tmethod: this.endpoint.method,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tauditContext = { auditor, storage: auditorStorage as AuditStorage };\n\t\t\t}\n\n\t\t\t// Warn if declarative audits are configured but no audit storage\n\t\t\tconst audits = this.endpoint.audits as MappedAudit<\n\t\t\t\tTAuditAction,\n\t\t\t\tTOutSchema\n\t\t\t>[];\n\t\t\tif (!auditContext && audits?.length) {\n\t\t\t\tlogger.warn('No auditor storage service available');\n\t\t\t}\n\n\t\t\t// Execute handler with automatic audit transaction support\n\t\t\tconst result = await executeWithAuditTransaction(\n\t\t\t\tauditContext,\n\t\t\t\tasync (auditor) => {\n\t\t\t\t\t// Use audit transaction as db if available (when storage has same database)\n\t\t\t\t\t// For testing, the tester controls whether to use transactional auditing\n\t\t\t\t\tconst trx = auditor?.getTransaction?.();\n\t\t\t\t\tconst db = trx ?? rawDb;\n\n\t\t\t\t\tconst responseBuilder = new ResponseBuilder();\n\t\t\t\t\tconst response = await this.endpoint.handler(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbody,\n\t\t\t\t\t\t\tquery,\n\t\t\t\t\t\t\tparams,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\tservices: ctx.services,\n\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\theader,\n\t\t\t\t\t\t\tcookie,\n\t\t\t\t\t\t\tauditor,\n\t\t\t\t\t\t\tdb,\n\t\t\t\t\t\t} as any,\n\t\t\t\t\t\tresponseBuilder,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check if response has metadata\n\t\t\t\t\tlet data = response;\n\t\t\t\t\tlet metadata = responseBuilder.getMetadata();\n\n\t\t\t\t\tif (Endpoint.hasMetadata(response)) {\n\t\t\t\t\t\tdata = response.data;\n\t\t\t\t\t\tmetadata = response.metadata;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst output = await this.endpoint.parseOutput(data);\n\n\t\t\t\t\treturn { output, metadata, responseBuilder };\n\t\t\t\t},\n\t\t\t\t// Process declarative audits after handler (inside transaction)\n\t\t\t\tasync (result, auditor) => {\n\t\t\t\t\tif (!audits?.length) return;\n\n\t\t\t\t\tfor (const audit of audits) {\n\t\t\t\t\t\tif (audit.when && !audit.when(result.output as any)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst payload = audit.payload(result.output as any);\n\t\t\t\t\t\tconst entityId = audit.entityId?.(result.output as any);\n\t\t\t\t\t\tauditor.audit(audit.type as any, payload as any, {\n\t\t\t\t\t\t\ttable: audit.table,\n\t\t\t\t\t\t\tentityId,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t// Pass rawDb so storage can reuse existing transactions\n\t\t\t\t{ db: rawDb },\n\t\t\t);\n\n\t\t\tconst { output, metadata } = result;\n\n\t\t\tctx.publisher && (await this.serviceDiscovery.register([ctx.publisher]));\n\t\t\tawait publishConstructEvents(\n\t\t\t\tthis.endpoint,\n\t\t\t\toutput,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t);\n\n\t\t\t// Convert cookies to Set-Cookie headers\n\t\t\tconst headers: Record<string, string | string[]> = {\n\t\t\t\t...(metadata.headers || {}),\n\t\t\t};\n\n\t\t\tif (metadata.cookies && metadata.cookies.size > 0) {\n\t\t\t\tconst setCookieValues: string[] = [];\n\t\t\t\tfor (const [name, cookie] of metadata.cookies.entries()) {\n\t\t\t\t\tsetCookieValues.push(\n\t\t\t\t\t\tserializeCookie(name, cookie.value, cookie.options),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\theaders['set-cookie'] = setCookieValues;\n\t\t\t}\n\n\t\t\t// Return HTTP response format\n\t\t\treturn {\n\t\t\t\tbody: output,\n\t\t\t\tstatus: metadata.status || 200,\n\t\t\t\theaders,\n\t\t\t};\n\t\t});\n\t}\n\n\tasync request(\n\t\tctx: TestRequestAdaptor<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName\n\t\t>,\n\t): Promise<InferStandardSchema<TOutSchema>> {\n\t\tconst response = await this.fullRequest(ctx);\n\t\treturn response.body;\n\t}\n}\n\n/**\n * Conditional audit storage requirement - required when TAuditStorage is configured\n */\ntype AuditStorageRequirement<\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n> = TAuditStorage extends undefined\n\t? {}\n\t: {\n\t\t\t/** Audit storage instance - required when endpoint uses .auditor() */\n\t\t\tauditorStorage: TAuditStorage;\n\t\t};\n\n/**\n * Conditional database requirement - required when TDatabase is configured\n */\ntype DatabaseRequirement<TDatabase = undefined> = TDatabase extends undefined\n\t? {}\n\t: {\n\t\t\t/** Database instance - required when endpoint uses .database() */\n\t\t\tdatabase: TDatabase;\n\t\t};\n\nexport type TestRequestAdaptor<\n\tTInput extends EndpointSchemas = {},\n\tTServices extends Service[] = [],\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t_TAuditStorageServiceName extends string = string,\n\tTDatabase = undefined,\n\t_TDatabaseServiceName extends string = string,\n> = {\n\tservices: ServiceRecord<TServices>;\n\theaders: Record<string, string>;\n\tpublisher?: Service<TEventPublisherServiceName, TEventPublisher>;\n} & InferComposableStandardSchema<TInput> &\n\tAuditStorageRequirement<TAuditStorage> &\n\tDatabaseRequirement<TDatabase>;\n","import type { AuditableAction, Auditor, AuditStorage } from '@geekmidas/audit';\nimport { DefaultAuditor } from '@geekmidas/audit';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport type { EventPublisher } from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type {\n\tComposableStandardSchema,\n\tInferComposableStandardSchema,\n\tInferStandardSchema,\n} from '@geekmidas/schema';\nimport {\n\trunWithRequestContext,\n\ttype Service,\n\tServiceDiscovery,\n\ttype ServiceRecord,\n} from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishEvents } from '../publisher';\nimport type { Function } from './Function';\nimport { FunctionBuilder } from './FunctionBuilder';\n\nexport class TestFunctionAdaptor<\n\tTInput extends ComposableStandardSchema | undefined = undefined,\n\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTAuditStorageServiceName extends string = string,\n\tTDatabase = undefined,\n\tTDatabaseServiceName extends string = string,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> {\n\tstatic getDefaultServiceDiscovery<\n\t\tTInput extends ComposableStandardSchema | undefined = undefined,\n\t\tTOutSchema extends StandardSchemaV1 | undefined = undefined,\n\t\tTServices extends Service[] = [],\n\t\tTLogger extends Logger = Logger,\n\t\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\t\tTEventPublisherServiceName extends string = string,\n\t\tTAuditStorage extends AuditStorage | undefined = undefined,\n\t\tTAuditStorageServiceName extends string = string,\n\t\tTDatabase = undefined,\n\t\tTDatabaseServiceName extends string = string,\n\t\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t>(\n\t\t_fn: Function<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName,\n\t\t\tTAuditAction,\n\t\t\tany\n\t\t>,\n\t) {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly fn: Function<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tTOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTAuditStorage,\n\t\t\tTAuditStorageServiceName,\n\t\t\tTDatabase,\n\t\t\tTDatabaseServiceName,\n\t\t\tTAuditAction,\n\t\t\tany\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestFunctionAdaptor.getDefaultServiceDiscovery(\n\t\t\tfn,\n\t\t),\n\t) {}\n\n\tasync invoke(\n\t\tctx: TestFunctionRequest<\n\t\t\tTInput,\n\t\t\tTServices,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTDatabase,\n\t\t\tTAuditAction\n\t\t>,\n\t): Promise<InferStandardSchema<TOutSchema>> {\n\t\t// Parse input if schema is provided\n\n\t\tconst parsedInput = await FunctionBuilder.parseComposableStandardSchema(\n\t\t\tctx.input,\n\t\t\tthis.fn.input,\n\t\t);\n\n\t\t// Create logger with context\n\t\tconst logger = this.fn.logger.child({\n\t\t\ttest: true,\n\t\t}) as TLogger;\n\n\t\t// Register services (use provided services or register from function)\n\t\tlet services: ServiceRecord<TServices>;\n\t\tif (ctx.services) {\n\t\t\tservices = ctx.services;\n\t\t} else {\n\t\t\tservices = await this.serviceDiscovery.register(this.fn.services);\n\t\t}\n\n\t\t// Resolve database (use provided db or register from function)\n\t\tlet db: TDatabase | undefined;\n\t\tif ('db' in ctx && ctx.db !== undefined) {\n\t\t\tdb = ctx.db;\n\t\t} else if (this.fn.databaseService) {\n\t\t\tconst dbServices = await this.serviceDiscovery.register([\n\t\t\t\tthis.fn.databaseService,\n\t\t\t]);\n\t\t\tdb = dbServices[\n\t\t\t\tthis.fn.databaseService.serviceName as keyof typeof dbServices\n\t\t\t] as TDatabase;\n\t\t}\n\n\t\t// Resolve auditor (use provided auditor or create from function)\n\t\tlet auditor: Auditor<TAuditAction> | undefined;\n\t\tif ('auditor' in ctx && ctx.auditor !== undefined) {\n\t\t\tauditor = ctx.auditor;\n\t\t} else if (this.fn.auditorStorageService) {\n\t\t\tconst auditServices = await this.serviceDiscovery.register([\n\t\t\t\tthis.fn.auditorStorageService,\n\t\t\t]);\n\t\t\tconst storage = auditServices[\n\t\t\t\tthis.fn.auditorStorageService.serviceName as keyof typeof auditServices\n\t\t\t] as AuditStorage;\n\n\t\t\tauditor = new DefaultAuditor<TAuditAction>({\n\t\t\t\tactor: { id: 'system', type: 'system' },\n\t\t\t\tstorage,\n\t\t\t\tmetadata: {\n\t\t\t\t\tfunction: this.fn.type,\n\t\t\t\t\ttest: true,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// Wrap execution in request context for service access via context.getLogger()\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\t// Execute the function\n\t\t\tconst response = await this.fn.fn({\n\t\t\t\tinput: parsedInput,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t\tdb,\n\t\t\t\tauditor,\n\t\t\t} as any);\n\n\t\t\t// Parse output if schema is provided\n\t\t\tconst output = await this.fn.parseOutput(response);\n\n\t\t\t// Flush audits if any were recorded\n\t\t\tif (auditor) {\n\t\t\t\tconst records = auditor.getRecords();\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t{ auditCount: records.length },\n\t\t\t\t\t\t'Flushing function audits',\n\t\t\t\t\t);\n\t\t\t\t\tawait auditor.flush();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Register publisher service if provided in context\n\n\t\t\tawait publishEvents(\n\t\t\t\tlogger,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t\tthis.fn.events,\n\t\t\t\toutput,\n\t\t\t\tthis.fn.publisherService,\n\t\t\t);\n\n\t\t\treturn output;\n\t\t}) as Promise<InferStandardSchema<TOutSchema>>;\n\t}\n}\n\nexport type TestFunctionRequest<\n\tTInput extends ComposableStandardSchema | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTDatabase = undefined,\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> = {\n\tinput: InferComposableStandardSchema<TInput>;\n\tservices: ServiceRecord<TServices>;\n\tpublisher?: Service<TEventPublisherServiceName, TEventPublisher>;\n\tdb?: TDatabase;\n\tauditor?: Auditor<TAuditAction>;\n} & InferComposableStandardSchema<{ input: TInput }>;\n","import { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport { runWithRequestContext, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Queue } from './Queue';\n\n/**\n * In-memory test driver for a {@link Queue} worker. Hand it a batch of messages\n * and it registers the queue's services, runs the handler in a request context,\n * and returns the handler result — no SQS/pg-boss required.\n *\n * @example\n * const adapter = new TestQueueAdaptor(ordersQueue);\n * await adapter.invoke({ messages: [{ orderId: '123' }] });\n */\nexport class TestQueueAdaptor<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tstatic getDefaultServiceDiscovery() {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly queue: Queue<TName, TMessage, TServices, TLogger>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestQueueAdaptor.getDefaultServiceDiscovery(),\n\t) {}\n\n\tasync invoke(\n\t\trequest: TestQueueRequest<TMessage, TServices>,\n\t): Promise<unknown> {\n\t\tconst logger = this.queue.logger.child({ test: true }) as TLogger;\n\n\t\tconst services =\n\t\t\trequest.services ??\n\t\t\t((await this.serviceDiscovery.register(\n\t\t\t\tthis.queue.services,\n\t\t\t)) as ServiceRecord<TServices>);\n\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, () =>\n\t\t\tthis.queue.handler({\n\t\t\t\tmessages: request.messages,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t}),\n\t\t);\n\t}\n}\n\nexport type TestQueueRequest<\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n> = {\n\tmessages: InferStandardSchema<TMessage>[];\n\tservices?: ServiceRecord<TServices>;\n};\n","import { EnvironmentParser } from '@geekmidas/envkit';\nimport type {\n\tEventPublisher,\n\tExtractPublisherMessage,\n} from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport { runWithRequestContext, ServiceDiscovery } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { publishEvents } from '../publisher';\nimport type { Subscriber } from './Subscriber';\n\n// Helper type to extract payload types for subscribed events\ntype ExtractEventPayloads<\n\tTPublisher extends EventPublisher<any> | undefined,\n\tTEventTypes extends any[],\n> = TPublisher extends EventPublisher<any>\n\t? Extract<ExtractPublisherMessage<TPublisher>, { type: TEventTypes[number] }>\n\t: never;\n\nexport class TestSubscriberAdaptor<\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTEventPublisherServiceName extends string = string,\n\tTSubscribedEvents extends\n\t\tExtractPublisherMessage<TEventPublisher>['type'][] = ExtractPublisherMessage<TEventPublisher>['type'][],\n> {\n\tstatic getDefaultServiceDiscovery() {\n\t\treturn ServiceDiscovery.getInstance(new EnvironmentParser({}));\n\t}\n\n\tconstructor(\n\t\tprivate readonly subscriber: Subscriber<\n\t\t\tTServices,\n\t\t\tTLogger,\n\t\t\tOutSchema,\n\t\t\tTEventPublisher,\n\t\t\tTEventPublisherServiceName,\n\t\t\tTSubscribedEvents\n\t\t>,\n\t\tprivate serviceDiscovery: ServiceDiscovery<any> = TestSubscriberAdaptor.getDefaultServiceDiscovery(),\n\t) {}\n\n\tasync invoke(\n\t\trequest: TestSubscriberRequest<\n\t\t\tTEventPublisher,\n\t\t\tTSubscribedEvents,\n\t\t\tTServices\n\t\t>,\n\t): Promise<InferStandardSchema<OutSchema>> {\n\t\t// Create logger with test context\n\t\tconst logger = this.subscriber.logger.child({\n\t\t\ttest: true,\n\t\t}) as TLogger;\n\n\t\t// Resolve services (use provided or auto-register)\n\t\tlet services: ServiceRecord<TServices>;\n\t\tif (request.services) {\n\t\t\tservices = request.services;\n\t\t} else {\n\t\t\tservices = await this.serviceDiscovery.register(this.subscriber.services);\n\t\t}\n\n\t\t// Filter events to only subscribed types\n\t\tconst filteredEvents = this.filterEvents(request.events);\n\n\t\t// Return early if no events after filtering (mirrors AWSLambdaSubscriber)\n\t\tif (filteredEvents.length === 0) {\n\t\t\treturn { batchItemFailures: [] } as any;\n\t\t}\n\n\t\t// Wrap execution in request context for service access via context.getLogger()\n\t\tconst requestId = `test-${Date.now()}`;\n\t\tconst startTime = Date.now();\n\n\t\treturn runWithRequestContext({ logger, requestId, startTime }, async () => {\n\t\t\t// Execute the subscriber handler\n\t\t\tconst result = await this.subscriber.handler({\n\t\t\t\tevents: filteredEvents,\n\t\t\t\tservices,\n\t\t\t\tlogger,\n\t\t\t});\n\n\t\t\t// Validate output if schema is provided\n\t\t\tlet output: any = result;\n\t\t\tif (this.subscriber.outputSchema && result) {\n\t\t\t\tconst validationResult =\n\t\t\t\t\tawait this.subscriber.outputSchema['~standard'].validate(result);\n\n\t\t\t\tif (validationResult.issues) {\n\t\t\t\t\tthrow new Error('Subscriber output validation failed');\n\t\t\t\t}\n\n\t\t\t\toutput = validationResult.value;\n\t\t\t}\n\n\t\t\t// Publish events if configured\n\t\t\tawait publishEvents(\n\t\t\t\tlogger,\n\t\t\t\tthis.serviceDiscovery,\n\t\t\t\tthis.subscriber.events,\n\t\t\t\toutput,\n\t\t\t\tthis.subscriber.publisherService,\n\t\t\t);\n\n\t\t\treturn output;\n\t\t}) as Promise<InferStandardSchema<OutSchema>>;\n\t}\n\n\tprivate filterEvents(\n\t\tevents: ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[],\n\t): ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[] {\n\t\tif (!this.subscriber.subscribedEvents) {\n\t\t\treturn events;\n\t\t}\n\n\t\treturn events.filter((event: any) =>\n\t\t\tthis.subscriber.subscribedEvents!.includes(event.type),\n\t\t);\n\t}\n}\n\nexport type TestSubscriberRequest<\n\tTEventPublisher extends EventPublisher<any> | undefined = undefined,\n\tTSubscribedEvents extends any[] = [],\n\tTServices extends Service[] = [],\n> = {\n\tevents: ExtractEventPayloads<TEventPublisher, TSubscribedEvents>[];\n\tservices?: ServiceRecord<TServices>;\n};\n","/**\n * The test manifest: what `gkm test` knows about an app, written down for the\n * suite it starts.\n *\n * `gkm test` discovers the constructs, reconciles the test stage and resolves\n * its environment — then, until this existed, threw all of it away and left a\n * test to declare it again by hand. The manifest keeps it: which construct and\n * endpoint is exported where, and the environment the test stage resolved,\n * under the keys the constructs themselves derive. A feature test is built from\n * it, so nothing a test says can drift from what the app declares.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport tls from 'node:tls';\nimport { fileURLToPath } from 'node:url';\n\n/** Where an export lives: what an import statement has to say. */\nexport interface TestManifestSource {\n\t/** Absolute path to the module. */\n\tfile: string;\n\t/** The name it is exported as. */\n\texport: string;\n}\n\nexport interface TestManifest {\n\t/** The stage the environment was resolved for — `test`. */\n\tstage: string;\n\t/** Every construct the app declares, by id. */\n\tconstructs: Record<string, { kind: string; source: TestManifestSource }>;\n\t/** Every endpoint, and the surface that serves it. */\n\tendpoints: { surface: string; source: TestManifestSource }[];\n\t/**\n\t * The test stage's environment — reconcile's output, keyed as the\n\t * constructs derive their keys.\n\t */\n\tenv: Record<string, string>;\n}\n\n/** The environment variable `gkm test` names the manifest in. */\nexport const TEST_MANIFEST_ENV = 'GKM_TEST_MANIFEST';\n\n/**\n * Read a test manifest: from a path, a `file:` URL, or — given nothing — the\n * one `gkm test` named in `GKM_TEST_MANIFEST`.\n */\nexport function loadTestManifest(from?: string | URL): TestManifest {\n\tconst location = from ?? process.env[TEST_MANIFEST_ENV];\n\tif (!location) throw new NoTestManifest();\n\n\ttrustLocalEdge();\n\n\tconst path =\n\t\tlocation instanceof URL || location.startsWith('file:')\n\t\t\t? fileURLToPath(location)\n\t\t\t: location;\n\n\treturn JSON.parse(readFileSync(path, 'utf-8')) as TestManifest;\n}\n\nlet trusted = false;\n\n/**\n * Trust the local edge's certificate authority in this thread.\n *\n * `NODE_EXTRA_CA_CERTS` does it for a process started with it — which a\n * suite started by `gkm test` is, and one started by plain `vitest` or an\n * editor is not: its setup only learns the path once Vitest is running, and a\n * worker *thread* shares that process and keeps a TLS store of its own. Every\n * feature test loads the manifest, so this is the one place each worker passes\n * through.\n */\nfunction trustLocalEdge(): void {\n\tconst certificate = process.env.NODE_EXTRA_CA_CERTS;\n\tif (trusted || !certificate || !existsSync(certificate)) return;\n\ttrusted = true;\n\n\ttls.setDefaultCACertificates([\n\t\t...tls.getCACertificates('default'),\n\t\treadFileSync(certificate, 'utf-8'),\n\t]);\n}\n\n/** A feature test ran without a manifest to build itself from. */\nexport class NoTestManifest extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t`No test manifest: run the suite with \\`gkm test\\`, or add ` +\n\t\t\t\t`\\`globalSetup: ['@geekmidas/cli/vitest']\\` to the root vitest config — ` +\n\t\t\t\t`either writes one and names it in ${TEST_MANIFEST_ENV}.`,\n\t\t);\n\t\tthis.name = 'NoTestManifest';\n\t}\n}\n","/**\n * `featureTest`: an app, driven the way it runs deployed.\n *\n * A browser signs in and calls the API; the API calls the auth server; each\n * over its URL, each database in its own transaction. Nothing runs as a server:\n * every URL an app's constructs answer on is served in-process through MSW,\n * from the real handler, for the test that made the request. Everything else\n * with a real local equivalent — Postgres, Mailpit, MinIO — is used directly.\n *\n * It is built from the test manifest `gkm test` writes — the constructs and\n * endpoints the app exports, and the environment the test stage resolved — so\n * a test declares none of it. `gkm test` also generates the configured `it`\n * and a `Browser` with a typed client per surface:\n *\n * ```ts\n * import { it } from '#test';\n *\n * it('shows the signed-in user their profile', async ({ browser, mailbox }) => {\n * await browser.auth.signIn.magicLink({ email: 'ada@shop.test' });\n * await browser.visit((await mailbox('ada@shop.test').last()).link!);\n * expect(await browser.api.get('/profile')).toMatchObject({ email: 'ada@shop.test' });\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport { provideKey } from '@geekmidas/manifest';\nimport { ServiceDiscovery, serviceContext } from '@geekmidas/services';\nimport { Browser as TestBrowser } from '@geekmidas/testkit/browser';\nimport {\n\tcurrentTestContext,\n\tinstallContextFetch,\n\trunAsServer,\n\trunInTestContext,\n\ttestContextOf,\n} from '@geekmidas/testkit/context';\nimport { type FakerFactory, faker } from '@geekmidas/testkit/faker';\nimport { createMailbox, type Mailbox } from '@geekmidas/testkit/mailbox';\nimport { TransactionRegistry } from '@geekmidas/testkit/transactions';\nimport { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport { http } from 'msw';\nimport { type SetupServer, setupServer } from 'msw/node';\nimport { afterAll, beforeAll, test } from 'vitest';\nimport { BetterAuth } from '../auth';\nimport { KyselyDatabase } from '../database/kysely';\nimport { Email } from '../email';\nimport { Endpoint } from '../endpoints/Endpoint';\nimport { HonoEndpoint } from '../endpoints/HonoEndpointAdaptor';\nimport type { Fake } from '../external-api';\nimport { Queue } from '../queue/Queue';\nimport { TestQueueAdaptor } from '../queue/TestQueueAdaptor';\nimport type { Subscriber } from '../subscribers/Subscriber';\nimport { TestSubscriberAdaptor } from '../subscribers/TestSubscriberAdaptor';\nimport { Topic } from '../topic/Topic';\nimport { loadTestManifest, type TestManifest } from './manifest';\n\n/** How each database's test factory is built, keyed by its service name. */\nexport type FactoryBuilders = Record<string, (db: Kysely<any>) => unknown>;\n\nexport interface FeatureTestOptions<\n\tTBrowser extends TestBrowser,\n\tTFactories extends FactoryBuilders = {},\n> {\n\t/**\n\t * What the app declares and the environment its test stage resolved.\n\t * Defaults to the manifest `gkm test` names in `GKM_TEST_MANIFEST`.\n\t */\n\tmanifest?: TestManifest;\n\t/**\n\t * The app's modules, already imported, by the absolute path the manifest\n\t * records for them. The generated harness imports them statically, from\n\t * inside the app, so they resolve the way the app's own code does — its\n\t * tsconfig paths included. A module not here is imported by path.\n\t */\n\tmodules?: Readonly<Record<string, Record<string, unknown>>>;\n\t/** The browser each test gets — the generated one, with the app's clients. */\n\tbrowser?: new () => TBrowser;\n\t/**\n\t * Each database's test factory — `createFactory` from\n\t * `test/factories/<construct>.ts`, which the generated harness imports —\n\t * keyed by the database's service name. Each is called once per test with\n\t * that database's transaction, so what a test inserts through it is rolled\n\t * back with everything else, and seen by the endpoints it calls.\n\t */\n\tfactories?: TFactories;\n\t/**\n\t * Each external API's fake — the default export of `test/fakes/<id>.ts`,\n\t * which the generated harness imports — keyed by the API's construct id.\n\t * An app fake is served at the URL the test stage resolved for it.\n\t */\n\tfakes?: Readonly<Record<string, Fake>>;\n}\n\n/** Each database's schema, keyed by its service name. */\nexport type DatabaseSchemas = Record<string, unknown>;\n\n/**\n * The app's databases for this test, by service name — `db.get('database')`.\n *\n * Each is this test's transaction on that database: opened by whatever reaches\n * it first — the test, a factory, an endpoint — and the same one for all of\n * them, rolled back after the test.\n */\nexport interface TestDatabases<TDatabases extends DatabaseSchemas> {\n\tget<K extends keyof TDatabases & string>(\n\t\tname: K,\n\t): Promise<Kysely<TDatabases[K]>>;\n}\n\n/**\n * Each of the app's databases' test factory, by service name —\n * `await factories.get('database')`. Built on first use, on that database's\n * transaction for this test — the one `db.get` returns — and the same factory\n * for the rest of the test.\n */\nexport interface TestFactories<TFactories extends FactoryBuilders> {\n\tget<K extends keyof TFactories & string>(\n\t\tname: K,\n\t): Promise<ReturnType<TFactories[K]>>;\n}\n\nexport interface FeatureContext<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n> {\n\t/** This test's browser, already the global `fetch`. */\n\tbrowser: TBrowser;\n\t/**\n\t * The app's own databases — not a tenant an auth server owns, not a\n\t * reader — by service name: `await db.get('database')`. The same\n\t * transaction the test's endpoints and factories use.\n\t */\n\tdb: TestDatabases<TDatabases>;\n\t/**\n\t * Each of the app's databases' test factory, by service name —\n\t * `await factories.get('database')` — on the same transaction `db.get`\n\t * returns.\n\t */\n\tfactories: TestFactories<TFactories>;\n\t/**\n\t * Testkit's faker — the one factories are handed — seeded from this test's\n\t * name, so a failing test fails again with the same data, and so a test\n\t * imports nothing to make some.\n\t */\n\tfaker: FakerFactory;\n\t/** The mail sent to an address during this test, read from Mailpit. */\n\tmailbox: (address: string) => Mailbox;\n\t/**\n\t * What this test published to a topic or enqueued on a queue, in order.\n\t * Nothing is delivered — a subscriber or a worker is tested on its own, by\n\t * handing it events — so this is where publishing is asserted.\n\t */\n\tpublished: (channel: { id: string }) => PublishedMessage[];\n\t/**\n\t * A subscriber, run on its own — handed events rather than delivered them —\n\t * with this test's services: its databases resolve to this test's\n\t * transactions, as an endpoint's do.\n\t */\n\tsubscriber: <S extends Subscriber<any, any, any, any, any, any>>(\n\t\tsubscriber: S,\n\t) => SubscriberAdaptorOf<S>;\n\t/** A queue's worker, run on its own with this test's services. */\n\tqueue: <Q extends Queue<any, any, any, any>>(queue: Q) => QueueAdaptorOf<Q>;\n}\n\ntype SubscriberAdaptorOf<S> =\n\tS extends Subscriber<\n\t\tinfer TServices,\n\t\tinfer TLogger,\n\t\tinfer OutSchema,\n\t\tinfer TEventPublisher,\n\t\tinfer TEventPublisherServiceName,\n\t\tinfer TSubscribedEvents\n\t>\n\t\t? TestSubscriberAdaptor<\n\t\t\t\tTServices,\n\t\t\t\tTLogger,\n\t\t\t\tOutSchema,\n\t\t\t\tTEventPublisher,\n\t\t\t\tTEventPublisherServiceName,\n\t\t\t\tTSubscribedEvents\n\t\t\t>\n\t\t: never;\n\ntype QueueAdaptorOf<Q> =\n\tQ extends Queue<infer TName, infer TMessage, infer TServices, infer TLogger>\n\t\t? TestQueueAdaptor<TName, TMessage, TServices, TLogger>\n\t\t: never;\n\n/** The schema a database construct was declared with — what `db` is typed by. */\nexport type DatabaseOf<C> =\n\tC extends KyselyDatabase<infer DB, any> ? DB : unknown;\n\n/** One message as it was published: its type and its payload. */\nexport interface PublishedMessage {\n\ttype: string;\n\tpayload: unknown;\n}\n\ntype FeatureFn<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas,\n\tTFactories extends FactoryBuilders,\n> = (context: FeatureContext<TBrowser, TDatabases, TFactories>) => unknown;\n\nexport interface FeatureIt<\n\tTBrowser extends TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n> {\n\t(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n\tonly(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n\tskip(\n\t\tname: string,\n\t\tfn: FeatureFn<TBrowser, TDatabases, TFactories>,\n\t\ttimeout?: number,\n\t): void;\n}\n\n/** Everything one test owns, found again from its id. */\ninterface ContextState {\n\ttransactions: TransactionRegistry;\n\tdiscovery: ServiceDiscovery<any>;\n\t/** One app per surface, with this test's services. */\n\tsurfaces: Map<string, Promise<Hono>>;\n\t/** One auth server per construct, on this test's transaction. */\n\tauth: Map<BetterAuth, Promise<Hono>>;\n\t/** The addresses this test read mail for, cleared after it. */\n\taddresses: Set<string>;\n\t/** This test's inbox, when the app sends mail — what `browser.signIn` reads. */\n\tmailbox?: (address: string) => Mailbox;\n\t/** What this test published, by topic or queue id. */\n\tpublished: Map<string, PublishedMessage[]>;\n}\n\n/** Every test in flight in this process, by id. */\nconst contexts = new Map<string, ContextState>();\n\n/** Databases already resolving to test transactions — patched once each. */\nconst bound = new WeakSet<KyselyDatabase>();\n\n/** What the manifest points at, imported: the app's own instances. */\ninterface LoadedApp {\n\tenvParser: EnvironmentParser<{}>;\n\tendpoints: Endpoint<any, any, any, any>[];\n\tdatabases: KyselyDatabase[];\n\tauths: BetterAuth[];\n\temails: Email[];\n\t/** Every topic and queue — what a test's publishing is recorded for. */\n\tchannels: (Topic<any, any> | Queue<any, any>)[];\n\treadMail?: (address: string) => Mailbox;\n}\n\nexport function featureTest<\n\tTBrowser extends TestBrowser = TestBrowser,\n\tTDatabases extends DatabaseSchemas = {},\n\tTFactories extends FactoryBuilders = {},\n>(\n\toptions: FeatureTestOptions<TBrowser, TFactories> = {},\n): FeatureIt<TBrowser, TDatabases, TFactories> {\n\tconst manifest = options.manifest ?? loadTestManifest();\n\tconst BrowserClass = (options.browser ?? TestBrowser) as new () => TBrowser;\n\n\tlet app: LoadedApp;\n\tlet factoryDatabases: [string, KyselyDatabase][] = [];\n\tlet owned: KyselyDatabase[] = [];\n\tlet network: SetupServer;\n\tlet restoreFetch: () => void = () => {};\n\n\tbeforeAll(async () => {\n\t\tapp = await load(manifest, options.modules ?? {});\n\t\towned = ownDatabases(app, manifest);\n\t\tfactoryDatabases = databasesFor(options.factories ?? {}, owned);\n\t\tfor (const database of app.databases) bindToTests(database);\n\n\t\tnetwork = setupServer(\n\t\t\t...surfaceHandlers(app.endpoints, manifest.env),\n\t\t\t...app.auths.map((auth) =>\n\t\t\t\tauthHandler(auth, manifest.env, app.envParser),\n\t\t\t),\n\t\t\t...Object.entries(options.fakes ?? {}).flatMap(([id, fake]) =>\n\t\t\t\tfakeHandler(id, fake, manifest.env),\n\t\t\t),\n\t\t);\n\t\tnetwork.listen({\n\t\t\tonUnhandledRequest(request, print) {\n\t\t\t\t// A real local service — Mailpit, MinIO — is used directly; only a\n\t\t\t\t// URL with nothing real behind it has to be answered here.\n\t\t\t\tif (isLocal(new URL(request.url))) return;\n\t\t\t\tprint.error();\n\t\t\t},\n\t\t});\n\t\t// After MSW's interceptor, so the stamp is on before a request is caught.\n\t\trestoreFetch = installContextFetch();\n\t});\n\tafterAll(() => {\n\t\trestoreFetch();\n\t\tnetwork?.close();\n\t});\n\n\tconst run =\n\t\t(name: string, fn: FeatureFn<TBrowser, TDatabases, TFactories>) =>\n\t\tasync (): Promise<void> => {\n\t\t\tconst id = randomUUID();\n\t\t\tfaker.seed(seedOf(name));\n\t\t\tconst state: ContextState = {\n\t\t\t\ttransactions: new TransactionRegistry(),\n\t\t\t\tdiscovery: new ServiceDiscovery(app.envParser),\n\t\t\t\tsurfaces: new Map(),\n\t\t\t\tauth: new Map(),\n\t\t\t\taddresses: new Set(),\n\t\t\t\tpublished: new Map(),\n\t\t\t};\n\t\t\tconst readMail = app.readMail;\n\t\t\tif (readMail) {\n\t\t\t\tstate.mailbox = (address) => {\n\t\t\t\t\tstate.addresses.add(address);\n\t\t\t\t\treturn readMail(address);\n\t\t\t\t};\n\t\t\t}\n\t\t\tcontexts.set(id, state);\n\t\t\t// Before anything resolves them: a publisher is a service, so the one this\n\t\t\t// test's endpoints get is whichever is registered under its name first.\n\t\t\tawait state.discovery.register(recorders(app.channels, state));\n\n\t\t\ttry {\n\t\t\t\tawait runInTestContext(id, async () => {\n\t\t\t\t\tconst browser = new BrowserClass();\n\t\t\t\t\tconst restore = browser.install();\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// A database's transaction for this test, opened on first use —\n\t\t\t\t\t\t// the registry hands everyone in the test the same one.\n\t\t\t\t\t\tconst transaction = (database: KyselyDatabase) =>\n\t\t\t\t\t\t\tstate.transactions.get(\n\t\t\t\t\t\t\t\tdatabase.id,\n\t\t\t\t\t\t\t\turlOf(database, app.envParser),\n\t\t\t\t\t\t\t\tdatabase.clientConfig,\n\t\t\t\t\t\t\t) as Promise<Kysely<any>>;\n\t\t\t\t\t\tconst db: TestDatabases<any> = {\n\t\t\t\t\t\t\tget: (name) => {\n\t\t\t\t\t\t\t\tconst database = owned.find(\n\t\t\t\t\t\t\t\t\t(candidate) => candidate.service.serviceName === name,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (!database) {\n\t\t\t\t\t\t\t\t\tthrow new UnknownDatabase(\n\t\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t\t\towned.map((candidate) => candidate.service.serviceName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn transaction(database);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst built = new Map<string, Promise<unknown>>();\n\t\t\t\t\t\tconst factories: TestFactories<any> = {\n\t\t\t\t\t\t\tget: (name) => {\n\t\t\t\t\t\t\t\tconst database = factoryDatabases.find(\n\t\t\t\t\t\t\t\t\t([candidate]) => candidate === name,\n\t\t\t\t\t\t\t\t)?.[1];\n\t\t\t\t\t\t\t\tif (!database) {\n\t\t\t\t\t\t\t\t\tthrow new UnknownFactory(\n\t\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t\t\tfactoryDatabases.map(([candidate]) => candidate),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet factory = built.get(name);\n\t\t\t\t\t\t\t\tif (!factory) {\n\t\t\t\t\t\t\t\t\tfactory = transaction(database).then(\n\t\t\t\t\t\t\t\t\t\toptions.factories![name]!,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tbuilt.set(name, factory);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn factory as Promise<any>;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\tawait fn({\n\t\t\t\t\t\t\tbrowser,\n\t\t\t\t\t\t\tdb: db as TestDatabases<TDatabases>,\n\t\t\t\t\t\t\tfactories: factories as TestFactories<TFactories>,\n\t\t\t\t\t\t\tfaker,\n\t\t\t\t\t\t\tmailbox: (address) => {\n\t\t\t\t\t\t\t\tif (!state.mailbox) throw new NoInbox();\n\t\t\t\t\t\t\t\treturn state.mailbox(address);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpublished: (channel) => [\n\t\t\t\t\t\t\t\t...(state.published.get(channel.id) ?? []),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tsubscriber: (subscriber) =>\n\t\t\t\t\t\t\t\tnew TestSubscriberAdaptor(\n\t\t\t\t\t\t\t\t\tsubscriber,\n\t\t\t\t\t\t\t\t\tstate.discovery,\n\t\t\t\t\t\t\t\t) as SubscriberAdaptorOf<typeof subscriber>,\n\t\t\t\t\t\t\tqueue: (queue) =>\n\t\t\t\t\t\t\t\tnew TestQueueAdaptor(queue, state.discovery) as QueueAdaptorOf<\n\t\t\t\t\t\t\t\t\ttypeof queue\n\t\t\t\t\t\t\t\t>,\n\t\t\t\t\t\t} as FeatureContext<TBrowser, TDatabases, TFactories>);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\trestore();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tcontexts.delete(id);\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tstate.transactions.rollbackAll(),\n\t\t\t\t\t...[...state.addresses].map((address) =>\n\t\t\t\t\t\tapp.readMail?.(address).clear(),\n\t\t\t\t\t),\n\t\t\t\t]);\n\t\t\t}\n\t\t};\n\n\tconst it = ((name, fn, timeout) =>\n\t\ttest(name, run(name, fn), timeout)) as FeatureIt<\n\t\tTBrowser,\n\t\tTDatabases,\n\t\tTFactories\n\t>;\n\tit.only = (name, fn, timeout) => test.only(name, run(name, fn), timeout);\n\tit.skip = (name, fn, timeout) => test.skip(name, run(name, fn), timeout);\n\treturn it;\n}\n\n/**\n * A test's faker seed, from its name: the same test draws the same data on\n * every run, and two tests draw different data.\n */\nfunction seedOf(name: string): number {\n\tlet hash = 0;\n\tfor (const char of name) {\n\t\thash = (Math.imul(hash, 31) + char.charCodeAt(0)) | 0;\n\t}\n\treturn hash >>> 0;\n}\n\n/**\n * Import what the manifest points at.\n *\n * The same modules the app runs, so a construct here *is* the app's construct —\n * the one its endpoints were built from — never a copy declared again.\n */\nasync function load(\n\tmanifest: TestManifest,\n\tmodules: Readonly<Record<string, Record<string, unknown>>>,\n): Promise<LoadedApp> {\n\tconst envParser = new EnvironmentParser({ ...manifest.env });\n\tconst imported = async ({\n\t\tfile,\n\t\texport: name,\n\t}: {\n\t\tfile: string;\n\t\texport: string;\n\t}) =>\n\t\t// Imported by path only as a fallback: from inside `node_modules`, a\n\t\t// dynamic import is left to Node, which knows nothing of the app's\n\t\t// tsconfig paths — `~/router.ts` resolves in the app and not here.\n\t\t(modules[file] ?? ((await import(file)) as Record<string, unknown>))[name];\n\n\tconst constructs = await Promise.all(\n\t\tObject.values(manifest.constructs).map(({ source }) => imported(source)),\n\t);\n\tconst endpoints = (\n\t\tawait Promise.all(manifest.endpoints.map(({ source }) => imported(source)))\n\t).filter((value): value is Endpoint<any, any, any, any> =>\n\t\tEndpoint.isEndpoint(value),\n\t);\n\n\tconst unique = <T>(values: T[]) => [...new Set(values)];\n\tconst databases = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is KyselyDatabase => value instanceof KyselyDatabase,\n\t\t),\n\t);\n\tconst auths = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is BetterAuth => value instanceof BetterAuth,\n\t\t),\n\t);\n\tconst emails = unique(\n\t\tconstructs.filter((value): value is Email => value instanceof Email),\n\t);\n\tconst channels = unique(\n\t\tconstructs.filter(\n\t\t\t(value): value is Topic<any, any> | Queue<any, any> =>\n\t\t\t\tvalue instanceof Topic || value instanceof Queue,\n\t\t),\n\t);\n\n\tconst inbox = emails\n\t\t.map((email) => manifest.env[provideKey(email.id, 'inboxUrl')])\n\t\t.find(Boolean);\n\n\treturn {\n\t\tenvParser,\n\t\tendpoints,\n\t\tdatabases,\n\t\tauths,\n\t\temails,\n\t\tchannels,\n\t\t...(inbox ? { readMail: createMailbox({ inbox }) } : {}),\n\t};\n}\n\n/**\n * A publisher that records rather than sends, under every name a topic or\n * queue is injected by — `users`, and the older `usersPublisher`.\n */\nfunction recorders(\n\tchannels: LoadedApp['channels'],\n\tstate: ContextState,\n): { serviceName: string; register: () => unknown }[] {\n\treturn channels.flatMap((channel) => {\n\t\tconst publisher = {\n\t\t\tasync publish(messages: PublishedMessage[]) {\n\t\t\t\tstate.published.set(channel.id, [\n\t\t\t\t\t...(state.published.get(channel.id) ?? []),\n\t\t\t\t\t...messages.map(({ type, payload }) => ({ type, payload })),\n\t\t\t\t]);\n\t\t\t},\n\t\t};\n\t\treturn [channel.service.serviceName, channel.publisher.serviceName].map(\n\t\t\t(serviceName) => ({ serviceName, register: () => publisher }),\n\t\t);\n\t});\n}\n\n/**\n * The databases a test is handed: the app's own. A schema tenant an auth server\n * owns is reached through that server, as the app reaches it, and a reader is\n * the same database through a read-only role — neither is another database to\n * test against.\n */\nfunction ownDatabases(\n\tapp: LoadedApp,\n\tmanifest: TestManifest,\n): KyselyDatabase[] {\n\tconst owners = new Set(app.auths.map((auth) => auth.databaseId));\n\treturn app.databases.filter(\n\t\t(database) =>\n\t\t\t!owners.has(database.id) &&\n\t\t\tmanifest.constructs[database.id]?.kind !== 'database-reader',\n\t);\n}\n\n/** Each factory's database, found by the service name it is keyed by. */\nfunction databasesFor(\n\tfactories: FactoryBuilders,\n\tdatabases: KyselyDatabase[],\n): [string, KyselyDatabase][] {\n\treturn Object.keys(factories).map((name) => {\n\t\tconst database = databases.find(\n\t\t\t(candidate) => candidate.service.serviceName === name,\n\t\t);\n\t\tif (!database) {\n\t\t\tthrow new UnknownFactory(\n\t\t\t\tname,\n\t\t\t\tdatabases.map((candidate) => candidate.service.serviceName),\n\t\t\t);\n\t\t}\n\t\treturn [name, database];\n\t});\n}\n\n/**\n * Make a database resolve, inside a test, to that test's transaction.\n *\n * Everything reaches a database through its service — an endpoint through\n * service discovery, the auth server through its tenant directly — so this is\n * the one place a test's connection can be handed over. Outside a test it is\n * the database it always was.\n */\nfunction bindToTests(database: KyselyDatabase): void {\n\tif (bound.has(database)) return;\n\tbound.add(database);\n\n\tconst service = database.service as { register: (options: any) => unknown };\n\tconst register = service.register;\n\n\tservice.register = (options) => {\n\t\tconst context = currentTestContext();\n\t\tconst state = context && contexts.get(context.id);\n\t\tif (!state) return register(options);\n\n\t\treturn state.transactions.get(\n\t\t\tdatabase.id,\n\t\t\turlOf(database, options.envParser),\n\t\t\tdatabase.clientConfig,\n\t\t);\n\t};\n}\n\nfunction urlOf(database: { id: string }, envParser: EnvironmentParser<{}>) {\n\treturn envParser\n\t\t.create((get) => ({ url: get(provideKey(database.id, 'url')).string() }))\n\t\t.parse().url;\n}\n\n/** Each surface's endpoints, served at its URL for the test that asked. */\nfunction surfaceHandlers(\n\tendpoints: Endpoint<any, any, any, any>[],\n\tenv: Record<string, string | undefined>,\n) {\n\tconst bySurface = new Map<string, Endpoint<any, any, any, any>[]>();\n\tfor (const endpoint of endpoints) {\n\t\tconst surface = endpoint.surface?.id;\n\t\tif (!surface) continue;\n\t\tbySurface.set(surface, [...(bySurface.get(surface) ?? []), endpoint]);\n\t}\n\n\treturn [...bySurface].flatMap(([surface, served]) => {\n\t\tconst base = env[provideKey(surface, 'url')];\n\t\tif (!base) return [];\n\n\t\treturn http.all(`${trim(base)}/*`, ({ request }) =>\n\t\t\tserve(request, async (state) => {\n\t\t\t\tlet app = state.surfaces.get(surface);\n\t\t\t\tif (!app) {\n\t\t\t\t\tapp = Promise.resolve().then(() => {\n\t\t\t\t\t\tconst hono = new Hono();\n\t\t\t\t\t\tHonoEndpoint.addRoutes(served, state.discovery, hono);\n\t\t\t\t\t\treturn hono;\n\t\t\t\t\t});\n\t\t\t\t\tstate.surfaces.set(surface, app);\n\t\t\t\t}\n\t\t\t\treturn (await app).fetch(request);\n\t\t\t}),\n\t\t);\n\t});\n}\n\n/**\n * An external API's app fake, answering at the URL the test stage resolved for\n * it — so a handler that calls Polar calls the fake, and the test sets up\n * nothing.\n *\n * Not per test, unlike a surface: a fake stands in for somebody else's server,\n * which no test's transaction reaches. An image fake has no handler here — its\n * container answers on localhost, which is let through.\n */\nfunction fakeHandler(\n\tid: string,\n\tfake: Fake,\n\tenv: Record<string, string | undefined>,\n) {\n\tconst base = env[provideKey(id, 'url')];\n\tif (fake.kind !== 'app' || !base) return [];\n\n\treturn [\n\t\thttp.all(`${trim(base)}/*`, ({ request }) => fake.handler.fetch(request)),\n\t];\n}\n\n/** An auth server, served at its URL, on the transaction of the test that asked. */\nfunction authHandler(\n\tauth: BetterAuth,\n\tenv: Record<string, string | undefined>,\n\tenvParser: EnvironmentParser<{}>,\n) {\n\tconst base = trim(env[provideKey(auth.id, 'url')] ?? '');\n\n\t// The whole origin, not only `basePath`: the auth server answers its own\n\t// host deployed, and a redirect it issues — to `/` after verifying a magic\n\t// link — lands on it. Paths it does not route get its own 404.\n\treturn http.all(`${base}/*`, ({ request }) =>\n\t\tserve(request, async (state) => {\n\t\t\tlet app = state.auth.get(auth);\n\t\t\tif (!app) {\n\t\t\t\tapp = auth\n\t\t\t\t\t.server({ envParser, context: serviceContext })\n\t\t\t\t\t.then(({ app }) => app);\n\t\t\t\tstate.auth.set(auth, app);\n\t\t\t}\n\t\t\treturn (await app).fetch(request);\n\t\t}),\n\t);\n}\n\n/** Handle a request as the server side of the test it was made for. */\nasync function serve(\n\trequest: Request,\n\thandle: (state: ContextState) => Promise<Response>,\n): Promise<Response> {\n\tconst id = testContextOf(request);\n\tconst state = id ? contexts.get(id) : undefined;\n\tif (!id || !state) throw new UnknownTestContext(request.url, id);\n\n\treturn runAsServer(id, () => handle(state));\n}\n\nfunction trim(url: string): string {\n\treturn url.replace(/\\/$/, '');\n}\n\nfunction isLocal(url: URL): boolean {\n\treturn (\n\t\turl.hostname === 'localhost' ||\n\t\turl.hostname === '127.0.0.1' ||\n\t\turl.hostname.endsWith('.localhost')\n\t);\n}\n\n/** What `signInWithMagicLink` uses of a better-auth client. */\nexport interface MagicLinkAuthClient<TSession> {\n\tsignIn: {\n\t\tmagicLink(input: {\n\t\t\temail: string;\n\t\t}): Promise<{ error?: { message?: string } | null }>;\n\t};\n\tgetSession(): Promise<{ data: TSession | null }>;\n}\n\n/**\n * Sign a test's browser in the way a person does: ask the auth server for a\n * magic link, open the email it sent, follow the link.\n *\n * What the generated `browser.signIn(email)` calls. The address's mail is\n * cleared first — so the link is this request's, not one an earlier test left\n * — and cleared again after the test, like any address a test reads. Returns\n * the session the auth server then reports, as better-auth hands it back.\n */\nexport async function signInWithMagicLink<TSession>(\n\tbrowser: TestBrowser,\n\tauth: MagicLinkAuthClient<TSession>,\n\temail: string = freshAddress(),\n): Promise<TSession> {\n\tconst context = currentTestContext();\n\tconst state = context && contexts.get(context.id);\n\tif (!state) throw new UnknownTestContext('signIn', context?.id);\n\tif (!state.mailbox) throw new NoInbox();\n\n\tconst inbox = state.mailbox(email);\n\tawait inbox.clear();\n\n\tconst requested = await auth.signIn.magicLink({ email });\n\tif (requested.error) {\n\t\tthrow new SignInFailed(\n\t\t\temail,\n\t\t\t`the auth server refused the magic link: ${requested.error.message ?? 'no reason given'}`,\n\t\t);\n\t}\n\n\tconst mail = await inbox.last();\n\tif (!mail.link) throw new SignInFailed(email, 'its email held no link');\n\tawait browser.visit(mail.link);\n\n\tconst { data } = await auth.getSession();\n\tif (!data) {\n\t\tthrow new SignInFailed(\n\t\t\temail,\n\t\t\t'the auth server reported no session after the link was opened',\n\t\t);\n\t}\n\treturn data;\n}\n\n/**\n * An address nobody else signs in as — what `browser.signIn()` uses when the\n * test does not care who it is.\n *\n * Unique rather than only seeded: tests in other files run at the same time,\n * against the same Mailpit and the same unique columns, and a seeded address\n * repeats wherever two tests share a name. The readable half is the seeded\n * faker's, so a failure still names somebody.\n */\nfunction freshAddress(): string {\n\tconst name = faker.internet\n\t\t.username()\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]/g, '');\n\n\treturn `${name}.${randomUUID().slice(0, 8)}@example.test`;\n}\n\n/** `browser.signIn` finished without a session. */\nexport class SignInFailed extends Error {\n\tconstructor(\n\t\treadonly email: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(`Signing ${email} in failed: ${reason}.`);\n\t\tthis.name = 'SignInFailed';\n\t}\n}\n\n/** A request reached an in-process server without a test to belong to. */\nexport class UnknownTestContext extends Error {\n\tconstructor(\n\t\treadonly url: string,\n\t\treadonly id: string | undefined,\n\t) {\n\t\tsuper(\n\t\t\tid\n\t\t\t\t? `${url} was requested for test ${id}, which is not running. A ` +\n\t\t\t\t\t\t`request that outlives its test — an un-awaited call — lands here.`\n\t\t\t\t: `${url} was requested outside any test. Make the request inside a ` +\n\t\t\t\t\t\t`featureTest, so it reaches that test's transactions.`,\n\t\t);\n\t\tthis.name = 'UnknownTestContext';\n\t}\n}\n\n/** `mailbox` was used in an app that declares no Email construct. */\nexport class NoInbox extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t`mailbox() reads the inbox of the app's Email construct, and none was ` +\n\t\t\t\t`found in featureTest's modules — or \\`gkm test\\` did not inject its ` +\n\t\t\t\t`<ID>_INBOX_URL.`,\n\t\t);\n\t\tthis.name = 'NoInbox';\n\t}\n}\n\n/** `db.get(name)` named no database the test is handed. */\nexport class UnknownDatabase extends Error {\n\tconstructor(\n\t\treadonly database: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`db.get('${database}') names no database of this app's. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `Its databases: ${known.join(', ')}. `\n\t\t\t\t\t: 'It declares no database of its own. ') +\n\t\t\t\t`A tenant an auth server owns is reached through that server, and a ` +\n\t\t\t\t`reader through the database it reads.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabase';\n\t}\n}\n\n/** A factory keyed by a service name no database the app declares has. */\nexport class UnknownFactory extends Error {\n\tconstructor(\n\t\treadonly factory: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`There is a test factory for '${factory}', and no database this app ` +\n\t\t\t\t`declares is called that. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `Its databases: ${known.join(', ')}. `\n\t\t\t\t\t: 'It declares no database. ') +\n\t\t\t\t`Name the file after the database construct: test/factories/<construct>.ts.`,\n\t\t);\n\t\tthis.name = 'UnknownFactory';\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as ServicesOf, n as Construct, r as Consumable, t as Authenticator } from "./construct-interface-DPusCUSQ.cjs";
1
+ import { c as ServicesOf, n as Construct, r as Consumable, t as Authenticator } from "./construct-interface-DcNaRKoC.cjs";
2
2
  import { EnvironmentParser } from "@geekmidas/envkit";
3
3
  import { Service, ServiceRecord, ServiceRegisterOptions } from "@geekmidas/services";
4
4
  import { ConstructName, Declaration } from "@geekmidas/manifest";
@@ -185,4 +185,4 @@ declare class SessionCheckFailed extends Error {
185
185
  declare function idempotent(statements: string): string;
186
186
  //#endregion
187
187
  export { BetterAuthOptions as a, SessionCheckFailed as c, BetterAuthConfig as i, deviceLink as l, AuthSession as n, BetterAuthOptionsContext as o, BetterAuth as r, ExpoPluginRequired as s, AuthServer as t, idempotent as u };
188
- //# sourceMappingURL=auth-c6nWnvD8.d.cts.map
188
+ //# sourceMappingURL=auth-6Cdx5amw.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-c6nWnvD8.d.cts","names":[],"sources":["../src/auth.ts"],"mappings":";;;;;;;;KAsDY,aAAa,kBAAkB;;KAG/B,cAAc;;KAGd,oBAAoB,KAC/B,kBAAkB;;;;;;KASP,yBAAyB,uBAAuB,gBAC3D;EACC,UAAU,kBAAkB,WAAW;;UAGxB,iBAChB,kBAAkB,YAClB,uBAAuB;;;;;;;;EASvB,UAAU;;;;;;;;EAQV;;;;;;;EAOA;;;;;;;;;;;;EAYA,YAAY;;;;;;;;EAQZ,UACG,sBAEA,SAAS,yBAAyB,WAC7B,oBAAoB,QAAQ;;cAKxB,WACZ,+BACA,kBAAkB,aAAa,kBACzB,uBAAuB,uCACjB,UAAU,OAAO,aAAa,cAAc;mBAoBtC;WAlBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;WACtC;;;;;mBAMQ;EAQjB,YACC,IAAI,cAAc,QACD,QAAQ,iBAAiB,WAAW;;;;;;;;;;;;;;;EAuCtD,WAAW;;;;;;;;;EA6CL,OACL,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;MAuBP;;;;;;;;;;;;;EAgBE,iBACL,SAAS,yBACP;;;;;;;;;;;;;;;;;;;;EAiDG,OAAO,SAAS,yBAAyB;IAAU,KAAK;;;;;;;EAoDxD,UAAU,SAAS,yBAAyB;;UAMpC;UAuBA;;;;;;;UAkBA;;;iBA8IC,WAAW,aAAa;;cAY3B,2BAA2B;WAE7B;;WAEA;EAHV,YACU,gBAEA;;EAAA;;;cAYE,2BAA2B;WAE7B;WACA;EAFV,YACU,uBACA;;;;;;;;;;;iBAoBK,WAAW"}
1
+ {"version":3,"file":"auth-6Cdx5amw.d.cts","names":[],"sources":["../src/auth.ts"],"mappings":";;;;;;;;KAsDY,aAAa,kBAAkB;;KAG/B,cAAc;;KAGd,oBAAoB,KAC/B,kBAAkB;;;;;;KASP,yBAAyB,uBAAuB,gBAC3D;EACC,UAAU,kBAAkB,WAAW;;UAGxB,iBAChB,kBAAkB,YAClB,uBAAuB;;;;;;;;EASvB,UAAU;;;;;;;;EAQV;;;;;;;EAOA;;;;;;;;;;;;EAYA,YAAY;;;;;;;;EAQZ,UACG,sBAEA,SAAS,yBAAyB,WAC7B,oBAAoB,QAAQ;;cAKxB,WACZ,+BACA,kBAAkB,aAAa,kBACzB,uBAAuB,uCACjB,UAAU,OAAO,aAAa,cAAc;mBAoBtC;WAlBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;WACtC;;;;;mBAMQ;EAQjB,YACC,IAAI,cAAc,QACD,QAAQ,iBAAiB,WAAW;;;;;;;;;;;;;;;EAuCtD,WAAW;;;;;;;;;EA6CL,OACL,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;MAuBP;;;;;;;;;;;;;EAgBE,iBACL,SAAS,yBACP;;;;;;;;;;;;;;;;;;;;EAiDG,OAAO,SAAS,yBAAyB;IAAU,KAAK;;;;;;;EAoDxD,UAAU,SAAS,yBAAyB;;UAMpC;UAuBA;;;;;;;UAkBA;;;iBA8IC,WAAW,aAAa;;cAY3B,2BAA2B;WAE7B;;WAEA;EAHV,YACU,gBAEA;;EAAA;;;cAYE,2BAA2B;WAE7B;WACA;EAFV,YACU,uBACA;;;;;;;;;;;iBAoBK,WAAW"}
@@ -1,4 +1,4 @@
1
- import { c as ServicesOf, n as Construct, r as Consumable, t as Authenticator } from "./construct-interface-D2gesxos.mjs";
1
+ import { c as ServicesOf, n as Construct, r as Consumable, t as Authenticator } from "./construct-interface-BLG_BEH5.mjs";
2
2
  import { ConstructName, Declaration } from "@geekmidas/manifest";
3
3
  import { betterAuth } from "better-auth";
4
4
  import { EnvironmentParser } from "@geekmidas/envkit";
@@ -185,4 +185,4 @@ declare class SessionCheckFailed extends Error {
185
185
  declare function idempotent(statements: string): string;
186
186
  //#endregion
187
187
  export { BetterAuthOptions as a, SessionCheckFailed as c, BetterAuthConfig as i, deviceLink as l, AuthSession as n, BetterAuthOptionsContext as o, BetterAuth as r, ExpoPluginRequired as s, AuthServer as t, idempotent as u };
188
- //# sourceMappingURL=auth-pS_U1H_X.d.mts.map
188
+ //# sourceMappingURL=auth-B_c9b54v.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-pS_U1H_X.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;;;;;;KAsDY,aAAa,kBAAkB;;KAG/B,cAAc;;KAGd,oBAAoB,KAC/B,kBAAkB;;;;;;KASP,yBAAyB,uBAAuB,gBAC3D;EACC,UAAU,kBAAkB,WAAW;;UAGxB,iBAChB,kBAAkB,YAClB,uBAAuB;;;;;;;;EASvB,UAAU;;;;;;;;EAQV;;;;;;;EAOA;;;;;;;;;;;;EAYA,YAAY;;;;;;;;EAQZ,UACG,sBAEA,SAAS,yBAAyB,WAC7B,oBAAoB,QAAQ;;cAKxB,WACZ,+BACA,kBAAkB,aAAa,kBACzB,uBAAuB,uCACjB,UAAU,OAAO,aAAa,cAAc;mBAoBtC;WAlBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;WACtC;;;;;mBAMQ;EAQjB,YACC,IAAI,cAAc,QACD,QAAQ,iBAAiB,WAAW;;;;;;;;;;;;;;;EAuCtD,WAAW;;;;;;;;;EA6CL,OACL,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;MAuBP;;;;;;;;;;;;;EAgBE,iBACL,SAAS,yBACP;;;;;;;;;;;;;;;;;;;;EAiDG,OAAO,SAAS,yBAAyB;IAAU,KAAK;;;;;;;EAoDxD,UAAU,SAAS,yBAAyB;;UAMpC;UAuBA;;;;;;;UAkBA;;;iBA8IC,WAAW,aAAa;;cAY3B,2BAA2B;WAE7B;;WAEA;EAHV,YACU,gBAEA;;EAAA;;;cAYE,2BAA2B;WAE7B;WACA;EAFV,YACU,uBACA;;;;;;;;;;;iBAoBK,WAAW"}
1
+ {"version":3,"file":"auth-B_c9b54v.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;;;;;;KAsDY,aAAa,kBAAkB;;KAG/B,cAAc;;KAGd,oBAAoB,KAC/B,kBAAkB;;;;;;KASP,yBAAyB,uBAAuB,gBAC3D;EACC,UAAU,kBAAkB,WAAW;;UAGxB,iBAChB,kBAAkB,YAClB,uBAAuB;;;;;;;;EASvB,UAAU;;;;;;;;EAQV;;;;;;;EAOA;;;;;;;;;;;;EAYA,YAAY;;;;;;;;EAQZ,UACG,sBAEA,SAAS,yBAAyB,WAC7B,oBAAoB,QAAQ;;cAKxB,WACZ,+BACA,kBAAkB,aAAa,kBACzB,uBAAuB,uCACjB,UAAU,OAAO,aAAa,cAAc;mBAoBtC;WAlBT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;WACtC;;;;;mBAMQ;EAQjB,YACC,IAAI,cAAc,QACD,QAAQ,iBAAiB,WAAW;;;;;;;;;;;;;;;EAuCtD,WAAW;;;;;;;;;EA6CL,OACL,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;MAuBP;;;;;;;;;;;;;EAgBE,iBACL,SAAS,yBACP;;;;;;;;;;;;;;;;;;;;EAiDG,OAAO,SAAS,yBAAyB;IAAU,KAAK;;;;;;;EAoDxD,UAAU,SAAS,yBAAyB;;UAMpC;UAuBA;;;;;;;UAkBA;;;iBA8IC,WAAW,aAAa;;cAY3B,2BAA2B;WAE7B;;WAEA;EAHV,YACU,gBAEA;;EAAA;;;cAYE,2BAA2B;WAE7B;WACA;EAFV,YACU,uBACA;;;;;;;;;;;iBAoBK,WAAW"}
package/dist/auth.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as BetterAuthOptions, c as SessionCheckFailed, i as BetterAuthConfig, l as deviceLink, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as ExpoPluginRequired, t as AuthServer, u as idempotent } from "./auth-c6nWnvD8.cjs";
1
+ import { a as BetterAuthOptions, c as SessionCheckFailed, i as BetterAuthConfig, l as deviceLink, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as ExpoPluginRequired, t as AuthServer, u as idempotent } from "./auth-6Cdx5amw.cjs";
2
2
  export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, BetterAuthOptionsContext, ExpoPluginRequired, SessionCheckFailed, deviceLink, idempotent };
package/dist/auth.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as BetterAuthOptions, c as SessionCheckFailed, i as BetterAuthConfig, l as deviceLink, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as ExpoPluginRequired, t as AuthServer, u as idempotent } from "./auth-pS_U1H_X.mjs";
1
+ import { a as BetterAuthOptions, c as SessionCheckFailed, i as BetterAuthConfig, l as deviceLink, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as ExpoPluginRequired, t as AuthServer, u as idempotent } from "./auth-B_c9b54v.mjs";
2
2
  export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, BetterAuthOptionsContext, ExpoPluginRequired, SessionCheckFailed, deviceLink, idempotent };
@@ -1,7 +1,7 @@
1
- import { n as Construct } from "./construct-interface-D2gesxos.mjs";
1
+ import { n as Construct } from "./construct-interface-DcNaRKoC.cjs";
2
+ import { Service } from "@geekmidas/services";
2
3
  import { ConstructName, Declaration } from "@geekmidas/manifest";
3
4
  import { Cache } from "@geekmidas/cache";
4
- import { Service } from "@geekmidas/services";
5
5
  //#region src/cache.d.ts
6
6
  interface CacheOptions {
7
7
  /**
@@ -41,4 +41,4 @@ declare class Cache$1<TName extends string = string> implements Construct<TName,
41
41
  }
42
42
  //#endregion
43
43
  export { CacheOptions as n, Cache$1 as t };
44
- //# sourceMappingURL=cache-B2s0b-ss.d.mts.map
44
+ //# sourceMappingURL=cache-CSJHbgwS.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache-B2s0b-ss.d.mts","names":["Cache","CacheClient"],"sources":["../src/cache.ts"],"mappings":";;;;;UA6BiB;;;;;;;;EAQhB;;EAEA;;cAGYA,QAAM,0CACP,UAAU,OAAOC;mBAaV;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA;;;;;mBAM9B;EAEjB,YACC,IAAI,cAAc,QACD,UAAS;EAc3B,WAAW;;;;;;;;;;;;UAuBG"}
1
+ {"version":3,"file":"cache-CSJHbgwS.d.cts","names":["Cache","CacheClient"],"sources":["../src/cache.ts"],"mappings":";;;;;UA6BiB;;;;;;;;EAQhB;;EAEA;;cAGYA,QAAM,0CACP,UAAU,OAAOC;mBAaV;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA;;;;;mBAM9B;EAEjB,YACC,IAAI,cAAc,QACD,UAAS;EAc3B,WAAW;;;;;;;;;;;;UAuBG"}
@@ -1,7 +1,7 @@
1
- import { n as Construct } from "./construct-interface-DPusCUSQ.cjs";
2
- import { Service } from "@geekmidas/services";
1
+ import { n as Construct } from "./construct-interface-BLG_BEH5.mjs";
3
2
  import { ConstructName, Declaration } from "@geekmidas/manifest";
4
3
  import { Cache } from "@geekmidas/cache";
4
+ import { Service } from "@geekmidas/services";
5
5
  //#region src/cache.d.ts
6
6
  interface CacheOptions {
7
7
  /**
@@ -41,4 +41,4 @@ declare class Cache$1<TName extends string = string> implements Construct<TName,
41
41
  }
42
42
  //#endregion
43
43
  export { CacheOptions as n, Cache$1 as t };
44
- //# sourceMappingURL=cache-CYe2Kzjc.d.cts.map
44
+ //# sourceMappingURL=cache-HHg5No4L.d.mts.map