@cosmicdrift/kumiko-framework 0.367.0 → 0.369.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/AGENTS.md +1 -7
  2. package/README.md +1 -1
  3. package/agents/examples/access-control.md +2 -2
  4. package/agents/examples/anonymous-access-multitenant.md +2 -2
  5. package/agents/examples/anonymous-access.md +3 -3
  6. package/agents/examples/apex-landing.md +1 -1
  7. package/agents/examples/apex-surface-auth.md +6 -7
  8. package/agents/examples/api-worker-split.md +7 -8
  9. package/agents/examples/auth-claims.md +2 -1
  10. package/agents/examples/auth-foundation-providers.md +3 -3
  11. package/agents/examples/auth-mfa.md +3 -4
  12. package/agents/examples/basic-entity.md +3 -4
  13. package/agents/examples/chat-channels.md +4 -4
  14. package/agents/examples/compliance-profiles-demo.md +2 -3
  15. package/agents/examples/cross-feature-events.md +4 -4
  16. package/agents/examples/crypto-shredding-hr.md +14 -9
  17. package/agents/examples/currencies-global.md +3 -4
  18. package/agents/examples/currencies-per-tenant.md +3 -4
  19. package/agents/examples/custom-fields-basic.md +2 -3
  20. package/agents/examples/custom-handlers.md +3 -5
  21. package/agents/examples/dashboard-stats.md +2 -3
  22. package/agents/examples/delivery-notifications.md +5 -6
  23. package/agents/examples/e2e-generator.md +1 -1
  24. package/agents/examples/embedded-entity-form.md +2 -3
  25. package/agents/examples/embedded.md +2 -3
  26. package/agents/examples/encrypted-tenant-config.md +5 -6
  27. package/agents/examples/error-contract.md +3 -4
  28. package/agents/examples/event-sourcing.md +19 -23
  29. package/agents/examples/feature-toggles.md +4 -4
  30. package/agents/examples/field-access.md +3 -4
  31. package/agents/examples/files-post-processing.md +12 -13
  32. package/agents/examples/golden-path.md +208 -0
  33. package/agents/examples/idempotency.md +2 -2
  34. package/agents/examples/inbound-mail-triage.md +7 -7
  35. package/agents/examples/lane-routing.md +3 -3
  36. package/agents/examples/ledger-banking.md +2 -3
  37. package/agents/examples/ledger-invoicing.md +2 -3
  38. package/agents/examples/legal-pages.md +1 -1
  39. package/agents/examples/lifecycle-hooks.md +3 -3
  40. package/agents/examples/managed-config.md +3 -4
  41. package/agents/examples/managed-pages.md +2 -2
  42. package/agents/examples/notes-history-basic.md +4 -5
  43. package/agents/examples/ownership.md +3 -5
  44. package/agents/examples/pdf-audit-export.md +2 -3
  45. package/agents/examples/pipeline-basics.md +2 -2
  46. package/agents/examples/public-share-token.md +3 -3
  47. package/agents/examples/rate-limiting.md +2 -1
  48. package/agents/examples/realtime-sse.md +2 -3
  49. package/agents/examples/record-detail-layout.md +1 -1
  50. package/agents/examples/reference-data.md +3 -3
  51. package/agents/examples/relations.md +2 -2
  52. package/agents/examples/screens-nav.md +1 -1
  53. package/agents/examples/search.md +2 -2
  54. package/agents/examples/secrets-demo.md +8 -8
  55. package/agents/examples/security-baseline.md +2 -2
  56. package/agents/examples/session-revocation.md +7 -8
  57. package/agents/examples/state-machine.md +181 -165
  58. package/agents/examples/tags-basic.md +2 -3
  59. package/agents/examples/tenant-isolation.md +2 -3
  60. package/agents/examples/tenant-settings.md +2 -2
  61. package/agents/examples/tier-admin.md +3 -4
  62. package/agents/examples/timezones.md +3 -3
  63. package/agents/examples/user-data-rights.md +12 -8
  64. package/agents/examples/user-profile.md +3 -3
  65. package/agents/examples/webhook-step.md +2 -3
  66. package/agents/examples/wizard-form.md +4 -5
  67. package/agents/examples/workflow-engine.md +7 -3
  68. package/agents/examples/writeform-section.md +1 -1
  69. package/agents/examples.md +1 -0
  70. package/agents/features.md +4 -4
  71. package/dist/consumer-cli.js +2 -3
  72. package/dist/crypto/index.d.ts +1 -1
  73. package/dist/crypto/index.js +1 -1
  74. package/dist/crypto/owner-transition-rekey.js +2 -0
  75. package/dist/crypto/pg-kms-adapter.js +1 -1
  76. package/dist/crypto/subject-resolver.d.ts +2 -0
  77. package/dist/crypto/subject-resolver.js +30 -15
  78. package/dist/db/bun-db-deprecated.d.ts +10 -0
  79. package/dist/{bun-db/index.js → db/bun-db-deprecated.js} +4 -3
  80. package/dist/db/config-seed.js +1 -1
  81. package/dist/db/connection.d.ts +23 -5
  82. package/dist/db/connection.js +10 -5
  83. package/dist/db/dialect.js +1 -1
  84. package/dist/db/entity-field-encryption.d.ts +5 -0
  85. package/dist/db/entity-field-encryption.js +29 -0
  86. package/dist/db/entity-table-meta.js +6 -5
  87. package/dist/db/event-store-executor-read.js +26 -5
  88. package/dist/db/index.d.ts +4 -4
  89. package/dist/db/index.js +1 -1
  90. package/dist/db/migrate-runner.js +1 -1
  91. package/dist/db/queries/backfill-pii.js +16 -9
  92. package/dist/db/queries/ddl.js +1 -1
  93. package/dist/db/queries/event-store.d.ts +2 -0
  94. package/dist/db/queries/event-store.js +26 -0
  95. package/dist/db/queries/test-stack.js +1 -1
  96. package/dist/db/query.d.ts +105 -1
  97. package/dist/db/query.js +1450 -3
  98. package/dist/{stack/table-helpers.d.ts → db/table-push.d.ts} +5 -15
  99. package/dist/{stack/table-helpers.js → db/table-push.js} +6 -46
  100. package/dist/db/tenant-db.d.ts +11 -0
  101. package/dist/db/tenant-db.js +51 -12
  102. package/dist/derivatives/derivatives-context.d.ts +1 -1
  103. package/dist/derivatives/derivatives-context.js +1 -1
  104. package/dist/engine/boot-validator/gdpr-storage.js +3 -3
  105. package/dist/engine/boot-validator/pii-retention.js +3 -3
  106. package/dist/engine/boot-validator/record-owned.js +3 -3
  107. package/dist/engine/boot-validator/screens.js +37 -1
  108. package/dist/engine/bridge-stub.d.ts +4 -0
  109. package/dist/engine/bridge-stub.js +62 -0
  110. package/dist/engine/build-config-feature-schema.js +197 -65
  111. package/dist/engine/event-helpers.d.ts +2 -1
  112. package/dist/engine/event-helpers.js +1 -0
  113. package/dist/engine/extension-selector-plugins.d.ts +3 -0
  114. package/dist/engine/extension-selector-plugins.js +14 -0
  115. package/dist/engine/feature-config-events-jobs.d.ts +2 -0
  116. package/dist/engine/feature-config-events-jobs.js +3 -2
  117. package/dist/engine/index.d.ts +1 -0
  118. package/dist/engine/index.js +1 -0
  119. package/dist/es-ops/operations-schema.js +1 -1
  120. package/dist/event-store/admin-api.js +1 -1
  121. package/dist/event-store/archive.js +1 -1
  122. package/dist/event-store/errors.d.ts +2 -1
  123. package/dist/event-store/errors.js +6 -2
  124. package/dist/event-store/event-store.js +17 -9
  125. package/dist/event-store/events-schema.js +1 -1
  126. package/dist/event-store/rebuild-dead-letter.js +3 -3
  127. package/dist/event-store/snapshot.js +1 -1
  128. package/dist/event-store/upcaster-dead-letter.js +3 -3
  129. package/dist/files/create-file-ref-from-bytes.js +1 -1
  130. package/dist/files/file-routes.js +1 -1
  131. package/dist/i18n/settings-hub-keys.js +1 -2
  132. package/dist/internal/stack-support.d.ts +12 -0
  133. package/dist/internal/stack-support.js +12 -0
  134. package/dist/internal/testing.d.ts +4 -0
  135. package/dist/internal/testing.js +5 -0
  136. package/dist/jobs/job-runner.js +23 -11
  137. package/dist/migrations/pending-rebuilds.js +1 -1
  138. package/dist/migrations/pii-event-backfill.js +2 -2
  139. package/dist/pipeline/dispatch-shared.js +43 -23
  140. package/dist/pipeline/dispatch-write.js +6 -2
  141. package/dist/pipeline/dispatcher-utils.js +6 -2
  142. package/dist/pipeline/event-consumer-state.js +1 -1
  143. package/dist/pipeline/projection-rebuild.d.ts +1 -0
  144. package/dist/pipeline/projection-rebuild.js +1 -1
  145. package/dist/pipeline/projection-state.js +1 -1
  146. package/dist/pipeline/system-hooks.js +1 -1
  147. package/dist/schema-cli.js +3 -3
  148. package/dist/search/purge-subject.js +20 -10
  149. package/dist/seeding/index.d.ts +1 -0
  150. package/dist/seeding/index.js +1 -0
  151. package/dist/stack/index.d.ts +10 -9
  152. package/dist/stack/index.js +11 -19
  153. package/dist/testing/e2e-generator.d.ts +5 -49
  154. package/dist/testing/e2e-generator.js +5 -382
  155. package/dist/testing/handler-context.d.ts +5 -4
  156. package/dist/testing/handler-context.js +5 -69
  157. package/dist/testing/index.d.ts +10 -26
  158. package/dist/testing/index.js +10 -34
  159. package/dist/testing/real-providers.d.ts +5 -3
  160. package/dist/testing/real-providers.js +5 -21
  161. package/dist/upgrade-cli.js +6 -4
  162. package/package.json +37 -9
  163. package/src/changes.json +64 -0
  164. package/src/scripts/codemod/README.md +43 -0
  165. package/src/scripts/codemod/codemod-support.ts +276 -0
  166. package/src/scripts/codemod/db-factory.ts +810 -0
  167. package/src/scripts/codemod/feature-factory.ts +429 -0
  168. package/src/scripts/codemod/s3-imports.ts +274 -0
  169. package/src/scripts/codemod/tenant-db-options.ts +454 -0
  170. package/src/scripts/codemod/testing-move-targets.ts +161 -0
  171. package/src/scripts/codemod/testing-move.ts +520 -0
  172. package/dist/bun-db/connection.d.ts +0 -17
  173. package/dist/bun-db/connection.js +0 -40
  174. package/dist/bun-db/index.d.ts +0 -4
  175. package/dist/bun-db/query.d.ts +0 -106
  176. package/dist/bun-db/query.js +0 -1457
  177. package/dist/db/api.d.ts +0 -19
  178. package/dist/db/api.js +0 -23
  179. package/dist/db/bun-provider.d.ts +0 -2
  180. package/dist/db/bun-provider.js +0 -28
  181. package/dist/db/postgres-provider.d.ts +0 -2
  182. package/dist/db/postgres-provider.js +0 -21
  183. package/dist/db/query-api.d.ts +0 -2
  184. package/dist/db/query-api.js +0 -6
  185. package/dist/stack/db.d.ts +0 -22
  186. package/dist/stack/db.js +0 -67
  187. package/dist/stack/drain-event-consumers.d.ts +0 -4
  188. package/dist/stack/drain-event-consumers.js +0 -51
  189. package/dist/stack/drain-jobs.d.ts +0 -22
  190. package/dist/stack/drain-jobs.js +0 -141
  191. package/dist/stack/event-collector.d.ts +0 -9
  192. package/dist/stack/event-collector.js +0 -12
  193. package/dist/stack/push-entity-projection-tables.d.ts +0 -24
  194. package/dist/stack/push-entity-projection-tables.js +0 -74
  195. package/dist/stack/redis.d.ts +0 -17
  196. package/dist/stack/redis.js +0 -52
  197. package/dist/stack/request-helper.d.ts +0 -35
  198. package/dist/stack/request-helper.js +0 -144
  199. package/dist/stack/test-stack.d.ts +0 -192
  200. package/dist/stack/test-stack.js +0 -429
  201. package/dist/testing/access-assertions.d.ts +0 -2
  202. package/dist/testing/access-assertions.js +0 -13
  203. package/dist/testing/assertions.d.ts +0 -10
  204. package/dist/testing/assertions.js +0 -22
  205. package/dist/testing/boot-validator-fixture.d.ts +0 -3
  206. package/dist/testing/boot-validator-fixture.js +0 -101
  207. package/dist/testing/closed-connection-error.d.ts +0 -7
  208. package/dist/testing/closed-connection-error.js +0 -46
  209. package/dist/testing/db-cleanup.d.ts +0 -9
  210. package/dist/testing/db-cleanup.js +0 -31
  211. package/dist/testing/expect-error.d.ts +0 -2
  212. package/dist/testing/expect-error.js +0 -22
  213. package/dist/testing/file-provider-contract.d.ts +0 -2
  214. package/dist/testing/file-provider-contract.js +0 -102
  215. package/dist/testing/http-cookies.d.ts +0 -7
  216. package/dist/testing/http-cookies.js +0 -47
  217. package/dist/testing/late-bound.d.ts +0 -9
  218. package/dist/testing/late-bound.js +0 -31
  219. package/dist/testing/manual-clock.d.ts +0 -14
  220. package/dist/testing/manual-clock.js +0 -12
  221. package/dist/testing/multipart-helper.d.ts +0 -22
  222. package/dist/testing/multipart-helper.js +0 -77
  223. package/dist/testing/mutable-master-key-provider.d.ts +0 -7
  224. package/dist/testing/mutable-master-key-provider.js +0 -35
  225. package/dist/testing/observability-recorder.d.ts +0 -9
  226. package/dist/testing/observability-recorder.js +0 -29
  227. package/dist/testing/seed.d.ts +0 -9
  228. package/dist/testing/seed.js +0 -21
  229. package/dist/testing/shared-entities.d.ts +0 -286
  230. package/dist/testing/shared-entities.js +0 -56
  231. package/dist/testing/utils.d.ts +0 -1
  232. package/dist/testing/utils.js +0 -1
  233. package/dist/testing/wait-for.d.ts +0 -20
  234. package/dist/testing/wait-for.js +0 -47
  235. package/dist/testing/without-ambient-temporal.d.ts +0 -5
  236. package/dist/testing/without-ambient-temporal.js +0 -17
  237. package/dist/testing/zip-fixture.d.ts +0 -1
  238. package/dist/testing/zip-fixture.js +0 -33
  239. /package/dist/{stack/test-users.d.ts → seeding/seed-users.d.ts} +0 -0
  240. /package/dist/{stack/test-users.js → seeding/seed-users.js} +0 -0
@@ -1,35 +0,0 @@
1
- import type { Hono } from "hono";
2
- import type { SessionCreator } from "../api/auth-routes.js";
3
- import type { JwtHelper } from "../api/jwt.js";
4
- import type { SessionUser } from "../engine/types/index.js";
5
- export type BatchCommand = {
6
- type: string;
7
- payload: unknown;
8
- };
9
- export type RequestHelper = {
10
- write: (type: string, payload: unknown, user: SessionUser, requestId?: string) => Promise<Response>;
11
- query: (type: string, payload: unknown, user: SessionUser) => Promise<Response>;
12
- command: (type: string, payload: unknown, user: SessionUser) => Promise<Response>;
13
- batch: (commands: readonly BatchCommand[], user: SessionUser, requestId?: string) => Promise<Response>;
14
- raw: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<Response>;
15
- /** write + json + assert isSuccess — returns data directly */
16
- writeOk: <T = Record<string, unknown>>(type: string, payload: unknown, user: SessionUser, requestId?: string) => Promise<T>;
17
- /** write + json + assert isSuccess === false — returns the structured
18
- * WriteErrorInfo with `httpStatus` filled in from the HTTP response. */
19
- writeErr: (type: string, payload: unknown, user: SessionUser) => Promise<import("../errors/index.js").WriteErrorInfo>;
20
- /** query + json — returns data directly */
21
- queryOk: <T = unknown>(type: string, payload: unknown, user: SessionUser) => Promise<T>;
22
- /** query + json + assert the response is an error — returns the structured
23
- * WriteErrorInfo with `httpStatus` filled in from the HTTP response. */
24
- queryErr: (type: string, payload: unknown, user: SessionUser) => Promise<import("../errors/index.js").WriteErrorInfo>;
25
- /** write + additional HTTP headers (e.g. X-Correlation-ID). Returns the
26
- * raw Response so callers can assert on status + headers + body as needed. */
27
- writeWithHeaders: (type: string, payload: unknown, user: SessionUser, extraHeaders: Record<string, string>) => Promise<Response>;
28
- /** query + additional HTTP headers (e.g. X-Forwarded-For). Returns the raw
29
- * Response so callers can assert on status + headers + body as needed. */
30
- queryWithHeaders: (type: string, payload: unknown, user: SessionUser, extraHeaders: Record<string, string>) => Promise<Response>;
31
- };
32
- export type RequestHelperOptions = {
33
- readonly sessionCreator?: SessionCreator;
34
- };
35
- export declare function createRequestHelper(app: Hono, jwt: JwtHelper, options?: RequestHelperOptions): RequestHelper;
@@ -1,144 +0,0 @@
1
- function formatFailure(kind, type, body) {
2
- const parsed = body;
3
- const code = (typeof parsed.error === "object" ? parsed.error?.code : undefined) ??
4
- (typeof parsed.error === "string" ? parsed.error : "unknown");
5
- const details = typeof parsed.error === "object" && parsed.error?.details !== undefined
6
- ? parsed.error.details
7
- : undefined;
8
- const causeMessage = details && typeof details === "object" && "causeMessage" in details
9
- ? String(details.causeMessage ?? "")
10
- : "";
11
- const causeName = details && typeof details === "object" && "causeName" in details
12
- ? String(details.causeName ?? "")
13
- : "";
14
- if (code === "internal_error" && (causeMessage || causeName)) {
15
- return `Expected ${kind} "${type}" to succeed but got error: ${code} (${causeName}: ${causeMessage})`;
16
- }
17
- if (details !== undefined) {
18
- return `Expected ${kind} "${type}" to succeed but got error: ${code} — ${JSON.stringify(details)}`;
19
- }
20
- return `Expected ${kind} "${type}" to succeed but got error: ${code}`;
21
- }
22
- export function createRequestHelper(app, jwt, options = {}) {
23
- // sid per (user.id, tenantId), not one mint per authHeader() call:
24
- // sessionCreator opens a live session row, so an unmemoized call mints a
25
- // fresh one on every request a test makes for the same user — tests
26
- // asserting on session counts / massRevoker / revokeAllOthers behavior
27
- // then see extra live sids that have nothing to do with what they're
28
- // testing. Keyed on tenantId too — the same user.id can hold sessions in
29
- // more than one tenant, and sessionCreator writes the row scoped to
30
- // `user.tenantId` (session-callbacks.ts), so a user.id-only key would
31
- // hand a cross-tenant test its wrong tenant's sid.
32
- // Cache the Promise (not the settled sid) so two concurrent authHeader
33
- // calls for the same key share one mint instead of racing on a miss.
34
- const sidByUserKey = new Map();
35
- async function authHeader(user) {
36
- let forJwt = user;
37
- if (options.sessionCreator && !user.sid) {
38
- const key = `${user.id}:${user.tenantId}`;
39
- let sidPromise = sidByUserKey.get(key);
40
- if (sidPromise === undefined) {
41
- sidPromise = options.sessionCreator(user, { ip: "test", userAgent: "request-helper" });
42
- sidByUserKey.set(key, sidPromise);
43
- }
44
- const sid = await sidPromise;
45
- forJwt = { ...user, sid };
46
- }
47
- const token = await jwt.sign(forJwt);
48
- return { Authorization: `Bearer ${token}` };
49
- }
50
- async function req(method, path, body, headers) {
51
- const init = {
52
- method,
53
- headers: { "Content-Type": "application/json", ...headers },
54
- };
55
- if (body)
56
- init.body = JSON.stringify(body);
57
- return app.request(path, init);
58
- }
59
- async function writeRaw(type, payload, user, requestId) {
60
- const headers = await authHeader(user);
61
- return req("POST", "/api/write", { type, payload, requestId }, headers);
62
- }
63
- async function queryRaw(type, payload, user) {
64
- const headers = await authHeader(user);
65
- return req("POST", "/api/query", { type, payload }, headers);
66
- }
67
- return {
68
- write: writeRaw,
69
- query: queryRaw,
70
- async command(type, payload, user) {
71
- const headers = await authHeader(user);
72
- return req("POST", "/api/command", { type, payload }, headers);
73
- },
74
- async batch(commands, user, requestId) {
75
- const headers = await authHeader(user);
76
- return req("POST", "/api/batch", { commands, requestId }, headers);
77
- },
78
- raw: req,
79
- async writeOk(type, payload, user, requestId) {
80
- const res = await writeRaw(type, payload, user, requestId);
81
- // wire-body shape direkt nach JSON.parse — Caller-Code prüft danach
82
- // selber ob isSuccess/error/data tatsächlich da sind.
83
- const rawBody = await res.json();
84
- const body = rawBody;
85
- // Success path still has { isSuccess: true, data }. Error responses now
86
- // follow the error-contract shape { error: { code, i18nKey, ... } } with
87
- // a 4xx/5xx status — no isSuccess flag. Detect either.
88
- if (body.isSuccess !== true) {
89
- throw new Error(formatFailure("write", type, body));
90
- }
91
- return body.data; // @cast-boundary engine-bridge
92
- },
93
- async writeErr(type, payload, user) {
94
- const res = await writeRaw(type, payload, user);
95
- const rawErrorBody = await res.json();
96
- const body = rawErrorBody;
97
- if (body.isSuccess === true) {
98
- throw new Error(`Expected write "${type}" to fail but it succeeded`);
99
- }
100
- const wire = body.error;
101
- if (!wire || typeof wire !== "object" || typeof wire.code !== "string") {
102
- throw new Error(`Expected error response for "${type}" but got unexpected shape: ${JSON.stringify(body)}`);
103
- }
104
- // The wire body doesn't carry httpStatus (it would be redundant with
105
- // the HTTP response status). Fill it in from res.status so callers can
106
- // assert against either code OR status without a second request round.
107
- return { ...wire, httpStatus: res.status };
108
- },
109
- async queryOk(type, payload, user) {
110
- const res = await queryRaw(type, payload, user);
111
- const rawBody = await res.json();
112
- const body = rawBody;
113
- // res.ok mirrors writeOk's isSuccess assertion — belt-and-suspenders
114
- // in case a future non-dispatcher rejection skips the error-contract shape.
115
- if (!res.ok || body.error !== undefined) {
116
- throw new Error(`${formatFailure("query", type, body)} [HTTP ${res.status}]`);
117
- }
118
- return body.data; // @cast-boundary engine-bridge
119
- },
120
- async queryErr(type, payload, user) {
121
- const res = await queryRaw(type, payload, user);
122
- const rawErrorBody = await res.json();
123
- const body = rawErrorBody;
124
- if (res.ok) {
125
- throw new Error(`Expected query "${type}" to fail but it succeeded`);
126
- }
127
- const wire = body.error;
128
- if (!wire || typeof wire !== "object" || typeof wire.code !== "string") {
129
- throw new Error(`Expected error response for "${type}" but got unexpected shape: ${JSON.stringify(body)}`);
130
- }
131
- // Same rationale as writeErr: the wire body has no httpStatus (it would
132
- // be redundant with the HTTP response status), so fill it in here.
133
- return { ...wire, httpStatus: res.status };
134
- },
135
- async writeWithHeaders(type, payload, user, extraHeaders) {
136
- const authHeaders = await authHeader(user);
137
- return req("POST", "/api/write", { type, payload }, { ...authHeaders, ...extraHeaders });
138
- },
139
- async queryWithHeaders(type, payload, user, extraHeaders) {
140
- const authHeaders = await authHeader(user);
141
- return req("POST", "/api/query", { type, payload }, { ...authHeaders, ...extraHeaders });
142
- },
143
- };
144
- }
@@ -1,192 +0,0 @@
1
- import type { Hono } from "hono";
2
- import type { AuthRoutesConfig } from "../api/auth-routes.js";
3
- import type { JwtHelper } from "../api/jwt.js";
4
- import { type SseBroker } from "../api/sse-broker.js";
5
- import type { AppContext, FeatureDefinition, JobRunIn, Registry, TenantId } from "../engine/types/index.js";
6
- import type { ErrorDocsConfig } from "../errors/docs-url.js";
7
- import { type JobRunner, type JobRunnerOptions } from "../jobs/index.js";
8
- import type { Lifecycle } from "../lifecycle/index.js";
9
- import { type ObservabilityProvider } from "../observability/index.js";
10
- import type { Dispatcher, EventDispatcher, SystemHooks } from "../pipeline/index.js";
11
- import { type ClosableCacheSyncBus } from "../redis/cache-sync-bus.js";
12
- import type { SearchAdapter } from "../search/types.js";
13
- import { type EventCollector } from "./event-collector.js";
14
- import { type TestRedis } from "./redis.js";
15
- import { type RequestHelper } from "./request-helper.js";
16
- export type TestStack = {
17
- app: Hono;
18
- jwt: JwtHelper;
19
- registry: Registry;
20
- db: import("../db/index.js").DbConnection;
21
- redis: TestRedis;
22
- cacheSync: ClosableCacheSyncBus;
23
- search: SearchAdapter;
24
- events: EventCollector;
25
- http: RequestHelper;
26
- observability: ObservabilityProvider;
27
- sseBroker: SseBroker;
28
- dispatcher: Dispatcher;
29
- context: AppContext;
30
- eventDispatcher?: EventDispatcher;
31
- lifecycle?: Lifecycle;
32
- jobRunner?: JobRunner;
33
- drainJobs: () => Promise<void>;
34
- cleanup: () => Promise<void>;
35
- };
36
- export type TestStackOptions = {
37
- features: readonly FeatureDefinition[];
38
- /** System hooks to wire up. Default: all (sse, search) */
39
- systemHooks?: ("sse" | "search")[];
40
- /** Pipeline system hooks handed to buildServer, e.g. the cascade-delete preDelete hook. None by default. */
41
- lifecycleSystemHooks?: SystemHooks;
42
- /** Search config per tenant — defaults to tenant 1 with all text fields */
43
- searchConfig?: {
44
- tenantId: TenantId;
45
- searchableFields: string[];
46
- rankingFields: string[];
47
- };
48
- jwtSecret?: string;
49
- /** Extra fields merged into the AppContext (e.g. _notifyFactory, configResolver).
50
- * Can be a function receiving (registry, db, sseBroker) for late binding. */
51
- /** Boot default locale (BCP-47) merged into AppContext before extraContext. */
52
- defaultLocale?: string;
53
- extraContext?: Record<string, unknown> | ((deps: {
54
- registry: Registry;
55
- db: import("../db/index.js").DbConnection;
56
- sseBroker: import("../api/sse-broker.js").SseBroker;
57
- redis: import("ioredis").Redis;
58
- }) => Record<string, unknown>);
59
- /** Wire up auth routes (login, tenant-switch). Leave undefined to skip. */
60
- authConfig?: AuthRoutesConfig;
61
- /** Register a file storage provider so uploads via POST /api/files work and
62
- * `ctx.files.ref(key)` is available to hooks/MSPs. Omit to skip — tests
63
- * without file handling don't need it. */
64
- files?: {
65
- storageProvider: import("../files/index.js").FileStorageProvider;
66
- };
67
- /** Observability provider — omit for NoopProvider (no spans/metrics).
68
- * Pass a ConsoleProvider to see the span tree in stdout, or a custom
69
- * provider (e.g. a recording provider for assertions in tests). */
70
- observability?: ObservabilityProvider;
71
- /** Inject a process lifecycle so tests can drain() and observe
72
- * /health/ready flipping to 503. Omit if the suite doesn't care. */
73
- lifecycle?: Lifecycle;
74
- /** Wire L1 (global-IP) and/or L2 (auth-endpoint) rate-limit middleware.
75
- * The resolver is auto-built from the test Redis. Mirrors
76
- * buildServer's `rateLimit` option 1:1 — see there for shape. */
77
- rateLimit?: import("../api/server.js").ServerOptions["rateLimit"];
78
- /** Forwarded to buildServer — Integration-Tests that exercise `extraRoutes`
79
- * MUST go through here (real HTTP via `stack.http`/`stack.app.fetch`),
80
- * never `createTestDispatcher`. */
81
- extraRoutes?: import("../api/server.js").ServerOptions["extraRoutes"];
82
- /** Forwarded to buildServer like runProdApp. Without a PrometheusMeter-backed
83
- * `observability` the route answers 503, so spread `resolveObservabilityWiring(token)`
84
- * rather than setting `metrics` alone. */
85
- metrics?: import("../api/server.js").ServerOptions["metrics"];
86
- /** Inject a MasterKeyProvider for secrets-backed tests. Lands typed in
87
- * AppContext — set/delete/get + rotation job pick it up. Omit for
88
- * suites that don't touch secrets. */
89
- masterKeyProvider?: import("../secrets/index.js").MasterKeyProvider;
90
- /** Feature-toggle resolver. When present the dispatcher's feature-gate,
91
- * hook-filter, and MSP-filter all consult it; absent = every feature
92
- * treated as always-on. Pass the callback from
93
- * GlobalFeatureToggleRuntime.effectiveFeatures for real DB-backed
94
- * toggles, or a plain `() => new Set<string>(registry.features.keys())`
95
- * to force a specific snapshot in a unit-style setup. */
96
- effectiveFeatures?: (tenantId: TenantId) => ReadonlySet<string>;
97
- /** Pin the underlying Postgres DB name instead of the default
98
- * `kumiko_test_<8chars>`. Forwarded to createTestDb. Primary use
99
- * case: dev servers that want persistent storage across restarts —
100
- * combine with `persistentDb: true`. */
101
- dbName?: string;
102
- /** When true, cleanup() keeps the Postgres DB around — the caller
103
- * owns its lifecycle. Default false (test contract). Used by
104
- * dev-server wiring to survive hot-reloads. */
105
- persistentDb?: boolean;
106
- /** Cap on the main app pool (default: driver default). Lets tests saturate it deliberately. */
107
- dbPoolMax?: number;
108
- /** Size of the dedicated pool behind ctx.dbOutsideTransaction / ctx.outsideTransaction. */
109
- outsideTransactionPoolMax?: number;
110
- /** Forwarded to buildServer — when set, requests without a JWT pass
111
- * through as anonymous instead of 401. See AnonymousAccessConfig.
112
- * Akzeptiert entweder einen statischen Config-Object ODER eine Factory
113
- * `({registry, db, sseBroker, redis}) => Config` — gleiches Pattern wie
114
- * `extraContext`. Die Factory wird einmal beim Boot aufgerufen, der
115
- * TenantResolver darin closure'd typischerweise `db` für Subdomain-
116
- * Lookups. */
117
- anonymousAccess?: import("../api/server.js").ServerOptions["anonymousAccess"] | ((deps: {
118
- registry: Registry;
119
- db: import("../db/index.js").DbConnection;
120
- sseBroker: import("../api/sse-broker.js").SseBroker;
121
- redis: import("ioredis").Redis;
122
- }) => import("../api/server.js").ServerOptions["anonymousAccess"]);
123
- /** Optional post-factory enricher (e.g. merge auth-foundation tenant
124
- * providers). Keeps framework free of a bundled-features dependency. */
125
- enrichAnonymousAccess?: (base: import("../api/server.js").ServerOptions["anonymousAccess"] | undefined, deps: {
126
- registry: Registry;
127
- db: import("../db/index.js").DbConnection;
128
- }) => Promise<import("../api/server.js").ServerOptions["anonymousAccess"] | undefined>;
129
- /** Opt-in JobRunner wired into ctx.jobRunner and merged into
130
- * dispatcherOptions so event-triggered jobs enqueue on commit — mirrors
131
- * the prod entrypoint's `buildJobRunnerWithHook`. Unlike prod (which
132
- * always builds one), this only builds a runner when `registry.getAllJobs()`
133
- * is non-empty — a deliberate test-stack-only shortcut, not parity.
134
- * Default `undefined`. Pass `consumerLane` only when this test IS the
135
- * sole consumer (otherwise enqueuer-only, avoiding double-running
136
- * `runOnBoot`/cron jobs against a caller-owned consumer). */
137
- jobs?: {
138
- consumerLane?: JobRunIn;
139
- /** Default: unique per stack, derived from its test Redis keyPrefix. Pass
140
- * the same value to every runner that has to share this stack's queues. */
141
- queueNamePrefix?: string;
142
- /** Source of active tenant ids for `perTenant: true` jobs. Omit when the
143
- * tenant feature is among `options.features` — the framework then
144
- * resolves tenants on its own via that feature's active-tenant-ids
145
- * query, same as prod. Only needed for suites that fan a job over
146
- * tenants without mounting the tenant feature. */
147
- getActiveTenantIds?: JobRunnerOptions["getActiveTenantIds"];
148
- /** Run-log callbacks (job_runs / tenant job failures), built once registry
149
- * and db exist. The framework can't import the bundled jobs feature, so the
150
- * dev-server wrapper supplies prod's `jobRunLoggerCallbacks` here. They are
151
- * awaited before drainJobs() observes the job's outcome. */
152
- runLogger?: (deps: {
153
- registry: Registry;
154
- db: import("../db/index.js").DbConnection;
155
- }) => Pick<JobRunnerOptions, "onJobStart" | "onJobComplete" | "onJobFailed" | "onJobQueued" | "onJobDropped"> | undefined;
156
- };
157
- /** Override the event dispatcher's polling-timer interval. Default 50ms.
158
- * Tests that assert LISTEN/NOTIFY wake-up latency need this pushed far
159
- * out (e.g. 60_000) so the polling timer can't land inside the
160
- * assertion window and mask a dead subscription — see E.4 (#2042). */
161
- eventDispatcherPollIntervalMs?: number;
162
- /** Opt out of auto-created r.entity() tables for callers that create/migrate
163
- * them themselves. Default true — false pushes only projection/MSP/
164
- * storeTable sources, same as before fw#3102. */
165
- entityTables?: boolean;
166
- /** Time source of the stack's rate-limit resolver, so tests can freeze the
167
- * clock instead of racing the window. Default: system time. */
168
- rateLimitNowMs?: () => number;
169
- /** Forwarded to buildServer's top-level `ServerOptions.trustedProxyHops`
170
- * — see there. Default 0. */
171
- trustedProxyHops?: number;
172
- /** Links the app's own error reasons to its own docs (`docsUrl` in error responses); framework reasons keep the framework docs link. Passed to buildServer's `ServerOptions.errorDocs`. */
173
- errorDocs?: ErrorDocsConfig;
174
- /** Second stack on the Redis namespace of `owner`: own connection, same
175
- * keyPrefix, so rate-limit buckets, locks, idempotency keys and the
176
- * cache-sync channel are shared across both. The derived
177
- * queueNamePrefix is identical too, so both stacks share BullMQ queues.
178
- * Cleanup only disconnects; the owner flushes the namespace and queues, so
179
- * clean up the borrowing stack first. The DB is NOT shared — pass the same
180
- * `dbName` + `persistentDb` for that. */
181
- sharedRedisWith?: TestStack;
182
- /** Publish/receive cache invalidations (tenant timezone, file providers,
183
- * tier sets) over Redis Pub/Sub, namespaced by this stack's keyPrefix.
184
- * Implied by `sharedRedisWith`; set it on the owner stack too, or its writes
185
- * never reach the borrower. Without it the stack uses a process-local bus. */
186
- cacheSync?: boolean;
187
- /** "full" runs the prod-boot validateBoot over `features` (screens and ref
188
- * entities across features included). Use it with the app's complete feature
189
- * composition; the default only checks what is safe on a feature subset. */
190
- validateBoot?: "full";
191
- };
192
- export declare function setupTestStack(options: TestStackOptions): Promise<TestStack>;