@cosmicdrift/kumiko-types 0.327.0 → 0.328.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 (174) hide show
  1. package/dist/action-icon.d.ts +3 -0
  2. package/dist/action-icon.js +51 -0
  3. package/{src/agent-exposure.ts → dist/agent-exposure.d.ts} +8 -15
  4. package/dist/agent-exposure.js +16 -0
  5. package/dist/concurrency-mode.d.ts +1 -0
  6. package/dist/config-scope.d.ts +1 -0
  7. package/dist/config-scope.js +1 -0
  8. package/dist/config.d.ts +234 -0
  9. package/dist/config.js +1 -0
  10. package/dist/cursor-types.d.ts +16 -0
  11. package/dist/cursor-types.js +1 -0
  12. package/dist/db-connection.d.ts +14 -0
  13. package/dist/db-connection.js +1 -0
  14. package/dist/define-handler.d.ts +58 -0
  15. package/dist/define-handler.js +1 -0
  16. package/dist/derivatives-types.d.ts +53 -0
  17. package/dist/derivatives-types.js +3 -0
  18. package/dist/entity-cache.d.ts +16 -0
  19. package/dist/entity-cache.js +1 -0
  20. package/dist/entity-handlers.d.ts +54 -0
  21. package/dist/entity-handlers.js +1 -0
  22. package/dist/entity-table-meta-types.d.ts +44 -0
  23. package/dist/entity-table-meta-types.js +6 -0
  24. package/dist/envelope-cipher-types.d.ts +5 -0
  25. package/dist/envelope-cipher-types.js +1 -0
  26. package/dist/event-store-executor-types.d.ts +86 -0
  27. package/dist/event-store-executor-types.js +1 -0
  28. package/dist/event-store-types.d.ts +37 -0
  29. package/dist/event-store-types.js +7 -0
  30. package/dist/event-type-map.d.ts +8 -0
  31. package/{src/event-type-map.ts → dist/event-type-map.js} +1 -16
  32. package/dist/executor-brand.d.ts +8 -0
  33. package/dist/executor-brand.js +1 -0
  34. package/dist/extension-options-map.d.ts +9 -0
  35. package/dist/extension-options-map.js +4 -0
  36. package/dist/feature.d.ts +518 -0
  37. package/dist/feature.js +1 -0
  38. package/dist/field-icon.d.ts +1 -0
  39. package/dist/field-icon.js +1 -0
  40. package/dist/fields.d.ts +729 -0
  41. package/dist/fields.js +50 -0
  42. package/dist/file-handle-types.d.ts +12 -0
  43. package/dist/file-handle-types.js +1 -0
  44. package/dist/file-provider-resolver-types.d.ts +3 -0
  45. package/dist/file-provider-resolver-types.js +1 -0
  46. package/dist/file-storage-provider-types.d.ts +17 -0
  47. package/dist/file-storage-provider-types.js +1 -0
  48. package/dist/geo-tz.d.ts +17 -0
  49. package/{src/geo-tz.ts → dist/geo-tz.js} +1 -20
  50. package/dist/handlers.d.ts +493 -0
  51. package/dist/handlers.js +49 -0
  52. package/dist/hook-phase.d.ts +1 -0
  53. package/dist/hook-phase.js +1 -0
  54. package/dist/hooks.d.ts +75 -0
  55. package/dist/hooks.js +1 -0
  56. package/dist/http-route.d.ts +52 -0
  57. package/dist/http-route.js +13 -0
  58. package/dist/identifiers.d.ts +8 -0
  59. package/{src/identifiers.ts → dist/identifiers.js} +7 -24
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/kms-adapter-types.d.ts +68 -0
  63. package/dist/kms-adapter-types.js +49 -0
  64. package/dist/logger.d.ts +7 -0
  65. package/dist/logger.js +1 -0
  66. package/dist/multi-stream-apply-context-types.d.ts +18 -0
  67. package/dist/multi-stream-apply-context-types.js +1 -0
  68. package/dist/nav-icon.d.ts +3 -0
  69. package/dist/nav-icon.js +100 -0
  70. package/dist/nav.d.ts +39 -0
  71. package/dist/nav.js +1 -0
  72. package/dist/observability/index.d.ts +3 -0
  73. package/dist/observability/index.js +2 -0
  74. package/dist/observability/metric.d.ts +34 -0
  75. package/dist/observability/metric.js +3 -0
  76. package/dist/observability/provider.d.ts +24 -0
  77. package/dist/observability/provider.js +4 -0
  78. package/dist/observability/span.d.ts +32 -0
  79. package/dist/observability/span.js +4 -0
  80. package/dist/ownership.d.ts +33 -0
  81. package/dist/ownership.js +5 -0
  82. package/dist/projection.d.ts +38 -0
  83. package/dist/projection.js +1 -0
  84. package/dist/rate-limit-types.d.ts +19 -0
  85. package/dist/rate-limit-types.js +1 -0
  86. package/dist/relations.d.ts +25 -0
  87. package/dist/relations.js +1 -0
  88. package/dist/schema-table-types.d.ts +15 -0
  89. package/{src/schema-table-types.ts → dist/schema-table-types.js} +0 -20
  90. package/dist/screen.d.ts +1265 -0
  91. package/dist/screen.js +3 -0
  92. package/dist/search-adapter.d.ts +32 -0
  93. package/dist/search-adapter.js +1 -0
  94. package/dist/secrets-types.d.ts +52 -0
  95. package/dist/secrets-types.js +30 -0
  96. package/dist/snapshot-types.d.ts +13 -0
  97. package/dist/snapshot-types.js +1 -0
  98. package/dist/step.d.ts +304 -0
  99. package/dist/step.js +11 -0
  100. package/dist/target-ref.d.ts +5 -0
  101. package/{src/target-ref.ts → dist/target-ref.js} +1 -6
  102. package/dist/tenancy-brand.d.ts +6 -0
  103. package/dist/tenancy-brand.js +1 -0
  104. package/dist/tenant-db-types.d.ts +67 -0
  105. package/dist/tenant-db-types.js +5 -0
  106. package/dist/tree-node.d.ts +26 -0
  107. package/dist/tree-node.js +22 -0
  108. package/dist/tz-context.d.ts +48 -0
  109. package/dist/tz-context.js +6 -0
  110. package/dist/websocket-route.d.ts +46 -0
  111. package/dist/websocket-route.js +10 -0
  112. package/dist/where-clause-types.d.ts +20 -0
  113. package/dist/where-clause-types.js +1 -0
  114. package/dist/workspace.d.ts +10 -0
  115. package/dist/workspace.js +1 -0
  116. package/dist/write-error-info-types.d.ts +12 -0
  117. package/dist/write-error-info-types.js +1 -0
  118. package/package.json +372 -124
  119. package/src/changes.json +18 -0
  120. package/src/__tests__/handlers.test.ts +0 -40
  121. package/src/__tests__/plural-forms.test.ts +0 -18
  122. package/src/action-icon.ts +0 -57
  123. package/src/concurrency-mode.ts +0 -5
  124. package/src/config-scope.ts +0 -5
  125. package/src/config.ts +0 -563
  126. package/src/cursor-types.ts +0 -18
  127. package/src/db-connection.ts +0 -25
  128. package/src/define-handler.ts +0 -138
  129. package/src/derivatives-types.ts +0 -106
  130. package/src/entity-cache.ts +0 -35
  131. package/src/entity-handlers.ts +0 -68
  132. package/src/entity-table-meta-types.ts +0 -101
  133. package/src/envelope-cipher-types.ts +0 -6
  134. package/src/event-store-executor-types.ts +0 -163
  135. package/src/event-store-types.ts +0 -85
  136. package/src/executor-brand.ts +0 -19
  137. package/src/extension-options-map.ts +0 -21
  138. package/src/feature.ts +0 -1141
  139. package/src/field-icon.ts +0 -23
  140. package/src/fields.ts +0 -1114
  141. package/src/file-handle-types.ts +0 -20
  142. package/src/file-provider-resolver-types.ts +0 -7
  143. package/src/file-storage-provider-types.ts +0 -54
  144. package/src/handlers.ts +0 -1246
  145. package/src/hook-phase.ts +0 -2
  146. package/src/hooks.ts +0 -170
  147. package/src/http-route.ts +0 -78
  148. package/src/kms-adapter-types.ts +0 -128
  149. package/src/logger.ts +0 -7
  150. package/src/multi-stream-apply-context-types.ts +0 -52
  151. package/src/nav-icon.ts +0 -105
  152. package/src/nav.ts +0 -139
  153. package/src/observability/index.ts +0 -28
  154. package/src/observability/metric.ts +0 -56
  155. package/src/observability/provider.ts +0 -32
  156. package/src/observability/span.ts +0 -58
  157. package/src/ownership.ts +0 -83
  158. package/src/projection.ts +0 -170
  159. package/src/rate-limit-types.ts +0 -32
  160. package/src/relations.ts +0 -52
  161. package/src/screen.ts +0 -1593
  162. package/src/search-adapter.ts +0 -45
  163. package/src/secrets-types.ts +0 -182
  164. package/src/snapshot-types.ts +0 -36
  165. package/src/step.ts +0 -390
  166. package/src/tenancy-brand.ts +0 -9
  167. package/src/tenant-db-types.ts +0 -116
  168. package/src/tree-node.ts +0 -134
  169. package/src/tz-context.ts +0 -63
  170. package/src/websocket-route.ts +0 -68
  171. package/src/where-clause-types.ts +0 -31
  172. package/src/workspace.ts +0 -42
  173. package/src/write-error-info-types.ts +0 -20
  174. /package/{src/index.ts → dist/concurrency-mode.js} +0 -0
package/src/feature.ts DELETED
@@ -1,1141 +0,0 @@
1
- import type * as z from "zod";
2
- import type { ZodType } from "zod";
3
- import type {
4
- ConfigKeyDefinition,
5
- ConfigKeyHandle,
6
- ConfigKeyType,
7
- ConfigSeedDef,
8
- ExtensionSelectorDef,
9
- JobDefinition,
10
- JobHandlerFn,
11
- NotificationDataFn,
12
- NotificationDefinition,
13
- NotificationRecipientFn,
14
- NotificationTemplateFn,
15
- ReferenceDataDef,
16
- RegistrarExtensionDef,
17
- RegistrarExtensionRegistration,
18
- TranslationKeys,
19
- TranslationsDef,
20
- } from "./config";
21
- import type {
22
- QueryHandlerDefinition,
23
- StreamHandlerDefinition,
24
- WriteHandlerDefinition,
25
- } from "./define-handler";
26
- import type { RegisterEntityCrudOptions } from "./entity-handlers";
27
- import type { EntityTableMeta } from "./entity-table-meta-types";
28
- import type { ExtensionOptionsArgs, ExtensionOptionsFor } from "./extension-options-map";
29
- import type { EntityDefinition } from "./fields";
30
- import type {
31
- AccessRule,
32
- AgentHandlerHints,
33
- AuthClaimsFn,
34
- AuthClaimsHookDef,
35
- ClaimKeyDefinition,
36
- ClaimKeyHandle,
37
- ClaimKeyType,
38
- DeclarativeEventMigration,
39
- EntityRef,
40
- EscapeHatchDeclaration,
41
- EventDef,
42
- EventMigrationDef,
43
- EventPiiStance,
44
- EventUpcastFn,
45
- HandlerRef,
46
- NameOrRef,
47
- QualifiedEventName,
48
- QueryHandlerDef,
49
- QueryHandlerFn,
50
- RateLimitDeclaration,
51
- StreamHandlerDef,
52
- StreamHandlerFn,
53
- WriteHandlerDef,
54
- WriteHandlerFn,
55
- } from "./handlers";
56
- import type {
57
- EntityHookMap,
58
- HookMap,
59
- HookPhase,
60
- OwnedFn,
61
- PostDeleteHookFn,
62
- PostQueryHookFn,
63
- PostSaveHookFn,
64
- PreDeleteHookFn,
65
- PreQueryHookFn,
66
- PreSaveHookFn,
67
- SearchPayloadContributorFn,
68
- ValidationHookFn,
69
- } from "./hooks";
70
- import type { HttpRouteDefinition } from "./http-route";
71
- import type { ContentCollectionDefinition, NavDefinition } from "./nav";
72
- import type {
73
- EntityProjectionExtension,
74
- MultiStreamProjectionDefinition,
75
- ProjectionDefinition,
76
- } from "./projection";
77
- import type { EntityRelations, RelationDefinition } from "./relations";
78
- import type { ScreenDefinition } from "./screen";
79
- import type { TreeActionDef, TreeActionsHandle } from "./tree-node";
80
- import type { WebSocketRouteDefinition } from "./websocket-route";
81
- import type { WorkspaceDefinition } from "./workspace";
82
-
83
- // --- Metrics (declared by features via r.metric()) ---
84
-
85
- export type FeatureMetricType = "counter" | "histogram" | "gauge";
86
-
87
- // The user-facing short form written in a feature. The Framework prefixes it
88
- // with `kumiko_<featureName>_` to produce the fully-qualified Prometheus name.
89
- export type FeatureMetricDef = {
90
- readonly shortName: string;
91
- readonly type: FeatureMetricType;
92
- readonly description?: string;
93
- readonly labels?: readonly string[];
94
- readonly buckets?: readonly number[];
95
- readonly unit?: string;
96
- // When true, Framework auto-adds tenant_id to labels (ctx-driven). Default
97
- // false — cardinality multiplies by tenant count, so opt-in.
98
- readonly tenantLabel?: boolean;
99
- };
100
-
101
- export type MetricOptions = Omit<FeatureMetricDef, "shortName">;
102
-
103
- // --- Secret Keys (declared by features via r.secret()) ---
104
-
105
- // A feature-declared secret. The fully-qualified name is
106
- // `<featureName>:<shortName>` — the Framework prefixes. Ops see the
107
- // qualified name in list / audit; feature code reads it via
108
- // ctx.secrets.get(tenantId, SecretKeys.stripeKey) with the typed handle.
109
- export type SecretKeyDefinition = {
110
- // Short name inside the feature (e.g. "stripe.apiKey"). Qualified to
111
- // `<feature>:<shortName>` at registry-build time.
112
- readonly shortName: string;
113
- // Qualified name — `<feature>:<shortName>`. Set during registry build.
114
- readonly qualifiedName: string;
115
- // i18n label for TenantAdmin UI.
116
- readonly label: { readonly [locale: string]: string };
117
- // Optional redaction function. Takes the plaintext, returns the preview
118
- // shown in list handlers. Default is first-3-chars + bullets.
119
- readonly redact?: (plaintext: string) => string;
120
- // Short human hint shown in UI ("Find this in your Stripe dashboard ...").
121
- readonly hint?: { readonly [locale: string]: string };
122
- // Per-secret scope. v1 only "tenant" — user / system scopes ship in v2.
123
- readonly scope: "tenant";
124
- // Tenant must set this secret before the owning feature works. Surfaced
125
- // by readiness:query:status; keep in sync with the missing-secret throw
126
- // in the feature's build-fn.
127
- readonly required?: boolean;
128
- };
129
-
130
- export type SecretOptions = Omit<SecretKeyDefinition, "shortName" | "qualifiedName">;
131
-
132
- // Typed reference returned by r.secret(). Lets feature code pass a
133
- // strongly-named handle to ctx.secrets.get instead of retyping the
134
- // qualified string. Parallels ConfigKeyHandle from the config system.
135
- export type SecretKeyHandle = {
136
- readonly name: string;
137
- };
138
-
139
- // --- Store tables (declared by features via r.storeTable()) ---
140
- // Post-drizzle-cut: unified with the former r.unmanagedTable() — both
141
- // carried the same reason/audit contract, differing only in whether the
142
- // table value was a legacy Drizzle PgTable (storeTable) or the framework-
143
- // native EntityTableMeta (unmanagedTable, consumed by migrate-runner).
144
- // EntityTableMeta was the forward-compatible shape, so storeTable adopted it.
145
-
146
- /** Options accepted by `r.storeTable()`. The `reason` is required so the
147
- * bypass leaves an audit trail at the registration site — reviewers can
148
- * judge legitimacy without spelunking into history, and a future cleanup
149
- * pass can find candidates for migration to `r.entity()`. */
150
- export type StoreTableOptions = {
151
- /** Why this table needs to bypass the event-sourcing system. Examples:
152
- * "imported from pre-ES system, read-only", "external Stripe webhook
153
- * payload cache, write-only by webhook handler", "denormalised
154
- * projection of a non-Kumiko data source". */
155
- readonly reason: string;
156
- /** Direct-write stores skip the executor, so the executor's PII
157
- * encryption never runs for them — a feature whose meta carries
158
- * piiSubjectFields must encrypt those fields itself before every
159
- * insert/update and declare that here, or boot fails (#820). */
160
- readonly piiEncryptedOnWrite?: true;
161
- };
162
-
163
- /** Per-feature store-table registration. `meta` is the `EntityTableMeta`
164
- * (framework-native shape used by `migrate-runner`). Carries the
165
- * bypass-justification reason but knows nothing about the owning
166
- * feature — that's added when the registry aggregates entries
167
- * cross-feature into `StoreTableDef`. */
168
- export type StoreTableEntry = {
169
- readonly name: string;
170
- readonly meta: EntityTableMeta;
171
- readonly reason: string;
172
- readonly piiEncryptedOnWrite?: true;
173
- };
174
-
175
- /** Registry-aggregated store-table — the per-feature `StoreTableEntry` plus
176
- * the owning feature name. This is what `Registry.getAllStoreTables()`
177
- * exposes to readers (dev-server, ops UIs). */
178
- export type StoreTableDef = StoreTableEntry & {
179
- readonly featureName: string;
180
- };
181
-
182
- // --- UI-Hints (manifest-only, picker/scaffolder metadata) ---
183
-
184
- // Optional, declarative UI metadata declared via `r.uiHints({...})`. Surfaces
185
- // in feature-manifest.json under `feature.uiHints`. Consumers (the picker in
186
- // `create-kumiko-app`, the docs feature-reference) treat absent hints as
187
- // "no special treatment" — bare feature.name + feature.description still work.
188
- //
189
- // Keep this list lean. Anything that already has a home on the feature
190
- // (configKeys.scope/default/encrypted, secretKeys, requires, etc.) lives there.
191
- // Only add fields here that are genuinely UI-only.
192
- // "select"/"text" variants dropped (569/1) — no bundled feature uses anything
193
- // but "boolean" yet and the picker doesn't render them either; re-add once a
194
- // real feature needs them.
195
- export type UiHintOption = {
196
- readonly key: string;
197
- readonly label: string;
198
- readonly type: "boolean";
199
- readonly default: boolean;
200
- };
201
-
202
- export type UiHints = {
203
- // Picker-facing label ("Auth · Email + Password" instead of the bare
204
- // feature-name "auth-email-password").
205
- readonly displayLabel?: string;
206
- // Grouping for the picker. Free-form string; the picker sorts/groups by it.
207
- readonly category?: string;
208
- // Pre-checked in the picker when the user runs `bun create kumiko-app`.
209
- readonly recommended?: boolean;
210
- // Sub-options the picker asks about per-feature (e.g. "Password-Reset-Flow
211
- // on/off"). The scaffolder maps each key to a generator decision; the
212
- // framework doesn't act on them at runtime.
213
- readonly configurableOptions?: readonly UiHintOption[];
214
- };
215
-
216
- // --- Feature Definition (output of defineFeature) ---
217
-
218
- // Ctx passed to r.bootCheck(fn) — the full mounted-feature set, so a check
219
- // can inspect any other feature's shape (entities, PII fields, etc.).
220
- export type BootCheckContext = {
221
- readonly features: readonly FeatureDefinition[];
222
- };
223
- export type BootCheckFn = (ctx: BootCheckContext) => void;
224
-
225
- export type FeatureDefinition = {
226
- readonly name: string;
227
- // Docs-lead paragraph declared via r.describe(). Flows through the
228
- // manifest introspection into the generated feature-reference pages.
229
- readonly description?: string;
230
- readonly systemScope: boolean;
231
- // Set from the setup-callback return — typed via `defineFeature<TExports>`.
232
- // `undefined` for setups that return nothing.
233
- readonly exports?: unknown;
234
- readonly requires: readonly string[];
235
- readonly optionalRequires: readonly string[];
236
- // Read-side projection-tables this feature is allowed to write via
237
- // r.step.unsafeProjectionUpsert / unsafeProjectionDelete. Declared via
238
- // r.requires.projection("table_name"). Hard requirement — boot-error
239
- // if a step targets a non-listed table or one that's already an
240
- // r.entity-registered aggregate-table. See step-vocabulary.md Q10.
241
- readonly requiredProjections: ReadonlySet<string>;
242
- // Tier-2 step kinds opted-in via r.requires.step("webhook.send"). Q9.
243
- readonly requiredSteps: ReadonlySet<string>;
244
- // Declared via r.toggleable({ default }). Presence makes the feature
245
- // operator-switchable via the feature-toggles bundled feature; absence
246
- // means the feature is always-on (e.g. auth, tenant, user — core infra
247
- // that would brick the system if switchable).
248
- readonly toggleableDefault?: boolean;
249
- // Declarative UI metadata for picker/scaffolder tooling. Set via r.uiHints().
250
- // Pure manifest-side info — the framework runtime doesn't read it.
251
- readonly uiHints?: UiHints;
252
- // entities/hooks/entityHooks are optional: defineFeature always
253
- // materializes them, but hand-built definitions at system boundaries
254
- // (test fixtures, partial boots — see registry.test.ts "slot robustness")
255
- // omit them and the registry guards against that. Type follows runtime.
256
- readonly entities?: Readonly<Record<string, EntityDefinition>>;
257
- // Optional backing Drizzle table per entity, declared via the third arg of
258
- // `r.entity(name, def, { table })`. Source of truth for the physical DDL
259
- // when the table carries columns/indexes the field-DSL can't express
260
- // (e.g. secrets' envelope jsonb without default). `collectTableMetas` and
261
- // the registry's implicit-projection use this object instead of the
262
- // field-derived table, so generate + test-push + executor share ONE table.
263
- readonly entityTables?: Readonly<Record<string, unknown>>;
264
- readonly relations: Readonly<Record<string, EntityRelations>>;
265
- readonly writeHandlers: Readonly<Record<string, WriteHandlerDef>>;
266
- readonly queryHandlers: Readonly<Record<string, QueryHandlerDef>>;
267
- readonly streamHandlers: Readonly<Record<string, StreamHandlerDef>>;
268
- readonly translations: TranslationKeys;
269
- readonly hooks?: HookMap;
270
- readonly entityHooks?: EntityHookMap;
271
- // F3 search-payload-extension — per-entity contributors that add flat fields
272
- // to the search-index payload during indexing. Keyed by entityName. Wrapped
273
- // in OwnedFn for feature-toggle filtering (consistent with postQuery-Hooks).
274
- readonly searchPayloadExtensions?: Readonly<
275
- Record<string, readonly OwnedFn<SearchPayloadContributorFn>[]>
276
- >;
277
- readonly configKeys: Readonly<Record<string, ConfigKeyDefinition>>;
278
- readonly configSeeds: readonly ConfigSeedDef[];
279
- readonly jobs: Readonly<Record<string, JobDefinition>>;
280
- readonly registrarExtensions: Readonly<Record<string, RegistrarExtensionDef>>;
281
- readonly extensionUsages: readonly RegistrarExtensionRegistration[];
282
- readonly extensionSelectors: readonly ExtensionSelectorDef[];
283
- /**
284
- * Cross-feature API names this feature exposes via `r.exposesApi(name)`.
285
- * Pure Marker-Deklaration — die echte Implementation wird als
286
- * Query-/Write-Handler unter dem QN-Pattern registriert (z.B.
287
- * `compliance-profiles:query:effective-profile`). Boot-Validator prüft
288
- * dass jedes `r.usesApi(name)` einen passenden Exposer hier findet —
289
- * Tippfehler oder Drop-Refactorings werden zu Boot-Fail statt Runtime-Crash.
290
- */
291
- readonly exposedApis: ReadonlySet<string>;
292
- /**
293
- * Cross-feature API names this feature calls. Pflicht-Boot-Check:
294
- * jeder Eintrag muss in `exposedApis` irgendeines Features auftauchen
295
- * UND das Provider-Feature muss in requires/optionalRequires sein.
296
- */
297
- readonly usedApis: ReadonlySet<string>;
298
- /**
299
- * Boot-time mount-invariant checks declared via `r.bootCheck(fn)`. Run
300
- * once per feature after all other boot-validators, each with a ctx
301
- * exposing the full mounted-feature set — throw to fail the boot with a
302
- * feature-authored message (framework wraps it with `[Feature <name>]`).
303
- */
304
- readonly bootChecks: readonly BootCheckFn[];
305
- readonly referenceData: readonly ReferenceDataDef[];
306
- readonly notifications: Readonly<Record<string, NotificationDefinition>>;
307
- readonly events: Readonly<Record<string, EventDef>>;
308
- // Event schema migrations declared via defineEvent's `migrations` option. Keyed by event
309
- // short-name; each entry carries the step transforms (fromVersion →
310
- // toVersion). The registry stitches these with the defineEvent-declared
311
- // current version and exposes a per-qualified-name upcaster chain.
312
- readonly eventMigrations: Readonly<Record<string, readonly EventMigrationDef[]>>;
313
- readonly configReads: readonly string[];
314
- // Handler → entity mapping inferred from the colon convention
315
- // ("entityName:verb") via tryMapEntity in defineFeature.
316
- readonly handlerEntityMappings: Readonly<Record<string, string>>;
317
- // Metrics declared via r.metric(). Short names — Framework prefixes on boot.
318
- readonly metrics: Readonly<Record<string, FeatureMetricDef>>;
319
- // Secret keys declared via r.secret(). Short names — Framework prefixes to
320
- // "<feature>:<short>" during registry build.
321
- readonly secretKeys: Readonly<Record<string, SecretKeyDefinition>>;
322
- // Projections declared via r.projection(). Keyed by projection name; executor
323
- // looks them up by source-entity at write-time.
324
- readonly projections: Readonly<Record<string, ProjectionDefinition>>;
325
- // Implicit-projection extensions declared via r.extendEntityProjection().
326
- // Keyed by entity name; merged into that entity's implicit projection at
327
- // registry build so rebuildProjection replays the extension's events.
328
- readonly entityProjectionExtensions?: Readonly<
329
- Record<string, readonly EntityProjectionExtension[]>
330
- >;
331
- // Multi-stream projections — cross-aggregate async read-models. Keyed by
332
- // short name; the dispatcher wraps each into an EventConsumer with its
333
- // own cursor.
334
- readonly multiStreamProjections: Readonly<Record<string, MultiStreamProjectionDefinition>>;
335
- // Auth-claims hooks declared via r.authClaims(). Executed at login (and
336
- // switch-tenant) time; their returned records are merged into
337
- // SessionUser.claims under the auto-prefix "<featureName>:<key>".
338
- readonly authClaimsHooks: readonly AuthClaimsFn[];
339
- // Declared claim keys via r.claimKey(). Shorts keyed by their JS-side
340
- // short name, qualified name qualified at registration time.
341
- readonly claimKeys: Readonly<Record<string, ClaimKeyDefinition>>;
342
- // Screen definitions declared via r.screen(). Keyed by the feature-local
343
- // short id; the registry qualifies to "<feature>:screen:<id>" on boot.
344
- // Pure data — ui-core + renderer packages interpret; engine only stores
345
- // and validates entity/field references against the feature's entities.
346
- readonly screens: Readonly<Record<string, ScreenDefinition>>;
347
- // Nav entries declared via r.nav(). Keyed by the feature-local short id;
348
- // registry qualifies to "<feature>:nav:<id>". Flat list — the renderer's
349
- // resolveNavigation assembles the tree from parent refs at mount time.
350
- readonly navs: Readonly<Record<string, NavDefinition>>;
351
- // Workspaces declared via r.workspace(). Keyed by feature-local short id;
352
- // registry qualifies to "<feature>:workspace:<id>". Pure UI metadata —
353
- // shellWorkspaces consumes the resolved per-workspace nav list at mount
354
- // time; engine validates roles + nav refs at boot.
355
- readonly workspaces: Readonly<Record<string, WorkspaceDefinition>>;
356
- // Tree-Actions-Map declared via r.treeActions(). At-most-one per feature
357
- // (only-once-guard at registration). Erased to `Record<string,
358
- // TreeActionDef>` for runtime registry-lookup (Visual-Tree-Component
359
- // dispatching, Pattern-AST consumers). The compile-time-typed surface
360
- // is the registrar's return value (TreeActionsHandle) which the
361
- // feature exports via setup-return — buildTarget consumes the handle,
362
- // not this slot. See visual-tree.md A5 + A7.
363
- readonly treeActions?: Readonly<Record<string, TreeActionDef>>;
364
- // Content collections declared via r.contentCollection(). Keyed by the
365
- // feature-local short id. Each one also produced a nav entry under the
366
- // same id — this slot carries the `kind` the client needs to build the
367
- // matching tree provider, which a NavDefinition has no place for.
368
- readonly contentCollections?: Readonly<Record<string, ContentCollectionDefinition>>;
369
- // HTTP-Routes declared via r.httpRoute(). Index is "METHOD path"
370
- // (z.B. "GET /feed.xml") — eindeutig pro Feature. Die App-Server-
371
- // Boot-Stage iteriert getAllHttpRoutes() und mountet jede Route auf
372
- // den Hono-app (außerhalb /api/*). Pattern symmetrisch zu queryHandlers/
373
- // writeHandlers — Routes leben mit dem Feature, nicht im Bootstrap.
374
- readonly httpRoutes: Readonly<Record<string, HttpRouteDefinition>>;
375
- // WebSocket routes declared via r.webSocketRoute(), keyed by path.
376
- readonly webSocketRoutes: Readonly<Record<string, WebSocketRouteDefinition>>;
377
- // Store tables declared via r.storeTable() — bypass the event-sourcing
378
- // system. Keyed by feature-local short name (derived from
379
- // meta.tableName). The registry attaches featureName on aggregation,
380
- // lifting StoreTableEntry → StoreTableDef. `kumiko schema generate`
381
- // aggregates these alongside r.entity()-derived metas to build the
382
- // full schema.
383
- readonly storeTables: Readonly<Record<string, StoreTableEntry>>;
384
- // Optional Zod-schema for env-vars this feature reads at runtime.
385
- // Declared via `r.envSchema(z.object({...}))`. `composeEnvSchema` reads
386
- // this to build one app-wide schema for boot-validation + dry-run
387
- // rendering. Absence means the feature reads no env-vars (or hasn't
388
- // been migrated yet — Sprint-9 migration is add-only per phase).
389
- readonly envSchema?: z.ZodObject<z.ZodRawShape>;
390
- // Factory guarantee: two instances with shallow-equal dedupeOptions are interchangeable.
391
- readonly dedupeOptions?: Readonly<Record<string, unknown>>;
392
- };
393
-
394
- // --- Feature Registrar (the "r" object in defineFeature) ---
395
-
396
- type RefOrRefs = NameOrRef | readonly NameOrRef[];
397
- // Entity-wide hook target — "all query/write handlers of this entity",
398
- // same reach r.entityHook() used to have. Only valid for postSave/
399
- // preDelete/postDelete/postQuery (the same 4 types entityHook covered);
400
- // hook() throws at registration time if used with validation/preSave/
401
- // preQuery.
402
- type HookTarget = RefOrRefs | { readonly allOf: NameOrRef };
403
-
404
- /**
405
- * `TFeature` is the literal feature-name from `defineFeature("foo", ...)` —
406
- * default-`string` keeps every existing usage zero-config. Strict-typed
407
- * features (apps that opt into the literal-name flavour) get propagated
408
- * through to `defineEvent` so the returned `EventDef.name` is a literal
409
- * `${CamelToKebab<TFeature>}:event:${CamelToKebab<TInner>}`. That literal
410
- * threads through `ctx.appendEvent({ type: eventDef.name, ... })`,
411
- * keeping strict-mode alive even when handlers route via `eventDef.name`
412
- * instead of hand-typed string literals.
413
- */
414
- /**
415
- * `r.requires` is a callable+namespace: existing call form takes feature
416
- * names (`r.requires("auth", "tenant")`), the `.projection` extension
417
- * declares read-side projection tables that this feature's pipeline
418
- * steps are allowed to write via `r.step.unsafeProjectionUpsert`.
419
- * Hard-required for any unsafeProjection-* step usage (see Q10).
420
- */
421
- export type RequiresApi = ((...featureNames: string[]) => void) &
422
- // Object-Form — the shape the feature-ast renderer (`render.ts`) emits
423
- // for Designer/AI-generated code. A single object argument with named
424
- // fields is easier to generate correctly than positional args whose
425
- // count/order vary per registrar method.
426
- ((options: { readonly features: readonly string[] }) => void) & {
427
- readonly projection: (tableName: string) => void;
428
- // Tier-2 step opt-in (Q9). Tier-1 implicit, Tier-2 must be declared.
429
- readonly step: (stepKind: string) => void;
430
- };
431
-
432
- export type FeatureRegistrar<TFeature extends string = string> = {
433
- systemScope(): void;
434
- // One-to-three-sentence docs-lead for the feature ("what it does + when
435
- // you need it"). At most once per feature; must be non-empty.
436
- describe(text: string): void;
437
- requires: RequiresApi;
438
- optionalRequires(...featureNames: string[]): void;
439
- optionalRequires(options: { readonly features: readonly string[] }): void;
440
- // Declare the feature as operator-togglable. `default` is the effective
441
- // state when no global-toggle row exists. Must be called at most once per
442
- // feature; calling on an always-on feature (e.g. auth/tenant/user) is a
443
- // bug — and one nothing catches at boot, so don't.
444
- toggleable(options: { default: boolean }): void;
445
- // Picker/scaffolder metadata — see UiHints. At most once per feature.
446
- uiHints(hints: UiHints): void;
447
-
448
- entity(
449
- name: string,
450
- definition: EntityDefinition,
451
- options?: { readonly table?: unknown },
452
- ): EntityRef;
453
- entity(definition: { readonly name: string } & EntityDefinition): EntityRef;
454
-
455
- // Shorthand for registerEntityCrud(r, ...), scoped to this registrar.
456
- crud(entityName: string, entity: EntityDefinition, options?: RegisterEntityCrudOptions): void;
457
-
458
- writeHandler<TName extends string, TSchema extends ZodType>(
459
- def: WriteHandlerDefinition<TName, TSchema>,
460
- ): HandlerRef;
461
- writeHandler<TSchema extends ZodType>(
462
- name: string,
463
- schema: TSchema,
464
- handler: WriteHandlerFn<z.infer<TSchema>>,
465
- options: {
466
- access: AccessRule;
467
- rateLimit?: RateLimitDeclaration;
468
- description?: string;
469
- agent?: AgentHandlerHints;
470
- escapeHatch?: EscapeHatchDeclaration;
471
- },
472
- ): HandlerRef;
473
-
474
- queryHandler<TName extends string, TSchema extends ZodType>(
475
- def: QueryHandlerDefinition<TName, TSchema>,
476
- ): HandlerRef;
477
- queryHandler<TSchema extends ZodType>(
478
- name: string,
479
- schema: TSchema,
480
- handler: QueryHandlerFn<z.infer<TSchema>>,
481
- options: {
482
- access: AccessRule;
483
- rateLimit?: RateLimitDeclaration;
484
- outputSchema?: ZodType;
485
- description?: string;
486
- agent?: AgentHandlerHints;
487
- escapeHatch?: EscapeHatchDeclaration;
488
- },
489
- ): HandlerRef;
490
-
491
- streamHandler<TName extends string, TSchema extends ZodType>(
492
- def: StreamHandlerDefinition<TName, TSchema>,
493
- ): HandlerRef;
494
- streamHandler<TSchema extends ZodType>(
495
- name: string,
496
- schema: TSchema,
497
- handler: StreamHandlerFn<z.infer<TSchema>>,
498
- options: {
499
- access: AccessRule;
500
- rateLimit?: RateLimitDeclaration;
501
- escapeHatch?: EscapeHatchDeclaration;
502
- },
503
- ): HandlerRef;
504
-
505
- relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
506
- // TDef inferred from the literal — keeps the excess-property check
507
- // resolved against the matching RelationDefinition union member instead
508
- // of distributing across all three (which spuriously rejects valid
509
- // combinations when checked directly against the raw union).
510
- relation<TDef extends RelationDefinition>(
511
- definition: { readonly entity: NameOrRef; readonly name: string } & TDef,
512
- ): void;
513
-
514
- hook(type: "validation", target: RefOrRefs, fn: ValidationHookFn): void;
515
- // escapeHatch grants this hook (not the handler) identity-switches beyond its caller — see system-identity-switch.ts.
516
- hook(
517
- type: "preSave",
518
- target: RefOrRefs,
519
- fn: PreSaveHookFn,
520
- options?: { escapeHatch?: EscapeHatchDeclaration },
521
- ): void;
522
- // postSave/preDelete/postDelete/postQuery accept `{ allOf: entityRef }` —
523
- // fires for every write/query handler of that entity, replacing the old
524
- // r.entityHook(type, entity, fn). postQuery's entity-wide form fires for
525
- // ALL query-handlers of the entity (e.g. customFields-bundle merging
526
- // custom-fields-jsonb into every read); no phase semantics there
527
- // (synchronous after handler-execute, before field-access-filter).
528
- hook(
529
- type: "postSave",
530
- target: HookTarget,
531
- fn: PostSaveHookFn,
532
- options?: { phase?: HookPhase; escapeHatch?: EscapeHatchDeclaration },
533
- ): void;
534
- // preDelete always runs in-transaction (it guards the delete — there is no
535
- // meaningful "after" for a pre-hook). No phase option.
536
- hook(
537
- type: "preDelete",
538
- target: HookTarget,
539
- fn: PreDeleteHookFn,
540
- options?: { escapeHatch?: EscapeHatchDeclaration },
541
- ): void;
542
- hook(
543
- type: "postDelete",
544
- target: HookTarget,
545
- fn: PostDeleteHookFn,
546
- options?: { phase?: HookPhase; escapeHatch?: EscapeHatchDeclaration },
547
- ): void;
548
- hook(
549
- type: "preQuery",
550
- target: RefOrRefs,
551
- fn: PreQueryHookFn,
552
- options?: { escapeHatch?: EscapeHatchDeclaration },
553
- ): void;
554
- hook(
555
- type: "postQuery",
556
- target: HookTarget,
557
- fn: PostQueryHookFn,
558
- options?: { escapeHatch?: EscapeHatchDeclaration },
559
- ): void;
560
-
561
- // F3 — Search-Payload-Extension: contributor function adds flat fields to
562
- // an entity's search-index document. Fires synchronously during
563
- // buildSearchDocument indexing. Use-case: custom-fields-bundle merging
564
- // customFields-jsonb-keys flat into search-doc; tags-bundle projecting
565
- // tags-array as searchable. See `SearchPayloadContributorFn`.
566
- searchPayloadExtension(entity: NameOrRef, fn: SearchPayloadContributorFn): void;
567
-
568
- // Single-key form: bare handle, no wrapping record, no seeds (callers
569
- // needing seeds use the multi-key form below).
570
- config<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
571
-
572
- // Multi-key form: returns a handle map keyed exactly like the input. Pass
573
- // any handle to `ctx.config(handle)` to get the value type narrowed by the
574
- // key's `type`. Optional `seeds` declare boot-time system-rows that are
575
- // written via the event-store executor — idempotent, skipped when the
576
- // stream already exists.
577
- config<TKeys extends Readonly<Record<string, ConfigKeyDefinition<ConfigKeyType>>>>(definition: {
578
- readonly keys: TKeys;
579
- readonly seeds?: Readonly<Record<string, ConfigSeedDef>>;
580
- }): { readonly [K in keyof TKeys]: ConfigKeyHandle<TKeys[K]["type"]> };
581
-
582
- job(name: string, options: Omit<JobDefinition, "name" | "handler">, handler: JobHandlerFn): void;
583
- job(definition: JobDefinition): void;
584
-
585
- notification(
586
- name: string,
587
- definition: {
588
- readonly trigger: { readonly on: NameOrRef };
589
- readonly recipient: NotificationRecipientFn;
590
- readonly data: NotificationDataFn;
591
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
592
- },
593
- ): void;
594
- notification(definition: {
595
- readonly name: string;
596
- readonly trigger: { readonly on: NameOrRef };
597
- readonly recipient: NotificationRecipientFn;
598
- readonly data: NotificationDataFn;
599
- readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
600
- }): void;
601
-
602
- translations(def: TranslationsDef): void;
603
-
604
- // Register an event payload shape. Returns the qualified def so callers
605
- // can pass `.name` to ctx.appendEvent without hand-building the
606
- // "<feature>:event:<short>" string.
607
- //
608
- // `options.version` declares the CURRENT schema generation. Defaults to 1
609
- // on first registration. When you bump the payload shape, add a step to
610
- // `options.migrations` covering N -> N+1 — the framework refuses to boot
611
- // if the chain from 1 to `version` has gaps. Migrations were formerly a
612
- // separate r.eventMigration() call; folded in here because an event and
613
- // its schema evolution are one lifecycle, not two registrar concepts
614
- // (#1082 step 8) — transforms are pure functions (old payload in, new
615
- // payload out) and run once per read, not once per event persisted, so
616
- // keep them cheap.
617
- //
618
- // `options.piiFields` is a mandatory, explicit PII stance (fw#2558) —
619
- // registration fails without one. Pass a map of payload fields to their
620
- // owning subject via `{ personal: { of: "<ownerField>" } }` (the older
621
- // `{ subjectField: "<ownerField>" }` form still works) for fields carrying
622
- // personal data (encrypted under that user's DEK, crypto-shredding #799),
623
- // or `"none"` to declare the payload holds no personal data. append()
624
- // enforces the resulting catalog on every write path.
625
- //
626
- // When the schema lets the owner field be null/undefined, the stance must
627
- // also say what happens on those writes — `whenAbsent: "tenant"` encrypts
628
- // under the envelope tenant key, `whenAbsent: "plaintext"` acknowledges an
629
- // unshreddable value. Registration fails without one, and an owner that is
630
- // empty at append time with no declared fallback aborts the write (fw#2776).
631
- defineEvent<const TInner extends string, TPayload>(
632
- name: TInner,
633
- schema: ZodType<TPayload>,
634
- options: {
635
- readonly version?: number;
636
- readonly piiFields: EventPiiStance;
637
- readonly migrations?: readonly {
638
- readonly fromVersion: number;
639
- readonly toVersion: number;
640
- readonly transform: EventUpcastFn | DeclarativeEventMigration;
641
- }[];
642
- },
643
- ): EventDef<TPayload, QualifiedEventName<TFeature, TInner>>;
644
-
645
- readsConfig(...qualifiedKeys: string[]): void;
646
- readsConfig(options: { readonly keys: readonly string[] }): void;
647
-
648
- referenceData(
649
- entity: NameOrRef,
650
- data: readonly Record<string, unknown>[],
651
- options?: { upsertKey?: string },
652
- ): void;
653
- referenceData(definition: {
654
- readonly entity: NameOrRef;
655
- readonly data: readonly Record<string, unknown>[];
656
- readonly upsertKey?: string;
657
- }): void;
658
-
659
- extendsRegistrar(name: string, def: RegistrarExtensionDef): void;
660
-
661
- useExtension<N extends string>(
662
- extensionName: N,
663
- entity: NameOrRef,
664
- ...options: ExtensionOptionsArgs<N>
665
- ): void;
666
- useExtension<N extends string>(
667
- definition: { readonly name: N; readonly entity: NameOrRef } & ExtensionOptionsFor<N>,
668
- ): void;
669
-
670
- /**
671
- * Declares which config key selects the active provider under an
672
- * extension point — called by the point-owning foundation (e.g.
673
- * `r.extensionSelector("mailTransport", configKeys.provider)`).
674
- * Readiness gating counts a provider-feature's `required` keys and
675
- * secrets only while that provider is the selected one. Registry-build
676
- * fails on duplicate declarations per extension and on selector keys
677
- * that no mounted feature declares. A masked tenant selector key makes the
678
- * Settings-Hub render one tenant dashboard for the owner: the selector as a
679
- * select of the mounted plugin ids, the selected plugin's config and secrets
680
- * as panels, and plugin features lose their own tenant nav. `config:write:set`
681
- * rejects unknown plugin ids (`""` clears).
682
- */
683
- extensionSelector(extensionName: string, key: { readonly name: string } | string): void;
684
-
685
- /**
686
- * Marker-Deklaration: dieses Feature stellt eine Cross-Feature-API
687
- * unter dem genannten Namen bereit. Die eigentliche Implementation
688
- * wird separat als Query- oder Write-Handler unter dem QN-Pattern
689
- * registriert; `r.exposesApi` ist reine Boot-Check-Surface.
690
- *
691
- * Boot-Validator prüft, dass jedes `r.usesApi(name)` einen passenden
692
- * Exposer findet, dass das Exposer-Feature in requires/optionalRequires
693
- * gelisted ist und dass kein API-Name doppelt exposed wird.
694
- *
695
- * ```ts
696
- * defineFeature("compliance-profiles", (r) => {
697
- * r.exposesApi("compliance.forTenant");
698
- * r.queryHandler({
699
- * name: "compliance:query:for-tenant",
700
- * // ... echte Implementation
701
- * });
702
- * });
703
- * ```
704
- */
705
- exposesApi(apiName: string): void;
706
-
707
- /**
708
- * Declares that this feature calls a cross-feature API. Boot-Validator
709
- * checkt dass irgendein anderes Feature `r.exposesApi(apiName)` macht
710
- * und dass dieses Feature `r.requires/optionalRequires` darauf hat.
711
- *
712
- * ```ts
713
- * defineFeature("user-data-rights", (r) => {
714
- * r.requires("compliance-profiles");
715
- * r.usesApi("compliance.forTenant");
716
- * });
717
- * ```
718
- */
719
- usesApi(apiName: string): void;
720
-
721
- /**
722
- * Declares a boot-time mount-invariant for this feature. `fn` runs once
723
- * at boot with a ctx exposing every mounted feature — throw to fail the
724
- * boot with a clear message. Multiple calls per feature are allowed.
725
- *
726
- * Use for cross-feature invariants that `r.requires` can't express
727
- * because they're conditional (only fail when the feature has a
728
- * specific shape), e.g. "if this feature has PII entities, some
729
- * user-data-hook feature must be mounted" (the prompt-store trap,
730
- * kumiko-enterprise#229 — a UserData feature was written but never
731
- * mounted, and nothing caught it at boot):
732
- *
733
- * ```ts
734
- * defineFeature("prompt-store", (r) => {
735
- * const promptFields = { text: createTextField({ personal: "self", find: "none" }) };
736
- * r.entity("prompt", { fields: promptFields });
737
- * r.bootCheck(({ features }) => {
738
- * // Conditional on this feature's own shape (has a pii field) —
739
- * // r.requires("user-data-hook") can't express that, it would fail
740
- * // even for a prompt-store variant with no PII fields at all.
741
- * const hasPiiField = Object.values(promptFields).some((f) => f.pii);
742
- * const hasUserDataHook = features.some((f) => f.name === "user-data-hook");
743
- * if (hasPiiField && !hasUserDataHook) {
744
- * throw new Error("prompt-store has PII fields but no user-data-hook feature is mounted");
745
- * }
746
- * });
747
- * });
748
- * ```
749
- */
750
- bootCheck(fn: BootCheckFn): void;
751
-
752
- // Declare a metric. Short name (without kumiko_<feature>_ prefix) — Framework
753
- // qualifies it on boot. Validation (snake_case + typ-suffix) runs at boot.
754
- // Usage at runtime: ctx.metrics.inc("created_total", { status: "new" }).
755
- metric(shortName: string, options: MetricOptions): void;
756
- metric(definition: { readonly name: string } & MetricOptions): void;
757
-
758
- // Declare a secret key. Qualified name follows "<feature>:secret:<kebab>"
759
- // via the QN helper. Returns a typed handle so feature code can pass it
760
- // to ctx.secrets.get without retyping the qualified string — same
761
- // ergonomics as r.config's handle.
762
- secret(shortName: string, options: SecretOptions): SecretKeyHandle;
763
- secret(definition: { readonly name: string } & SecretOptions): SecretKeyHandle;
764
-
765
- // Register a projection driven by events of one or more source entities.
766
- // The runtime fires projection.apply[event.type] inside the event-store's
767
- // transaction, so projections stay consistent with the events that feed them.
768
- projection(definition: ProjectionDefinition): void;
769
-
770
- // Register a cross-aggregate async projection. The event-dispatcher owns
771
- // delivery via a dedicated cursor — at-least-once, strictly-ordered by
772
- // events.id. Handlers must be idempotent. Marten's MultiStreamProjection
773
- // equivalent: customer billing summaries, cross-feature audit views,
774
- // saga state machines where a single view spans many aggregate types.
775
- // Omit `table` for pure side-effect handlers (notifications, webhooks,
776
- // external-system sync) — the dispatcher still delivers at-least-once with
777
- // per-consumer ordering and dead-letter behaviour.
778
- multiStreamProjection(definition: MultiStreamProjectionDefinition): void;
779
-
780
- // Merge extra apply handlers (+ extra event sources) into an entity's
781
- // implicit projection so rebuildProjection replays event types a bundled
782
- // extension materializes into the HOST entity's table (custom-fields
783
- // pattern). Rebuild-only: the inline runner skips implicit projections —
784
- // live delivery stays with the extension's own MSP. The entity must be
785
- // declared via r.entity in the SAME feature; unknown entities and
786
- // apply-key collisions fail at registry build.
787
- extendEntityProjection(entityName: string, extension: EntityProjectionExtension): void;
788
-
789
- // Register a function that contributes claims into SessionUser.claims at
790
- // login time. Multiple features (and multiple calls within one feature)
791
- // are allowed; returns are merged. Keys are auto-prefixed with the feature
792
- // name ("<feature>:<key>") — cross-feature collisions are impossible by
793
- // construction. Same-feature duplicate keys follow last-wins.
794
- //
795
- // Hooks run in parallel. If one throws, the error is logged and that
796
- // feature's claims are simply missing from the merged record — login
797
- // still succeeds. This is a deliberate best-effort policy: identity-facts
798
- // are convenience, not access-gates (that's what `roles` + field-access
799
- // rules are for).
800
- authClaims(fn: AuthClaimsFn): void;
801
-
802
- // Declare a claim key. Qualified name follows "<feature>:<shortName>" —
803
- // NO kebab conversion (it would break the claim round-trip, unlike
804
- // r.secret / r.config). Returns a
805
- // typed handle so feature code can pass it to `readClaim(user, handle)`
806
- // without retyping the qualified string and with the right narrowed
807
- // return type.
808
- //
809
- // Declaring claim keys also turns on a runtime check: when the feature's
810
- // r.authClaims hooks return an inner-key not in the declared list, the
811
- // resolver logs a warning (the claim still lands in the JWT — declared
812
- // or not — so strict-mode isn't on; this is typo-drift protection).
813
- claimKey<T extends ClaimKeyType>(
814
- shortName: string,
815
- options: { readonly type: T },
816
- ): ClaimKeyHandle<T>;
817
- claimKey<T extends ClaimKeyType>(definition: {
818
- readonly name: string;
819
- readonly type: T;
820
- }): ClaimKeyHandle<T>;
821
-
822
- // Register a screen. The id is the feature-local short name (kebab-case);
823
- // the registry qualifies to "<feature>:screen:<id>". Boot-validation checks
824
- // that entity-bound screens reference a registered entity and that the
825
- // columns / form-field refs name real fields — cross-feature component-QN
826
- // validation (r.uiComponent) comes in M4/M5. Optional `nav` field is
827
- // sugar for a single nav entry pointing at this screen — equivalent to
828
- // a standalone r.nav({ id: <same id>, screen: "<feature>:screen:<id>", ... }).
829
- screen(definition: ScreenDefinition): void;
830
-
831
- // Register a nav entry. The id is the feature-local short name (kebab-case);
832
- // the registry qualifies to "<feature>:nav:<id>". Boot-validation checks
833
- // that `screen` and `parent` refs exist (cross-feature QNs allowed) and
834
- // that parent chains don't contain cycles.
835
- nav(definition: NavDefinition): void;
836
-
837
- // Register a content collection — a set of template-resources of one kind,
838
- // navigable where it fachlich belongs (mail templates under Mail, not under
839
- // a central "Content" section). Sugar over r.nav(): registers the nav entry
840
- // with `provider: true` under the same id and records the kind so the client
841
- // can wire the tree provider without repeating navId + kind.
842
- //
843
- // Returns the qualified nav name ("<feature>:nav:<id>") — apps that pass the
844
- // collection to a client feature explicitly take it from here instead of
845
- // re-typing the QN.
846
- contentCollection(definition: ContentCollectionDefinition): string;
847
-
848
- // Register a workspace — a persona-/role-scoped UI surface. Pure UI
849
- // composition; the registry qualifies the short id to
850
- // "<feature>:workspace:<id>". Boot-validation checks that any nav refs
851
- // exist, that workspace ids referenced from r.nav() are real, and that
852
- // at most one workspace per app declares `default: true`.
853
- workspace(definition: WorkspaceDefinition): void;
854
-
855
- // Register an HTTP-route owned by this feature. The route is mounted
856
- // outside the dispatcher pipeline (= outside /api/write|query|batch),
857
- // directly on the app — use case: RSS/Atom feeds, OG images, OpenAPI specs.
858
- // Duplicate "method path"-Combinations are rejected per feature at setup
859
- // time; there is no cross-feature check.
860
- // Symmetric to queryHandler/writeHandler — routes live with the feature,
861
- // not in the bootstrap. Escape-hatch for non-feature-bound routes stays
862
- // runProdApp.extraRoutes (declarative list with `entry` tier).
863
- httpRoute(definition: HttpRouteDefinition): void;
864
-
865
- // Register a WebSocket endpoint under /api/ws/*. Rides the /api/* auth
866
- // chain; the framework adds a cookie-session origin check, a per-message
867
- // size cap and a heartbeat with session re-validation. Duplicate paths are
868
- // rejected per feature here and across features in buildServer.
869
- webSocketRoute(definition: WebSocketRouteDefinition): void;
870
-
871
- // Declare an "unmanaged" framework-native table that bypasses the
872
- // event-sourcing system. Reserved for legacy-import, read-only caches,
873
- // write-only webhook payload buffers, or read-side projections of
874
- // event-streams (delivery-attempts, job-run-logs) where r.entity()'s
875
- // aggregate-lifecycle assumptions don't fit. The dev-server iterates
876
- // these alongside r.entity() projections at boot so the table exists
877
- // before the first query.
878
- //
879
- // EntityTableMeta carries the same column-shape that r.entity() builds,
880
- // minus the audit-trail + base-columns scaffolding. The `meta` argument
881
- // is the result of `defineUnmanagedTable(...)` / `deriveEntityTableMeta(...)`
882
- // from `@cosmicdrift/kumiko-framework/db`.
883
- //
884
- // The required `reason` string is the marker that justifies the bypass —
885
- // a non-empty string is the contract. If you can't write a reason,
886
- // declare data via `r.entity()` instead.
887
- storeTable(meta: EntityTableMeta, options: StoreTableOptions): void;
888
-
889
- // Register the tree-actions schema for this feature — a map of
890
- // action-name → action-definition (with optional typed args). At-most-
891
- // one call per feature.
892
- //
893
- // Returns a TreeActionsHandle that the feature exports via setup-return
894
- // (Memory `[EventDef-Exports-Pattern]`). The handle carries the
895
- // literal-typed action-map that `buildTarget` consumes for compile-
896
- // time validation:
897
- //
898
- // const handle = r.treeActions({
899
- // edit: { args: { slug: "" as string } },
900
- // list: {},
901
- // });
902
- // return { handle };
903
- //
904
- // Without this typed return, the action-map collapses to
905
- // `Record<string, TreeActionDef>` at the buildTarget call-site and
906
- // every action becomes accept-anything-string. See visual-tree.md A5.
907
- //
908
- // The runtime FeatureDefinition.treeActions slot stores the same map
909
- // as erased Record (registry lookup, Pattern-AST consumers).
910
- treeActions<const TActions extends Record<string, TreeActionDef>>(
911
- actions: TActions,
912
- ): TreeActionsHandle<TFeature, TActions>;
913
-
914
- // Declare the Zod-schema for env-vars this feature reads at runtime.
915
- // At-most-one call per feature. composeEnvSchema reads it across all
916
- // features to build one app-wide schema, which runProdApp parses
917
- // process.env against at boot. App-Authors can also call
918
- // `KUMIKO_DRY_RUN_ENV=human|json|pulumi|k8s` to introspect the
919
- // required env-vars without booting.
920
- //
921
- // Convention: keys are SHOUTING_SNAKE_CASE env-var names. Per-var
922
- // metadata (Pulumi-config-key override, openssl-generator suggestion,
923
- // k8s-secret hints) goes into `.meta({ kumiko: { pulumi: {...} } })`
924
- // — see framework/env/index.ts for the meta-shape.
925
- envSchema(schema: z.ZodObject<z.ZodRawShape>): void;
926
- };
927
-
928
- // --- Registry (created from features) ---
929
-
930
- export type Registry = {
931
- readonly features: ReadonlyMap<string, FeatureDefinition>;
932
-
933
- getFeature(name: string): FeatureDefinition | undefined;
934
- getEntity(name: string): EntityDefinition | undefined;
935
- getAllEntities(): ReadonlyMap<string, EntityDefinition>;
936
- getWriteHandler(name: string): WriteHandlerDef | undefined;
937
- getQueryHandler(name: string): QueryHandlerDef | undefined;
938
- getAllQueryHandlers(): ReadonlyMap<string, QueryHandlerDef>;
939
- getAllWriteHandlers(): ReadonlyMap<string, WriteHandlerDef>;
940
- getStreamHandler(name: string): StreamHandlerDef | undefined;
941
- getAllStreamHandlers(): ReadonlyMap<string, StreamHandlerDef>;
942
- getSearchableFields(entityName: string): readonly string[];
943
- getSortableFields(entityName: string): readonly string[];
944
- getRelations(entityName: string): EntityRelations;
945
- getSearchableReferences(entityName: string): ReadonlyArray<{
946
- fieldName: string;
947
- targetEntityName: string;
948
- labelField: string;
949
- }>;
950
- getSortableReferences(entityName: string): ReadonlyArray<{
951
- fieldName: string;
952
- targetEntityName: string;
953
- labelField: string;
954
- }>;
955
- getIncomingRelations(entityName: string): ReadonlyArray<{
956
- sourceEntity: string;
957
- relationName: string;
958
- relation: RelationDefinition;
959
- }>;
960
- // Hook getters — pass `effectiveFeatures` to drop hooks registered by
961
- // globally-disabled features. Omit the arg to get all hooks (legacy
962
- // callers + places where the feature-toggles feature isn't wired).
963
- getPreSaveHooks(name: string, effectiveFeatures?: ReadonlySet<string>): readonly PreSaveHookFn[];
964
- getPostSaveHooks(
965
- name: string,
966
- phase?: HookPhase,
967
- effectiveFeatures?: ReadonlySet<string>,
968
- ): readonly PostSaveHookFn[];
969
- getPreDeleteHooks(
970
- name: string,
971
- phase?: HookPhase,
972
- effectiveFeatures?: ReadonlySet<string>,
973
- ): readonly PreDeleteHookFn[];
974
- getPostDeleteHooks(
975
- name: string,
976
- phase?: HookPhase,
977
- effectiveFeatures?: ReadonlySet<string>,
978
- ): readonly PostDeleteHookFn[];
979
- getPreQueryHooks(
980
- name: string,
981
- effectiveFeatures?: ReadonlySet<string>,
982
- ): readonly PreQueryHookFn[];
983
- getPostQueryHooks(
984
- name: string,
985
- effectiveFeatures?: ReadonlySet<string>,
986
- ): readonly PostQueryHookFn[];
987
- getEntityPostSaveHooks(
988
- entityName: string,
989
- phase?: HookPhase,
990
- effectiveFeatures?: ReadonlySet<string>,
991
- ): readonly PostSaveHookFn[];
992
- getEntityPreDeleteHooks(
993
- entityName: string,
994
- phase?: HookPhase,
995
- effectiveFeatures?: ReadonlySet<string>,
996
- ): readonly PreDeleteHookFn[];
997
- getEntityPostDeleteHooks(
998
- entityName: string,
999
- phase?: HookPhase,
1000
- effectiveFeatures?: ReadonlySet<string>,
1001
- ): readonly PostDeleteHookFn[];
1002
- getEntityPostQueryHooks(
1003
- entityName: string,
1004
- effectiveFeatures?: ReadonlySet<string>,
1005
- ): readonly PostQueryHookFn[];
1006
- // F3 — contributors for an entity's search-doc-payload, fired during
1007
- // buildSearchDocument indexing. See `SearchPayloadContributorFn`.
1008
- // `effectiveFeatures` filters out contributors owned by feature-toggle-
1009
- // disabled features (parallel to other getters' filtering semantic).
1010
- getSearchPayloadExtensions(
1011
- entityName: string,
1012
- effectiveFeatures?: ReadonlySet<string>,
1013
- ): readonly SearchPayloadContributorFn[];
1014
- getHandlerEntity(qualifiedHandler: string): string | undefined;
1015
- isHandlerSystemScoped(qualifiedHandler: string): boolean;
1016
- // Job counterpart to isHandlerSystemScoped — a job's owning feature is
1017
- // tracked separately (jobFeatureMap) since jobs aren't write/query/stream
1018
- // handlers and don't populate handlerFeatureMap.
1019
- isJobSystemScoped(qualifiedJobName: string): boolean;
1020
- getHandlerFeature(qualifiedHandler: string): string | undefined;
1021
- // True iff at least one registered handler declares a `rateLimit`
1022
- // option. Pre-computed at registry-build so the boot path can skip
1023
- // wiring the RateLimitResolver (and its Lua-script registration on
1024
- // Redis) entirely when nobody opted in. Per-request cost stays zero
1025
- // for apps that don't use the feature.
1026
- hasRateLimitedHandler(): boolean;
1027
- // All metrics from all features, keyed by fully-qualified name
1028
- // (kumiko_<feature>_<shortName>). Consumed at boot to register them on the
1029
- // active Meter.
1030
- getAllMetrics(): ReadonlyMap<string, FeatureMetricDef & { readonly featureName: string }>;
1031
- getAllTranslations(): TranslationKeys;
1032
- getConfigKey(qualifiedKey: string): ConfigKeyDefinition | undefined;
1033
- getAllConfigKeys(): ReadonlyMap<string, ConfigKeyDefinition>;
1034
- getAllConfigSeeds(): readonly ConfigSeedDef[];
1035
- // Feature-declared secrets, aggregated across all registered features.
1036
- // Keyed by qualified name ("<feature>:<shortName>"). Used by the rotation
1037
- // job (to iterate "known" secrets) and admin-UIs to list available keys.
1038
- getAllSecretKeys(): ReadonlyMap<string, SecretKeyDefinition>;
1039
- getSecretKey(qualifiedName: string): SecretKeyDefinition | undefined;
1040
- getJob(qualifiedName: string): JobDefinition | undefined;
1041
- getAllJobs(): ReadonlyMap<string, JobDefinition>;
1042
- getEvent(qualifiedName: string): EventDef | undefined;
1043
-
1044
- // Upcaster chain per qualified event name. Entries describe the current
1045
- // schema version and the step-wise transforms that upgrade older stored
1046
- // payloads. Empty chain when an event has never been migrated (version=1).
1047
- getEventUpcasters(): ReadonlyMap<
1048
- string,
1049
- { readonly currentVersion: number; readonly chain: ReadonlyMap<number, EventUpcastFn> }
1050
- >;
1051
- getExtension(name: string): RegistrarExtensionDef | undefined;
1052
- getExtensionUsages(extensionName: string): readonly RegistrarExtensionRegistration[];
1053
- // Extension point → selector config key, from r.extensionSelector calls.
1054
- getAllExtensionSelectors(): ReadonlyMap<string, string>;
1055
- getAllNotifications(): ReadonlyMap<string, NotificationDefinition>;
1056
- getAllReferenceData(): readonly ReferenceDataDef[];
1057
- // Look up projections by source-entity name. Empty list when no projection
1058
- // feeds off the entity — event-store-executor uses this as the hot-path.
1059
- getProjectionsForSource(entityName: string): readonly ProjectionDefinition[];
1060
- getAllProjections(): ReadonlyMap<string, ProjectionDefinition>;
1061
-
1062
- // All r.storeTable() registrations across all features, keyed by
1063
- // feature-local short name. The dev-server iterates this alongside
1064
- // implicit projections at boot. Cross-feature uniqueness is enforced
1065
- // at registry-build — duplicate names from different features fail
1066
- // the boot, so callers can rely on a flat keyspace.
1067
- getAllStoreTables(): ReadonlyMap<string, StoreTableDef>;
1068
-
1069
- // Multi-stream projections registered via r.multiStreamProjection().
1070
- // Keyed by qualified name. The server wires each into the event-dispatcher
1071
- // as its own EventConsumer with a dedicated cursor.
1072
- getAllMultiStreamProjections(): ReadonlyMap<string, MultiStreamProjectionDefinition>;
1073
- // The feature that registered the given MSP. Used by the event-dispatcher
1074
- // to pause MSP-consumers whose owning feature is globally disabled.
1075
- getMultiStreamProjectionFeature(qualifiedName: string): string | undefined;
1076
-
1077
- // All r.authClaims() hooks across all features, tagged with the declaring
1078
- // feature name so the resolver can apply the auto-prefix. Pre-aggregated
1079
- // at registry-build so the login hot path is a single Map read.
1080
- getAuthClaimsHooks(): readonly AuthClaimsHookDef[];
1081
-
1082
- // Feature-declared claim keys, aggregated across all features. Keyed by
1083
- // qualified name ("<feature>:<short>"). Ops-UI + Boot-Validator use this
1084
- // to introspect what claims the app can produce.
1085
- getAllClaimKeys(): ReadonlyMap<string, ClaimKeyDefinition>;
1086
- getClaimKey(qualifiedName: string): ClaimKeyDefinition | undefined;
1087
-
1088
- // Screens declared via r.screen() across all features. Keyed by qualified
1089
- // name ("<feature>:screen:<id>"). ui-core / renderer consume this to build
1090
- // navigation + screen-tree at mount time.
1091
- getAllScreens(): ReadonlyMap<string, ScreenDefinition>;
1092
- getScreen(qualifiedName: string): ScreenDefinition | undefined;
1093
- // The feature that registered the given screen. Consumed by the nav
1094
- // resolver to gate a nav-entry whose screen belongs to a disabled feature.
1095
- getScreenFeature(qualifiedName: string): string | undefined;
1096
- // All entity-bound screens (entityList / entityEdit) that target the given
1097
- // entity. Pre-grouped so ui-core's view-model builders don't re-filter
1098
- // getAllScreens() on every render. Custom screens have no entity and are
1099
- // never returned here — walk getAllScreens() for those.
1100
- getScreensByEntity(entityName: string): readonly ScreenDefinition[];
1101
-
1102
- // Nav entries declared via r.nav() across all features. Keyed by qualified
1103
- // name ("<feature>:nav:<id>"). Flat list — the renderer's resolveNavigation
1104
- // assembles the tree from parent refs and gates by effective-features.
1105
- getAllNavs(): ReadonlyMap<string, NavDefinition>;
1106
- getNav(qualifiedName: string): NavDefinition | undefined;
1107
- // The feature that registered the given nav entry. Used by the nav
1108
- // resolver to drop entries whose owning feature is globally disabled.
1109
- getNavFeature(qualifiedName: string): string | undefined;
1110
- // Direct children of the given parent nav entry. Empty array when the
1111
- // parent has no children. Pre-grouped for O(1) tree-walk — resolveNavigation
1112
- // recurses with getNavsByParent(child.qn) instead of filtering getAllNavs().
1113
- getNavsByParent(parentQualifiedName: string): readonly NavDefinition[];
1114
- // Nav entries that declare no parent — the roots of the navigation tree.
1115
- // resolveNavigation starts its walk here and descends via getNavsByParent.
1116
- getTopLevelNavs(): readonly NavDefinition[];
1117
-
1118
- // Workspaces declared via r.workspace() across all features. Keyed by
1119
- // qualified name ("<feature>:workspace:<id>"). The active web shell
1120
- // (shellWorkspaces) consumes this to render the switcher.
1121
- getAllWorkspaces(): ReadonlyMap<string, WorkspaceDefinition>;
1122
- getWorkspace(qualifiedName: string): WorkspaceDefinition | undefined;
1123
- // The feature that registered the workspace. Mirrors getNavFeature —
1124
- // lets the resolver drop workspaces whose owning feature is disabled.
1125
- getWorkspaceFeature(qualifiedName: string): string | undefined;
1126
- // Resolved nav QNs that belong to the given workspace. Pre-computed at
1127
- // boot from BOTH r.workspace.nav AND r.nav.workspaces — the shell
1128
- // doesn't have to merge sources at render time.
1129
- getWorkspaceNavs(workspaceQualifiedName: string): readonly string[];
1130
- // The single workspace whose `default: true` is set, if any. Boot
1131
- // validator rejects more than one. Apps without a default fall back to
1132
- // the first workspace the user has access to.
1133
- getDefaultWorkspace(): WorkspaceDefinition | undefined;
1134
-
1135
- // Tree-Actions-Map des Features. Returns the erased Record (compile-
1136
- // time-typed handle wandert über setup-export, nicht hier). Die
1137
- // Content-Tree-Nav nutzt das für Runtime-Action-Lookup beim Klick auf
1138
- // einen TreeNode.target — der Resolver findet das Feature via
1139
- // TargetRef.featureId und holt sich die zugehörige Action-Definition.
1140
- getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
1141
- };