@cosmicdrift/kumiko-framework 0.158.2 → 0.160.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/package.json +7 -2
- package/src/__tests__/consumer-cli.integration.test.ts +110 -0
- package/src/api/__tests__/api.test.ts +65 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +237 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +1 -0
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +150 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +41 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/api-constants.ts +5 -0
- package/src/api/auth-middleware.ts +48 -59
- package/src/api/auth-routes.ts +51 -17
- package/src/api/index.ts +3 -3
- package/src/api/jwt.ts +148 -7
- package/src/api/pii-leak-guard.ts +5 -2
- package/src/api/routes.ts +57 -0
- package/src/api/server.ts +19 -5
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/query.ts +46 -27
- package/src/consumer-cli.ts +87 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +69 -13
- package/src/crypto/blind-index.ts +8 -4
- package/src/crypto/event-pii.ts +1 -0
- package/src/crypto/kms-adapter.ts +2 -118
- package/src/crypto/pii-field-encryption.ts +49 -15
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +396 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +5 -5
- package/src/db/blind-index-cleanup.ts +3 -1
- package/src/db/connection.ts +3 -11
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +8 -19
- package/src/db/encryption.ts +2 -3
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +16 -90
- package/src/db/event-store-executor.ts +4 -96
- package/src/db/queries/backfill-pii.ts +1 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/table-builder.ts +2 -19
- package/src/db/tenant-db.ts +6 -55
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -233
- package/src/engine/__tests__/boot-validator.test.ts +46 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +139 -10
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +28 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/registry-facade-sweep.test.ts +80 -0
- package/src/engine/__tests__/registry.test.ts +40 -0
- package/src/engine/__tests__/store-table.test.ts +12 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/boot-validator/action-wiring.ts +1 -1
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/entity-handler.ts +10 -1
- package/src/engine/boot-validator/entity-list-screens.ts +1 -1
- package/src/engine/boot-validator/gdpr-storage.ts +0 -112
- package/src/engine/boot-validator/index.ts +3 -9
- package/src/engine/boot-validator/screens.ts +1 -1
- package/src/engine/define-feature.ts +2 -0
- package/src/engine/define-handler.ts +11 -91
- package/src/engine/entity-handlers.ts +15 -27
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +11 -1
- package/src/engine/feature-ast/__tests__/parse.test.ts +983 -3
- package/src/engine/feature-ast/__tests__/patch.test.ts +168 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +7 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +9 -0
- package/src/engine/feature-ast/extractors/handlers.ts +19 -2
- package/src/engine/feature-ast/extractors/index.ts +1 -0
- package/src/engine/feature-ast/index.ts +2 -0
- package/src/engine/feature-ast/parse.ts +3 -0
- package/src/engine/feature-ast/patch.ts +2 -0
- package/src/engine/feature-ast/patcher.ts +21 -0
- package/src/engine/feature-ast/patterns.ts +16 -0
- package/src/engine/feature-ast/render.ts +15 -0
- package/src/engine/feature-builder-state.ts +6 -0
- package/src/engine/feature-config-events-jobs.ts +1 -1
- package/src/engine/feature-entity-handlers.ts +36 -2
- package/src/engine/feature-ui-extensions.ts +5 -1
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +14 -0
- package/src/engine/index.ts +5 -2
- package/src/engine/ownership.ts +22 -76
- package/src/engine/pattern-library/__tests__/library.test.ts +9 -0
- package/src/engine/pattern-library/library.ts +2 -0
- package/src/engine/pattern-library/mixed-schemas.ts +37 -0
- package/src/engine/registry-facade.ts +9 -0
- package/src/engine/registry-ingest.ts +10 -0
- package/src/engine/registry-state.ts +3 -0
- package/src/engine/registry-validate.ts +1 -1
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -497
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -976
- package/src/engine/types/fields.ts +2 -697
- package/src/engine/types/handlers.ts +2 -839
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -72
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +66 -33
- package/src/engine/types/nav.ts +2 -67
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -165
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -793
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -129
- package/src/engine/types/workspace.ts +2 -42
- package/src/entrypoint/index.ts +2 -2
- package/src/errors/write-error-info.ts +6 -22
- package/src/event-store/__tests__/event-store.integration.test.ts +31 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +43 -0
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +28 -51
- package/src/event-store/events-schema.ts +1 -10
- package/src/event-store/index.ts +3 -2
- package/src/event-store/snapshot.ts +11 -35
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/provider-resolver.ts +3 -5
- package/src/files/types.ts +5 -54
- package/src/i18n/required-surface-keys.ts +1 -1
- package/src/jobs/__tests__/jobs.integration.test.ts +102 -1
- package/src/logging/types.ts +1 -7
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -58
- package/src/pipeline/__tests__/dispatcher.test.ts +134 -1
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +180 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +208 -0
- package/src/pipeline/dispatch-shared.ts +51 -3
- package/src/pipeline/dispatch-stream.ts +74 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +7 -0
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +28 -3
- package/src/pipeline/event-dispatcher-admin.ts +4 -0
- package/src/pipeline/event-dispatcher-delivery.ts +29 -3
- package/src/pipeline/event-dispatcher.ts +27 -1
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/system-hooks.ts +7 -0
- package/src/rate-limit/resolver.ts +10 -30
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/envelope-cipher.test.ts +2 -30
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/envelope-cipher.ts +17 -45
- package/src/secrets/types.ts +2 -177
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/testing/__tests__/late-bound.test.ts +25 -0
- package/src/testing/__tests__/wait-for.test.ts +53 -0
- package/src/testing/boot-validator-fixture.ts +1 -1
- package/src/testing/file-provider-contract.ts +84 -0
- package/src/testing/handler-context.ts +1 -1
- package/src/testing/index.ts +1 -0
- package/src/time/geo-tz.ts +1 -32
- package/src/time/tz-context.ts +9 -56
- package/src/ui-types/index.ts +7 -7
package/src/engine/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ export { crossTenantOverrideDenied } from "./cross-tenant";
|
|
|
42
42
|
export { defineFeature } from "./define-feature";
|
|
43
43
|
export type {
|
|
44
44
|
QueryHandlerDefinition,
|
|
45
|
+
StreamHandlerDefinition,
|
|
45
46
|
WriteHandlerDefinition,
|
|
46
47
|
WriteHandlerInput,
|
|
47
48
|
} from "./define-handler";
|
|
@@ -177,6 +178,7 @@ export {
|
|
|
177
178
|
checkWriteFieldRoles,
|
|
178
179
|
filterReadFields,
|
|
179
180
|
} from "./field-access";
|
|
181
|
+
export { resolveName, withResponseData } from "./handler-helpers";
|
|
180
182
|
// findForbiddenMembershipRole/isForbiddenMembershipRole/
|
|
181
183
|
// stripForbiddenMembershipRoles/buildSessionRoles are Public API for host
|
|
182
184
|
// apps that build their own membership handlers. FORBIDDEN_MEMBERSHIP_ROLES
|
|
@@ -203,6 +205,7 @@ export { runsInLane } from "./run-in";
|
|
|
203
205
|
export type { StepListOutcome } from "./run-pipeline";
|
|
204
206
|
export { runPipeline, runStepList } from "./run-pipeline";
|
|
205
207
|
export { buildInsertSchema, buildUpdateSchema, fieldToZod } from "./schema-builder";
|
|
208
|
+
export { isExtensionEditSection, normalizeEditField, normalizeListColumn } from "./screen-helpers";
|
|
206
209
|
export type { TransitionGraph } from "./state-machine";
|
|
207
210
|
export { defineTransitions, guardTransition } from "./state-machine";
|
|
208
211
|
export {
|
|
@@ -362,6 +365,8 @@ export type {
|
|
|
362
365
|
SecretKeyHandle,
|
|
363
366
|
SelectFieldDef,
|
|
364
367
|
SessionUser,
|
|
368
|
+
StreamHandlerDef,
|
|
369
|
+
StreamHandlerFn,
|
|
365
370
|
Subscribe,
|
|
366
371
|
TargetRef,
|
|
367
372
|
TenantId,
|
|
@@ -385,9 +390,7 @@ export type {
|
|
|
385
390
|
WriteResult,
|
|
386
391
|
} from "./types";
|
|
387
392
|
export { DEFAULT_CURRENCIES, HookPhases } from "./types";
|
|
388
|
-
export { resolveName, withResponseData } from "./types/handlers";
|
|
389
393
|
export { isSystemTenant, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers";
|
|
390
|
-
export { isExtensionEditSection, normalizeEditField, normalizeListColumn } from "./types/screen";
|
|
391
394
|
export type {
|
|
392
395
|
PipelineBuildCtx,
|
|
393
396
|
PipelineCtx,
|
package/src/engine/ownership.ts
CHANGED
|
@@ -20,64 +20,28 @@
|
|
|
20
20
|
import { toSnakeCase } from "../db/table-builder";
|
|
21
21
|
import type { SessionUser } from "./types";
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export type FromRuleKind = "user" | "claim";
|
|
46
|
-
|
|
47
|
-
export type FromRule = {
|
|
48
|
-
readonly kind: "from";
|
|
49
|
-
readonly refKind: FromRuleKind;
|
|
50
|
-
// For "user:id" → "id"; for "user:tenantId" → "tenantId".
|
|
51
|
-
// For "claim:<featureName>:<key>" → "<featureName>:<key>" (the full QN,
|
|
52
|
-
// which is exactly the key under which the JWT stores the value).
|
|
53
|
-
readonly refPath: string;
|
|
54
|
-
// Row-column to match against. For claim rules defaults to the claim's
|
|
55
|
-
// shortName (second segment of the claim QN). For user-rules the column
|
|
56
|
-
// is always explicit.
|
|
57
|
-
readonly column: string;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Context passed to a WhereRule escape-hatch. The author returns a SqlFragment
|
|
61
|
-
// whose placeholders start at `paramStart` ($N, $N+1, ...); the framework
|
|
62
|
-
// concatenates the fragment into the larger query.
|
|
63
|
-
export type WhereRuleContext<TTable = unknown> = {
|
|
64
|
-
readonly table: TTable;
|
|
65
|
-
readonly tableName: string;
|
|
66
|
-
readonly paramStart: number;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export type WhereRule<TTable = unknown> = {
|
|
70
|
-
readonly kind: "where";
|
|
71
|
-
readonly where: (user: SessionUser, ctx: WhereRuleContext<TTable>) => SqlFragment;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// "all" collapses to a primitive so map authors can write `Admin: "all"`
|
|
75
|
-
// without importing a helper.
|
|
76
|
-
export type OwnershipRule = "all" | FromRule | WhereRule;
|
|
77
|
-
|
|
78
|
-
// Per-role map: every key is a role name, value is the rule that role
|
|
79
|
-
// satisfies to pass the access check.
|
|
80
|
-
export type OwnershipMap = Readonly<Record<string, OwnershipRule>>;
|
|
23
|
+
// Types live in engine/types/ownership.ts (no runtime dependency); re-exported
|
|
24
|
+
// here for backwards compatibility with existing importers of this file.
|
|
25
|
+
export type {
|
|
26
|
+
FromRule,
|
|
27
|
+
FromRuleKind,
|
|
28
|
+
OwnershipClause,
|
|
29
|
+
OwnershipMap,
|
|
30
|
+
OwnershipRef,
|
|
31
|
+
OwnershipRule,
|
|
32
|
+
SqlFragment,
|
|
33
|
+
WhereRule,
|
|
34
|
+
WhereRuleContext,
|
|
35
|
+
} from "./types/ownership";
|
|
36
|
+
|
|
37
|
+
import type {
|
|
38
|
+
FromRule,
|
|
39
|
+
OwnershipClause,
|
|
40
|
+
OwnershipMap,
|
|
41
|
+
OwnershipRef,
|
|
42
|
+
OwnershipRule,
|
|
43
|
+
SqlFragment,
|
|
44
|
+
} from "./types/ownership";
|
|
81
45
|
|
|
82
46
|
// Parse an OwnershipRef into kind + resolved path + default column.
|
|
83
47
|
// Throws on malformed input so the error surfaces at `from()`-call-site (in
|
|
@@ -254,24 +218,6 @@ export function userCanCreateFieldRow(
|
|
|
254
218
|
return false;
|
|
255
219
|
}
|
|
256
220
|
|
|
257
|
-
// Result of buildOwnershipClause. The discriminant lets the caller handle
|
|
258
|
-
// the three outcomes without inspecting SQL internals:
|
|
259
|
-
//
|
|
260
|
-
// "pass" → user is unrestricted. Run the query as-is.
|
|
261
|
-
// "empty" → user has a role mapped but no rule accepts any row (missing
|
|
262
|
-
// claim, empty array, role not in map). Skip the DB call entirely
|
|
263
|
-
// — returning [] is equivalent and avoids a pointless roundtrip.
|
|
264
|
-
// "sql" → apply the parameterised fragment as an AND on the query.
|
|
265
|
-
// Caller is responsible for renumbering placeholders when
|
|
266
|
-
// concatenating with other fragments (see `shiftParams` below).
|
|
267
|
-
//
|
|
268
|
-
// "empty" vs. "pass" is the critical distinction for a safe default:
|
|
269
|
-
// undefined/pass = allow, empty = deny-by-construction.
|
|
270
|
-
export type OwnershipClause =
|
|
271
|
-
| { readonly kind: "pass" }
|
|
272
|
-
| { readonly kind: "empty" }
|
|
273
|
-
| { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
|
|
274
|
-
|
|
275
221
|
const PASS_CLAUSE: OwnershipClause = { kind: "pass" };
|
|
276
222
|
const EMPTY_CLAUSE: OwnershipClause = { kind: "empty" };
|
|
277
223
|
|
|
@@ -46,6 +46,7 @@ const ALL_KINDS: FeaturePatternKind[] = [
|
|
|
46
46
|
"screen",
|
|
47
47
|
"writeHandler",
|
|
48
48
|
"queryHandler",
|
|
49
|
+
"streamHandler",
|
|
49
50
|
"hook",
|
|
50
51
|
"job",
|
|
51
52
|
"notification",
|
|
@@ -270,6 +271,14 @@ function makePlaceholderPattern(kind: FeaturePatternKind): FeaturePattern {
|
|
|
270
271
|
schemaSource: PLACEHOLDER_BODY_LOC,
|
|
271
272
|
handlerBody: PLACEHOLDER_BODY_LOC,
|
|
272
273
|
};
|
|
274
|
+
case "streamHandler":
|
|
275
|
+
return {
|
|
276
|
+
kind,
|
|
277
|
+
source: PLACEHOLDER_LOC,
|
|
278
|
+
handlerName: "x",
|
|
279
|
+
schemaSource: PLACEHOLDER_BODY_LOC,
|
|
280
|
+
handlerBody: PLACEHOLDER_BODY_LOC,
|
|
281
|
+
};
|
|
273
282
|
case "hook":
|
|
274
283
|
return {
|
|
275
284
|
kind,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
projectionSchema,
|
|
26
26
|
queryHandlerSchema,
|
|
27
27
|
screenSchema,
|
|
28
|
+
streamHandlerSchema,
|
|
28
29
|
writeHandlerSchema,
|
|
29
30
|
} from "./mixed-schemas";
|
|
30
31
|
import {
|
|
@@ -79,6 +80,7 @@ export const PATTERN_LIBRARY: Readonly<Record<FeaturePatternKind, PatternFormSch
|
|
|
79
80
|
screen: screenSchema,
|
|
80
81
|
writeHandler: writeHandlerSchema,
|
|
81
82
|
queryHandler: queryHandlerSchema,
|
|
83
|
+
streamHandler: streamHandlerSchema,
|
|
82
84
|
hook: hookSchema,
|
|
83
85
|
job: jobSchema,
|
|
84
86
|
notification: notificationSchema,
|
|
@@ -125,6 +125,43 @@ export const queryHandlerSchema: PatternFormSchema = {
|
|
|
125
125
|
],
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
+
export const streamHandlerSchema: PatternFormSchema = {
|
|
129
|
+
kind: "streamHandler",
|
|
130
|
+
label: { en: "Stream handler", de: "Stream-Handler" },
|
|
131
|
+
summary: { en: "Streaming read endpoint (SSE) with Zod schema + async-generator body." },
|
|
132
|
+
category: "behaviour",
|
|
133
|
+
editability: "mixed",
|
|
134
|
+
fields: [
|
|
135
|
+
{
|
|
136
|
+
path: "handlerName",
|
|
137
|
+
label: { en: "Name", de: "Name" },
|
|
138
|
+
input: "text",
|
|
139
|
+
required: true,
|
|
140
|
+
placeholder: "chat:complete",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
path: "schemaSource",
|
|
144
|
+
label: { en: "Zod schema (source)", de: "Zod-Schema (Source)" },
|
|
145
|
+
input: "code-block",
|
|
146
|
+
language: "zod",
|
|
147
|
+
readOnly: true,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
path: "handlerBody",
|
|
151
|
+
label: { en: "Handler body (source)", de: "Handler-Body (Source)" },
|
|
152
|
+
input: "code-block",
|
|
153
|
+
language: "typescript",
|
|
154
|
+
readOnly: true,
|
|
155
|
+
},
|
|
156
|
+
accessRuleField,
|
|
157
|
+
{
|
|
158
|
+
path: "rateLimit",
|
|
159
|
+
label: { en: "Rate limit", de: "Rate-Limit" },
|
|
160
|
+
input: "json-readonly",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
|
|
128
165
|
export const hookSchema: PatternFormSchema = {
|
|
129
166
|
kind: "hook",
|
|
130
167
|
label: { en: "Lifecycle hook", de: "Lifecycle-Hook" },
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
SearchPayloadContributorFn,
|
|
31
31
|
SecretKeyDefinition,
|
|
32
32
|
StoreTableDef,
|
|
33
|
+
StreamHandlerDef,
|
|
33
34
|
TranslationKeys,
|
|
34
35
|
TreeActionDef,
|
|
35
36
|
WorkspaceDefinition,
|
|
@@ -72,6 +73,14 @@ export function buildRegistryFacade(state: RegistryState): Registry {
|
|
|
72
73
|
return state.queryHandlerMap;
|
|
73
74
|
},
|
|
74
75
|
|
|
76
|
+
getStreamHandler(name: string): StreamHandlerDef | undefined {
|
|
77
|
+
return state.streamHandlerMap.get(name);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
getAllStreamHandlers(): ReadonlyMap<string, StreamHandlerDef> {
|
|
81
|
+
return state.streamHandlerMap;
|
|
82
|
+
},
|
|
83
|
+
|
|
75
84
|
getSearchableFields(entityName: string): readonly string[] {
|
|
76
85
|
return state.searchableFieldsCache.get(entityName) ?? [];
|
|
77
86
|
},
|
|
@@ -80,6 +80,16 @@ export function populateHandlers(state: RegistryState, feature: FeatureDefinitio
|
|
|
80
80
|
state.queryHandlerMap.set(qualified, { ...handler, name: qualified });
|
|
81
81
|
state.handlerFeatureMap.set(qualified, feature.name);
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
// Stream handlers: scope:stream:name
|
|
85
|
+
for (const [name, handler] of Object.entries(feature.streamHandlers ?? {})) {
|
|
86
|
+
const qualified = qualify(feature.name, "stream", name);
|
|
87
|
+
if (state.streamHandlerMap.has(qualified)) {
|
|
88
|
+
throw new Error(`Duplicate stream handler: "${qualified}" (registered by multiple features)`);
|
|
89
|
+
}
|
|
90
|
+
state.streamHandlerMap.set(qualified, { ...handler, name: qualified });
|
|
91
|
+
state.handlerFeatureMap.set(qualified, feature.name);
|
|
92
|
+
}
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
// Config keys: scope:config:name, duplicate-guarded.
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
SearchPayloadContributorFn,
|
|
38
38
|
SecretKeyDefinition,
|
|
39
39
|
StoreTableDef,
|
|
40
|
+
StreamHandlerDef,
|
|
40
41
|
TreeActionDef,
|
|
41
42
|
WorkspaceDefinition,
|
|
42
43
|
WriteHandlerDef,
|
|
@@ -165,6 +166,7 @@ export type RegistryState = {
|
|
|
165
166
|
relationMap: Map<string, Record<string, RelationDefinition>>;
|
|
166
167
|
writeHandlerMap: Map<string, WriteHandlerDef>;
|
|
167
168
|
queryHandlerMap: Map<string, QueryHandlerDef>;
|
|
169
|
+
streamHandlerMap: Map<string, StreamHandlerDef>;
|
|
168
170
|
preSaveHooks: Map<string, OwnedFn<PreSaveHookFn>[]>;
|
|
169
171
|
postSaveHooks: Map<string, PhasedHook<PostSaveHookFn>[]>;
|
|
170
172
|
preDeleteHooks: Map<string, PhasedHook<PreDeleteHookFn>[]>;
|
|
@@ -229,6 +231,7 @@ export function createInitialState(): RegistryState {
|
|
|
229
231
|
relationMap: new Map(),
|
|
230
232
|
writeHandlerMap: new Map(),
|
|
231
233
|
queryHandlerMap: new Map(),
|
|
234
|
+
streamHandlerMap: new Map(),
|
|
232
235
|
preSaveHooks: new Map(),
|
|
233
236
|
postSaveHooks: new Map(),
|
|
234
237
|
preDeleteHooks: new Map(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { configureEventPiiCatalog } from "../crypto/event-pii";
|
|
2
|
+
import { resolveName } from "./handler-helpers";
|
|
2
3
|
import type { RegistryState } from "./registry-state";
|
|
3
4
|
import { buildImplicitProjection, hasFieldAccessRules, qualify } from "./registry-state";
|
|
4
5
|
import {
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
} from "./soft-delete-cleanup";
|
|
12
13
|
import type { EventPiiFields, EventUpcastFn, FeatureDefinition } from "./types";
|
|
13
14
|
import { HookPhases } from "./types";
|
|
14
|
-
import { resolveName } from "./types/handlers";
|
|
15
15
|
|
|
16
16
|
function allHandlerQns(state: RegistryState): ReadonlySet<string> {
|
|
17
17
|
return new Set([...state.writeHandlerMap.keys(), ...state.queryHandlerMap.keys()]);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EditExtensionSection,
|
|
3
|
+
EditFieldSpec,
|
|
4
|
+
EditSectionSpec,
|
|
5
|
+
FieldCondition,
|
|
6
|
+
FormatSpec,
|
|
7
|
+
ListColumnSpec,
|
|
8
|
+
} from "./types/screen";
|
|
9
|
+
|
|
10
|
+
export function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection {
|
|
11
|
+
return section.kind === "extension";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Type guard — narrows FieldRenderer to FormatSpec. Useful for renderer
|
|
15
|
+
// authors who branch on the three FieldRenderer variants without manual
|
|
16
|
+
// "format" in renderer checks.
|
|
17
|
+
export function isFormatSpec(r: unknown): r is FormatSpec {
|
|
18
|
+
return typeof r === "object" && r !== null && "format" in r && typeof r.format === "string";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Collapse the string-shorthand into the object form. Both the boot-validator
|
|
22
|
+
// and (later) ui-core's view-model builder iterate over fields/columns — the
|
|
23
|
+
// helper keeps that loop from growing two branches everywhere.
|
|
24
|
+
export function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string> {
|
|
25
|
+
const col = typeof c === "string" ? { field: c } : c;
|
|
26
|
+
if (
|
|
27
|
+
typeof process !== "undefined" &&
|
|
28
|
+
process.env.NODE_ENV !== "production" &&
|
|
29
|
+
col.renderer !== undefined &&
|
|
30
|
+
typeof col.renderer === "function"
|
|
31
|
+
) {
|
|
32
|
+
// biome-ignore lint/suspicious/noConsole: dev-only warning
|
|
33
|
+
console.warn(
|
|
34
|
+
`[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return col;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Evaluates a declarative FieldCondition against the current row/form
|
|
41
|
+
* values. THE single implementation — renderer (row-action visibility),
|
|
42
|
+
* headless view-model (visible/readOnly/required) and render-edit
|
|
43
|
+
* (form-condition closures) reuse it; three hand-rolled copies had
|
|
44
|
+
* already drifted in shape. */
|
|
45
|
+
export function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean {
|
|
46
|
+
if (typeof cond === "boolean") return cond;
|
|
47
|
+
const val = values[cond.field];
|
|
48
|
+
if ("eq" in cond) return val === cond.eq;
|
|
49
|
+
return val !== cond.ne;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string> {
|
|
53
|
+
return typeof f === "string" ? { field: f } : f;
|
|
54
|
+
}
|
|
@@ -48,8 +48,9 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
|
|
|
48
48
|
* Signup-Datum des Tenants live (tenant.inserted_at) und returnt true wenn
|
|
49
49
|
* der Tenant im Trial-Fenster ist UND das Feature zum Trial-Tier gehört.
|
|
50
50
|
* Async + nicht im Boot-Cache, weil der Trial zeit-abgeleitet ist (ändert
|
|
51
|
-
* sich zwischen Requests).
|
|
52
|
-
*
|
|
51
|
+
* sich zwischen Requests). Konsultiert an 3 Stellen: den 2 Dispatch-Gate-
|
|
52
|
+
* Aufrufstellen (checkFeatureEnabled) UND ctx.hasFeature im Handler-Body
|
|
53
|
+
* (fällt darauf zurück, wenn der synchrone Feature-Set-Check negativ ist).
|
|
53
54
|
*/
|
|
54
55
|
readonly trialGate?: TrialGate;
|
|
55
56
|
};
|