@cosmicdrift/kumiko-framework 0.159.1 → 0.161.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/package.json +3 -3
  2. package/src/api/__tests__/api.test.ts +65 -0
  3. package/src/api/__tests__/auth-routes-cookie.test.ts +1 -0
  4. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +237 -0
  5. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +1 -0
  6. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  7. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +41 -0
  8. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  9. package/src/api/api-constants.ts +1 -0
  10. package/src/api/auth-middleware.ts +17 -44
  11. package/src/api/auth-routes.ts +6 -2
  12. package/src/api/index.ts +1 -0
  13. package/src/api/routes.ts +57 -0
  14. package/src/api/server.ts +5 -4
  15. package/src/bun-db/query.ts +12 -25
  16. package/src/crypto/kms-adapter.ts +2 -118
  17. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  18. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +91 -0
  19. package/src/db/cursor.ts +1 -18
  20. package/src/db/dialect.ts +8 -19
  21. package/src/db/entity-table-meta-types.ts +2 -92
  22. package/src/db/event-store-executor.ts +4 -96
  23. package/src/db/table-builder.ts +2 -19
  24. package/src/db/tenant-db.ts +6 -55
  25. package/src/engine/__tests__/boot-validator.test.ts +46 -0
  26. package/src/engine/__tests__/codemod-pipeline.test.ts +139 -10
  27. package/src/engine/__tests__/engine.test.ts +28 -0
  28. package/src/engine/__tests__/registry-facade-sweep.test.ts +80 -0
  29. package/src/engine/__tests__/registry.test.ts +40 -0
  30. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  31. package/src/engine/boot-validator/entity-handler.ts +10 -1
  32. package/src/engine/define-feature.ts +1 -0
  33. package/src/engine/define-handler.ts +1 -0
  34. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +11 -1
  35. package/src/engine/feature-ast/__tests__/parse.test.ts +983 -3
  36. package/src/engine/feature-ast/__tests__/patch.test.ts +168 -0
  37. package/src/engine/feature-ast/__tests__/patcher.test.ts +7 -0
  38. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +9 -0
  39. package/src/engine/feature-ast/extractors/handlers.ts +19 -2
  40. package/src/engine/feature-ast/extractors/index.ts +1 -0
  41. package/src/engine/feature-ast/index.ts +2 -0
  42. package/src/engine/feature-ast/parse.ts +3 -0
  43. package/src/engine/feature-ast/patch.ts +2 -0
  44. package/src/engine/feature-ast/patcher.ts +21 -0
  45. package/src/engine/feature-ast/patterns.ts +16 -0
  46. package/src/engine/feature-ast/render.ts +15 -0
  47. package/src/engine/feature-builder-state.ts +3 -0
  48. package/src/engine/feature-entity-handlers.ts +35 -1
  49. package/src/engine/index.ts +3 -0
  50. package/src/engine/pattern-library/__tests__/library.test.ts +9 -0
  51. package/src/engine/pattern-library/library.ts +2 -0
  52. package/src/engine/pattern-library/mixed-schemas.ts +37 -0
  53. package/src/engine/registry-facade.ts +9 -0
  54. package/src/engine/registry-ingest.ts +10 -0
  55. package/src/engine/registry-state.ts +3 -0
  56. package/src/engine/types/config.ts +2 -497
  57. package/src/engine/types/define-handler.ts +2 -94
  58. package/src/engine/types/entity-handlers.ts +2 -30
  59. package/src/engine/types/feature.ts +2 -1021
  60. package/src/engine/types/fields.ts +2 -685
  61. package/src/engine/types/handlers.ts +2 -820
  62. package/src/engine/types/hooks.ts +2 -170
  63. package/src/engine/types/index.ts +44 -36
  64. package/src/engine/types/nav.ts +2 -67
  65. package/src/engine/types/ownership.ts +2 -83
  66. package/src/engine/types/projection.ts +2 -165
  67. package/src/engine/types/screen.ts +2 -747
  68. package/src/engine/types/step.ts +2 -334
  69. package/src/engine/types/workspace.ts +2 -42
  70. package/src/errors/write-error-info.ts +6 -22
  71. package/src/event-store/errors.ts +2 -35
  72. package/src/event-store/event-store.ts +2 -21
  73. package/src/event-store/snapshot.ts +11 -35
  74. package/src/event-store/types.ts +2 -22
  75. package/src/files/provider-resolver.ts +3 -5
  76. package/src/files/types.ts +5 -54
  77. package/src/jobs/__tests__/jobs.integration.test.ts +102 -1
  78. package/src/pipeline/__tests__/dispatcher.test.ts +96 -0
  79. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +208 -0
  80. package/src/pipeline/dispatch-shared.ts +39 -1
  81. package/src/pipeline/dispatch-stream.ts +74 -0
  82. package/src/pipeline/dispatcher-utils.ts +1 -1
  83. package/src/pipeline/dispatcher.ts +7 -0
  84. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  85. package/src/rate-limit/resolver.ts +10 -30
  86. package/src/secrets/envelope-cipher.ts +4 -6
  87. package/src/secrets/types.ts +2 -177
  88. package/src/stack/request-helper.ts +19 -2
  89. package/src/stack/test-stack.ts +33 -14
  90. package/src/time/tz-context.ts +9 -56
@@ -1,170 +1,2 @@
1
- import type { StoredEvent } from "../../event-store/event-store";
2
- import type { HookPhases } from "../hook-helpers";
3
- import type { AppContext } from "./handlers";
4
- import type { EntityId } from "./identifiers";
5
-
6
- // --- Validation ---
7
-
8
- export type ValidationError = {
9
- readonly field: string;
10
- readonly error: string;
11
- };
12
-
13
- export type ValidationHookFn = (
14
- data: Readonly<Record<string, unknown>>,
15
- ) => readonly ValidationError[] | null;
16
-
17
- // --- Save/Delete Context (what hooks receive) ---
18
-
19
- export type SaveContext = {
20
- readonly kind: "save";
21
- readonly id: EntityId;
22
- readonly data: Readonly<Record<string, unknown>>;
23
- readonly changes: Readonly<Record<string, unknown>>;
24
- readonly previous: Readonly<Record<string, unknown>>;
25
- readonly isNew: boolean;
26
- readonly entityName?: string | undefined;
27
- // The event that produced this save. Populated by the event-store-executor;
28
- // the pipeline uses it to drive projections inside the same transaction.
29
- // Optional because hand-crafted SaveContexts (tests, custom executors) may
30
- // not have an event — projections just skip in that case.
31
- readonly event?: StoredEvent | undefined;
32
- };
33
-
34
- export type DeleteContext = {
35
- readonly kind: "delete";
36
- readonly id: EntityId;
37
- readonly data: Readonly<Record<string, unknown>>;
38
- readonly entityName?: string | undefined;
39
- // See SaveContext.event — same semantics.
40
- readonly event?: StoredEvent | undefined;
41
- };
42
-
43
- export type LifecycleResult = SaveContext | DeleteContext;
44
-
45
- // --- Lifecycle Hooks ---
46
-
47
- export type PreSaveHookFn = (
48
- changes: Record<string, unknown>,
49
- context: AppContext & {
50
- readonly previous: Readonly<Record<string, unknown>>;
51
- readonly isNew: boolean;
52
- },
53
- ) => Promise<Record<string, unknown>>;
54
-
55
- export type PostSaveHookFn = (result: SaveContext, context: AppContext) => Promise<void>;
56
-
57
- // Batch-variant: called once at the end of a dispatcher batch with every
58
- // successful SaveContext. The per-save PostSaveHookFn still fires for
59
- // side-effects that need per-entity semantics (SSE); PostSaveBatch exists
60
- // for adapters that can amortise work across the whole batch (e.g. search
61
- // index batch-writes, bulk webhook fanout).
62
- export type PostSaveBatchHookFn = (
63
- results: readonly SaveContext[],
64
- context: AppContext,
65
- ) => Promise<void>;
66
-
67
- export type PreDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
68
-
69
- export type PostDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
70
-
71
- export type PostDeleteBatchHookFn = (
72
- payloads: readonly DeleteContext[],
73
- context: AppContext,
74
- ) => Promise<void>;
75
-
76
- export type PreQueryHookFn = (
77
- payload: Record<string, unknown>,
78
- context: AppContext,
79
- ) => Promise<Record<string, unknown>>;
80
-
81
- // postQuery — fires after query-handler-execute, before field-access-read-filter.
82
- // Hook receives normalized rows + entityName + can mutate rows (e.g., merge
83
- // custom-fields, add computed-counts, attach related-data). Mutation result
84
- // replaces original rows. Hook is responsible for its own field-access-logic
85
- // on added fields (field-access-filter only knows entity's stammfields).
86
- export type PostQueryHookFn = (
87
- result: {
88
- // undefined for standalone queries (no-colon handler names like
89
- // "ns:dashboard") — those have no backing entity, but handler-keyed
90
- // postQuery hooks still fire on them.
91
- readonly entityName: string | undefined;
92
- readonly rows: ReadonlyArray<Record<string, unknown>>;
93
- },
94
- context: AppContext,
95
- ) => Promise<{ rows: ReadonlyArray<Record<string, unknown>> }>;
96
-
97
- export type LifecycleHookFn =
98
- | PreSaveHookFn
99
- | PostSaveHookFn
100
- | PreDeleteHookFn
101
- | PostDeleteHookFn
102
- | PreQueryHookFn
103
- | PostQueryHookFn;
104
-
105
- export type HookPhase = (typeof HookPhases)[keyof typeof HookPhases];
106
-
107
- // Owner-tag shared across every hook structure. The lifecycle pipeline uses
108
- // it to skip hooks whose owning feature is globally disabled:
109
- // - A concrete feature name like "orders" → subject to the feature-toggle
110
- // filter (skipped when "orders" is disabled).
111
- // - "*" (star) → invariant plumbing, never filtered. Reserved for
112
- // extension-provided hooks and framework-internal hooks that belong to
113
- // the pipeline itself, not a feature.
114
- // - Omitted (undefined) → treated as "*". Supports tests that hand-build
115
- // HookMap objects without caring about ownership.
116
- export type HookOwner = { readonly featureName?: string };
117
-
118
- export type PhasedHook<TFn> = {
119
- readonly fn: TFn;
120
- readonly phase: HookPhase;
121
- } & HookOwner;
122
-
123
- // Flat (non-phased) hook — preSave, preQuery. Same owner contract, no
124
- // phase semantics because these hooks run exactly once per handler pass
125
- // before/around the DB transaction.
126
- export type OwnedFn<TFn> = {
127
- readonly fn: TFn;
128
- } & HookOwner;
129
-
130
- // --- Hook Maps ---
131
-
132
- // Slots are optional: defineFeature materializes every slot, but hand-built
133
- // FeatureDefinitions at system boundaries (test fixtures, partial boots —
134
- // see registry.test.ts "slot robustness") legitimately omit them, and the
135
- // registry merge paths tolerate undefined. The type mirrors that contract.
136
- export type HookMap = {
137
- readonly validation?: Readonly<Record<string, ValidationHookFn>>;
138
- readonly preSave?: Readonly<Record<string, readonly OwnedFn<PreSaveHookFn>[]>>;
139
- readonly postSave?: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
140
- readonly preDelete?: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
141
- readonly postDelete?: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
142
- readonly preQuery?: Readonly<Record<string, readonly OwnedFn<PreQueryHookFn>[]>>;
143
- readonly postQuery?: Readonly<Record<string, readonly OwnedFn<PostQueryHookFn>[]>>;
144
- };
145
-
146
- export type EntityHookMap = {
147
- readonly postSave?: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
148
- readonly preDelete?: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
149
- readonly postDelete?: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
150
- readonly postQuery?: Readonly<Record<string, readonly OwnedFn<PostQueryHookFn>[]>>;
151
- };
152
-
153
- // Search-Payload-Extension (F3) — contributor function that adds flat
154
- // fields to an entity's search-document. Fires synchronously during
155
- // buildSearchDocument (in `system-hooks.ts`), receives current entity
156
- // state, returns extra fields to merge into the search-index payload.
157
- //
158
- // Use-cases: custom-fields-bundle (merge customFields-jsonb-keys flat
159
- // into index), tags-bundle (project tags-array as searchable), computed-
160
- // fields (denormalize related-counts).
161
- //
162
- // IMPORTANT: contributor must be deterministic per (entityName, entityId,
163
- // state). Async-allowed for future-proofing but discouraged — the
164
- // indexing path runs once per entity-write, sync extension is
165
- // near-zero-cost.
166
- export type SearchPayloadContributorFn = (args: {
167
- readonly entityName: string;
168
- readonly entityId: EntityId;
169
- readonly state: Record<string, unknown>;
170
- }) => Record<string, unknown> | Promise<Record<string, unknown>>;
1
+ // Legacy path re-exported for callers still importing this module directly.
2
+ export * from "@cosmicdrift/kumiko-types/hooks";
@@ -1,23 +1,8 @@
1
- // Barrel: re-exports all types from logical modules
1
+ // Barrel: re-exports all types from @cosmicdrift/kumiko-types, plus the
2
+ // runtime helpers below that stay framework-side.
2
3
  // Duplicate types (OnDeleteStrategy, ConfigScope, ConcurrencyMode, LifecycleHookType)
3
4
  // are defined ONLY in constants.ts — re-exported here for backwards compatibility.
4
5
 
5
- // Re-export types that were duplicated in types.ts but are canonical in constants.ts
6
- export type {
7
- ConcurrencyMode,
8
- ConfigScope,
9
- LifecycleHookType,
10
- OnDeleteStrategy,
11
- } from "../constants";
12
- export { DEFAULT_CURRENCIES, isFileField } from "../field-helpers";
13
- export { resolveName, withResponseData } from "../handler-helpers";
14
- export { HookPhases } from "../hook-helpers";
15
- export {
16
- isExtensionEditSection,
17
- isFormatSpec,
18
- normalizeEditField,
19
- normalizeListColumn,
20
- } from "../screen-helpers";
21
6
  export type {
22
7
  ConfigAccessor,
23
8
  ConfigAccessorFactory,
@@ -62,26 +47,27 @@ export type {
62
47
  TranslationKeys,
63
48
  TranslationsDef,
64
49
  UiExtensionDef,
65
- } from "./config";
50
+ } from "@cosmicdrift/kumiko-types/config";
66
51
  export type {
67
52
  QueryHandlerDefinition,
53
+ StreamHandlerDefinition,
68
54
  WriteHandlerDefinition,
69
55
  WriteHandlerInput,
70
- } from "./define-handler";
56
+ } from "@cosmicdrift/kumiko-types/define-handler";
71
57
  export type {
72
58
  EntityCrudRegistrar,
73
59
  EntityCrudVerb,
74
60
  EntityHandlerOptions,
75
61
  EntityQueryHandlerOptions,
76
62
  RegisterEntityCrudOptions,
77
- } from "./entity-handlers";
63
+ } from "@cosmicdrift/kumiko-types/entity-handlers";
78
64
  // Cross-Feature Compile-Time-Type-Map — features extend per declare-module.
79
65
  export type {
80
66
  KumikoEntityTypeMap,
81
67
  KumikoEventTypeMap,
82
68
  KumikoHandlerPayloadMap,
83
69
  KumikoHandlerResultMap,
84
- } from "./event-type-map";
70
+ } from "@cosmicdrift/kumiko-types/event-type-map";
85
71
  export type {
86
72
  BootCheckContext,
87
73
  BootCheckFn,
@@ -99,7 +85,7 @@ export type {
99
85
  StoreTableOptions,
100
86
  UiHintOption,
101
87
  UiHints,
102
- } from "./feature";
88
+ } from "@cosmicdrift/kumiko-types/feature";
103
89
  export type {
104
90
  AnyFileFieldDef,
105
91
  BigIntFieldDef,
@@ -137,7 +123,7 @@ export type {
137
123
  TimestampFieldDef,
138
124
  TransitionMap,
139
125
  TzFieldDef,
140
- } from "./fields";
126
+ } from "@cosmicdrift/kumiko-types/fields";
141
127
  export type {
142
128
  AccessRule,
143
129
  AggregateStreamHandle,
@@ -176,6 +162,8 @@ export type {
176
162
  RateLimitOption,
177
163
  RateLimitPer,
178
164
  SessionUser,
165
+ StreamHandlerDef,
166
+ StreamHandlerFn,
179
167
  TryAppendEventFn,
180
168
  TryAppendEventResult,
181
169
  UnsafeAppendEventFn,
@@ -183,7 +171,7 @@ export type {
183
171
  WriteHandlerDef,
184
172
  WriteHandlerFn,
185
173
  WriteResult,
186
- } from "./handlers";
174
+ } from "@cosmicdrift/kumiko-types/handlers";
187
175
  export type {
188
176
  DeleteContext,
189
177
  EntityHookMap,
@@ -206,17 +194,21 @@ export type {
206
194
  SearchPayloadContributorFn,
207
195
  ValidationError,
208
196
  ValidationHookFn,
209
- } from "./hooks";
197
+ } from "@cosmicdrift/kumiko-types/hooks";
210
198
  export type {
211
199
  HttpRouteDefinition,
212
200
  HttpRouteHandler,
213
201
  HttpRouteHandlerDeps,
214
202
  HttpRouteMethod,
215
- } from "./http-route";
203
+ } from "@cosmicdrift/kumiko-types/http-route";
216
204
  // Domain-identifier type aliases — see identifiers.ts for rationale.
217
- export type { EntityId, TenantId } from "./identifiers";
218
- export { isSystemTenant, parseTenantId, SYSTEM_TENANT_ID } from "./identifiers";
219
- export type { NavDefinition } from "./nav";
205
+ export type { EntityId, TenantId } from "@cosmicdrift/kumiko-types/identifiers";
206
+ export {
207
+ isSystemTenant,
208
+ parseTenantId,
209
+ SYSTEM_TENANT_ID,
210
+ } from "@cosmicdrift/kumiko-types/identifiers";
211
+ export type { NavDefinition } from "@cosmicdrift/kumiko-types/nav";
220
212
  export type {
221
213
  FromRule,
222
214
  FromRuleKind,
@@ -227,7 +219,7 @@ export type {
227
219
  SqlFragment,
228
220
  WhereRule,
229
221
  WhereRuleContext,
230
- } from "./ownership";
222
+ } from "@cosmicdrift/kumiko-types/ownership";
231
223
  export type {
232
224
  EntityProjectionExtension,
233
225
  MspErrorMode,
@@ -237,14 +229,14 @@ export type {
237
229
  ProjectionDefinition,
238
230
  ProjectionTable,
239
231
  SingleStreamApplyFn,
240
- } from "./projection";
232
+ } from "@cosmicdrift/kumiko-types/projection";
241
233
  export type {
242
234
  BelongsToRelation,
243
235
  EntityRelations,
244
236
  HasManyRelation,
245
237
  ManyToManyRelation,
246
238
  RelationDefinition,
247
- } from "./relations";
239
+ } from "@cosmicdrift/kumiko-types/relations";
248
240
  export type {
249
241
  ActionFormScreenDefinition,
250
242
  ConfigEditScreenDefinition,
@@ -283,8 +275,8 @@ export type {
283
275
  ScreenFilterOp,
284
276
  ScreenSlots,
285
277
  ToolbarAction,
286
- } from "./screen";
287
- export type { TargetRef } from "./target-ref";
278
+ } from "@cosmicdrift/kumiko-types/screen";
279
+ export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
288
280
  export type {
289
281
  Subscribe,
290
282
  TreeAction,
@@ -293,5 +285,21 @@ export type {
293
285
  TreeChildrenSubscribe,
294
286
  TreeNode,
295
287
  TreeNodeState,
296
- } from "./tree-node";
297
- export type { WorkspaceDefinition } from "./workspace";
288
+ } from "@cosmicdrift/kumiko-types/tree-node";
289
+ export type { WorkspaceDefinition } from "@cosmicdrift/kumiko-types/workspace";
290
+ // Re-export types that were duplicated in types.ts but are canonical in constants.ts
291
+ export type {
292
+ ConcurrencyMode,
293
+ ConfigScope,
294
+ LifecycleHookType,
295
+ OnDeleteStrategy,
296
+ } from "../constants";
297
+ export { DEFAULT_CURRENCIES, isFileField } from "../field-helpers";
298
+ export { resolveName, withResponseData } from "../handler-helpers";
299
+ export { HookPhases } from "../hook-helpers";
300
+ export {
301
+ isExtensionEditSection,
302
+ isFormatSpec,
303
+ normalizeEditField,
304
+ normalizeListColumn,
305
+ } from "../screen-helpers";
@@ -1,67 +1,2 @@
1
- import type { AccessRule } from "./handlers";
2
- import type { TargetRef } from "./target-ref";
3
- import type { TreeAction } from "./tree-node";
4
-
5
- // Nav entry declaration. Every feature that wants to appear in the app's
6
- // navigation tree registers one or more entries via r.nav(). The engine
7
- // keeps the list flat — ui-core's resolveNavigation assembles the parent/
8
- // child tree at render time, so changes (toggles, access-gating) don't
9
- // require re-indexing a tree shape server-side.
10
- //
11
- // Cross-feature references are allowed: `screen` may point at any
12
- // registered screen QN, `parent` at any registered nav QN. The boot
13
- // validator checks both references exist + rejects parent cycles.
14
- export type NavDefinition = {
15
- // Feature author writes the feature-local short id ("catalog"); the
16
- // registry overwrites `id` with the qualified name ("shop:nav:catalog")
17
- // in its stored copy. Callers of `registry.getNav(qn)` /
18
- // `getTopLevelNavs()` / `getNavsByParent(...)` always see the qualified
19
- // id — no parallel reverse index needed. `feature.navs[shortId]` on the
20
- // unregistered FeatureDefinition keeps the short form.
21
- readonly id: string;
22
- // i18n translation key. Resolved at render time by the renderer's
23
- // useTranslation hook; engine keeps it opaque.
24
- readonly label: string;
25
- // Icon key — whatever the icon registry of the active renderer understands.
26
- // Engine doesn't validate; unknown icons surface as a missing icon on screen,
27
- // not a boot failure.
28
- readonly icon?: string;
29
- // Qualified name of a parent nav entry ("<feature>:nav:<id>"). Omit for
30
- // top-level entries. Boot-validator rejects cycles + dangling refs.
31
- readonly parent?: string;
32
- // Sort weight within the parent's children (lower = earlier). Ties are
33
- // broken by registration order — features registered later appear lower.
34
- readonly order?: number;
35
- // Qualified name of the screen this entry navigates to
36
- // ("<feature>:screen:<id>"). Omit for pure grouping entries (a parent-only
37
- // nav node that renders a sub-tree but has no target screen itself).
38
- readonly screen?: string;
39
- // Polymorphes Klick-Ziel (öffnet die EditorPanel-Maske via Target-
40
- // Resolver) — Alternative zu `screen`. Ein Knoten trägt screen XOR
41
- // target; der Renderer dispatcht das target statt einen Route-Link zu
42
- // rendern. Gespiegelt aus dem alten Visual-Tree (TreeNode.target).
43
- readonly target?: TargetRef;
44
- // Hover-Actions rechts in der Zeile (VS-Code-Pattern) — erst bei Hover
45
- // sichtbar. Reihenfolge wie deklariert.
46
- readonly actions?: readonly TreeAction[];
47
- // „+"-Affordance am Knoten. Klick dispatcht createAction.target; der
48
- // Provider weiß was „leer befüllen" für ihn heißt (neuer Page-Slug etc.).
49
- readonly createAction?: TreeAction;
50
- // Children kommen zur Laufzeit aus einem registrierten nav-provider
51
- // (lazy beim Ausklappen, SSE-live via treeEntities), keyed auf diese
52
- // Nav-QN. Macht den Knoten expandable auch ohne statische children.
53
- readonly provider?: boolean;
54
- // Role / openToAll gate. The nav resolver hides entries the user can't
55
- // reach; leave unset to always show (engine stays un-opinionated about
56
- // who sees what — apps that need default-deny can set { roles: [] }).
57
- // If `screen` is set and `access` is left unset, the client-side nav
58
- // builder (buildNavRegistrySliceForApp) fills this in from the target
59
- // screen's own `access` — a nav entry never invites a role into a 403.
60
- // An explicit `access` here always wins over the screen's.
61
- readonly access?: AccessRule;
62
- // Workspace QNs this entry self-assigns to. Merged at boot with any
63
- // r.workspace({ nav: [...] }) explicit lists. Omit to leave workspace
64
- // membership decided solely by the workspace's nav list (or both empty
65
- // → entry belongs to no workspace).
66
- readonly workspaces?: readonly string[];
67
- };
1
+ // Legacy path re-exported for callers still importing this module directly.
2
+ export * from "@cosmicdrift/kumiko-types/nav";
@@ -1,83 +1,2 @@
1
- // --- Ownership ---
2
- // Pure types for the declarative Claims → Access bridge. Runtime (from(),
3
- // matchesRule(), buildOwnershipClause()) stays in engine/ownership.ts, which
4
- // re-exports these for backwards compatibility.
5
-
6
- import type { SessionUser } from "./handlers";
7
-
8
- // Parameterised SQL fragment — produced by buildOwnershipClause + by the
9
- // WhereRule escape-hatch. Caller weaves `sqlText` into a larger statement,
10
- // renumbering placeholders if needed (shiftParams in engine/ownership.ts).
11
- export type SqlFragment = {
12
- readonly sqlText: string;
13
- readonly params: readonly unknown[];
14
- };
15
-
16
- // Reference spec supported by `from()`:
17
- // "user:id" → user.id
18
- // "user:tenantId" → user.tenantId (rarely needed — TenantDb scopes anyway)
19
- // "claim:<featureName>:<key>" → user.claims["<featureName>:<key>"]
20
- //
21
- // The string form is keyed so the framework can look up the referenced
22
- // Registry entry at boot (Claim-QN exists? Column type compatible?). A typed
23
- // object form would force features to import each other's handles — the
24
- // whole point of H.2's unified path is string-based references, no imports.
25
- export type OwnershipRef = string;
26
-
27
- // Resolved during `from()` — the parser eagerly splits the prefix so the
28
- // runtime evaluator avoids string-parsing on every row. `kind` drives the
29
- // evaluator branch; the rest is the resolved metadata.
30
- export type FromRuleKind = "user" | "claim";
31
-
32
- export type FromRule = {
33
- readonly kind: "from";
34
- readonly refKind: FromRuleKind;
35
- // For "user:id" → "id"; for "user:tenantId" → "tenantId".
36
- // For "claim:<featureName>:<key>" → "<featureName>:<key>" (the full QN,
37
- // which is exactly the key under which the JWT stores the value).
38
- readonly refPath: string;
39
- // Row-column to match against. For claim rules defaults to the claim's
40
- // shortName (second segment of the claim QN). For user-rules the column
41
- // is always explicit.
42
- readonly column: string;
43
- };
44
-
45
- // Context passed to a WhereRule escape-hatch. The author returns a SqlFragment
46
- // whose placeholders start at `paramStart` ($N, $N+1, ...); the framework
47
- // concatenates the fragment into the larger query.
48
- export type WhereRuleContext<TTable = unknown> = {
49
- readonly table: TTable;
50
- readonly tableName: string;
51
- readonly paramStart: number;
52
- };
53
-
54
- export type WhereRule<TTable = unknown> = {
55
- readonly kind: "where";
56
- readonly where: (user: SessionUser, ctx: WhereRuleContext<TTable>) => SqlFragment;
57
- };
58
-
59
- // "all" collapses to a primitive so map authors can write `Admin: "all"`
60
- // without importing a helper.
61
- export type OwnershipRule = "all" | FromRule | WhereRule;
62
-
63
- // Per-role map: every key is a role name, value is the rule that role
64
- // satisfies to pass the access check.
65
- export type OwnershipMap = Readonly<Record<string, OwnershipRule>>;
66
-
67
- // Result of buildOwnershipClause. The discriminant lets the caller handle
68
- // the three outcomes without inspecting SQL internals:
69
- //
70
- // "pass" → user is unrestricted. Run the query as-is.
71
- // "empty" → user has a role mapped but no rule accepts any row (missing
72
- // claim, empty array, role not in map). Skip the DB call entirely
73
- // — returning [] is equivalent and avoids a pointless roundtrip.
74
- // "sql" → apply the parameterised fragment as an AND on the query.
75
- // Caller is responsible for renumbering placeholders when
76
- // concatenating with other fragments (see `shiftParams`).
77
- //
78
- // "empty" vs. "pass" is the critical distinction for a safe default:
79
- // undefined/pass = allow, empty = deny-by-construction.
80
- export type OwnershipClause =
81
- | { readonly kind: "pass" }
82
- | { readonly kind: "empty" }
83
- | { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
1
+ // Legacy path — re-exported for callers still importing this module directly.
2
+ export * from "@cosmicdrift/kumiko-types/ownership";