@cosmicdrift/kumiko-framework 0.398.0 → 0.400.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -19
- package/agents/api/dev-server-testing.md +23 -0
- package/agents/api/framework-db.md +277 -0
- package/agents/api/framework-engine.md +1810 -0
- package/agents/api/framework-errors.md +119 -0
- package/agents/api/framework-event-store.md +99 -0
- package/agents/api/framework-seeding.md +26 -0
- package/agents/api/framework-ui-types.md +542 -0
- package/agents/api/renderer-web.md +1450 -0
- package/agents/api/testing-helpers.md +189 -0
- package/agents/api/testing-stack.md +144 -0
- package/agents/api/types.md +5124 -0
- package/agents/api.md +20 -0
- package/agents/examples/derived-fields-sql.md +251 -0
- package/agents/examples/documents.md +161 -0
- package/agents/examples/inspection-checklist.md +807 -0
- package/agents/examples.md +3 -0
- package/agents/features-index.md +178 -0
- package/agents/features.md +9 -1
- package/dist/api/entity-read-access.d.ts +2 -0
- package/dist/api/entity-read-access.js +46 -2
- package/dist/api/server-boot-checks.js +2 -2
- package/dist/api/server-routes.js +3 -1
- package/dist/db/derived-field-sql.d.ts +23 -0
- package/dist/db/derived-field-sql.js +115 -0
- package/dist/db/derived-related-sql.d.ts +31 -0
- package/dist/db/derived-related-sql.js +137 -0
- package/dist/db/driver-row-coercion.d.ts +1 -0
- package/dist/db/driver-row-coercion.js +13 -0
- package/dist/db/eagerload.d.ts +3 -1
- package/dist/db/eagerload.js +4 -6
- package/dist/db/event-store-executor-context.js +1 -1
- package/dist/db/event-store-executor-read-paging.js +1 -1
- package/dist/db/event-store-executor-read-search.d.ts +3 -0
- package/dist/db/event-store-executor-read-search.js +17 -9
- package/dist/db/event-store-executor-read-sort.d.ts +9 -2
- package/dist/db/event-store-executor-read-sort.js +27 -14
- package/dist/db/event-store-executor-read-where.d.ts +0 -5
- package/dist/db/event-store-executor-read-where.js +3 -9
- package/dist/db/event-store-executor-read.js +23 -4
- package/dist/db/event-store-executor-write-steps.d.ts +6 -1
- package/dist/db/event-store-executor-write-steps.js +20 -1
- package/dist/db/event-store-executor-write.js +8 -8
- package/dist/db/money.d.ts +4 -0
- package/dist/db/money.js +34 -29
- package/dist/db/parent-ref-clause.d.ts +1 -0
- package/dist/db/parent-ref-clause.js +18 -8
- package/dist/db/read-gate-clause.d.ts +7 -0
- package/dist/db/read-gate-clause.js +188 -0
- package/dist/db/read-ownership.d.ts +9 -0
- package/dist/db/read-ownership.js +12 -0
- package/dist/engine/boot-validator/boot-check.d.ts +3 -1
- package/dist/engine/boot-validator/boot-check.js +7 -11
- package/dist/engine/boot-validator/boot-context.d.ts +26 -0
- package/dist/engine/boot-validator/boot-context.js +57 -0
- package/dist/engine/boot-validator/boot-options.d.ts +33 -0
- package/dist/engine/boot-validator/boot-options.js +1 -0
- package/dist/engine/boot-validator/boot-phases-app.d.ts +5 -0
- package/dist/engine/boot-validator/boot-phases-app.js +103 -0
- package/dist/engine/boot-validator/boot-phases.d.ts +11 -0
- package/dist/engine/boot-validator/boot-phases.js +140 -0
- package/dist/engine/boot-validator/boot-report.d.ts +6 -0
- package/dist/engine/boot-validator/boot-report.js +47 -0
- package/dist/engine/boot-validator/boot-rule.d.ts +12 -0
- package/dist/engine/boot-validator/boot-rule.js +14 -0
- package/dist/engine/boot-validator/boot-warnings.d.ts +2 -0
- package/dist/engine/boot-validator/boot-warnings.js +20 -0
- package/dist/engine/boot-validator/derived-sql-fields.d.ts +4 -0
- package/dist/engine/boot-validator/derived-sql-fields.js +184 -0
- package/dist/engine/boot-validator/entity-handler.d.ts +3 -0
- package/dist/engine/boot-validator/entity-handler.js +6 -3
- package/dist/engine/boot-validator/entity-list-screens.js +3 -1
- package/dist/engine/boot-validator/field-conditions.d.ts +2 -0
- package/dist/engine/boot-validator/field-conditions.js +178 -0
- package/dist/engine/boot-validator/i18n-keys.d.ts +2 -2
- package/dist/engine/boot-validator/i18n-keys.js +1 -3
- package/dist/engine/boot-validator/index.d.ts +5 -34
- package/dist/engine/boot-validator/index.js +24 -204
- package/dist/engine/boot-validator/pii-retention.js +2 -7
- package/dist/engine/boot-validator/projection-list-screens.js +19 -1
- package/dist/engine/boot-validator/read-gate.d.ts +2 -0
- package/dist/engine/boot-validator/read-gate.js +54 -0
- package/dist/engine/boot-validator/screens-forms.js +19 -6
- package/dist/engine/boot-validator/screens-kind-entity-list.js +8 -6
- package/dist/engine/boot-validator/screens-lists.js +8 -0
- package/dist/engine/boot-validator/zod-shape.js +4 -2
- package/dist/engine/build-app-schema.js +11 -5
- package/dist/engine/embedded-derived.d.ts +8 -6
- package/dist/engine/embedded-derived.js +25 -7
- package/dist/engine/entity-handlers.d.ts +1 -1
- package/dist/engine/entity-handlers.js +17 -18
- package/dist/engine/entity-list-runtime-options.d.ts +5 -1
- package/dist/engine/entity-list-runtime-options.js +103 -4
- package/dist/engine/factories.d.ts +5 -5
- package/dist/engine/factories.js +3 -5
- package/dist/engine/field-access.d.ts +2 -0
- package/dist/engine/field-access.js +48 -1
- package/dist/engine/field-state.d.ts +12 -0
- package/dist/engine/field-state.js +67 -0
- package/dist/engine/handler-helpers.d.ts +3 -0
- package/dist/engine/handler-helpers.js +7 -1
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +2 -1
- package/dist/engine/ownership.d.ts +1 -1
- package/dist/engine/ownership.js +9 -5
- package/dist/engine/registry-state.d.ts +1 -0
- package/dist/engine/registry-state.js +1 -1
- package/dist/engine/schema-builder.js +3 -0
- package/dist/engine/strip-unreadable-related-lists.d.ts +4 -0
- package/dist/engine/strip-unreadable-related-lists.js +80 -0
- package/dist/entrypoint/index.js +1 -0
- package/dist/entrypoint/internals.d.ts +2 -0
- package/dist/files/assert-file-refs-belong-to-tenant.js +26 -6
- package/dist/files/bind-pending-file-refs.d.ts +3 -2
- package/dist/files/bind-pending-file-refs.js +11 -12
- package/dist/files/bind-referenced-files.write.js +5 -5
- package/dist/files/create-file-ref-from-bytes.d.ts +2 -0
- package/dist/files/create-file-ref-from-bytes.js +2 -0
- package/dist/files/create-file-ref.d.ts +2 -0
- package/dist/files/create-file-ref.js +39 -22
- package/dist/files/dispose-removed-file-refs.js +5 -8
- package/dist/files/file-field-slots.d.ts +10 -0
- package/dist/files/file-field-slots.js +53 -0
- package/dist/files/file-ref-entity.d.ts +1 -1
- package/dist/files/file-ref-table.d.ts +1 -1
- package/dist/files/file-routes.js +30 -9
- package/dist/files/index.d.ts +1 -0
- package/dist/files/index.js +1 -0
- package/dist/files/load-save-file-refs.js +3 -4
- package/dist/files/record-read-check.js +2 -30
- package/dist/files/record-write-check.d.ts +2 -0
- package/dist/files/record-write-check.js +18 -3
- package/dist/files/upload-field-resolution.d.ts +4 -1
- package/dist/files/upload-field-resolution.js +57 -12
- package/dist/pipeline/dispatch-batch.js +6 -5
- package/dist/pipeline/dispatch-write.js +2 -1
- package/dist/pipeline/write-result-masking.js +2 -1
- package/dist/schema-cli/validate-command.js +46 -12
- package/dist/schema-cli.js +1 -1
- package/dist/ui-types/index.d.ts +2 -1
- package/dist/ui-types/index.js +2 -1
- package/package.json +3 -3
- package/src/changes.json +67 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<!-- Generated by `bun run gen:agent-docs` from packages/testing/src/helpers/index.ts. Do not edit. -->
|
|
2
|
+
|
|
3
|
+
# @cosmicdrift/kumiko-testing/helpers
|
|
4
|
+
|
|
5
|
+
Import from `@cosmicdrift/kumiko-testing/helpers`. Types used here but not exported by any entry point are in `api/types.md`.
|
|
6
|
+
|
|
7
|
+
## Functions
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function buildMultipartBody(fd: FormData): Promise<{ body: BodyInit; contentType: string; }>; // Serializes a FormData instance to multipart/form-data bytes.
|
|
11
|
+
function burst(stack: { dbPoolMax: number; }, count: number, request: (index: number) => Promise<Response>): Promise<BurstResult>;
|
|
12
|
+
function captureClosedConnectionError(url?: string): Promise<unknown>;
|
|
13
|
+
function clearTables(db: AnyDb, tables: ClearableTable[]): Promise<void>; // Delete all rows from each table (order preserved — FK-sensitive callers order explicitly).
|
|
14
|
+
function consoleCallsFromNamespace(spy: ConsoleSpyLike, namespace: string): ReadonlyArray<ReadonlyArray<unknown>>;
|
|
15
|
+
function consoleCallsMentioning(spy: ConsoleSpyLike, fragment: string): ReadonlyArray<ReadonlyArray<unknown>>;
|
|
16
|
+
function createBarrier(options: { expected: number; timeoutMs?: number; }): Barrier;
|
|
17
|
+
function createLateBoundHolder<T>(label?): LateBoundHolder<T>;
|
|
18
|
+
function createManualClock(start: Temporal.Instant): ManualClock;
|
|
19
|
+
function createMutableMasterKeyProvider(initial: MasterKeyProvider): MutableMasterKeyProvider;
|
|
20
|
+
function createRecordingProvider(options?: ObservabilityOptions): RecordingProvider;
|
|
21
|
+
function createTestEnvelopeCipher(keyBase64?: string, opts?: EnvelopeCipherOptions): EnvelopeCipher;
|
|
22
|
+
function createTestMasterKeyProvider(keyBase64?: string): MasterKeyProvider;
|
|
23
|
+
function createTestStoredEvent(overrides?: Partial<StoredEvent>): StoredEvent;
|
|
24
|
+
function deleteRowViaEventStore(db: DbConnection, target: EventStoreWriteTarget, where: WhereObject, scope?: EventStoreWriteScope): Promise<void>;
|
|
25
|
+
function deleteRows(db: AnyDb, table: SeedTable, where: WhereObject): Promise<number>;
|
|
26
|
+
function deleteRowsViaEventStore(db: DbConnection, target: EventStoreWriteTarget, where: WhereObject, scope?: EventStoreWriteScope): Promise<void>; // Deletes every row matching `where`, each through its own event-store delete.
|
|
27
|
+
function describeFileProviderContract(name: string, factory: () => FileStorageProvider | Promise<FileStorageProvider>): void;
|
|
28
|
+
function expectError(result: WriteResult, matcher?: string): asserts result is { isSuccess: false; error: WriteErrorInfo; };
|
|
29
|
+
function expectErrorIncludes(err: WriteErrorInfo | string | null | undefined, substring: string): void;
|
|
30
|
+
function expectRoleRestrictedHandlersDenyForeignRole(stack: Pick<TestStack, "registry" | "http">, options?: { minimumHandlerCount?: number; probeUser?: SessionUser; }): Promise<CheckedHandlerCounts>; // Contract test: every role-restricted write/query handler in the stack rejects a signed-in caller who holds none of its roles with 403 access_denied, over real HTTP.
|
|
31
|
+
function expectSuccess<T>(result: WriteResult<T>): asserts result is { isSuccess: true; data: T; };
|
|
32
|
+
function generateE2ESpec(registry: Registry, options?: E2EGeneratorOptions): E2ETestSpec[];
|
|
33
|
+
function generateZodFixture(schema: z.ZodTypeAny): unknown;
|
|
34
|
+
function getSetCookieRaw(res: Response, name: string): string | undefined;
|
|
35
|
+
function getSetCookieValue(res: Response, name: string): string | undefined;
|
|
36
|
+
function getSetCookies(res: Response): Map<string, ParsedSetCookie>;
|
|
37
|
+
function isRealProviderRun(env?: Readonly<Record<string, string | undefined>>): boolean;
|
|
38
|
+
function patchFileInstanceofForBunTest(): void; // Patches File[Symbol.hasInstance] so that cross-realm Blob objects returned by Hono's parseBody() pass `instanceof File` checks in bun:test.
|
|
39
|
+
function readSseFrames(response: Response, options: ReadSseFramesOptions): Promise<SseFrame[]>;
|
|
40
|
+
function requireRealProviders(): void;
|
|
41
|
+
function resetTestTables(db: AnyDb, tables: ClearableTable[]): Promise<void>; // Alias — same as clearTables, reads better in beforeEach.
|
|
42
|
+
function rolesOf(access: AccessRule | undefined): string[];
|
|
43
|
+
function runInRowLevelSecurityScope<T>(scope: RowLevelSecurityScope, fn: () => T): T;
|
|
44
|
+
function runInSystemRowLevelSecurityScope<T>(reason: string, fn: () => T): T;
|
|
45
|
+
function seedRow<TRow = unknown>(db: AnyDb, table: SeedTable, values: Record<string, unknown>): Promise<TRow | undefined>;
|
|
46
|
+
function seedRowViaEventStore(db: DbConnection, table: EventStoreWriteTarget["table"], values: Readonly<Record<string, unknown>>): Promise<void>;
|
|
47
|
+
function seedRows<TRow = unknown>(db: AnyDb, table: SeedTable, rows: ReadonlyArray<Record<string, unknown>>): Promise<TRow[]>;
|
|
48
|
+
function sleep(ms: number): Promise<unknown>;
|
|
49
|
+
function tenantRowLevelSecurityScope(tenantId: TenantId): RowLevelSecurityScope;
|
|
50
|
+
function terminateBackendsByApplicationName(admin: AdminClient, applicationName: string): Promise<void>;
|
|
51
|
+
function testDatabaseUrl(): string;
|
|
52
|
+
function updateRowViaEventStore(db: DbConnection, target: EventStoreWriteTarget, where: WhereObject, changes: Readonly<Record<string, unknown>>, scope?: EventStoreWriteScope): Promise<void>;
|
|
53
|
+
function updateRows<TRow = unknown>(db: AnyDb, table: SeedTable, set: Record<string, unknown>, where: WhereObject): Promise<TRow[]>;
|
|
54
|
+
function waitFor(fn: () => void | boolean | Promise<void | boolean>, options?: { delays?: number[]; }): Promise<void>; // Polls a condition with escalating timeouts.
|
|
55
|
+
function withBootValidatorFixture(features: FeatureDefinition[]): FeatureDefinition[]; // Test-fixture helper: satisfy entityList + i18n boot rules without bloating every makeFeature.
|
|
56
|
+
function withEnv<T>(overrides: Readonly<Record<string, string | undefined>>, run: () => T | Promise<T>): Promise<T>;
|
|
57
|
+
function withoutAmbientTemporal<T>(fn: () => T | Promise<T>): Promise<T>; // Run `fn` with `globalThis.Temporal` deleted, then restore.
|
|
58
|
+
function zipWithEntries(...entryNames: string[]): Uint8Array;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Constants
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const REAL_PROVIDERS_ENV: "KUMIKO_REAL_PROVIDERS";
|
|
65
|
+
const sharedItemEntity;
|
|
66
|
+
const sharedItemTable;
|
|
67
|
+
const sharedUserEntity;
|
|
68
|
+
const sharedUserTable;
|
|
69
|
+
const sharedWidgetEntity: EntityDefinition<{ name: EntityTextFieldDef & { required: true; }; }, "tenant">;
|
|
70
|
+
const sharedWidgetTable: EntityTable<EntityDefinition<{ name: EntityTextFieldDef & { required: true; }; }, "tenant">>;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Types
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
type Barrier = {
|
|
77
|
+
arrive: () => Promise<void>;
|
|
78
|
+
skip: () => void;
|
|
79
|
+
arrived: number;
|
|
80
|
+
skipped: number;
|
|
81
|
+
};
|
|
82
|
+
type BurstResult = {
|
|
83
|
+
responses: Response[];
|
|
84
|
+
statuses: Readonly<Record<number, number>>;
|
|
85
|
+
};
|
|
86
|
+
type ClearableTable = // EntityTableMeta, a built table, or a plain table name string.
|
|
87
|
+
| string
|
|
88
|
+
| {
|
|
89
|
+
tableName?: string;
|
|
90
|
+
}
|
|
91
|
+
| unknown
|
|
92
|
+
;
|
|
93
|
+
type ConsoleSpyLike = {
|
|
94
|
+
mock: {
|
|
95
|
+
calls: ReadonlyArray<ReadonlyArray<unknown>>;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
type E2EGeneratorOptions = {
|
|
99
|
+
tenantPlaceholder?: string;
|
|
100
|
+
};
|
|
101
|
+
type E2ETestSpec =
|
|
102
|
+
| {
|
|
103
|
+
kind: "list-renders";
|
|
104
|
+
screenQn: string;
|
|
105
|
+
title: string;
|
|
106
|
+
urlPath: string;
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
kind: "list-has-fixture-row";
|
|
110
|
+
screenQn: string;
|
|
111
|
+
title: string;
|
|
112
|
+
urlPath: string;
|
|
113
|
+
writeHandlerQn: string;
|
|
114
|
+
fixture: Readonly<Record<string, unknown>>;
|
|
115
|
+
identifyingValue: string;
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
kind: "edit-validates-required";
|
|
119
|
+
screenQn: string;
|
|
120
|
+
title: string;
|
|
121
|
+
urlPath: string;
|
|
122
|
+
requiredFields: string[];
|
|
123
|
+
}
|
|
124
|
+
| {
|
|
125
|
+
kind: "edit-save-persists";
|
|
126
|
+
screenQn: string;
|
|
127
|
+
title: string;
|
|
128
|
+
urlPath: string;
|
|
129
|
+
listUrlPath?: string;
|
|
130
|
+
fills: EditFillOp[];
|
|
131
|
+
identifyingValue: string;
|
|
132
|
+
identifyingField: string;
|
|
133
|
+
}
|
|
134
|
+
;
|
|
135
|
+
type EditFillOp =
|
|
136
|
+
| {
|
|
137
|
+
kind: "fill";
|
|
138
|
+
field: string;
|
|
139
|
+
value: string;
|
|
140
|
+
}
|
|
141
|
+
| {
|
|
142
|
+
kind: "check";
|
|
143
|
+
field: string;
|
|
144
|
+
value: boolean;
|
|
145
|
+
}
|
|
146
|
+
| {
|
|
147
|
+
kind: "select";
|
|
148
|
+
field: string;
|
|
149
|
+
value: string;
|
|
150
|
+
}
|
|
151
|
+
;
|
|
152
|
+
type EventStoreWriteScope = {
|
|
153
|
+
tenantId?: TenantId; // Tenant of the acting system user and of the tenant-bound db; defaults to the system tenant.
|
|
154
|
+
streamTenantId?: TenantId; // Address a stream that lives on another tenant than the actor's.
|
|
155
|
+
};
|
|
156
|
+
type EventStoreWriteTarget = {
|
|
157
|
+
table: ExecutorArgs[0];
|
|
158
|
+
entity: ExecutorArgs[1];
|
|
159
|
+
entityName: string;
|
|
160
|
+
};
|
|
161
|
+
type LateBoundHolder<T> = {
|
|
162
|
+
set(value: T): void; // Store the concrete value.
|
|
163
|
+
get(): T; // Fetch the concrete value or throw if set() hasn't been called yet.
|
|
164
|
+
isReady(): boolean; // True after set() has been called.
|
|
165
|
+
};
|
|
166
|
+
type ManualClock = Clock & {
|
|
167
|
+
set(instant: Temporal.Instant): void;
|
|
168
|
+
advance(step: ClockStep): void;
|
|
169
|
+
};
|
|
170
|
+
type MutableMasterKeyProvider = MasterKeyProvider & {
|
|
171
|
+
replace(next: MasterKeyProvider): void;
|
|
172
|
+
};
|
|
173
|
+
type ParsedSetCookie = {
|
|
174
|
+
value: string;
|
|
175
|
+
raw: string;
|
|
176
|
+
};
|
|
177
|
+
type RecordingProvider = ObservabilityProvider & {
|
|
178
|
+
spans: RecordedSpan[];
|
|
179
|
+
metricEvents: MetricEvent[];
|
|
180
|
+
spansByName(name: string): RecordedSpan[];
|
|
181
|
+
spansByTraceId(traceId: string): RecordedSpan[];
|
|
182
|
+
reset(): void;
|
|
183
|
+
};
|
|
184
|
+
type SseFrame = {
|
|
185
|
+
event: string | undefined;
|
|
186
|
+
data: string;
|
|
187
|
+
id: string | undefined;
|
|
188
|
+
};
|
|
189
|
+
```
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!-- Generated by `bun run gen:agent-docs` from packages/testing/src/stack/index.ts. Do not edit. -->
|
|
2
|
+
|
|
3
|
+
# @cosmicdrift/kumiko-testing/stack
|
|
4
|
+
|
|
5
|
+
Import from `@cosmicdrift/kumiko-testing/stack`. Types used here but not exported by any entry point are in `api/types.md`.
|
|
6
|
+
|
|
7
|
+
## Functions
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function applicationRoleUrlOf(url: string, dbName: string): string; // URL of database `dbName` on the host of `url`, logged in as TEST_APPLICATION_ROLE with the password of `url` (the caller's TEST_DATABASE_URL).
|
|
11
|
+
function createEventCollector(): EventCollector;
|
|
12
|
+
function createRequestHelper(app: Hono, jwt: JwtHelper, options?: RequestHelperOptions): RequestHelper;
|
|
13
|
+
function createTestDb(arg?: string | CreateTestDbOptions): Promise<TestDb>;
|
|
14
|
+
function createTestRedis(opts?: CreateTestRedisOptions): Promise<TestRedis>;
|
|
15
|
+
function drainEventConsumers(stack: Pick<TestStack, "db" | "eventDispatcher">, consumerNames: [ string, ...string[] ], options?: { maxPasses?: number; }): Promise<void>;
|
|
16
|
+
function pushEntityProjectionTables(stack: DbHolder, registry: Registry): Promise<void>; // Push all implicit-projection tables — one per `r.entity()` — that the registry knows about.
|
|
17
|
+
function resetEventStore(stack: { db: unknown; eventDispatcher?: EventDispatcher; }, extraTables?: (unknown | string)[]): Promise<void>; // Wipes event store + framework-state + the given feature read-models in one TRUNCATE, then re-registers the event-consumer state rows.
|
|
18
|
+
function setupTestStack(options: TestStackOptions): Promise<TestStack>;
|
|
19
|
+
function unsafeCreateEntityTable(db: DbConnection, entity: EntityDefinition, entityName?: string): Promise<void>; // Bypass: creates an entity-table directly without going through the full registry.
|
|
20
|
+
function unsafeEnsureEntityTable(db: DbConnection, entity: EntityDefinition, entityName?: string): Promise<boolean>;
|
|
21
|
+
function unsafePushTables(db: DbConnection, tables: Record<string, unknown>, prevTables?: Record<string, unknown>, options?: { rowLevelSecurity?: RowLevelSecurityMode; }): Promise<void>; // Bypass: pushes table definitions to the database directly.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Constants
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const DEFAULT_TEST_DB_POOL_MAX: 10;
|
|
28
|
+
const TEST_APPLICATION_ROLE: "kumiko_app";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Types
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
type CreateTestDbOptions = {
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
dbName?: string;
|
|
37
|
+
persistent?: boolean;
|
|
38
|
+
poolMax?: number;
|
|
39
|
+
outsideTransactionPoolMax?: number;
|
|
40
|
+
templateDbName?: string; // Clone this already-migrated database instead of starting empty.
|
|
41
|
+
applicationRole?: boolean; // Own the database as the non-bypass application role and enforce row-level security on the pools.
|
|
42
|
+
};
|
|
43
|
+
type EventCollector = {
|
|
44
|
+
sse: SseEvent[];
|
|
45
|
+
postSave: SaveContext[];
|
|
46
|
+
reset(): void; // Clears all collected events — call in beforeEach for per-test isolation
|
|
47
|
+
};
|
|
48
|
+
type RequestHelper = {
|
|
49
|
+
write: (type: string, payload: unknown, user: SessionUser, requestId?: string) => Promise<Response>;
|
|
50
|
+
query: (type: string, payload: unknown, user: SessionUser) => Promise<Response>;
|
|
51
|
+
command: (type: string, payload: unknown, user: SessionUser) => Promise<Response>;
|
|
52
|
+
batch: (commands: BatchCommand[], user: SessionUser, requestId?: string) => Promise<Response>;
|
|
53
|
+
raw: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<Response>;
|
|
54
|
+
writeOk: <T = Record<string, unknown>>(type: string, payload: unknown, user: SessionUser, requestId?: string) => Promise<T>; // write + json + assert isSuccess — returns data directly
|
|
55
|
+
writeErr: (type: string, payload: unknown, user: SessionUser) => Promise<WriteErrorInfo>; // write + json + assert isSuccess === false — returns the structured WriteErrorInfo with `httpStatus` filled in from the HTTP response.
|
|
56
|
+
queryOk: <T = unknown>(type: string, payload: unknown, user: SessionUser) => Promise<T>; // query + json — returns data directly
|
|
57
|
+
queryErr: (type: string, payload: unknown, user: SessionUser) => Promise<WriteErrorInfo>; // query + json + assert the response is an error — returns the structured WriteErrorInfo with `httpStatus` filled in from the HTTP response.
|
|
58
|
+
writeWithHeaders: (type: string, payload: unknown, user: SessionUser, extraHeaders: Record<string, string>) => Promise<Response>; // write + additional HTTP headers (e.g. X-Correlation-ID).
|
|
59
|
+
queryWithHeaders: (type: string, payload: unknown, user: SessionUser, extraHeaders: Record<string, string>) => Promise<Response>; // query + additional HTTP headers (e.g. X-Forwarded-For).
|
|
60
|
+
};
|
|
61
|
+
type TestDb = {
|
|
62
|
+
db: DbConnection;
|
|
63
|
+
poolMax: number;
|
|
64
|
+
outsideTransactionDb: DbConnection;
|
|
65
|
+
client: PgListenClient;
|
|
66
|
+
dbName: string;
|
|
67
|
+
cleanup: () => Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
type TestRedis = {
|
|
70
|
+
redis: Redis;
|
|
71
|
+
redisUrl: string;
|
|
72
|
+
keyPrefix: string; // The per-test `keyPrefix` on `redis`, e.g. `kt:1a2b3c4d:`.
|
|
73
|
+
flushNamespace: () => Promise<void>; // Delete every key this test created (prefix-scoped).
|
|
74
|
+
cleanup: () => Promise<void>;
|
|
75
|
+
};
|
|
76
|
+
type TestStack = {
|
|
77
|
+
app: Hono;
|
|
78
|
+
jwt: JwtHelper;
|
|
79
|
+
registry: Registry;
|
|
80
|
+
db: DbConnection; // Sees every tenant's rows (system scope) at rowLevelSecurity "on".
|
|
81
|
+
unscopedDb: DbConnection; // The enforced pool the services use: a statement without a scope sees no tenant row.
|
|
82
|
+
dbPoolMax: number; // Resolved connection pool size of `db`.
|
|
83
|
+
redis: TestRedis;
|
|
84
|
+
cacheSync: ClosableCacheSyncBus;
|
|
85
|
+
search: SearchAdapter;
|
|
86
|
+
events: EventCollector;
|
|
87
|
+
http: RequestHelper;
|
|
88
|
+
observability: ObservabilityProvider;
|
|
89
|
+
sseBroker: SseBroker;
|
|
90
|
+
dispatcher: Dispatcher;
|
|
91
|
+
context: AppContext;
|
|
92
|
+
eventDispatcher?: EventDispatcher;
|
|
93
|
+
lifecycle?: Lifecycle;
|
|
94
|
+
jobRunner?: JobRunner;
|
|
95
|
+
drainJobs: () => Promise<void>;
|
|
96
|
+
cleanup: () => Promise<void>;
|
|
97
|
+
};
|
|
98
|
+
type TestStackOptions = {
|
|
99
|
+
features: FeatureDefinition[];
|
|
100
|
+
systemHooks?: ("sse" | "search")[]; // System hooks to wire up.
|
|
101
|
+
lifecycleSystemHooks?: SystemHooks; // Pipeline system hooks handed to buildServer, e.g. the cascade-delete preDelete hook.
|
|
102
|
+
searchConfig?: {
|
|
103
|
+
tenantId: TenantId;
|
|
104
|
+
searchableFields: string[];
|
|
105
|
+
rankingFields: string[];
|
|
106
|
+
}; // Search config per tenant — defaults to tenant 1 with all text fields
|
|
107
|
+
jwtSecret?: string;
|
|
108
|
+
defaultLocale?: string; // Boot default locale (BCP-47) merged into AppContext before extraContext.
|
|
109
|
+
extraContext?: Record<string, unknown> | ((deps: { registry: Registry; db: DbConnection; sseBroker: SseBroker; redis: Redis; }) => Record<string, unknown>);
|
|
110
|
+
authConfig?: AuthRoutesConfig; // Wire up auth routes (login, tenant-switch).
|
|
111
|
+
files?: {
|
|
112
|
+
storageProvider: FileStorageProvider;
|
|
113
|
+
}; // Register a file storage provider so uploads via POST /api/files work and `ctx.services.files.ref(key)` is available to hooks/MSPs.
|
|
114
|
+
observability?: ObservabilityProvider; // Observability provider — omit for NoopProvider (no spans/metrics).
|
|
115
|
+
lifecycle?: Lifecycle; // Inject a process lifecycle so tests can drain() and observe /health/ready flipping to 503.
|
|
116
|
+
sseBroker?: SseBroker; // Replaces the default in-memory broker; the caller owns its lifecycle.
|
|
117
|
+
rateLimit?: ServerOptions["rateLimit"]; // Wire L1 (global-IP) and/or L2 (auth-endpoint) rate-limit middleware.
|
|
118
|
+
extraRoutes?: ServerOptions["extraRoutes"]; // Forwarded to buildServer — Integration-Tests that exercise `extraRoutes` MUST go through here (real HTTP via `stack.http`/`stack.app.fetch`), never `createTestDispatcher`.
|
|
119
|
+
metrics?: ServerOptions["metrics"]; // Forwarded to buildServer like runProdApp.
|
|
120
|
+
masterKeyProvider?: MasterKeyProvider; // Inject a MasterKeyProvider for secrets-backed tests.
|
|
121
|
+
effectiveFeatures?: (tenantId: TenantId) => ReadonlySet<string>; // Feature-toggle resolver.
|
|
122
|
+
dbName?: string; // Pin the underlying Postgres DB name instead of the default `kumiko_test_<8chars>`.
|
|
123
|
+
persistentDb?: boolean; // When true, cleanup() keeps the Postgres DB around — the caller owns its lifecycle.
|
|
124
|
+
dbPoolMax?: number; // Cap on the main app pool (default: driver default).
|
|
125
|
+
outsideTransactionPoolMax?: number; // Size of the dedicated pool behind ctx.db.outsideTransaction / ctx.outsideTransaction.
|
|
126
|
+
anonymousAccess?: ServerOptions["anonymousAccess"] | ((deps: { registry: Registry; db: DbConnection; sseBroker: SseBroker; redis: Redis; }) => ServerOptions["anonymousAccess"]); // Forwarded to buildServer — when set, requests without a JWT pass through as anonymous instead of 401.
|
|
127
|
+
enrichAnonymousAccess?: (base: ServerOptions["anonymousAccess"] | undefined, deps: { registry: Registry; db: DbConnection; }) => Promise<ServerOptions["anonymousAccess"] | undefined>; // Optional post-factory enricher (e.g. merge auth-foundation tenant providers).
|
|
128
|
+
jobs?: {
|
|
129
|
+
consumerLane?: JobRunIn;
|
|
130
|
+
queueNamePrefix?: string; // Default: unique per stack, derived from its test Redis keyPrefix.
|
|
131
|
+
getActiveTenantIds?: JobRunnerOptions["getActiveTenantIds"]; // Source of active tenant ids for `perTenant: true` jobs.
|
|
132
|
+
runLogger?: (deps: { registry: Registry; db: DbConnection; }) => Pick<JobRunnerOptions, "onJobStart" | "onJobComplete" | "onJobFailed" | "onJobQueued" | "onJobDropped"> | undefined; // Run-log callbacks (job_runs / tenant job failures), built once registry and db exist.
|
|
133
|
+
}; // Opt-in JobRunner wired into ctx.services.jobRunner and merged into dispatcherOptions so event-triggered jobs enqueue on commit — mirrors the prod entrypoint's `buildJobRunnerWithHook`.
|
|
134
|
+
eventDispatcherPollIntervalMs?: number; // Override the event dispatcher's polling-timer interval.
|
|
135
|
+
entityTables?: boolean; // Opt out of auto-created r.entity() tables for callers that create/migrate them themselves.
|
|
136
|
+
rateLimitNowMs?: () => number; // Time source of the stack's rate-limit resolver, so tests can freeze the clock instead of racing the window.
|
|
137
|
+
trustedProxyHops?: number; // Forwarded to buildServer's top-level `ServerOptions.trustedProxyHops` — see there.
|
|
138
|
+
errorDocs?: ErrorDocsConfig; // Links the app's own error reasons to its own docs (`docsUrl` in error responses); framework reasons keep the framework docs link.
|
|
139
|
+
sharedRedisWith?: TestStack; // Second stack on the Redis namespace of `owner`: own connection, same keyPrefix, so rate-limit buckets, locks, idempotency keys and the cache-sync channel are shared across both.
|
|
140
|
+
cacheSync?: boolean; // Publish/receive cache invalidations (tenant timezone, file providers, tier sets) over Redis Pub/Sub, namespaced by this stack's keyPrefix.
|
|
141
|
+
validateBoot?: "full"; // "full" runs the prod-boot validateBoot over `features` (screens and ref entities across features included).
|
|
142
|
+
rowLevelSecurity?: RowLevelSecurityMode; // Postgres tenant isolation, as in prod.
|
|
143
|
+
};
|
|
144
|
+
```
|