@cosmicdrift/kumiko-bundled-features 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.
- package/LICENSE +57 -0
- package/package.json +3 -3
- package/src/audit/__tests__/audit.integration.ts +2 -2
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +5 -5
- package/src/auth-email-password/__tests__/account-lockout.integration.ts +5 -5
- package/src/auth-email-password/__tests__/auth-claims.integration.ts +14 -14
- package/src/auth-email-password/__tests__/auth.integration.ts +8 -8
- package/src/auth-email-password/__tests__/email-verification.integration.ts +5 -5
- package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +5 -5
- package/src/auth-email-password/__tests__/invite-flow.integration.ts +6 -6
- package/src/auth-email-password/__tests__/multi-roles.integration.ts +5 -5
- package/src/auth-email-password/__tests__/password-reset.integration.ts +5 -5
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +5 -5
- package/src/auth-email-password/__tests__/seed-admin.integration.ts +5 -5
- package/src/auth-email-password/__tests__/session-callbacks.integration.ts +5 -5
- package/src/auth-email-password/__tests__/signup-flow.integration.ts +6 -6
- package/src/cap-counter/__tests__/cap-counter.integration.ts +2 -2
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +2 -2
- package/src/config/__tests__/config.integration.ts +2 -2
- package/src/delivery/__tests__/delivery-events.integration.ts +4 -4
- package/src/delivery/__tests__/delivery.integration.ts +4 -4
- package/src/feature-toggles/__tests__/feature-toggles.integration.ts +5 -5
- package/src/feature-toggles/__tests__/registered-system-tenant.test.ts +84 -0
- package/src/feature-toggles/handlers/registered.query.ts +7 -2
- package/src/file-foundation/__tests__/file-foundation.integration.ts +4 -4
- package/src/jobs/__tests__/job-system-user.integration.ts +3 -3
- package/src/jobs/__tests__/jobs-events.integration.ts +2 -2
- package/src/jobs/__tests__/jobs-feature.integration.ts +3 -3
- package/src/legal-pages/__tests__/legal-pages.integration.ts +3 -3
- package/src/mail-foundation/__tests__/mail-foundation.integration.ts +4 -4
- package/src/secrets/__tests__/rotate.integration.ts +2 -2
- package/src/secrets/__tests__/secrets-events.integration.ts +2 -2
- package/src/secrets/__tests__/secrets.integration.ts +2 -2
- package/src/sessions/__tests__/cleanup.integration.ts +2 -2
- package/src/sessions/__tests__/password-auto-revoke.integration.ts +6 -6
- package/src/sessions/__tests__/sessions.integration.ts +6 -6
- package/src/tenant/__tests__/multi-tenant.integration.ts +4 -4
- package/src/tenant/__tests__/seed-testing.integration.ts +4 -4
- package/src/tenant/__tests__/tenant.integration.ts +4 -4
- package/src/tenant/seeding.ts +12 -1
- package/src/text-content/README.md +6 -2
- package/src/text-content/__tests__/text-content.integration.ts +2 -2
- package/src/tier-engine/__tests__/resolver.integration.ts +183 -0
- package/src/tier-engine/__tests__/tier-engine.integration.ts +5 -5
- package/src/tier-engine/feature.ts +345 -48
- package/src/tier-engine/index.ts +5 -1
- package/src/user/__tests__/seed-testing.integration.ts +4 -4
- package/src/user/__tests__/user.integration.ts +2 -2
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
17
17
|
import { createEventDispatcher, type EventConsumer } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
18
18
|
import {
|
|
19
|
-
createEntityTable,
|
|
20
19
|
createTestUser,
|
|
21
|
-
pushTables,
|
|
22
20
|
setupTestStack,
|
|
23
21
|
type TestStack,
|
|
22
|
+
unsafeCreateEntityTable,
|
|
23
|
+
unsafePushTables,
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
25
25
|
import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
|
|
26
26
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
@@ -165,12 +165,12 @@ beforeAll(async () => {
|
|
|
165
165
|
systemHooks: [],
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
await
|
|
168
|
+
await unsafePushTables(stack.db, { globalFeatureStateTable });
|
|
169
169
|
// widgetTrackerTable is auto-pushed by setupTestStack because it's the
|
|
170
170
|
// projection-table of a registered r.multiStreamProjection — manually
|
|
171
171
|
// pushing again would re-run the CREATE TABLE and fail duplicate.
|
|
172
|
-
await
|
|
173
|
-
await
|
|
172
|
+
await unsafeCreateEntityTable(stack.db, widgetEntity);
|
|
173
|
+
await unsafeCreateEntityTable(stack.db, widgetAuditEntity, "widget-audit");
|
|
174
174
|
|
|
175
175
|
runtime = new GlobalFeatureToggleRuntime(stack.db, stack.registry);
|
|
176
176
|
await runtime.initialize();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Sprint 8a recipe-test pin: registered.query nutzt SYSTEM_TENANT_ID
|
|
2
|
+
// statt event.user.tenantId. Operator-tooling muss PLATTFORM-truth
|
|
3
|
+
// sehen, nicht den eigenen tier-cut. Convention ist in DispatcherOptions.
|
|
4
|
+
// effectiveFeatures dokumentiert; dieser test pinst sie damit ein
|
|
5
|
+
// future-refactor (z.B. mechanisches sed oder copy-paste) sie nicht
|
|
6
|
+
// silent zurückdreht zu event.user.tenantId.
|
|
7
|
+
//
|
|
8
|
+
// Pure unit-test ist nicht möglich weil registered.query einen DB-select
|
|
9
|
+
// auf globalFeatureStateTable macht BEVOR der effectiveFeatures-call
|
|
10
|
+
// läuft. Wir mocken den ctx.db.select-pfad damit der handler komplett
|
|
11
|
+
// durchläuft. Die Convention-Pin ist die einzige Aussage des tests —
|
|
12
|
+
// echtes integration-Verhalten deckt feature-toggles.integration.ts ab.
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createEntity,
|
|
16
|
+
createRegistry,
|
|
17
|
+
createTextField,
|
|
18
|
+
defineFeature,
|
|
19
|
+
SYSTEM_TENANT_ID,
|
|
20
|
+
type TenantId,
|
|
21
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
22
|
+
import { createDispatcher } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
23
|
+
import { createTestUser } from "@cosmicdrift/kumiko-framework/stack";
|
|
24
|
+
import { describe, expect, test } from "vitest";
|
|
25
|
+
import { createFeatureTogglesFeature } from "../feature";
|
|
26
|
+
import type { GlobalFeatureToggleRuntime } from "../toggle-runtime";
|
|
27
|
+
|
|
28
|
+
describe("Sprint 8a: registered.query SYSTEM_TENANT_ID convention", () => {
|
|
29
|
+
test("ruft effectiveFeatures mit SYSTEM_TENANT_ID, nicht mit caller-tenantId", async () => {
|
|
30
|
+
const observed: string[] = [];
|
|
31
|
+
|
|
32
|
+
const dummy = defineFeature("dummy", (r) => {
|
|
33
|
+
r.entity("widget", createEntity({ table: "Widgets", fields: { name: createTextField() } }));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const runtime: GlobalFeatureToggleRuntime | null = null;
|
|
37
|
+
const featureToggles = createFeatureTogglesFeature({
|
|
38
|
+
getRuntime: () => {
|
|
39
|
+
if (!runtime) throw new Error("runtime not initialized");
|
|
40
|
+
return runtime;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const registry = createRegistry([dummy, featureToggles]);
|
|
45
|
+
|
|
46
|
+
// Mock ctx.db.select-chain damit der handler durch den DB-Pfad
|
|
47
|
+
// kommt. Wir liefern leere overrides (.from() returnt []), das
|
|
48
|
+
// genügt — registered.query iteriert dann über registry.features
|
|
49
|
+
// und ruft ctx.effectiveFeatures, was unser observable ist.
|
|
50
|
+
const mockDb = {
|
|
51
|
+
select: () => ({ from: async () => [] as unknown[] }),
|
|
52
|
+
} as unknown as Parameters<typeof createDispatcher>[1]["db"];
|
|
53
|
+
|
|
54
|
+
const callerTenant = "00000000-0000-4000-8000-0000000000c1" as TenantId;
|
|
55
|
+
|
|
56
|
+
const dispatcher = createDispatcher(
|
|
57
|
+
registry,
|
|
58
|
+
{ db: mockDb },
|
|
59
|
+
{
|
|
60
|
+
effectiveFeatures: (tenantId) => {
|
|
61
|
+
observed.push(tenantId);
|
|
62
|
+
return new Set(["dummy", "feature-toggles"]);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const admin = createTestUser({
|
|
68
|
+
id: "admin-1",
|
|
69
|
+
tenantId: callerTenant,
|
|
70
|
+
roles: ["SystemAdmin"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await dispatcher.query("feature-toggles:query:registered", {}, admin);
|
|
74
|
+
|
|
75
|
+
// Pin: registered.query call führt zu MINDESTENS zwei effectiveFeatures-
|
|
76
|
+
// calls:
|
|
77
|
+
// 1. dispatcher's checkFeatureEnabled (mit user.tenantId = callerTenant)
|
|
78
|
+
// 2. registered.query handler-body (mit SYSTEM_TENANT_ID)
|
|
79
|
+
// Wenn ein future-refactor SYSTEM_TENANT_ID zu event.user.tenantId zurück-
|
|
80
|
+
// dreht, fehlt der zweite call und dieser test fail't.
|
|
81
|
+
expect(observed).toContain(callerTenant);
|
|
82
|
+
expect(observed).toContain(SYSTEM_TENANT_ID);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { globalFeatureStateTable } from "../global-feature-state-table";
|
|
4
4
|
|
|
@@ -24,7 +24,12 @@ export const registeredQuery = defineQueryHandler({
|
|
|
24
24
|
.from(globalFeatureStateTable)) as OverrideRow[];
|
|
25
25
|
const overrides = new Map(overrideRows.map((r) => [r.featureName, r.enabled]));
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// SystemAdmin operator-tooling: das listing soll die PLATTFORM-truth
|
|
28
|
+
// zeigen (alle features im Registry), nicht den eigenen tier-cut.
|
|
29
|
+
// Sprint-8a per-tenant signature → wir rufen mit SYSTEM_TENANT_ID,
|
|
30
|
+
// App-resolver returnt union-of-all-tier-features. Sentinel-Convention
|
|
31
|
+
// dokumentiert in DispatcherOptions.effectiveFeatures.
|
|
32
|
+
const effective = ctx.effectiveFeatures?.(SYSTEM_TENANT_ID);
|
|
28
33
|
|
|
29
34
|
const items = [];
|
|
30
35
|
for (const feature of ctx.registry.features.values()) {
|
|
@@ -8,12 +8,12 @@ import { defineFeature, defineWriteHandler } from "@cosmicdrift/kumiko-framework
|
|
|
8
8
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
9
9
|
import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
10
10
|
import {
|
|
11
|
-
createEntityTable,
|
|
12
11
|
createTestUser,
|
|
13
|
-
pushTables,
|
|
14
12
|
setupTestStack,
|
|
15
13
|
type TestStack,
|
|
16
14
|
testTenantId,
|
|
15
|
+
unsafeCreateEntityTable,
|
|
16
|
+
unsafePushTables,
|
|
17
17
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
18
18
|
import {
|
|
19
19
|
createMutableMasterKeyProvider,
|
|
@@ -102,8 +102,8 @@ beforeAll(async () => {
|
|
|
102
102
|
});
|
|
103
103
|
db = stack.db;
|
|
104
104
|
|
|
105
|
-
await
|
|
106
|
-
await
|
|
105
|
+
await unsafeCreateEntityTable(db, tenantEntity);
|
|
106
|
+
await unsafePushTables(db, { configValuesTable, tenant_secrets: tenantSecretsTable });
|
|
107
107
|
await createEventsTable(db);
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -20,10 +20,10 @@ import { createJobRunner, type JobRunner } from "@cosmicdrift/kumiko-framework/j
|
|
|
20
20
|
import {
|
|
21
21
|
createTestDb,
|
|
22
22
|
createTestRedis,
|
|
23
|
-
pushTables,
|
|
24
23
|
type TestDb,
|
|
25
24
|
type TestRedis,
|
|
26
25
|
TestUsers,
|
|
26
|
+
unsafePushTables,
|
|
27
27
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
28
28
|
import { bridgeStub, sleep } from "@cosmicdrift/kumiko-framework/testing";
|
|
29
29
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
@@ -99,11 +99,11 @@ beforeAll(async () => {
|
|
|
99
99
|
testRedis = await createTestRedis();
|
|
100
100
|
db = testDb.db;
|
|
101
101
|
|
|
102
|
-
await
|
|
102
|
+
await unsafePushTables(db, { configValuesTable });
|
|
103
103
|
// Post-ES config writes go through the event-store executor, which needs
|
|
104
104
|
// the framework events + archived-streams tables to exist before the
|
|
105
105
|
// first append. setupTestStack provisions them automatically; this test
|
|
106
|
-
// builds its DB manually (createTestDb +
|
|
106
|
+
// builds its DB manually (createTestDb + unsafePushTables), so we do it here.
|
|
107
107
|
await createEventsTable(db);
|
|
108
108
|
await createArchivedStreamsTable(db);
|
|
109
109
|
|
|
@@ -13,9 +13,9 @@ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/ev
|
|
|
13
13
|
import {
|
|
14
14
|
createTestDb,
|
|
15
15
|
createTestRedis,
|
|
16
|
-
pushTables,
|
|
17
16
|
type TestDb,
|
|
18
17
|
type TestRedis,
|
|
18
|
+
unsafePushTables,
|
|
19
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
20
20
|
import { eq } from "drizzle-orm";
|
|
21
21
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
@@ -37,7 +37,7 @@ beforeAll(async () => {
|
|
|
37
37
|
testDb = await createTestDb();
|
|
38
38
|
testRedis = await createTestRedis();
|
|
39
39
|
const registry = createRegistry([createJobsFeature()]);
|
|
40
|
-
await
|
|
40
|
+
await unsafePushTables(testDb.db, { jobRunsTable, jobRunLogsTable });
|
|
41
41
|
await createEventsTable(testDb.db);
|
|
42
42
|
logger = createJobRunLogger({ db: testDb.db, registry });
|
|
43
43
|
});
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
createTestDb,
|
|
12
12
|
createTestRedis,
|
|
13
13
|
createTestUser,
|
|
14
|
-
pushTables,
|
|
15
14
|
type TestDb,
|
|
16
15
|
type TestRedis,
|
|
17
16
|
TestUsers,
|
|
17
|
+
unsafePushTables,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
19
19
|
import { sleep } from "@cosmicdrift/kumiko-framework/testing";
|
|
20
20
|
import type { Hono } from "hono";
|
|
@@ -72,10 +72,10 @@ beforeAll(async () => {
|
|
|
72
72
|
const registry = createRegistry([appFeature, jobsFeature]);
|
|
73
73
|
|
|
74
74
|
// jobRuns + jobRunLogs are projection tables (auto-pushed by
|
|
75
|
-
//
|
|
75
|
+
// unsafePushTables via the registry-declared inline projections in jobs-feature).
|
|
76
76
|
// We need events + archived_streams for the ES writes the job-runner's
|
|
77
77
|
// logger does.
|
|
78
|
-
await
|
|
78
|
+
await unsafePushTables(db, { jobRunsTable, jobRunLogsTable });
|
|
79
79
|
await createEventsTable(db);
|
|
80
80
|
|
|
81
81
|
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
@@ -9,9 +9,9 @@ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
|
9
9
|
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
11
11
|
import {
|
|
12
|
-
createEntityTable,
|
|
13
12
|
setupTestStack,
|
|
14
13
|
type TestStack,
|
|
14
|
+
unsafeCreateEntityTable,
|
|
15
15
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
16
16
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
17
17
|
import { createLegalPagesFeature, runLegalPagesBootCheck } from "../feature";
|
|
@@ -36,7 +36,7 @@ beforeAll(async () => {
|
|
|
36
36
|
}),
|
|
37
37
|
});
|
|
38
38
|
db = stack.db;
|
|
39
|
-
await
|
|
39
|
+
await unsafeCreateEntityTable(db, textBlockEntity);
|
|
40
40
|
await createEventsTable(db);
|
|
41
41
|
|
|
42
42
|
// Seed legal blocks für SYSTEM_TENANT in DE
|
|
@@ -219,7 +219,7 @@ describe("legal-pages :: SYSTEM_TENANT-routing (production-bug-regression)", ()
|
|
|
219
219
|
}),
|
|
220
220
|
});
|
|
221
221
|
try {
|
|
222
|
-
await
|
|
222
|
+
await unsafeCreateEntityTable(hostScopedStack.db, textBlockEntity);
|
|
223
223
|
await createEventsTable(hostScopedStack.db);
|
|
224
224
|
|
|
225
225
|
// Block NUR im SYSTEM_TENANT seeden — NICHT im otherTenantId
|
|
@@ -14,12 +14,12 @@ import { defineFeature, defineWriteHandler } from "@cosmicdrift/kumiko-framework
|
|
|
14
14
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
15
15
|
import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
16
16
|
import {
|
|
17
|
-
createEntityTable,
|
|
18
17
|
createTestUser,
|
|
19
|
-
pushTables,
|
|
20
18
|
setupTestStack,
|
|
21
19
|
type TestStack,
|
|
22
20
|
testTenantId,
|
|
21
|
+
unsafeCreateEntityTable,
|
|
22
|
+
unsafePushTables,
|
|
23
23
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
24
24
|
import {
|
|
25
25
|
createMutableMasterKeyProvider,
|
|
@@ -106,8 +106,8 @@ beforeAll(async () => {
|
|
|
106
106
|
});
|
|
107
107
|
db = stack.db;
|
|
108
108
|
|
|
109
|
-
await
|
|
110
|
-
await
|
|
109
|
+
await unsafeCreateEntityTable(db, tenantEntity);
|
|
110
|
+
await unsafePushTables(db, { configValuesTable, tenant_secrets: tenantSecretsTable });
|
|
111
111
|
await createEventsTable(db);
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
13
13
|
import {
|
|
14
14
|
createTestUser,
|
|
15
|
-
pushTables,
|
|
16
15
|
setupTestStack,
|
|
17
16
|
type TestStack,
|
|
17
|
+
unsafePushTables,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
19
19
|
import { eq, sql } from "drizzle-orm";
|
|
20
20
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
@@ -76,7 +76,7 @@ beforeAll(async () => {
|
|
|
76
76
|
secrets: createSecretsContext({ db, masterKeyProvider: seedProvider }),
|
|
77
77
|
}),
|
|
78
78
|
});
|
|
79
|
-
await
|
|
79
|
+
await unsafePushTables(stack.db, {
|
|
80
80
|
tenant_secrets: tenantSecretsTable,
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
14
14
|
import {
|
|
15
15
|
createTestUser,
|
|
16
|
-
pushTables,
|
|
17
16
|
setupTestStack,
|
|
18
17
|
type TestStack,
|
|
18
|
+
unsafePushTables,
|
|
19
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
20
20
|
import { eq } from "drizzle-orm";
|
|
21
21
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
@@ -50,7 +50,7 @@ beforeAll(async () => {
|
|
|
50
50
|
secrets: createSecretsContext({ db, masterKeyProvider: provider }),
|
|
51
51
|
}),
|
|
52
52
|
});
|
|
53
|
-
await
|
|
53
|
+
await unsafePushTables(stack.db, { tenantSecretsTable });
|
|
54
54
|
await createEventsTable(stack.db);
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
} from "@cosmicdrift/kumiko-framework/secrets";
|
|
13
13
|
import {
|
|
14
14
|
createTestUser,
|
|
15
|
-
pushTables,
|
|
16
15
|
setupTestStack,
|
|
17
16
|
type TestStack,
|
|
17
|
+
unsafePushTables,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
19
19
|
import { and, eq } from "drizzle-orm";
|
|
20
20
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
@@ -49,7 +49,7 @@ beforeAll(async () => {
|
|
|
49
49
|
// table (tenant_secrets) still needs an explicit push here, since it
|
|
50
50
|
// belongs to an ES entity (and entity-tables aren't auto-pushed by
|
|
51
51
|
// setupTestStack).
|
|
52
|
-
await
|
|
52
|
+
await unsafePushTables(stack.db, { tenant_secrets: tenantSecretsTable });
|
|
53
53
|
await createEventsTable(stack.db);
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import type { AppContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
8
|
import {
|
|
9
|
-
createEntityTable,
|
|
10
9
|
setupTestStack,
|
|
11
10
|
type TestStack,
|
|
12
11
|
testTenantId,
|
|
12
|
+
unsafeCreateEntityTable,
|
|
13
13
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
14
14
|
import { sql } from "drizzle-orm";
|
|
15
15
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
@@ -38,7 +38,7 @@ beforeAll(async () => {
|
|
|
38
38
|
stack = await setupTestStack({
|
|
39
39
|
features: [createSessionsFeature()],
|
|
40
40
|
});
|
|
41
|
-
await
|
|
41
|
+
await unsafeCreateEntityTable(stack.db, userSessionEntity);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
afterAll(async () => {
|
|
@@ -2,11 +2,11 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import {
|
|
5
|
-
createEntityTable,
|
|
6
|
-
pushTables,
|
|
7
5
|
setupTestStack,
|
|
8
6
|
type TestStack,
|
|
9
7
|
testTenantId,
|
|
8
|
+
unsafeCreateEntityTable,
|
|
9
|
+
unsafePushTables,
|
|
10
10
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
11
11
|
import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
|
@@ -77,10 +77,10 @@ beforeAll(async () => {
|
|
|
77
77
|
callbacks.set(createSessionCallbacks({ db: stack.db }));
|
|
78
78
|
h = makeSessionHelpers(stack, TENANT);
|
|
79
79
|
|
|
80
|
-
await
|
|
81
|
-
await
|
|
82
|
-
await
|
|
83
|
-
await
|
|
80
|
+
await unsafeCreateEntityTable(stack.db, userEntity);
|
|
81
|
+
await unsafeCreateEntityTable(stack.db, tenantEntity);
|
|
82
|
+
await unsafeCreateEntityTable(stack.db, userSessionEntity);
|
|
83
|
+
await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
afterAll(async () => {
|
|
@@ -2,11 +2,11 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import {
|
|
5
|
-
createEntityTable,
|
|
6
|
-
pushTables,
|
|
7
5
|
setupTestStack,
|
|
8
6
|
type TestStack,
|
|
9
7
|
testTenantId,
|
|
8
|
+
unsafeCreateEntityTable,
|
|
9
|
+
unsafePushTables,
|
|
10
10
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
11
11
|
import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
|
|
12
12
|
import { and, eq } from "drizzle-orm";
|
|
@@ -64,10 +64,10 @@ beforeAll(async () => {
|
|
|
64
64
|
callbacks.set(createSessionCallbacks({ db: stack.db }));
|
|
65
65
|
h = makeSessionHelpers(stack, TENANT);
|
|
66
66
|
|
|
67
|
-
await
|
|
68
|
-
await
|
|
69
|
-
await
|
|
70
|
-
await
|
|
67
|
+
await unsafeCreateEntityTable(stack.db, userEntity);
|
|
68
|
+
await unsafeCreateEntityTable(stack.db, tenantEntity);
|
|
69
|
+
await unsafeCreateEntityTable(stack.db, userSessionEntity);
|
|
70
|
+
await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
afterAll(async () => {
|
|
@@ -9,15 +9,15 @@ import {
|
|
|
9
9
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
10
10
|
import { createJobRunner, type JobRunner } from "@cosmicdrift/kumiko-framework/jobs";
|
|
11
11
|
import {
|
|
12
|
-
createEntityTable,
|
|
13
12
|
createTestDb,
|
|
14
13
|
createTestRedis,
|
|
15
14
|
createTestUser,
|
|
16
|
-
pushTables,
|
|
17
15
|
type TestDb,
|
|
18
16
|
type TestRedis,
|
|
19
17
|
TestUsers,
|
|
20
18
|
testTenantId,
|
|
19
|
+
unsafeCreateEntityTable,
|
|
20
|
+
unsafePushTables,
|
|
21
21
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
22
22
|
import { bridgeStub, sleep } from "@cosmicdrift/kumiko-framework/testing";
|
|
23
23
|
import type { Hono } from "hono";
|
|
@@ -60,8 +60,8 @@ beforeAll(async () => {
|
|
|
60
60
|
testRedis = await createTestRedis();
|
|
61
61
|
db = testDb.db;
|
|
62
62
|
|
|
63
|
-
await
|
|
64
|
-
await
|
|
63
|
+
await unsafeCreateEntityTable(db, tenantEntity);
|
|
64
|
+
await unsafePushTables(db, { tenantMembershipsTable, configValuesTable });
|
|
65
65
|
await createEventsTable(db);
|
|
66
66
|
|
|
67
67
|
const configFeature = createConfigFeature();
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
15
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
16
16
|
import {
|
|
17
|
-
createEntityTable,
|
|
18
17
|
createTestUser,
|
|
19
|
-
pushTables,
|
|
20
18
|
setupTestStack,
|
|
21
19
|
type TestStack,
|
|
22
20
|
TestUsers,
|
|
21
|
+
unsafeCreateEntityTable,
|
|
22
|
+
unsafePushTables,
|
|
23
23
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
24
24
|
import { and, eq } from "drizzle-orm";
|
|
25
25
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
@@ -43,8 +43,8 @@ beforeAll(async () => {
|
|
|
43
43
|
features: [createConfigFeature(), createTenantFeature()],
|
|
44
44
|
extraContext: { configResolver: resolver },
|
|
45
45
|
});
|
|
46
|
-
await
|
|
47
|
-
await
|
|
46
|
+
await unsafeCreateEntityTable(stack.db, tenantEntity);
|
|
47
|
+
await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
|
|
48
48
|
await createEventsTable(stack.db);
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -2,12 +2,12 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { createEncryptionProvider, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
4
4
|
import {
|
|
5
|
-
createEntityTable,
|
|
6
5
|
createTestUser,
|
|
7
|
-
pushTables,
|
|
8
6
|
setupTestStack,
|
|
9
7
|
type TestStack,
|
|
10
8
|
TestUsers,
|
|
9
|
+
unsafeCreateEntityTable,
|
|
10
|
+
unsafePushTables,
|
|
11
11
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
12
12
|
import { expectErrorIncludes, rolesOf } from "@cosmicdrift/kumiko-framework/testing";
|
|
13
13
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
@@ -42,8 +42,8 @@ beforeAll(async () => {
|
|
|
42
42
|
});
|
|
43
43
|
db = stack.db;
|
|
44
44
|
|
|
45
|
-
await
|
|
46
|
-
await
|
|
45
|
+
await unsafeCreateEntityTable(db, tenantEntity);
|
|
46
|
+
await unsafePushTables(db, { configValuesTable });
|
|
47
47
|
await createEventsTable(db);
|
|
48
48
|
});
|
|
49
49
|
|
package/src/tenant/seeding.ts
CHANGED
|
@@ -38,8 +38,9 @@ import {
|
|
|
38
38
|
fetchOne,
|
|
39
39
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
40
40
|
import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
41
|
+
import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
41
42
|
import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
|
|
42
|
-
import { eq } from "drizzle-orm";
|
|
43
|
+
import { eq, max as maxFn } from "drizzle-orm";
|
|
43
44
|
import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table";
|
|
44
45
|
import { tenantEntity, tenantTable } from "./schema/tenant";
|
|
45
46
|
|
|
@@ -94,6 +95,16 @@ export async function seedTenant(db: DbConnection, options: SeedTenantOptions):
|
|
|
94
95
|
const existing = await fetchOne(db, tenantTable, eq(tenantTable["id"], options.id));
|
|
95
96
|
if (existing) return options.id;
|
|
96
97
|
|
|
98
|
+
// Idempotenz: Aggregate kann im Event-Store existieren ohne Projection-Row
|
|
99
|
+
// (Projection-Drift nach rebuild, manuellem DELETE, oder async-lag). Wenn
|
|
100
|
+
// Stream-Version > 0 → kein create() — wäre version_conflict. Caller
|
|
101
|
+
// bekommt die ID, Projection wird beim nächsten Dispatcher-Cycle aufgebaut.
|
|
102
|
+
const [streamRow] = await db
|
|
103
|
+
.select({ v: maxFn(eventsTable.version) })
|
|
104
|
+
.from(eventsTable)
|
|
105
|
+
.where(eq(eventsTable.aggregateId, options.id));
|
|
106
|
+
if ((streamRow?.v ?? 0) > 0) return options.id;
|
|
107
|
+
|
|
97
108
|
const result = await tenantExecutor.create(
|
|
98
109
|
{ id: options.id, key: options.key, name: options.name },
|
|
99
110
|
by,
|
|
@@ -38,12 +38,16 @@ container exits. No auto-heal in production. See
|
|
|
38
38
|
In integration tests (vitest) it's enough to do:
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
|
-
import {
|
|
41
|
+
import { unsafeCreateEntityTable } from "@cosmicdrift/kumiko-framework/stack";
|
|
42
42
|
import { textBlockEntity } from "@cosmicdrift/kumiko-bundled-features/text-content";
|
|
43
43
|
|
|
44
|
-
await
|
|
44
|
+
await unsafeCreateEntityTable(stack.db, textBlockEntity);
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
The `unsafe` prefix is intentional — it bypasses the projection
|
|
48
|
+
registry and is reserved for test setup and framework-internals. Apps
|
|
49
|
+
declare data via `r.entity(...)` everywhere else.
|
|
50
|
+
|
|
47
51
|
## Use cases
|
|
48
52
|
|
|
49
53
|
text-content is generic — anything that's static Markdown text per
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
3
3
|
import {
|
|
4
|
-
createEntityTable,
|
|
5
4
|
createTestUser,
|
|
6
5
|
setupTestStack,
|
|
7
6
|
type TestStack,
|
|
8
7
|
TestUsers,
|
|
8
|
+
unsafeCreateEntityTable,
|
|
9
9
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
10
10
|
import { expectErrorIncludes } from "@cosmicdrift/kumiko-framework/testing";
|
|
11
11
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
@@ -26,7 +26,7 @@ const feature = createTextContentFeature();
|
|
|
26
26
|
beforeAll(async () => {
|
|
27
27
|
stack = await setupTestStack({ features: [feature] });
|
|
28
28
|
db = stack.db;
|
|
29
|
-
await
|
|
29
|
+
await unsafeCreateEntityTable(db, textBlockEntity);
|
|
30
30
|
await createEventsTable(db);
|
|
31
31
|
});
|
|
32
32
|
|