@cosmicdrift/kumiko-framework 0.1.0 → 0.2.1

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 (74) hide show
  1. package/LICENSE +57 -0
  2. package/package.json +3 -3
  3. package/src/__tests__/anonymous-access.integration.ts +7 -7
  4. package/src/__tests__/error-contract.integration.ts +2 -2
  5. package/src/__tests__/field-access.integration.ts +2 -2
  6. package/src/__tests__/full-stack.integration.ts +2 -2
  7. package/src/__tests__/ownership.integration.ts +2 -2
  8. package/src/__tests__/raw-table.integration.ts +128 -0
  9. package/src/__tests__/reference-data.integration.ts +2 -2
  10. package/src/__tests__/transition-guard.integration.ts +4 -4
  11. package/src/api/__tests__/batch.integration.ts +3 -3
  12. package/src/api/__tests__/dispatcher-live.integration.ts +2 -2
  13. package/src/api/__tests__/nested-write.integration.ts +3 -3
  14. package/src/db/__tests__/drizzle-helpers.integration.ts +2 -2
  15. package/src/db/__tests__/event-store-executor-list.integration.ts +2 -2
  16. package/src/db/__tests__/event-store-executor.integration.ts +9 -3
  17. package/src/db/__tests__/implicit-projection-equivalence.integration.ts +3 -3
  18. package/src/db/__tests__/multi-row-insert.integration.ts +3 -3
  19. package/src/db/__tests__/schema-migration.integration.ts +9 -9
  20. package/src/db/__tests__/tenant-db.integration.ts +4 -4
  21. package/src/db/__tests__/unique-violation-mapping.integration.ts +2 -2
  22. package/src/db/schema-inspection.ts +1 -1
  23. package/src/engine/__tests__/raw-table.test.ts +149 -0
  24. package/src/engine/define-feature.ts +38 -0
  25. package/src/engine/index.ts +6 -0
  26. package/src/engine/registry.ts +46 -0
  27. package/src/engine/tier-resolver-extension.ts +78 -0
  28. package/src/engine/types/feature.ts +55 -0
  29. package/src/engine/types/handlers.ts +13 -5
  30. package/src/engine/types/index.ts +3 -0
  31. package/src/event-store/__tests__/upcaster.integration.ts +11 -5
  32. package/src/event-store/archive.ts +2 -2
  33. package/src/event-store/events-schema.ts +2 -2
  34. package/src/event-store/snapshot.ts +2 -2
  35. package/src/event-store/upcaster-dead-letter.ts +2 -2
  36. package/src/files/__tests__/file-field-column.integration.ts +4 -4
  37. package/src/files/__tests__/file-field-pipeline.integration.ts +2 -2
  38. package/src/files/__tests__/files.integration.ts +8 -8
  39. package/src/observability/__tests__/observability.integration.ts +2 -2
  40. package/src/pipeline/__tests__/archive-stream.integration.ts +2 -2
  41. package/src/pipeline/__tests__/cascade-handler.integration.ts +9 -9
  42. package/src/pipeline/__tests__/causation-chain.integration.ts +2 -2
  43. package/src/pipeline/__tests__/ctx-bridge.integration.ts +3 -3
  44. package/src/pipeline/__tests__/dispatcher.test.ts +73 -0
  45. package/src/pipeline/__tests__/domain-events-projections.integration.ts +2 -2
  46. package/src/pipeline/__tests__/event-dedup.integration.ts +2 -2
  47. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +2 -2
  48. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +3 -3
  49. package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +2 -2
  50. package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +2 -2
  51. package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +2 -2
  52. package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +4 -4
  53. package/src/pipeline/__tests__/event-dispatcher.integration.ts +4 -4
  54. package/src/pipeline/__tests__/event-retention.integration.ts +2 -2
  55. package/src/pipeline/__tests__/fetch-for-writing.integration.ts +2 -2
  56. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +100 -0
  57. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +2 -2
  58. package/src/pipeline/__tests__/msp-error-mode.integration.ts +2 -2
  59. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +2 -2
  60. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  61. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +3 -3
  62. package/src/pipeline/__tests__/perf-rebuild.integration.ts +2 -2
  63. package/src/pipeline/__tests__/projection-rebuild.integration.ts +9 -3
  64. package/src/pipeline/__tests__/query-projection.integration.ts +2 -2
  65. package/src/pipeline/dispatcher.ts +35 -15
  66. package/src/pipeline/event-consumer-state.ts +2 -2
  67. package/src/pipeline/event-dispatcher.ts +10 -1
  68. package/src/pipeline/lifecycle-pipeline.ts +22 -4
  69. package/src/pipeline/projection-state.ts +3 -3
  70. package/src/stack/index.ts +4 -3
  71. package/src/stack/push-entity-projection-tables.ts +51 -0
  72. package/src/stack/table-helpers.ts +20 -13
  73. package/src/stack/test-stack.ts +14 -5
  74. package/src/testing/__tests__/ensure-entity-table.integration.ts +16 -11
@@ -12,7 +12,7 @@ import {
12
12
  } from "../db/dialect";
13
13
  import { tableExists } from "../db/schema-inspection";
14
14
  import type { TenantId } from "../engine/types";
15
- import { pushTables } from "../stack";
15
+ import { unsafePushTables } from "../stack";
16
16
  import { isStreamArchived } from "./archive";
17
17
  import { loadEventsAfterVersion, type StoredEvent } from "./event-store";
18
18
 
@@ -73,7 +73,7 @@ export const snapshotsTable = pgTable(
73
73
  export async function createSnapshotsTable(db: DbConnection): Promise<void> {
74
74
  // skip: table already exists — idempotent boot + test-setup call
75
75
  if (await tableExists(db, "public.kumiko_snapshots")) return;
76
- await pushTables(db, { kumikoSnapshots: snapshotsTable });
76
+ await unsafePushTables(db, { kumikoSnapshots: snapshotsTable });
77
77
  }
78
78
 
79
79
  export type Snapshot<TState extends Record<string, unknown> = Record<string, unknown>> = {
@@ -15,7 +15,7 @@
15
15
  import { bigint, index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
16
16
  import type { DbConnection, DbRunner } from "../db/connection";
17
17
  import { tableExists } from "../db/schema-inspection";
18
- import { pushTables } from "../stack";
18
+ import { unsafePushTables } from "../stack";
19
19
  import type { StoredEvent } from "./event-store";
20
20
 
21
21
  export const upcasterDeadLetterTable = pgTable(
@@ -50,7 +50,7 @@ export const upcasterDeadLetterTable = pgTable(
50
50
  export async function createUpcasterDeadLetterTable(db: DbConnection): Promise<void> {
51
51
  // skip: table already exists — bootstrap called from multiple paths
52
52
  if (await tableExists(db, "public.kumiko_upcaster_dead_letters")) return;
53
- await pushTables(db, { kumikoUpcasterDeadLetters: upcasterDeadLetterTable });
53
+ await unsafePushTables(db, { kumikoUpcasterDeadLetters: upcasterDeadLetterTable });
54
54
  }
55
55
 
56
56
  // Writes a dead-letter row. Called by upcastStoredEvent when errorPolicy
@@ -10,7 +10,7 @@
10
10
  import { sql } from "drizzle-orm";
11
11
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
12
12
  import { createEntity, createFileField, createImageField } from "../../engine";
13
- import { createEntityTable, createTestDb, pushTables, type TestDb } from "../../stack";
13
+ import { createTestDb, type TestDb, unsafeCreateEntityTable, unsafePushTables } from "../../stack";
14
14
  import { generateId } from "../../utils";
15
15
  import { fileRefsTable } from "../file-ref-table";
16
16
 
@@ -28,8 +28,8 @@ let testDb: TestDb;
28
28
 
29
29
  beforeAll(async () => {
30
30
  testDb = await createTestDb();
31
- await pushTables(testDb.db, { fileRefsTable });
32
- await createEntityTable(testDb.db, documentEntity);
31
+ await unsafePushTables(testDb.db, { fileRefsTable });
32
+ await unsafeCreateEntityTable(testDb.db, documentEntity);
33
33
  });
34
34
 
35
35
  afterAll(async () => {
@@ -40,7 +40,7 @@ describe("file-field entity-column type", () => {
40
40
  test("`file` and `image` fields generate UUID columns (not integer)", async () => {
41
41
  // Pull the actual column type from information_schema. This is the
42
42
  // load-bearing assertion: the type emitted by drizzle-kit during
43
- // `createEntityTable` must be `uuid`. A regression to `integer` would
43
+ // `unsafeCreateEntityTable` must be `uuid`. A regression to `integer` would
44
44
  // fail here even if higher-level code happened to still work through
45
45
  // implicit casts.
46
46
  const rows = await testDb.db.execute<{ column_name: string; data_type: string }>(sql`
@@ -30,11 +30,11 @@ import {
30
30
  defineFeature,
31
31
  } from "../../engine";
32
32
  import {
33
- createEntityTable,
34
33
  createTestUser,
35
34
  setupTestStack,
36
35
  type TestStack,
37
36
  testTenantId,
37
+ unsafeCreateEntityTable,
38
38
  } from "../../stack";
39
39
  import { createLocalProvider } from "../local-provider";
40
40
 
@@ -74,7 +74,7 @@ beforeAll(async () => {
74
74
  features: [documentFeature],
75
75
  files: { storageProvider: createLocalProvider(storagePath) },
76
76
  });
77
- await createEntityTable(stack.db, documentEntity);
77
+ await unsafeCreateEntityTable(stack.db, documentEntity);
78
78
  });
79
79
 
80
80
  afterAll(async () => {
@@ -15,12 +15,12 @@ import {
15
15
  } from "../../engine";
16
16
  import { createEventsTable, loadAggregate } from "../../event-store";
17
17
  import {
18
- createEntityTable,
19
18
  createTestDb,
20
19
  createTestUser,
21
- pushTables,
22
20
  type TestDb,
23
21
  TestUsers,
22
+ unsafeCreateEntityTable,
23
+ unsafePushTables,
24
24
  } from "../../stack";
25
25
  import { expectErrorIncludes } from "../../testing";
26
26
  import { fileRefsTable } from "../file-ref-table";
@@ -64,8 +64,8 @@ beforeAll(async () => {
64
64
  storagePath = await mkdtemp(join(tmpdir(), "kumiko-files-test-"));
65
65
 
66
66
  // Create tables
67
- await pushTables(testDb.db, { fileRefsTable });
68
- await createEntityTable(testDb.db, testTenantEntity);
67
+ await unsafePushTables(testDb.db, { fileRefsTable });
68
+ await unsafeCreateEntityTable(testDb.db, testTenantEntity);
69
69
  // Event-store table: the upload route appends files:event:uploaded in the
70
70
  // same tx as the FileRef insert. Without events, upload would 500.
71
71
  await createEventsTable(testDb.db);
@@ -402,8 +402,8 @@ describe("custom file access guard", () => {
402
402
  ): Promise<void> {
403
403
  const { storageProvider: providerOverride, ...routeOptions } = options;
404
404
  const isolatedDb = await createTestDb();
405
- await pushTables(isolatedDb.db, { fileRefsTable });
406
- await createEntityTable(isolatedDb.db, testTenantEntity);
405
+ await unsafePushTables(isolatedDb.db, { fileRefsTable });
406
+ await unsafeCreateEntityTable(isolatedDb.db, testTenantEntity);
407
407
  const storagePath = await mkdtemp(join(tmpdir(), "kumiko-files-custom-"));
408
408
  const provider = providerOverride ?? createLocalProvider(storagePath);
409
409
  const isolatedRegistry = createRegistry([tenantFeature]);
@@ -716,8 +716,8 @@ describe("download-url endpoint", () => {
716
716
  }) => Promise<void>,
717
717
  ): Promise<void> {
718
718
  const isolatedDb = await createTestDb();
719
- await pushTables(isolatedDb.db, { fileRefsTable });
720
- await createEntityTable(isolatedDb.db, testTenantEntity);
719
+ await unsafePushTables(isolatedDb.db, { fileRefsTable });
720
+ await unsafeCreateEntityTable(isolatedDb.db, testTenantEntity);
721
721
  const isolatedRegistry = createRegistry([tenantFeature]);
722
722
  const isolatedServer = buildServer({
723
723
  registry: isolatedRegistry,
@@ -6,11 +6,11 @@ import type { AppContext, SaveContext } from "../../engine/types";
6
6
  import { createJobRunner } from "../../jobs";
7
7
  import { createLogger } from "../../logging/pino-logger";
8
8
  import {
9
- createEntityTable,
10
9
  createTestRedis,
11
10
  setupTestStack,
12
11
  type TestRedis,
13
12
  type TestStack,
13
+ unsafeCreateEntityTable,
14
14
  } from "../../stack";
15
15
  import { createRecordingProvider, type RecordingProvider, waitFor } from "../../testing";
16
16
 
@@ -194,7 +194,7 @@ describe("Observability (integration) — DB + pipeline hook spans", () => {
194
194
  observability: provider,
195
195
  systemHooks: [],
196
196
  });
197
- await createEntityTable(stack.db, todoEntity, "todo");
197
+ await unsafeCreateEntityTable(stack.db, todoEntity, "todo");
198
198
  });
199
199
 
200
200
  afterEach(async () => {
@@ -16,11 +16,11 @@ import {
16
16
  loadAggregate as loadAggregateRaw,
17
17
  } from "../../event-store";
18
18
  import {
19
- createEntityTable,
20
19
  resetEventStore,
21
20
  setupTestStack,
22
21
  type TestStack,
23
22
  TestUsers,
23
+ unsafeCreateEntityTable,
24
24
  } from "../../stack";
25
25
 
26
26
  const itemEntity = createEntity({
@@ -103,7 +103,7 @@ const admin = TestUsers.admin;
103
103
 
104
104
  beforeAll(async () => {
105
105
  stack = await setupTestStack({ features: [archFeature], systemHooks: [] });
106
- await createEntityTable(stack.db, itemEntity, "arch-item");
106
+ await unsafeCreateEntityTable(stack.db, itemEntity, "arch-item");
107
107
  });
108
108
 
109
109
  afterAll(async () => {
@@ -11,7 +11,7 @@ import {
11
11
  type Registry,
12
12
  } from "../../engine";
13
13
  import { createEventsTable } from "../../event-store";
14
- import { createEntityTable, createTestDb, type TestDb, TestUsers } from "../../stack";
14
+ import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
15
15
  import { createCascadeDeleteHook } from "../cascade-handler";
16
16
 
17
17
  // biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables
@@ -81,14 +81,14 @@ beforeAll(async () => {
81
81
  await createEventsTable(testDb.db);
82
82
  tdb = createTenantDb(testDb.db, admin.tenantId);
83
83
 
84
- await createEntityTable(testDb.db, departmentEntity);
85
- await createEntityTable(testDb.db, userEntity);
86
- await createEntityTable(testDb.db, sessionEntity);
87
- await createEntityTable(testDb.db, groupEntity);
88
- await createEntityTable(testDb.db, userGroupRestrictEntity);
89
- await createEntityTable(testDb.db, userGroupCascadeEntity);
90
- await createEntityTable(testDb.db, teamEntity);
91
- await createEntityTable(testDb.db, memberEntity);
84
+ await unsafeCreateEntityTable(testDb.db, departmentEntity);
85
+ await unsafeCreateEntityTable(testDb.db, userEntity);
86
+ await unsafeCreateEntityTable(testDb.db, sessionEntity);
87
+ await unsafeCreateEntityTable(testDb.db, groupEntity);
88
+ await unsafeCreateEntityTable(testDb.db, userGroupRestrictEntity);
89
+ await unsafeCreateEntityTable(testDb.db, userGroupCascadeEntity);
90
+ await unsafeCreateEntityTable(testDb.db, teamEntity);
91
+ await unsafeCreateEntityTable(testDb.db, memberEntity);
92
92
 
93
93
  departmentTable = buildDrizzleTable("department", departmentEntity);
94
94
  userTable = buildDrizzleTable("user", userEntity);
@@ -23,11 +23,11 @@ import { buildDrizzleTable } from "../../db/table-builder";
23
23
  import { createEntity, createTextField, defineFeature } from "../../engine";
24
24
  import { eventsTable } from "../../event-store";
25
25
  import {
26
- createEntityTable,
27
26
  resetEventStore,
28
27
  setupTestStack,
29
28
  type TestStack,
30
29
  TestUsers,
30
+ unsafeCreateEntityTable,
31
31
  } from "../../stack";
32
32
 
33
33
  // --- Feature ---
@@ -104,7 +104,7 @@ beforeAll(async () => {
104
104
  features: [causationFeature],
105
105
  systemHooks: [],
106
106
  });
107
- await createEntityTable(stack.db, orderEntity, "causation-order");
107
+ await unsafeCreateEntityTable(stack.db, orderEntity, "causation-order");
108
108
  });
109
109
 
110
110
  afterAll(async () => {
@@ -11,7 +11,7 @@ import {
11
11
  defineFeature,
12
12
  } from "../../engine";
13
13
  import { UnprocessableError, writeFailure } from "../../errors";
14
- import { createEntityTable, setupTestStack, type TestStack, TestUsers } from "../../stack";
14
+ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
15
15
 
16
16
  // Two entities: `bag` (outer) + `secret` (inner). The outer handler calls
17
17
  // the inner via ctx.queryAs / ctx.writeAs. We verify:
@@ -144,8 +144,8 @@ const bridgeFeature = defineFeature("ctxbridge", (r) => {
144
144
 
145
145
  beforeAll(async () => {
146
146
  stack = await setupTestStack({ features: [bridgeFeature] });
147
- await createEntityTable(stack.db, bagEntity);
148
- await createEntityTable(stack.db, secretEntity);
147
+ await unsafeCreateEntityTable(stack.db, bagEntity);
148
+ await unsafeCreateEntityTable(stack.db, secretEntity);
149
149
  });
150
150
 
151
151
  afterAll(async () => {
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
  import { z } from "zod";
3
3
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
4
+ import type { TenantId } from "../../engine/types/identifiers";
4
5
  import { createTestUser } from "../../stack";
5
6
  import { createDispatcher } from "../dispatcher";
6
7
 
@@ -321,6 +322,78 @@ describe("dispatcher feature-gate", () => {
321
322
  items: [],
322
323
  });
323
324
  });
325
+
326
+ test("Sprint 8a: per-tenant gating — Tenant A passes, Tenant B gets feature_disabled", async () => {
327
+ // Beweist die Phase-1-Architektur: dispatcher ruft effectiveFeatures
328
+ // mit user.tenantId, resolver kann pro Tenant unterschiedliche Sets
329
+ // returnen → Tier-A sieht feature, Tier-B nicht.
330
+ const registry = createRegistry([toggled()]);
331
+ const tenantA = "00000000-0000-4000-8000-0000000000a1" as TenantId;
332
+ const tenantB = "00000000-0000-4000-8000-0000000000b2" as TenantId;
333
+
334
+ const dispatcher = createDispatcher(
335
+ registry,
336
+ {},
337
+ {
338
+ effectiveFeatures: (tenantId) => (tenantId === tenantA ? new Set(["toggled"]) : new Set()),
339
+ },
340
+ );
341
+
342
+ const userA = createTestUser({ id: "u-a", tenantId: tenantA, roles: ["Admin"] });
343
+ const userB = createTestUser({ id: "u-b", tenantId: tenantB, roles: ["Admin"] });
344
+
345
+ await expect(dispatcher.query("toggled:query:widget:list", {}, userA)).resolves.toEqual({
346
+ items: [],
347
+ });
348
+ await expect(dispatcher.query("toggled:query:widget:list", {}, userB)).rejects.toThrow(
349
+ /feature toggled is disabled/,
350
+ );
351
+
352
+ const writeA = await dispatcher.write("toggled:write:widget:create", { name: "from-a" }, userA);
353
+ expect(writeA.isSuccess).toBe(true);
354
+
355
+ const writeB = await dispatcher.write("toggled:write:widget:create", { name: "from-b" }, userB);
356
+ expect(writeB.isSuccess).toBe(false);
357
+ if (!writeB.isSuccess) {
358
+ expect(writeB.error.code).toBe("feature_disabled");
359
+ }
360
+ });
361
+
362
+ test("Sprint 8a: ctx.hasFeature is current-user-scoped", async () => {
363
+ // Pin: hasFeature() in handler-bodies resolves against ctx.user.tenantId,
364
+ // NICHT gegen einen globalen Set. Two tenants call same handler,
365
+ // beide rufen hasFeature("toggled") — A bekommt true, B false.
366
+ const tenantA = "00000000-0000-4000-8000-0000000000a3" as TenantId;
367
+ const tenantB = "00000000-0000-4000-8000-0000000000b4" as TenantId;
368
+
369
+ const probe = defineFeature("probe", (r) => {
370
+ r.queryHandler(
371
+ "check",
372
+ z.object({}).passthrough(),
373
+ async (_event, ctx) => ({ enabled: ctx.hasFeature("toggled") }),
374
+ { access: { openToAll: true } },
375
+ );
376
+ });
377
+ const registry = createRegistry([toggled(), probe]);
378
+ const dispatcher = createDispatcher(
379
+ registry,
380
+ {},
381
+ {
382
+ effectiveFeatures: (tenantId) =>
383
+ tenantId === tenantA ? new Set(["toggled", "probe"]) : new Set(["probe"]),
384
+ },
385
+ );
386
+
387
+ const userA = createTestUser({ id: "u-a2", tenantId: tenantA, roles: ["Admin"] });
388
+ const userB = createTestUser({ id: "u-b2", tenantId: tenantB, roles: ["Admin"] });
389
+
390
+ await expect(dispatcher.query("probe:query:check", {}, userA)).resolves.toEqual({
391
+ enabled: true,
392
+ });
393
+ await expect(dispatcher.query("probe:query:check", {}, userB)).resolves.toEqual({
394
+ enabled: false,
395
+ });
396
+ });
324
397
  });
325
398
 
326
399
  describe("write-handler shape guard", () => {
@@ -24,11 +24,11 @@ import { buildDrizzleTable } from "../../db/table-builder";
24
24
  import { createEntity, createTextField, defineFeature } from "../../engine";
25
25
  import { loadAggregate } from "../../event-store";
26
26
  import {
27
- createEntityTable,
28
27
  resetEventStore,
29
28
  setupTestStack,
30
29
  type TestStack,
31
30
  TestUsers,
31
+ unsafeCreateEntityTable,
32
32
  } from "../../stack";
33
33
 
34
34
  // --- Entity ---
@@ -175,7 +175,7 @@ beforeAll(async () => {
175
175
  features: [shippingFeature],
176
176
  systemHooks: [],
177
177
  });
178
- await createEntityTable(stack.db, shipmentEntity, "domain-shipment");
178
+ await unsafeCreateEntityTable(stack.db, shipmentEntity, "domain-shipment");
179
179
  });
180
180
 
181
181
  afterAll(async () => {
@@ -3,12 +3,12 @@ import { z } from "zod";
3
3
  import { createEventStoreExecutor } from "../../db/event-store-executor";
4
4
  import { defineFeature, type SaveContext } from "../../engine";
5
5
  import {
6
- createEntityTable,
7
6
  createTestRedis,
8
7
  setupTestStack,
9
8
  type TestRedis,
10
9
  type TestStack,
11
10
  TestUsers,
11
+ unsafeCreateEntityTable,
12
12
  } from "../../stack";
13
13
  import { sharedItemEntity, sharedItemTable } from "../../testing";
14
14
  import { createEventDedup } from "../event-dedup";
@@ -71,7 +71,7 @@ beforeAll(async () => {
71
71
  systemHooks: [],
72
72
  });
73
73
 
74
- await createEntityTable(stack.db, sharedItemEntity, "item");
74
+ await unsafeCreateEntityTable(stack.db, sharedItemEntity, "item");
75
75
  });
76
76
 
77
77
  afterAll(async () => {
@@ -17,11 +17,11 @@ import { z } from "zod";
17
17
  import { defineFeature } from "../../engine";
18
18
  import { eventsTable } from "../../event-store";
19
19
  import {
20
- createEntityTable,
21
20
  resetEventStore,
22
21
  setupTestStack,
23
22
  type TestStack,
24
23
  TestUsers,
24
+ unsafeCreateEntityTable,
25
25
  } from "../../stack";
26
26
  import { sharedWidgetEntity } from "../../testing";
27
27
  import { generateId } from "../../utils";
@@ -120,7 +120,7 @@ beforeAll(async () => {
120
120
  features: [emitterFeature, neighborFeature],
121
121
  systemHooks: [],
122
122
  });
123
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
123
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
124
124
  });
125
125
 
126
126
  afterEach(async () => {
@@ -25,11 +25,11 @@ import {
25
25
  RecordingTracer,
26
26
  } from "../../observability";
27
27
  import {
28
- createEntityTable,
29
28
  resetEventStore,
30
29
  setupTestStack,
31
30
  type TestStack,
32
31
  TestUsers,
32
+ unsafeCreateEntityTable,
33
33
  } from "../../stack";
34
34
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
35
35
 
@@ -85,7 +85,7 @@ beforeAll(async () => {
85
85
  features: [wiringFeature],
86
86
  systemHooks: [],
87
87
  });
88
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
88
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
89
89
  tdb = createTenantDb(stack.db, admin.tenantId);
90
90
  });
91
91
 
@@ -181,7 +181,7 @@ describe("E.1 — consumer-lag metric", () => {
181
181
  observability: recordingProvider,
182
182
  });
183
183
  try {
184
- await createEntityTable(recStack.db, sharedWidgetEntity, "widget");
184
+ await unsafeCreateEntityTable(recStack.db, sharedWidgetEntity, "widget");
185
185
  const recTdb = createTenantDb(recStack.db, admin.tenantId);
186
186
  await executor.create({ name: "lag-check" }, admin, recTdb);
187
187
 
@@ -30,11 +30,11 @@ import {
30
30
  getConsumerState,
31
31
  } from "../../pipeline";
32
32
  import {
33
- createEntityTable,
34
33
  resetEventStore,
35
34
  setupTestStack,
36
35
  type TestStack,
37
36
  TestUsers,
37
+ unsafeCreateEntityTable,
38
38
  } from "../../stack";
39
39
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
40
40
  import { generateId } from "../../utils";
@@ -63,7 +63,7 @@ beforeAll(async () => {
63
63
  features: [multiFeature],
64
64
  systemHooks: [],
65
65
  });
66
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
66
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
67
67
  tdb = createTenantDb(stack.db, admin.tenantId);
68
68
  });
69
69
 
@@ -15,7 +15,7 @@ import { afterAll, beforeAll, describe, expect, test } from "vitest";
15
15
  import { createEventStoreExecutor } from "../../db/event-store-executor";
16
16
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
17
17
  import { defineFeature } from "../../engine";
18
- import { createEntityTable, setupTestStack, type TestStack, TestUsers } from "../../stack";
18
+ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
19
19
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
20
20
 
21
21
  // --- Fixture ---
@@ -48,7 +48,7 @@ beforeAll(async () => {
48
48
  features: [listenFeature],
49
49
  systemHooks: [],
50
50
  });
51
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
51
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
52
52
  tdb = createTenantDb(stack.db, admin.tenantId);
53
53
  });
54
54
 
@@ -26,11 +26,11 @@ import {
26
26
  skipPoisonEvent,
27
27
  } from "../../pipeline";
28
28
  import {
29
- createEntityTable,
30
29
  resetEventStore,
31
30
  setupTestStack,
32
31
  type TestStack,
33
32
  TestUsers,
33
+ unsafeCreateEntityTable,
34
34
  } from "../../stack";
35
35
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
36
36
 
@@ -71,7 +71,7 @@ beforeAll(async () => {
71
71
  features: [recoveryFeature],
72
72
  systemHooks: [],
73
73
  });
74
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
74
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
75
75
  tdb = createTenantDb(stack.db, admin.tenantId);
76
76
  });
77
77
 
@@ -27,11 +27,11 @@ import {
27
27
  } from "../../observability";
28
28
  import { ConsumerLagError, eventConsumerStateTable, pruneEvents } from "../../pipeline";
29
29
  import {
30
- createEntityTable,
31
30
  resetEventStore,
32
31
  setupTestStack,
33
32
  type TestStack,
34
33
  TestUsers,
34
+ unsafeCreateEntityTable,
35
35
  } from "../../stack";
36
36
  import { sharedWidgetEntity } from "../../testing";
37
37
  import { generateId } from "../../utils";
@@ -62,7 +62,7 @@ beforeAll(async () => {
62
62
  features: [auditFeature],
63
63
  systemHooks: [],
64
64
  });
65
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
65
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
66
66
  });
67
67
 
68
68
  afterEach(async () => {
@@ -179,7 +179,7 @@ describe("Second audit — LISTEN gauge", () => {
179
179
  observability: recordingProvider,
180
180
  });
181
181
  try {
182
- await createEntityTable(recStack.db, sharedWidgetEntity, "widget");
182
+ await unsafeCreateEntityTable(recStack.db, sharedWidgetEntity, "widget");
183
183
 
184
184
  await recStack.eventDispatcher?.start();
185
185
  try {
@@ -233,7 +233,7 @@ describe("Second audit — LISTEN gauge", () => {
233
233
  observability: recordingProvider,
234
234
  });
235
235
  try {
236
- await createEntityTable(recStack.db, sharedWidgetEntity, "widget");
236
+ await unsafeCreateEntityTable(recStack.db, sharedWidgetEntity, "widget");
237
237
 
238
238
  await recStack.eventDispatcher?.start();
239
239
  try {
@@ -26,12 +26,12 @@ import { defineFeature, type FeatureDefinition } from "../../engine";
26
26
  import type { StoredEvent } from "../../event-store";
27
27
  import { eventConsumerStateTable, getAllConsumerProgress, getConsumerState } from "../../pipeline";
28
28
  import {
29
- createEntityTable,
30
- pushTables,
31
29
  resetEventStore,
32
30
  setupTestStack,
33
31
  type TestStack,
34
32
  TestUsers,
33
+ unsafeCreateEntityTable,
34
+ unsafePushTables,
35
35
  } from "../../stack";
36
36
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
37
37
 
@@ -98,8 +98,8 @@ beforeAll(async () => {
98
98
  // hook chain. SSE / search are irrelevant to cursor behaviour.
99
99
  systemHooks: [],
100
100
  });
101
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
102
- await pushTables(stack.db, { subscriberLog: subscriberLogTable });
101
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
102
+ await unsafePushTables(stack.db, { subscriberLog: subscriberLogTable });
103
103
  tdb = createTenantDb(stack.db, admin.tenantId);
104
104
  });
105
105
 
@@ -23,11 +23,11 @@ import {
23
23
  pruneEvents,
24
24
  } from "../../pipeline";
25
25
  import {
26
- createEntityTable,
27
26
  resetEventStore,
28
27
  setupTestStack,
29
28
  type TestStack,
30
29
  TestUsers,
30
+ unsafeCreateEntityTable,
31
31
  } from "../../stack";
32
32
  import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
33
33
  import { generateId } from "../../utils";
@@ -61,7 +61,7 @@ beforeAll(async () => {
61
61
  features: [retentionFeature],
62
62
  systemHooks: [],
63
63
  });
64
- await createEntityTable(stack.db, sharedWidgetEntity, "widget");
64
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
65
65
  tdb = createTenantDb(stack.db, admin.tenantId);
66
66
  });
67
67
 
@@ -15,7 +15,7 @@ import { buildDrizzleTable } from "../../db/table-builder";
15
15
  import { createEntity, createTextField, defineFeature } from "../../engine";
16
16
  import { UnprocessableError, writeFailure } from "../../errors";
17
17
  import { loadAggregate } from "../../event-store";
18
- import { createEntityTable, setupTestStack, type TestStack, TestUsers } from "../../stack";
18
+ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
19
19
 
20
20
  // --- Feature ---
21
21
 
@@ -136,7 +136,7 @@ const admin = TestUsers.admin;
136
136
 
137
137
  beforeAll(async () => {
138
138
  stack = await setupTestStack({ features: [cartFeature], systemHooks: [] });
139
- await createEntityTable(stack.db, cartEntity, "f4wCart");
139
+ await unsafeCreateEntityTable(stack.db, cartEntity, "f4wCart");
140
140
  });
141
141
 
142
142
  afterAll(async () => {