@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 (424) hide show
  1. package/README.md +9 -38
  2. package/package.json +21 -2
  3. package/src/__tests__/anonymous-access.integration.test.ts +185 -0
  4. package/src/__tests__/consumer-cli.integration.test.ts +172 -0
  5. package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
  6. package/src/__tests__/full-stack.integration.test.ts +1 -1
  7. package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
  8. package/src/__tests__/schema-cli.integration.test.ts +50 -0
  9. package/src/__tests__/store-table.integration.test.ts +93 -0
  10. package/src/api/__tests__/api.test.ts +373 -0
  11. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  13. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
  14. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
  15. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
  16. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
  17. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  18. package/src/api/__tests__/batch.integration.test.ts +101 -11
  19. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  20. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  21. package/src/api/__tests__/jwt.test.ts +200 -1
  22. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
  23. package/src/api/__tests__/pat-scope.test.ts +36 -0
  24. package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
  25. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
  26. package/src/api/__tests__/request-id-middleware.test.ts +51 -0
  27. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  28. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  29. package/src/api/__tests__/sse-broker.test.ts +57 -0
  30. package/src/api/__tests__/sse-route.test.ts +4 -0
  31. package/src/api/api-constants.ts +11 -0
  32. package/src/api/auth-middleware.ts +248 -40
  33. package/src/api/auth-routes.ts +576 -95
  34. package/src/api/index.ts +13 -4
  35. package/src/api/jwt.ts +170 -11
  36. package/src/api/pat-scope.ts +14 -0
  37. package/src/api/pii-leak-guard.ts +47 -0
  38. package/src/api/request-context.ts +3 -0
  39. package/src/api/request-id-middleware.ts +2 -0
  40. package/src/api/routes.ts +169 -2
  41. package/src/api/server.ts +112 -16
  42. package/src/api/sse-broker.ts +39 -0
  43. package/src/bun-db/__tests__/PATTERN.md +0 -1
  44. package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
  45. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  46. package/src/bun-db/__tests__/write-brand.test.ts +21 -0
  47. package/src/bun-db/connection.ts +3 -3
  48. package/src/bun-db/index.ts +2 -0
  49. package/src/bun-db/query.ts +105 -32
  50. package/src/consumer-cli.ts +134 -0
  51. package/src/crypto/__tests__/blind-index.test.ts +130 -0
  52. package/src/crypto/__tests__/event-pii.test.ts +161 -0
  53. package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
  54. package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
  55. package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
  56. package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
  57. package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
  58. package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
  59. package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
  60. package/src/crypto/blind-index.ts +118 -0
  61. package/src/crypto/event-pii.ts +70 -0
  62. package/src/crypto/in-memory-kms-adapter.ts +50 -0
  63. package/src/crypto/index.ts +67 -0
  64. package/src/crypto/kms-adapter.ts +2 -0
  65. package/src/crypto/pg-kms-adapter.ts +295 -0
  66. package/src/crypto/pii-field-encryption.ts +248 -0
  67. package/src/crypto/request-kms-cache.ts +38 -0
  68. package/src/crypto/subject-resolver.ts +91 -0
  69. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
  70. package/src/db/__tests__/blind-index.integration.test.ts +248 -0
  71. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  72. package/src/db/__tests__/collect-table-metas.test.ts +10 -10
  73. package/src/db/__tests__/config-seed.integration.test.ts +13 -5
  74. package/src/db/__tests__/dialect-instant.test.ts +1 -4
  75. package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
  76. package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
  77. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  78. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
  79. package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
  80. package/src/db/__tests__/feature-table-sources.test.ts +34 -0
  81. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
  82. package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
  83. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  84. package/src/db/__tests__/migrate-generator.test.ts +21 -0
  85. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  86. package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
  87. package/src/db/__tests__/rebuild-marker.test.ts +13 -3
  88. package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
  89. package/src/db/__tests__/schema-inspection.test.ts +7 -0
  90. package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
  91. package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
  92. package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
  93. package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
  94. package/src/db/api.ts +2 -2
  95. package/src/db/apply-entity-event.ts +18 -14
  96. package/src/db/blind-index-cleanup.ts +55 -0
  97. package/src/db/bun-provider.ts +2 -2
  98. package/src/db/collect-table-metas.ts +6 -7
  99. package/src/db/config-seed.ts +9 -9
  100. package/src/db/connection.ts +7 -12
  101. package/src/db/cursor.ts +1 -18
  102. package/src/db/dialect.ts +20 -26
  103. package/src/db/entity-field-encryption.ts +81 -24
  104. package/src/db/entity-table-meta-types.ts +2 -0
  105. package/src/db/entity-table-meta.ts +63 -90
  106. package/src/db/event-store-executor-context.ts +404 -0
  107. package/src/db/event-store-executor-read.ts +275 -0
  108. package/src/db/event-store-executor-write.ts +644 -0
  109. package/src/db/event-store-executor.ts +28 -1155
  110. package/src/db/feature-table-sources.ts +6 -5
  111. package/src/db/index.ts +20 -3
  112. package/src/db/located-timestamp.ts +4 -0
  113. package/src/db/migrate-generator.ts +39 -6
  114. package/src/db/migrate-runner.ts +107 -11
  115. package/src/db/pg-error.ts +9 -1
  116. package/src/db/postgres-provider.ts +2 -2
  117. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  118. package/src/db/queries/backfill-pii.ts +277 -0
  119. package/src/db/queries/ddl.ts +45 -0
  120. package/src/db/queries/event-consumer.ts +35 -2
  121. package/src/db/queries/event-store.ts +126 -19
  122. package/src/db/queries/projection-rebuild.ts +22 -8
  123. package/src/db/queries/shadow-swap.ts +183 -0
  124. package/src/db/queries/test-stack.ts +4 -30
  125. package/src/db/query-api.ts +1 -0
  126. package/src/db/query.ts +1 -0
  127. package/src/db/rebuild-marker.ts +18 -2
  128. package/src/db/reference-data.ts +2 -3
  129. package/src/db/replay-migration-sql.ts +257 -0
  130. package/src/db/schema-inspection.ts +1 -1
  131. package/src/db/table-builder.ts +102 -71
  132. package/src/db/tenant-db.ts +15 -53
  133. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
  134. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  135. package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
  136. package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
  137. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
  138. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
  139. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
  140. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
  141. package/src/engine/__tests__/boot-validator.test.ts +219 -15
  142. package/src/engine/__tests__/build-app-schema.test.ts +82 -0
  143. package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
  144. package/src/engine/__tests__/build-target.test.ts +3 -11
  145. package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
  146. package/src/engine/__tests__/config-helpers.test.ts +16 -0
  147. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
  148. package/src/engine/__tests__/define-roles.test.ts +21 -0
  149. package/src/engine/__tests__/engine.test.ts +163 -1
  150. package/src/engine/__tests__/entity-handlers.test.ts +80 -0
  151. package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
  152. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  153. package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
  154. package/src/engine/__tests__/factories-time.test.ts +2 -66
  155. package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
  156. package/src/engine/__tests__/feature-manifest.test.ts +31 -1
  157. package/src/engine/__tests__/field-access.test.ts +23 -1
  158. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  159. package/src/engine/__tests__/membership-roles.test.ts +4 -10
  160. package/src/engine/__tests__/nav.test.ts +86 -1
  161. package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
  162. package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
  163. package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
  164. package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
  165. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
  166. package/src/engine/__tests__/post-query-hook.test.ts +7 -7
  167. package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
  168. package/src/engine/__tests__/registry.test.ts +144 -0
  169. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  170. package/src/engine/__tests__/screen.test.ts +147 -1
  171. package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
  172. package/src/engine/__tests__/store-table.test.ts +229 -0
  173. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  174. package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
  175. package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
  176. package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
  177. package/src/engine/boot-validator/action-wiring.ts +149 -0
  178. package/src/engine/boot-validator/boot-check.ts +21 -0
  179. package/src/engine/boot-validator/config-deps.ts +40 -4
  180. package/src/engine/boot-validator/entity-handler.ts +20 -21
  181. package/src/engine/boot-validator/entity-list-screens.ts +88 -0
  182. package/src/engine/boot-validator/gdpr-storage.ts +0 -20
  183. package/src/engine/boot-validator/i18n-keys.ts +58 -4
  184. package/src/engine/boot-validator/index.ts +33 -12
  185. package/src/engine/boot-validator/nav.ts +125 -0
  186. package/src/engine/boot-validator/pii-retention.ts +95 -10
  187. package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
  188. package/src/engine/boot-validator/workspaces.ts +68 -0
  189. package/src/engine/build-app-schema.ts +16 -0
  190. package/src/engine/build-config-feature-schema.ts +44 -7
  191. package/src/engine/codemod/pipeline-codemod.ts +5 -5
  192. package/src/engine/config-helpers.ts +15 -0
  193. package/src/engine/constants.ts +32 -6
  194. package/src/engine/create-app.ts +11 -0
  195. package/src/engine/define-feature.ts +95 -947
  196. package/src/engine/define-handler.ts +28 -94
  197. package/src/engine/define-workflow.ts +1 -1
  198. package/src/engine/effective-features.ts +12 -2
  199. package/src/engine/entity-handlers.ts +76 -11
  200. package/src/engine/extensions/tenant-data.ts +19 -0
  201. package/src/engine/extensions/user-data.ts +29 -2
  202. package/src/engine/factories.ts +8 -49
  203. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
  204. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
  205. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
  206. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
  207. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
  208. package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
  209. package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
  210. package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
  211. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
  212. package/src/engine/feature-ast/extractors/events.ts +322 -0
  213. package/src/engine/feature-ast/extractors/handlers.ts +234 -0
  214. package/src/engine/feature-ast/extractors/hooks.ts +243 -0
  215. package/src/engine/feature-ast/extractors/index.ts +34 -31
  216. package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
  217. package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
  218. package/src/engine/feature-ast/extractors/round1.ts +3 -3
  219. package/src/engine/feature-ast/extractors/round5.ts +3 -3
  220. package/src/engine/feature-ast/extractors/round6.ts +2 -36
  221. package/src/engine/feature-ast/extractors/shared.ts +64 -6
  222. package/src/engine/feature-ast/index.ts +2 -4
  223. package/src/engine/feature-ast/parse.ts +130 -23
  224. package/src/engine/feature-ast/patch.ts +39 -50
  225. package/src/engine/feature-ast/patcher.ts +37 -38
  226. package/src/engine/feature-ast/patterns.ts +50 -62
  227. package/src/engine/feature-ast/render.ts +67 -44
  228. package/src/engine/feature-builder-state.ts +168 -0
  229. package/src/engine/feature-config-events-jobs.ts +403 -0
  230. package/src/engine/feature-entity-handlers.ts +208 -0
  231. package/src/engine/feature-manifest.ts +2 -1
  232. package/src/engine/feature-ui-extensions.ts +500 -0
  233. package/src/engine/field-access.ts +13 -2
  234. package/src/engine/field-helpers.ts +31 -0
  235. package/src/engine/handler-helpers.ts +26 -0
  236. package/src/engine/hook-helpers.ts +16 -0
  237. package/src/engine/index.ts +23 -7
  238. package/src/engine/membership-roles.ts +13 -0
  239. package/src/engine/object-form.ts +27 -0
  240. package/src/engine/ownership.ts +26 -79
  241. package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
  242. package/src/engine/pattern-library/library.ts +44 -1152
  243. package/src/engine/pattern-library/mixed-schemas.ts +450 -0
  244. package/src/engine/pattern-library/opaque-schemas.ts +124 -0
  245. package/src/engine/pattern-library/shared-fields.ts +75 -0
  246. package/src/engine/pattern-library/static-schemas.ts +456 -0
  247. package/src/engine/pipeline.ts +6 -11
  248. package/src/engine/registry-facade.ts +362 -0
  249. package/src/engine/registry-ingest.ts +478 -0
  250. package/src/engine/registry-state.ts +388 -0
  251. package/src/engine/registry-validate.ts +642 -0
  252. package/src/engine/registry.ts +78 -1658
  253. package/src/engine/run-pipeline.ts +1 -1
  254. package/src/engine/schema-builder.ts +1 -0
  255. package/src/engine/screen-helpers.ts +54 -0
  256. package/src/engine/soft-delete-cleanup.ts +6 -2
  257. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  258. package/src/engine/steps/_duration-utils.ts +2 -0
  259. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  260. package/src/engine/tier-resolver-extension.ts +3 -2
  261. package/src/engine/types/config.ts +2 -482
  262. package/src/engine/types/define-handler.ts +2 -0
  263. package/src/engine/types/entity-handlers.ts +2 -0
  264. package/src/engine/types/event-type-map.ts +1 -37
  265. package/src/engine/types/feature.ts +2 -972
  266. package/src/engine/types/fields.ts +2 -675
  267. package/src/engine/types/handlers.ts +2 -774
  268. package/src/engine/types/hooks.ts +2 -184
  269. package/src/engine/types/http-route.ts +1 -54
  270. package/src/engine/types/identifiers.ts +1 -47
  271. package/src/engine/types/index.ts +86 -40
  272. package/src/engine/types/nav.ts +2 -63
  273. package/src/engine/types/ownership.ts +2 -0
  274. package/src/engine/types/projection.ts +2 -138
  275. package/src/engine/types/relations.ts +1 -51
  276. package/src/engine/types/screen.ts +2 -574
  277. package/src/engine/types/step.ts +2 -334
  278. package/src/engine/types/target-ref.ts +1 -21
  279. package/src/engine/types/tree-node.ts +1 -132
  280. package/src/engine/types/workspace.ts +2 -49
  281. package/src/engine/validate-projection-allowlist.ts +6 -6
  282. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
  283. package/src/entrypoint/index.ts +49 -6
  284. package/src/errors/classes.ts +22 -1
  285. package/src/errors/field-issue.ts +0 -3
  286. package/src/errors/index.ts +1 -1
  287. package/src/errors/write-error-info.ts +10 -22
  288. package/src/es-ops/README.md +1 -1
  289. package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
  290. package/src/es-ops/context.ts +4 -2
  291. package/src/es-ops/types.ts +8 -1
  292. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  293. package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
  294. package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
  295. package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
  296. package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
  297. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
  298. package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
  299. package/src/event-store/admin-api.ts +11 -4
  300. package/src/event-store/errors.ts +2 -35
  301. package/src/event-store/event-store.ts +64 -78
  302. package/src/event-store/events-schema.ts +11 -13
  303. package/src/event-store/index.ts +18 -3
  304. package/src/event-store/rebuild-dead-letter.ts +111 -0
  305. package/src/event-store/snapshot.ts +63 -40
  306. package/src/event-store/types.ts +2 -0
  307. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  308. package/src/files/__tests__/file-ref-entity.test.ts +8 -0
  309. package/src/files/__tests__/files.integration.test.ts +24 -0
  310. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  311. package/src/files/__tests__/local-provider.test.ts +31 -0
  312. package/src/files/__tests__/provider-resolver.test.ts +70 -0
  313. package/src/files/__tests__/write-stream.test.ts +13 -0
  314. package/src/files/file-handle.ts +2 -19
  315. package/src/files/file-ref-entity.ts +2 -2
  316. package/src/files/file-routes.ts +13 -0
  317. package/src/files/index.ts +1 -1
  318. package/src/files/local-provider.ts +27 -8
  319. package/src/files/provider-resolver.ts +31 -20
  320. package/src/files/types.ts +13 -55
  321. package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
  322. package/src/i18n/required-surface-keys.ts +120 -1
  323. package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
  324. package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
  325. package/src/jobs/job-runner.ts +82 -12
  326. package/src/logging/types.ts +1 -7
  327. package/src/migrations/pending-rebuilds.ts +1 -1
  328. package/src/observability/__tests__/observability.integration.test.ts +4 -1
  329. package/src/observability/__tests__/recording-tracer.test.ts +6 -4
  330. package/src/observability/index.ts +2 -0
  331. package/src/observability/noop-provider.ts +0 -9
  332. package/src/observability/recording-tracer.ts +0 -12
  333. package/src/observability/standard-metrics.ts +64 -2
  334. package/src/observability/types/index.ts +1 -29
  335. package/src/observability/types/metric.ts +1 -56
  336. package/src/observability/types/provider.ts +1 -32
  337. package/src/observability/types/span.ts +1 -64
  338. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
  339. package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
  340. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  341. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  342. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
  343. package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
  344. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
  345. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
  346. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
  347. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  348. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
  349. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
  350. package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
  351. package/src/pipeline/dispatch-batch.ts +187 -0
  352. package/src/pipeline/dispatch-query.ts +165 -0
  353. package/src/pipeline/dispatch-shared.ts +826 -0
  354. package/src/pipeline/dispatch-stream.ts +90 -0
  355. package/src/pipeline/dispatch-write.ts +451 -0
  356. package/src/pipeline/dispatcher-utils.ts +1 -1
  357. package/src/pipeline/dispatcher.ts +44 -1372
  358. package/src/pipeline/entity-cache.ts +2 -33
  359. package/src/pipeline/event-consumer-state.ts +30 -2
  360. package/src/pipeline/event-dispatcher-admin.ts +293 -0
  361. package/src/pipeline/event-dispatcher-delivery.ts +305 -0
  362. package/src/pipeline/event-dispatcher.ts +85 -542
  363. package/src/pipeline/index.ts +2 -0
  364. package/src/pipeline/msp-rebuild.ts +42 -3
  365. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  366. package/src/pipeline/projection-rebuild.ts +105 -3
  367. package/src/pipeline/system-hooks.ts +144 -1
  368. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  369. package/src/rate-limit/resolver.ts +16 -32
  370. package/src/schema-cli.ts +76 -16
  371. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
  372. package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
  373. package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
  374. package/src/search/index.ts +6 -0
  375. package/src/search/meilisearch-adapter.ts +13 -12
  376. package/src/search/reindex-entity.ts +177 -0
  377. package/src/search/types.ts +1 -39
  378. package/src/secrets/__tests__/contains-secret.test.ts +34 -0
  379. package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
  380. package/src/secrets/__tests__/envelope.test.ts +1 -1
  381. package/src/secrets/dek-cache.ts +26 -5
  382. package/src/secrets/envelope-cipher.ts +53 -0
  383. package/src/secrets/envelope.ts +5 -3
  384. package/src/secrets/index.ts +13 -1
  385. package/src/secrets/stored-envelope.ts +46 -0
  386. package/src/secrets/types.ts +2 -162
  387. package/src/stack/__tests__/event-collector.test.ts +42 -0
  388. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
  389. package/src/stack/db.ts +2 -1
  390. package/src/stack/push-entity-projection-tables.ts +4 -3
  391. package/src/stack/redis.ts +8 -0
  392. package/src/stack/request-helper.ts +38 -2
  393. package/src/stack/table-helpers.ts +6 -4
  394. package/src/stack/test-stack.ts +249 -131
  395. package/src/testing/__tests__/late-bound.test.ts +32 -0
  396. package/src/testing/__tests__/wait-for.test.ts +59 -0
  397. package/src/testing/boot-validator-fixture.ts +121 -0
  398. package/src/testing/e2e-generator.ts +8 -0
  399. package/src/testing/file-provider-contract.ts +104 -0
  400. package/src/testing/handler-context.ts +3 -1
  401. package/src/testing/index.ts +5 -0
  402. package/src/testing/late-bound.ts +5 -3
  403. package/src/testing/mutable-master-key-provider.ts +29 -3
  404. package/src/testing/wait-for.ts +3 -0
  405. package/src/testing/without-ambient-temporal.ts +14 -0
  406. package/src/time/__tests__/tz-dateline.test.ts +8 -10
  407. package/src/time/geo-tz.ts +1 -32
  408. package/src/time/index.ts +1 -0
  409. package/src/time/legacy-date.ts +18 -0
  410. package/src/time/polyfill.ts +21 -38
  411. package/src/time/tz-context.ts +44 -85
  412. package/src/ui-types/app-schema.ts +12 -0
  413. package/src/ui-types/index.ts +20 -7
  414. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  415. package/src/utils/safe-json.ts +13 -1
  416. package/src/__tests__/raw-table.integration.test.ts +0 -116
  417. package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
  418. package/src/db/__tests__/encryption.test.ts +0 -39
  419. package/src/db/encryption.ts +0 -39
  420. package/src/db/row-helpers.ts +0 -4
  421. package/src/engine/__tests__/raw-table.test.ts +0 -150
  422. package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
  423. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
  424. package/src/engine/feature-ast/extractors/round4.ts +0 -1366
@@ -1,574 +1,2 @@
1
- import type { FieldDefinition } from "./fields";
2
- import type { AccessRule } from "./handlers";
3
-
4
- // Screen definitions describe how a feature surfaces data to the user.
5
- // Pure data — the engine stores these verbatim and ui-core / the renderer
6
- // packages decide what to do with them. The framework must not import
7
- // React / react-native from here; renderer components stay opaque so
8
- // `engine/` imports don't pull the whole UI toolchain into every bundle.
9
- //
10
- // Note on `id`: feature authors write the short form ("product-list"); the
11
- // registry overwrites `id` with the qualified name ("shop:screen:product-list")
12
- // in its stored copies. Callers of `registry.getScreen(qn)` /
13
- // `getAllScreens()` / `getScreensByEntity(...)` always see the qualified id.
14
- // `feature.screens[shortId]` on the unregistered FeatureDefinition keeps
15
- // the short form.
16
-
17
- // A per-platform component pair — used anywhere a feature attaches a
18
- // rendered component (screens, slots, routes, field renderers, future
19
- // r.uiComponent). Both fields are `unknown` so the engine doesn't depend
20
- // on React types; ui-core resolves the correct platform at mount-time.
21
- // Framework code only checks structural presence.
22
- export type PlatformComponent = {
23
- readonly react?: unknown;
24
- readonly native?: unknown;
25
- };
26
-
27
- // Built-in value formatters. Apps extend via module augmentation:
28
- // declare module "@cosmicdrift/kumiko-framework/engine/types" {
29
- // interface FieldFormatRegistry { myFormat: { myOption?: string } }
30
- // }
31
- // renderer-web handles all built-in keys; unknown app-specific keys fall back
32
- // to String(value).
33
- export interface FieldFormatRegistry {
34
- timestamp: {
35
- readonly locale?: string;
36
- readonly dateStyle?: "full" | "long" | "medium" | "short";
37
- readonly timeStyle?: "full" | "long" | "medium" | "short";
38
- };
39
- date: {
40
- readonly locale?: string;
41
- readonly dateStyle?: "full" | "long" | "medium" | "short";
42
- };
43
- boolean: { readonly trueLabel?: string; readonly falseLabel?: string };
44
- currency: { readonly symbol?: string };
45
- priority: { readonly emptyLabel?: string; readonly prefix?: string };
46
- }
47
-
48
- // Discriminated union derived from the registry — one variant per key.
49
- // JSON-safe: no function members, survives buildAppSchema → window.__KUMIKO_SCHEMA__.
50
- export type FormatSpec = {
51
- [K in keyof FieldFormatRegistry]: { readonly format: K } & FieldFormatRegistry[K];
52
- }[keyof FieldFormatRegistry];
53
-
54
- // Level-2 field renderer (ui-architecture.md §Renderer Customization):
55
- // - PlatformComponent → platform-specific component from the same feature
56
- // - string → cross-feature QN reference (resolved at mount-time)
57
- // - FormatSpec → declarative value formatter, JSON-safe ({ format: "timestamp" } etc.)
58
- export type FieldRenderer = PlatformComponent | string | FormatSpec;
59
-
60
- // Declarative field-state condition. Evaluated by the renderer against the
61
- // current row/form values. Three forms:
62
- // boolean — static on/off (e.g. readOnly: true)
63
- // { field, eq } — true when row[field] === eq
64
- // { field, ne } — true when row[field] !== ne
65
- // JSON-safe: survives buildAppSchema → window.__KUMIKO_SCHEMA__ stringify.
66
- export type FieldCondition =
67
- | boolean
68
- | { readonly field: string; readonly eq: unknown }
69
- | { readonly field: string; readonly ne: unknown };
70
-
71
- // --- entityList ---
72
-
73
- // `string` shorthand when the column only needs its field name; the object
74
- // form carries renderer / display overrides. normalizeListColumn() below
75
- // collapses both into the object form for downstream consumers.
76
- export type ListColumnSpec =
77
- | string
78
- | {
79
- readonly field: string;
80
- readonly renderer?: FieldRenderer;
81
- /** Optional header, overriding the default
82
- * `<feature>:entity:<entity>:field:<field>` i18n convention. Resolved
83
- * through `translate` like any header — an i18n key, or a plain literal
84
- * shown verbatim if it isn't a key. Also the way to declare a *virtual*
85
- * column whose `field` is NOT an entity field — a presentational column
86
- * drawn entirely by a `columnRenderer` component that reads the `row`
87
- * (e.g. a tag-chips cell), not a stored value. Such a column needs a
88
- * label; without one it is rejected at boot as an unknown field. `field`
89
- * is then just a stable column key (pick any unique slug). */
90
- readonly label?: string;
91
- };
92
-
93
- // Pagination-Modi für entityList:
94
- // - "pages": klassischer Pager (← 1 2 ... N →) — bookmarkable
95
- // via ?page=N in der URL. Server liefert `total`
96
- // damit der Pager "Page X of Y" rendern kann.
97
- // Default für CRUD-/Admin-Listen.
98
- // - "infinite": IntersectionObserver am Bottom — beim Sichtbar
99
- // werden lädt cursor-basiert die nächste Page und
100
- // appended an `rows`. Kein page-State in URL (Scroll-
101
- // Position ist Browser-eigen).
102
- // - false: Pagination aus — `executor.list()` lädt alles
103
- // was zur Tenant-Sicht passt. Sinnvoll für kleine
104
- // Lookup-/Master-Daten (≤ ~200 Rows).
105
- export type ListPaginationMode = "pages" | "infinite" | false;
106
-
107
- // Sort-State auf der Wire — Field-Name muss zur Entity-Definition
108
- // passen UND als sortable: true markiert sein. Validator lehnt sonst
109
- // beim Boot ab.
110
- export type ListSortDir = "asc" | "desc";
111
- export type ListSortSpec = {
112
- readonly field: string;
113
- readonly dir: ListSortDir;
114
- };
115
-
116
- // Screen-Level Filter — Author deklariert pro List-Screen einen festen
117
- // Filter der bei jedem Query angewendet wird. Use-case: drei Buckets
118
- // derselben Entity ("Upcoming Maintenance" / "Active Maintenance" /
119
- // "Past Maintenance") ohne drei Custom-Pages — Filter pro Screen
120
- // unterscheidet sie.
121
- //
122
- // Operatoren (Drizzle-konsistent):
123
- // eq/ne → field = value | field != value
124
- // lt/gt → field < value | field > value (numerisch / temporal)
125
- // in → field IN (...values), value muss readonly array sein
126
- //
127
- // Field muss in der Entity existieren UND `filterable: true` haben
128
- // (Boot-Validator pinned beides). `lt`/`gt` nur auf vergleichbaren
129
- // Field-Types (number/money/date/timestamp/locatedTimestamp); auf
130
- // text/boolean/select/multiSelect lehnt der Validator das ab.
131
- //
132
- // Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
133
- // Server appliziert den filter aus dem Payload — der Client kann ihn
134
- // weglassen oder durch einen anderen ersetzen. Boundary bleiben
135
- // access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
136
- // restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
137
- export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "in";
138
- export type ScreenFilter = {
139
- readonly field: string;
140
- readonly op: ScreenFilterOp;
141
- readonly value: unknown;
142
- };
143
-
144
- /** Deklarativer Row-Field-Extraktor — JSON-sicher (kein Function-Prop,
145
- * überlebt window.__KUMIKO_SCHEMA__ / buildAppSchema).
146
- *
147
- * `pick`: extrahiert Felder 1:1. `{ pick: ["id", "version"] }` → `{ id: row.id, version: row.version }`
148
- * `map`: benennt um. `{ map: { incidentId: "id" } }` → `{ incidentId: row.id }`
149
- *
150
- * Limitation: computed/template-Werte können nicht ausgedrückt werden
151
- * — solche Logik gehört server-side in den Write-Handler. */
152
- export type RowFieldExtractor =
153
- | { readonly pick: readonly string[] }
154
- | { readonly map: Readonly<Record<string, string>> };
155
-
156
- // RowAction — per-Row Button/Dropdown-Item das einen Write-Handler
157
- // triggert oder zu einem anderen Screen navigiert.
158
- //
159
- // Pattern: row-level Lifecycle-Operations (Maintenance start/cancel/
160
- // complete, Incident resolve, Order ship etc.) — Sachen die in einem
161
- // CRUD-update kein passendes Verb haben aber als WriteHandler existieren.
162
- //
163
- // Discriminated Union mit `kind`:
164
- // - "writeHandler" (default): dispatched einen Write-Handler mit
165
- // Payload pro Row.
166
- // - "navigate" (Tier 2.7e): navigiert zu einem anderen Screen,
167
- // optional mit URL-Search-Params aus `params`. Use-case: "Edit",
168
- // "View Audit-Log", "Open in actionForm" etc.
169
- export type RowAction = RowActionWriteHandler | RowActionNavigate;
170
-
171
- export type RowActionWriteHandler = {
172
- /** Default für RowActions ohne explizit gesetzten `kind` —
173
- * Backwards-kompatible Form. Kann auch explizit gesetzt werden. */
174
- readonly kind?: "writeHandler";
175
- /** Stable id pro Screen — kebab-case, eindeutig im Action-Set. */
176
- readonly id: string;
177
- /** Anzeige-Text (i18n-Key). */
178
- readonly label: string;
179
- /** Qualified-Name des Server-Handlers, z.B.
180
- * "publicstatus:write:maintenance:start". Wird via useDispatcher
181
- * dispatcht. */
182
- readonly handler: string;
183
- /** Deklarativer Payload pro Row. Default = `{ id: row.id }`.
184
- * `pick` extrahiert Felder gleichen Namens; `map` benennt um. */
185
- readonly payload?: RowFieldExtractor;
186
- /** i18n-Key für die Confirm-Dialog-Description. Wenn gesetzt, öffnet
187
- * ein Modal vor der Ausführung — der User muss explizit bestätigen.
188
- * Zusammen mit `style: "danger"` ist das die Standard-Sicherheits-
189
- * Garde für destruktive Aktionen. */
190
- readonly confirm?: string;
191
- /** i18n-Key für den Confirm-Button-Text im Dialog. Default = `label`
192
- * (also "Delete" → "Delete"-Button im Confirm). Setzen wenn die
193
- * Action einen langen Namen hat ("Mark Subscription as Cancelled")
194
- * und der Button kürzer sein soll ("Cancel Subscription"). */
195
- readonly confirmLabel?: string;
196
- /** Conditional Visibility pro Row. */
197
- readonly visible?: FieldCondition;
198
- /** Visual-Style. "danger" rendert rot + erzwingt einen Confirm-
199
- * Dialog (auch ohne expliziten `confirm`-Key). */
200
- readonly style?: "primary" | "secondary" | "danger";
201
- };
202
-
203
- export type RowActionNavigate = {
204
- readonly kind: "navigate";
205
- readonly id: string;
206
- readonly label: string;
207
- /** Screen-id (kurz, unqualified) zu dem navigiert wird. Boot-
208
- * Validator prüft Existenz im selben Feature. */
209
- readonly screen: string;
210
- /** Feldname dessen Wert als entityId in den URL-Pfad eingebettet wird
211
- * (`/<workspace>/<screen>/<entityId>`). entityEdit liest die Id
212
- * AUSSCHLIESSLICH aus dem Pfad. Default: "id" wenn der Ziel-Screen
213
- * ein entityEdit ist. */
214
- readonly entityId?: string;
215
- /** Deklarative URL-Search-Params aus row-Context. Wird in actionForm-
216
- * Targets als initial values gelesen (actionForm pre-fillen).
217
- * `pick` extrahiert Felder gleichen Namens; `map` benennt um. */
218
- readonly params?: RowFieldExtractor;
219
- /** Conditional Visibility pro Row. */
220
- readonly visible?: FieldCondition;
221
- readonly style?: "primary" | "secondary";
222
- /** Wenn true, löst ein Klick auf die ganze Zeile (nicht nur das Aktionsmenü)
223
- * diese navigate-Action aus. Max. eine pro Liste (Boot-Validator prüft). Nur
224
- * auf navigate — ein Row-Klick darf keinen (evtl. destruktiven, unbestätigten)
225
- * Write auslösen, daher nicht auf writeHandler-Actions. */
226
- readonly rowClick?: boolean;
227
- };
228
-
229
- // ToolbarAction — Button im List-Header. Zwei Varianten: navigate auf
230
- // einen anderen Screen (z.B. zu einem actionForm) oder direkt einen
231
- // Handler dispatchen (z.B. "Sync All" ohne Form).
232
- export type ToolbarAction =
233
- | {
234
- readonly kind: "navigate";
235
- readonly id: string;
236
- readonly label: string;
237
- /** Screen-id (kurz, unqualified) zu dem navigiert wird. */
238
- readonly screen: string;
239
- readonly style?: "primary" | "secondary";
240
- }
241
- | {
242
- readonly kind: "writeHandler";
243
- readonly id: string;
244
- readonly label: string;
245
- readonly handler: string;
246
- /** Statischer Payload ohne row-Context. Default = `{}`. */
247
- readonly payload?: Record<string, unknown>;
248
- /** i18n-Key für Confirm-Dialog-Description. Wenn gesetzt UND/ODER
249
- * style="danger": Modal vor der Ausführung. */
250
- readonly confirm?: string;
251
- /** i18n-Key für Confirm-Button-Text im Dialog. Default = `label`. */
252
- readonly confirmLabel?: string;
253
- readonly style?: "primary" | "secondary" | "danger";
254
- };
255
-
256
- export type EntityListScreenDefinition = {
257
- readonly id: string;
258
- readonly type: "entityList";
259
- readonly entity: string;
260
- readonly columns: readonly ListColumnSpec[];
261
- // Row renderer (Desktop) — when omitted, renderer draws the default table
262
- // from `columns`. cardRenderer fills the same role on compact layouts.
263
- readonly rowRenderer?: PlatformComponent;
264
- readonly cardRenderer?: PlatformComponent;
265
- /** Per-Row-Aktionen — rendert eine Actions-Spalte rechts in der Tabelle.
266
- * Bis zu 2 actions als inline-Buttons; >2 als Kebab-Dropdown.
267
- * Reihenfolge im Array = Reihenfolge in der UI. */
268
- readonly rowActions?: readonly RowAction[];
269
- /** Toolbar-Aktionen (List-Header). "Open Incident", "Schedule Maintenance"
270
- * etc. — neben "+ Neu" wenn vorhanden. Reihenfolge im Array = UI-
271
- * Reihenfolge, primary-style links. */
272
- readonly toolbarActions?: readonly ToolbarAction[];
273
- /** Server-side Filter, fest am Screen — drei Buckets derselben
274
- * Entity ohne Custom-Pages (z.B. "Upcoming" / "Active" / "Past"
275
- * Maintenance). User-side q-Search läuft AUF dem gefilterten Set
276
- * oben drauf. Boot-Validator pinst dass field in der Entity existiert. */
277
- readonly filter?: ScreenFilter;
278
- // Pagination-Modus (Default "pages"). Bestimmt UI (Pager vs Scroll-
279
- // Sentinel) und ob der Server `total` mitliefern muss.
280
- readonly pagination?: ListPaginationMode;
281
- // Page-Größe. Default 50 — guter Kompromiss zwischen "viel sichtbar"
282
- // und "DB liefert schnell". Apps mit teurem Read (Joins, Computed-
283
- // Fields) gehen runter; Power-User-Listen (z.B. internal Analytics)
284
- // gehen hoch.
285
- readonly pageSize?: number;
286
- // Default-Sortierung beim Erst-Mount. Wenn URL-Param `?sort=…`
287
- // gesetzt ist, gewinnt der; sonst nutzt RenderList diesen Default.
288
- // `field` muss in der Entity sortable: true sein — Boot-Validator
289
- // pinnt das.
290
- readonly defaultSort?: ListSortSpec;
291
- // Search-Toolbar im UI an/aus. Server-Search geht IMMER über den
292
- // SearchAdapter (Meilisearch) — kein DB-ILIKE-Drift. Default true
293
- // wenn die Entity searchable Felder hat, sonst false.
294
- readonly searchable?: boolean;
295
- readonly slots?: ScreenSlots;
296
- readonly access?: AccessRule;
297
- };
298
-
299
- // --- entityEdit ---
300
-
301
- // camelCase `readOnly` instead of the spec's lowercase `readonly`: TS's
302
- // `readonly` modifier on the same line would make the declaration read
303
- // `readonly readonly?: FieldCondition`, which is legal but a real lese-knick.
304
- // Mirrors React's `readOnly` prop so the ergonomic cost of the divergence
305
- // from ui-architecture.md is minimal.
306
- export type EditFieldSpec =
307
- | string
308
- | {
309
- readonly field: string;
310
- readonly span?: number;
311
- readonly visible?: FieldCondition;
312
- readonly readOnly?: FieldCondition;
313
- readonly required?: FieldCondition;
314
- readonly renderer?: FieldRenderer;
315
- };
316
-
317
- // A section is either a normal field-grid (default — `kind` omitted keeps
318
- // every existing screen-def working) or an extension slot that mounts a
319
- // feature-provided component. The extension component is resolved client-
320
- // side by name (same `__component` marker as custom screens / column
321
- // renderers) and receives the host entity name + id, so a bundled feature
322
- // (e.g. custom-fields) can load and persist its own data inside the form.
323
- export type EditSectionSpec = EditFieldsSection | EditExtensionSection;
324
-
325
- export type EditFieldsSection = {
326
- readonly kind?: "fields";
327
- /** Optional. Ohne Titel rendert die Section nur ihre Felder (keine h3-
328
- * Überschrift) — für flache Forms (Card-Titel + Felder direkt, ein
329
- * einzelner Abschnitt) wie bei den meisten shadcn-Form-Mustern. */
330
- readonly title?: string;
331
- readonly columns?: number;
332
- readonly fields: readonly EditFieldSpec[];
333
- };
334
-
335
- export type EditExtensionSection = {
336
- readonly kind: "extension";
337
- readonly title: string;
338
- readonly component: PlatformComponent;
339
- };
340
-
341
- export function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection {
342
- return section.kind === "extension";
343
- }
344
-
345
- export type EditLayout = {
346
- readonly sections: readonly EditSectionSpec[];
347
- };
348
-
349
- export type EntityEditScreenDefinition = {
350
- readonly id: string;
351
- readonly type: "entityEdit";
352
- readonly entity: string;
353
- readonly layout: EditLayout;
354
- /** Optionaler i18n-Key (oder Roh-String) für den Submit-Button. Default
355
- * `kumiko.actions.save`. Lässt den Auto-Edit-Screen domain-spezifische
356
- * CTAs zeigen ("Save Address", "Create item") statt generisch "Speichern". */
357
- readonly submitLabel?: string;
358
- /** Default true. `false` für Entities deren Create über einen eigenen
359
- * Lifecycle-Write läuft (z.B. incident:open mit Event-Stream + Joins)
360
- * statt über `<entity>:create`: unterdrückt den automatischen
361
- * „+ Neu"-Button auf entityList-Screens dieser Entity und rendert den
362
- * Create-Branch (Aufruf ohne entityId) als Fehler statt eines Forms,
363
- * dessen Submit gegen einen nicht registrierten Handler liefe. */
364
- readonly allowCreate?: boolean;
365
- /** Default true. `false` wenn kein `<entity>:delete`-Handler existiert
366
- * (History-/Audit-Erhalt): unterdrückt den Löschen-Button im
367
- * Update-Form. */
368
- readonly allowDelete?: boolean;
369
- /** Optionaler per-Field-Label-i18n-Key (Field-Name → Key), überschreibt
370
- * die Default-Konvention `<feature>:entity:<entity>:field:<name>`.
371
- * Primär für configEdit: dessen Pseudo-Entity `__config-edit__` hat
372
- * keinen natürlichen Field-Namespace — der Settings-Hub injiziert hier
373
- * das `mask.title`-Label des Config-Keys. Fehlt ein Eintrag, gilt die
374
- * Konvention. */
375
- readonly fieldLabels?: Readonly<Record<string, string>>;
376
- readonly slots?: ScreenSlots;
377
- readonly access?: AccessRule;
378
- };
379
-
380
- // --- actionForm ---
381
-
382
- // Form-Screen für non-CRUD Write-Handler. Wird gerendert wie ein
383
- // EntityEditScreen (sections + fields), aber:
384
- // - kein detail-fetch beim Mount (initial-state = field-defaults)
385
- // - kein CRUD-verb-mapping ("create"/"update") — Author gibt
386
- // explizit die Write-Handler-QN an
387
- // - Form-Object landet 1:1 als payload beim Handler; sein Zod-Schema
388
- // validiert weiter
389
- // - optional `redirect` zu einem anderen Screen nach Submit-Success
390
- //
391
- // Beispiele: "Send invitation" (mit recipient-email + role), "Approve
392
- // invoice" (mit notes), "Bulk-import" (mit CSV-string + mode).
393
- //
394
- // Field-Shape: inline am Screen statt entity-Reference. Author hat
395
- // explizite Kontrolle was die Form rendert ohne eine ganze Entity
396
- // dafür anzulegen. Die FieldDefinitions sind dieselben wie auf
397
- // Entities (text/select/number/...) — alle DefaultInput-Renderer
398
- // greifen unverändert.
399
- export type ActionFormScreenDefinition = {
400
- readonly id: string;
401
- readonly type: "actionForm";
402
- /** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
403
- * 1:1 als payload — Handler-Schema (Zod) validiert weiter. */
404
- readonly handler: string;
405
- /** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
406
- * wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
407
- * Validator). */
408
- readonly fields: Readonly<Record<string, FieldDefinition>>;
409
- /** Layout analog zu EntityEditScreen: sections mit fields aus dem
410
- * fields-Map oben. */
411
- readonly layout: EditLayout;
412
- /** i18n-key für den Submit-Button. Default: i18n-Default des
413
- * Renderers (typischerweise "actions.submit"). */
414
- readonly submitLabel?: string;
415
- /** Nach erfolgreichem Submit zu dieser Screen-ID navigieren (kurze
416
- * ID, z.B. "item-list" — gleiche Feature, der nav-Router resolved
417
- * zum vollen Pfad). Cross-Feature-Redirect ist nicht supported.
418
- * Wenn nicht gesetzt, bleibt der User auf dem Form-Screen. Boot-
419
- * Validator prüft dass die ID einen registrierten Screen meint. */
420
- readonly redirect?: string;
421
- /** Ziel des Abbrechen-Buttons. Default: `redirect` (historisches
422
- * Verhalten — Cancel und Submit-Redirect landen dann am selben Ort).
423
- * `false` = kein Abbrechen-Button; richtig für Single-Action-Screens
424
- * ohne verwerfbaren Zustand (z.B. "Test-Mail senden"), wo Abbrechen
425
- * nur ein zweiter Weg zum selben Ziel wäre. Boot-Validator prüft
426
- * String-Targets wie `redirect`. */
427
- readonly cancelTarget?: string | false;
428
- readonly slots?: ScreenSlots;
429
- readonly access?: AccessRule;
430
- };
431
-
432
- // --- custom ---
433
-
434
- // Sub-route declared by a custom screen (Expo Router / URL-routing use).
435
- // `path` is the route-segment appended to the screen's own path; the
436
- // framework owns the outer routing. Components stay opaque.
437
- export type CustomScreenRoute = {
438
- readonly path: string;
439
- readonly component: PlatformComponent;
440
- };
441
-
442
- export type CustomScreenDefinition = {
443
- readonly id: string;
444
- readonly type: "custom";
445
- readonly renderer: PlatformComponent;
446
- readonly routes?: readonly CustomScreenRoute[];
447
- readonly access?: AccessRule;
448
- };
449
-
450
- // --- configEdit ---
451
-
452
- // Form-Screen der Tenant-/User-/System-Settings aus dem bundled
453
- // config-Feature liest und schreibt. Wird gerendert wie ein
454
- // EntityEditScreen (sections + fields), aber:
455
- // - Detail-Load via `config:query:values` (statt `<entity>:detail`)
456
- // - Pre-Fill nutzt `configKeys[shortName]` → qualifizierter Key, dann
457
- // `values[qualifiedKey].value`
458
- // - Submit feuert pro geändertem Feld einen `config:write:set` mit
459
- // {key, value, scope}; das config-feature behandelt jeden Key als
460
- // eigenes Aggregate (configValue.<keyHash>) und alle N Writes laufen
461
- // parallel (Promise.all). Per-Key idempotent → Retry safe.
462
- // - kein Singleton-Hack nötig: pro (key+tenantId) gibt's by-design
463
- // genau eine Row, der Bridge-Pattern aus dem Branding-MVP fällt weg
464
- //
465
- // Partial-Failure-Semantik: wenn von N parallelen Writes einer scheitert,
466
- // bleiben die anderen committed (pro-Aggregate, kein Multi-Stream-Rollback).
467
- // Das Form bleibt dirty bis der User retried — die schon erfolgreichen
468
- // Writes feuern dann nochmal mit demselben Wert. Für `text` / `number` /
469
- // `boolean` Keys ist das idempotent. Wer einen ConfigKey mit nicht-
470
- // idempotentem Setter baut (Counter, append-only-list o.ä.) muss die
471
- // Idempotenz im Setter sicherstellen.
472
- //
473
- // Field-Shape: inline am Screen wie bei `actionForm`. Author hat damit
474
- // explizite Kontrolle über Input-Type (text/number/select/...) ohne
475
- // Resolve-Ceremony — die FieldDefinitions sind dieselben wie auf
476
- // Entities, alle DefaultInput-Renderer greifen unverändert. Field-
477
- // Labels gehen über bestehende i18n-Konventionen (`<feature>:entity:
478
- // <namespace>:field:<name>` o.ä. — der Author wählt den Namespace).
479
- //
480
- // scope MUSS mit der `createTenantConfig`/`createSystemConfig`/
481
- // `createUserConfig`-Deklaration der referenzierten Keys
482
- // übereinstimmen — der Boot-Validator pinnt das.
483
- export type ConfigEditScreenDefinition = {
484
- readonly id: string;
485
- readonly type: "configEdit";
486
- /** scope für config:write:set Calls. Muss zur Scope-Deklaration der
487
- * in `configKeys` referenzierten Keys passen — Boot-Validator
488
- * prüft das gegen die Registry. */
489
- readonly scope: "tenant" | "system" | "user";
490
- /** Map: form-field-name (kurz, wie im Layout referenziert) → voll-
491
- * qualifizierter Config-Key (`<feature>:config:<short>`). Boot-
492
- * Validator prüft dass jeder qualifizierte Key in der Registry
493
- * bekannt ist. */
494
- readonly configKeys: Readonly<Record<string, string>>;
495
- /** Form-Shape pro Field-Name. Selbe FieldDefinitions wie auf
496
- * Entities/ActionForm. Field-Names matchen die Keys in `configKeys`
497
- * — Boot-Validator pinnt das. */
498
- readonly fields: Readonly<Record<string, FieldDefinition>>;
499
- /** Layout: Sections mit Field-Refs. Identisch zu entityEdit/
500
- * actionForm. */
501
- readonly layout: EditLayout;
502
- /** Optionaler per-Field-Label-i18n-Key (Field-Name → Key). Der
503
- * Settings-Hub setzt hier `mask.title` des jeweiligen Config-Keys,
504
- * damit das am Key deklarierte Label am generierten Feld erscheint —
505
- * ohne es unter der `__config-edit__`-Konvention zu duplizieren.
506
- * Fehlt ein Eintrag, gilt die Konvention. */
507
- readonly fieldLabels?: Readonly<Record<string, string>>;
508
- /** i18n-key für den Submit-Button. Default: "kumiko.actions.save". */
509
- readonly submitLabel?: string;
510
- readonly slots?: ScreenSlots;
511
- readonly access?: AccessRule;
512
- };
513
-
514
- // --- shared slots (Level 4 from ui-architecture.md) ---
515
-
516
- export type ScreenSlots = {
517
- readonly header?: PlatformComponent;
518
- readonly beforeForm?: PlatformComponent;
519
- readonly afterForm?: PlatformComponent;
520
- readonly sidebar?: PlatformComponent;
521
- readonly footer?: PlatformComponent;
522
- readonly toolbar?: PlatformComponent;
523
- };
524
-
525
- // --- discriminated union ---
526
-
527
- export type ScreenDefinition =
528
- | EntityListScreenDefinition
529
- | EntityEditScreenDefinition
530
- | ActionFormScreenDefinition
531
- | ConfigEditScreenDefinition
532
- | CustomScreenDefinition;
533
-
534
- // Type guard — narrows FieldRenderer to FormatSpec. Useful for renderer
535
- // authors who branch on the three FieldRenderer variants without manual
536
- // "format" in renderer checks.
537
- export function isFormatSpec(r: unknown): r is FormatSpec {
538
- return typeof r === "object" && r !== null && "format" in r && typeof r.format === "string";
539
- }
540
-
541
- // Collapse the string-shorthand into the object form. Both the boot-validator
542
- // and (later) ui-core's view-model builder iterate over fields/columns — the
543
- // helper keeps that loop from growing two branches everywhere.
544
- export function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string> {
545
- const col = typeof c === "string" ? { field: c } : c;
546
- if (
547
- typeof process !== "undefined" &&
548
- process.env.NODE_ENV !== "production" &&
549
- col.renderer !== undefined &&
550
- typeof col.renderer === "function"
551
- ) {
552
- // biome-ignore lint/suspicious/noConsole: dev-only warning
553
- console.warn(
554
- `[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`,
555
- );
556
- }
557
- return col;
558
- }
559
-
560
- /** Evaluates a declarative FieldCondition against the current row/form
561
- * values. THE single implementation — renderer (row-action visibility),
562
- * headless view-model (visible/readOnly/required) and render-edit
563
- * (form-condition closures) reuse it; three hand-rolled copies had
564
- * already drifted in shape. */
565
- export function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean {
566
- if (typeof cond === "boolean") return cond;
567
- const val = values[cond.field];
568
- if ("eq" in cond) return val === cond.eq;
569
- return val !== cond.ne;
570
- }
571
-
572
- export function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string> {
573
- return typeof f === "string" ? { field: f } : f;
574
- }
1
+ // Legacy path re-exported for callers still importing this module directly.
2
+ export type * from "@cosmicdrift/kumiko-types/screen";