@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
@@ -64,7 +64,7 @@ export async function runPipeline<TPayload, TData, TMap extends object = KumikoE
64
64
  if (outcome.kind === "return") {
65
65
  // RETURN_RESULT_KEY is only produced by r.step.return, whose run()
66
66
  // returns WriteResult<unknown>. The pipeline's generic TData is
67
- // bound at build time (defineWriteHandler ↔ pipeline<P, D>(...));
67
+ // bound at build time (defineWriteHandler ↔ stepsPipeline<P, D>(...));
68
68
  // matching the runtime value to that compile-time type is the
69
69
  // contract user-side. Cast crosses that boundary.
70
70
  return outcome.result as WriteResult<TData>;
@@ -97,6 +97,7 @@ export function fieldToZod(field: FieldDefinition, currencies: readonly string[]
97
97
  let schema = z.number();
98
98
  if (field.integer) schema = schema.int();
99
99
  if (field.min !== undefined) schema = schema.min(field.min);
100
+ if (field.max !== undefined) schema = schema.max(field.max);
100
101
  return field.default !== undefined ? schema.default(field.default) : schema;
101
102
  }
102
103
  case "decimal": {
@@ -0,0 +1,54 @@
1
+ import type {
2
+ EditExtensionSection,
3
+ EditFieldSpec,
4
+ EditSectionSpec,
5
+ FieldCondition,
6
+ FormatSpec,
7
+ ListColumnSpec,
8
+ } from "./types/screen";
9
+
10
+ export function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection {
11
+ return section.kind === "extension";
12
+ }
13
+
14
+ // Type guard — narrows FieldRenderer to FormatSpec. Useful for renderer
15
+ // authors who branch on the three FieldRenderer variants without manual
16
+ // "format" in renderer checks.
17
+ export function isFormatSpec(r: unknown): r is FormatSpec {
18
+ return typeof r === "object" && r !== null && "format" in r && typeof r.format === "string";
19
+ }
20
+
21
+ // Collapse the string-shorthand into the object form. Both the boot-validator
22
+ // and (later) ui-core's view-model builder iterate over fields/columns — the
23
+ // helper keeps that loop from growing two branches everywhere.
24
+ export function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string> {
25
+ const col = typeof c === "string" ? { field: c } : c;
26
+ if (
27
+ typeof process !== "undefined" &&
28
+ process.env.NODE_ENV !== "production" &&
29
+ col.renderer !== undefined &&
30
+ typeof col.renderer === "function"
31
+ ) {
32
+ // biome-ignore lint/suspicious/noConsole: dev-only warning
33
+ console.warn(
34
+ `[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`,
35
+ );
36
+ }
37
+ return col;
38
+ }
39
+
40
+ /** Evaluates a declarative FieldCondition against the current row/form
41
+ * values. THE single implementation — renderer (row-action visibility),
42
+ * headless view-model (visible/readOnly/required) and render-edit
43
+ * (form-condition closures) reuse it; three hand-rolled copies had
44
+ * already drifted in shape. */
45
+ export function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean {
46
+ if (typeof cond === "boolean") return cond;
47
+ const val = values[cond.field];
48
+ if ("eq" in cond) return val === cond.eq;
49
+ return val !== cond.ne;
50
+ }
51
+
52
+ export function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string> {
53
+ return typeof f === "string" ? { field: f } : f;
54
+ }
@@ -29,7 +29,11 @@ export const softDeleteGraceDaysConfig: ConfigKeyDefinition = {
29
29
  default: DEFAULT_GRACE_DAYS,
30
30
  scope: "tenant",
31
31
  access: { read: ["TenantAdmin", "SystemAdmin"], write: ["SystemAdmin"] },
32
- bounds: { min: 0 },
32
+ // min: 1, not 0 (565/2) — graceDays: 0 doesn't mean "no cleanup", it means
33
+ // "hard-delete every currently soft-deleted row on the next cron run".
34
+ // Clamping (not rejecting) keeps the resolve-config-or-param clamp path's
35
+ // existing silent-below-bound / audited-crossing behavior.
36
+ bounds: { min: 1 },
33
37
  };
34
38
 
35
39
  export const softDeleteCleanupJob: JobHandlerFn = async (_payload, ctx) => {
@@ -56,7 +60,7 @@ export const softDeleteCleanupJob: JobHandlerFn = async (_payload, ctx) => {
56
60
  db,
57
61
  )
58
62
  : undefined;
59
- const graceDays = typeof resolved === "number" && resolved >= 0 ? resolved : DEFAULT_GRACE_DAYS;
63
+ const graceDays = typeof resolved === "number" && resolved >= 1 ? resolved : DEFAULT_GRACE_DAYS;
60
64
  const cutoff = Temporal.Now.instant().subtract({ hours: graceDays * 24 });
61
65
 
62
66
  for (const proj of registry.getAllProjections().values()) {
@@ -0,0 +1,20 @@
1
+ // kumiko-framework#1525: addDuration must not rely on globalThis.Temporal —
2
+ // it's a pure free function called from step handlers before boot may have
3
+ // run ensureTemporalPolyfill() on every code path.
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { addDuration } from "../_duration-utils";
7
+
8
+ describe("addDuration — kumiko-framework#1525: no ambient Temporal global", () => {
9
+ test("adds a duration without relying on globalThis.Temporal", () => {
10
+ const savedGlobal = (globalThis as { Temporal?: unknown }).Temporal;
11
+ delete (globalThis as { Temporal?: unknown }).Temporal;
12
+ try {
13
+ const result = addDuration("2026-01-01T00:00:00Z", "P1DT1H");
14
+ expect(result).toBe("2026-01-02T01:00:00Z");
15
+ } finally {
16
+ if (savedGlobal === undefined) delete (globalThis as { Temporal?: unknown }).Temporal;
17
+ else (globalThis as { Temporal?: unknown }).Temporal = savedGlobal;
18
+ }
19
+ });
20
+ });
@@ -2,6 +2,8 @@
2
2
  // Accepts "P1D", "PT1H", "P1Y2M3DT4H5M6S" etc.
3
3
  // Uses approximate calendar math (365d/year, 30d/month). See #23.
4
4
 
5
+ import { Temporal } from "temporal-polyfill";
6
+
5
7
  export function addDuration(baseIso: string, duration: string): string {
6
8
  const pattern = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
7
9
  const match = duration.match(pattern);
@@ -11,6 +11,7 @@
11
11
  // rejected by boot-validation — domain mutation MUST go through
12
12
  // r.step.aggregate.*.
13
13
 
14
+ import { KUMIKO_COLUMNS_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
14
15
  import { extractTableName } from "../../db";
15
16
  import { executeRawQuery } from "../../db/queries/raw-sql";
16
17
  import { defineStep } from "../define-step";
@@ -23,10 +24,6 @@ type UnsafeProjectionUpsertArgs = {
23
24
  readonly row: StepResolver<Record<string, unknown>>;
24
25
  };
25
26
 
26
- // @cast-boundary drizzle-bridge — reads column snake_case names from
27
- // drizzle Symbol-based metadata without importing drizzle-orm.
28
- const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
29
-
30
27
  function resolveColumnName(table: unknown, field: string): string {
31
28
  if (typeof table !== "object" || table === null) return field;
32
29
  const cols = (table as Record<symbol, unknown>)[KUMIKO_COLUMNS_SYMBOL];
@@ -48,8 +48,9 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
48
48
  * Signup-Datum des Tenants live (tenant.inserted_at) und returnt true wenn
49
49
  * der Tenant im Trial-Fenster ist UND das Feature zum Trial-Tier gehört.
50
50
  * Async + nicht im Boot-Cache, weil der Trial zeit-abgeleitet ist (ändert
51
- * sich zwischen Requests). Nur die 2 Gate-Aufrufstellen awaiten ihn; der
52
- * synchrone Hot-Path (ctx.hasFeature, Feature-Set) bleibt unberührt.
51
+ * sich zwischen Requests). Konsultiert an 3 Stellen: den 2 Dispatch-Gate-
52
+ * Aufrufstellen (checkFeatureEnabled) UND ctx.hasFeature im Handler-Body
53
+ * (fällt darauf zurück, wenn der synchrone Feature-Set-Check negativ ist).
53
54
  */
54
55
  readonly trialGate?: TrialGate;
55
56
  };
@@ -1,482 +1,2 @@
1
- import type { ZodType } from "zod";
2
- import type { DbConnection } from "../../db/connection";
3
- import type { TenantDb } from "../../db/tenant-db";
4
- import type { ConcurrencyMode, ConfigScope } from "../constants";
5
- import type { FieldDefinition } from "./fields";
6
- import type { AppContext } from "./handlers";
7
- import type {
8
- PostDeleteHookFn,
9
- PostSaveHookFn,
10
- PreDeleteHookFn,
11
- PreQueryHookFn,
12
- PreSaveHookFn,
13
- } from "./hooks";
14
- import type { TenantId } from "./identifiers";
15
-
16
- // --- Config ---
17
-
18
- export type ConfigKeyAccess = {
19
- readonly read: readonly string[];
20
- readonly write: readonly string[];
21
- };
22
-
23
- export type ConfigKeyType = "text" | "number" | "boolean" | "select";
24
-
25
- export type ConfigValue<T extends ConfigKeyType> = T extends "number"
26
- ? number
27
- : T extends "boolean"
28
- ? boolean
29
- : T extends "text" | "select"
30
- ? string
31
- : never;
32
-
33
- // Bounds for numeric config keys. Enforced as hard-reject (not silent-clamp)
34
- // in set.write.ts: a tenant-admin setting a value outside [min, max] gets a
35
- // 400 "out_of_bounds" — silent clamping would be a UX trap ("I entered 9999,
36
- // it saved as 1000, why?"). Per-Request helpers MAY clamp — that's a
37
- // different call site where the caller often can't control the exact value.
38
- // Only meaningful for type="number"; boot-validator rejects on other types.
39
- export type ConfigBounds = {
40
- readonly min?: number;
41
- readonly max?: number;
42
- };
43
-
44
- // Ctx a `computed` key-resolver gets. Mirrors what the resolver itself has:
45
- // tenantId + userId for scope-aware lookups, db for ad-hoc queries (e.g.
46
- // "read the current subscription plan for this tenant"). Intentionally
47
- // narrow — giving it the full AppContext would leak deps like `redis`
48
- // into declaration-level code that shouldn't need them.
49
- export type ConfigComputedContext = {
50
- readonly tenantId: TenantId;
51
- readonly userId: string;
52
- readonly db: DbConnection | TenantDb;
53
- };
54
-
55
- // Computed-value resolver. Called when no scope-row AND no app-boot-override
56
- // exist for this key — sits one step above keyDef.default.
57
- //
58
- // Use-case: plan-based limits ("Pro tenants get maxUploadSizeMB=100"). The
59
- // feature declares *how* to compute the value, the handler stays neutral:
60
- // const max = await ctx.config(handle); // resolver calls computed
61
- //
62
- // Row wins over computed: a tenant-admin that sets a specific value
63
- // overrides the plan-default. If you want "plan is a hard policy", reject
64
- // set on the handler-side — don't try to invert the cascade.
65
- export type ConfigComputedFn<T extends ConfigKeyType = ConfigKeyType> = (
66
- ctx: ConfigComputedContext,
67
- ) => Promise<ConfigValue<T>>;
68
-
69
- // Storage-Backing eines provisionierten Config-Keys. "config" (Default) =
70
- // config_values-Projektion mit voller Cascade (user→tenant→system→app→default).
71
- // "secrets" = read_tenant_secrets (flach pro (tenant,key), AES-GCM-Envelope mit
72
- // Rotation/Audit, KEINE Cascade) — nur sinnvoll für scope:system ohne
73
- // Tenant-Override. Die backing×scope-Matrix erzwingt der boot-validator.
74
- export type ConfigBacking = "config" | "secrets";
75
-
76
- // Minimal read surface the config resolver needs to dispatch a
77
- // backing="secrets" key to the secrets store, without coupling the engine
78
- // types to the full SecretsContext. The app's `ctx.secrets` (a SecretsContext)
79
- // is structurally assignable. Threaded per-call (not at resolver construction)
80
- // because the resolver is framework-auto-created while `ctx.secrets` is
81
- // app-provided — only the request context sees both.
82
- export type ConfigSecretsReader = {
83
- get(tenantId: TenantId, key: string): Promise<{ readonly reveal: () => string } | undefined>;
84
- };
85
-
86
- export type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
87
- readonly type: T;
88
- readonly default?: ConfigValue<T>;
89
- readonly scope: ConfigScope;
90
- readonly access: ConfigKeyAccess;
91
- readonly encrypted?: boolean;
92
- readonly options?: readonly string[];
93
- readonly bounds?: ConfigBounds;
94
- // Per-key string-pattern validation for type="text". The value must match
95
- // the regex at write time — set.write hard-rejects a mismatch with
96
- // ValidationError("invalid_format"), same posture as bounds. Stored as a
97
- // serializable {regex, flags} pair (not a RegExp/predicate) so it survives
98
- // JSON like `bounds`/`options` (feature-manifest, docgen) and compiles per
99
- // write via new RegExp. Keep patterns anchored + length-bounded: the value
100
- // is tenant-supplied (untrusted), an unbounded catastrophic-backtracking
101
- // regex applied to it would be a ReDoS vector.
102
- readonly pattern?: { readonly regex: string; readonly flags?: string };
103
- readonly computed?: ConfigComputedFn<T>;
104
- // Per-Request opt-in. Default false — resolveConfigOrParam wirft für
105
- // Keys ohne diese Marke, auch wenn der Caller paramValue übergibt. Das
106
- // zwingt Feature-Devs zur expliziten Entscheidung "dieser Key darf pro
107
- // Request überschrieben werden" — statt versehentlich zu erlauben dass
108
- // ein Query-Param jede beliebige Tenant-Config umgeht.
109
- //
110
- // Nicht kombinierbar mit type="text" (Boot-Reject) — Text-Werte sind
111
- // immer gesperrt wegen XSS/SQL/Shell-Risiko, selbst mit Opt-in.
112
- // Nicht kombinierbar mit encrypted (Boot-Reject) — encrypted Keys
113
- // werden nicht transient aus Query-Strings heraus gelesen.
114
- readonly allowPerRequest?: boolean;
115
- // Tenant must supply a real value before the owning feature works — for
116
- // text keys an empty/whitespace value counts as unset. Surfaced by
117
- // config:query:readiness; keep in sync with the feature's requireNonEmpty
118
- // calls in its build-fn.
119
- readonly required?: boolean;
120
-
121
- // --- Provisioning-Metadata (optional auf createTenant/System/UserConfig) ---
122
- // ENV-Var-Name, dessen Wert beim Boot als app-override-Default dieses Keys
123
- // gebrückt wird. Reiner Fallback — überschreibt keinen gesetzten Row.
124
- readonly env?: string;
125
- // false → der geerbte system-row-Wert wird für Tenant-Admins redigiert
126
- // (cascade.query + values.query): der Tenant sieht weder den Wert noch dass
127
- // er gesetzt ist, nur den eigenen Override. Greift quell-basiert auf jeden
128
- // Wert aus der system-row — nicht scope-gebunden; typischer Fall ist ein
129
- // scope:tenant Key, dessen Plattform-Default in der system-row liegt (SMTP-
130
- // Creds). Default true = transparente Cascade.
131
- readonly inheritedToTenant?: boolean;
132
- // "config" (Default, volle Cascade) oder "secrets" (flach pro (tenant,key)).
133
- readonly backing?: ConfigBacking;
134
- // Markiert den Key als user-facing Einstellung: der Self-Populating
135
- // Settings-Hub leitet daraus automatisch Screen+Nav-Eintrag ab (kein
136
- // manuelles r.screen/r.nav). Fehlt `mask`, gilt der Key als internes
137
- // Plumbing (ENV-provisioniert/computed) und erscheint NICHT im Hub.
138
- readonly mask?: ConfigMask;
139
- };
140
-
141
- // Label-Träger für den Settings-Hub. `title` ist ein i18n-Key (kein Literal —
142
- // Guard), `icon` ein Icon-Registry-Key für den Nav-Eintrag, `order` die
143
- // Sortier-Gewichtung innerhalb seiner Audience-Gruppe.
144
- export type ConfigMask = {
145
- readonly title: string;
146
- readonly icon?: string;
147
- readonly order?: number;
148
- };
149
-
150
- export type ConfigDefinition = {
151
- readonly keys: Readonly<Record<string, ConfigKeyDefinition>>;
152
- };
153
-
154
- export type ConfigKeyHandle<T extends ConfigKeyType = ConfigKeyType> = {
155
- readonly name: string;
156
- readonly type: T;
157
- };
158
-
159
- export type ConfigAccessor = {
160
- (qualifiedKey: string): Promise<string | number | boolean | undefined>;
161
- <T extends ConfigKeyType>(handle: ConfigKeyHandle<T>): Promise<ConfigValue<T> | undefined>;
162
- };
163
-
164
- export type ConfigAccessorFactory = (deps: {
165
- readonly user: { readonly id: string; readonly tenantId: TenantId };
166
- readonly db: DbConnection | TenantDb;
167
- // Present when the app wired `extraContext.secrets`. Lets the internal
168
- // `ctx.config.get` read a backing="secrets" key transparently from the
169
- // secrets store; absent → a backing="secrets" read throws loud.
170
- readonly secrets?: ConfigSecretsReader;
171
- }) => ConfigAccessor;
172
-
173
- // Row shape returned by ConfigResolver.getAll — just enough for the
174
- // values.query handler to project. Stored as `unknown` value because the
175
- // resolver hands raw JSON strings; deserialization is the resolver's job.
176
- //
177
- // Post-ES the config_values projection PK is a UUID (event-store aggregate
178
- // id) and tenantId is non-null (system-scope rows carry SYSTEM_TENANT_ID).
179
- // The shape stays backward-compatible for read callers: they only touch
180
- // `value` and `key`.
181
- export type ConfigStoredRow = {
182
- readonly id: string;
183
- readonly key: string;
184
- readonly value: string | null;
185
- readonly tenantId: string;
186
- readonly userId: string | null;
187
- };
188
-
189
- // Extended row returned by ConfigResolver.getAllWithSource — includes the
190
- // resolution source so the UI can display where each value came from.
191
- export type ConfigStoredRowWithSource = ConfigStoredRow & {
192
- readonly source: ConfigValueSource;
193
- };
194
-
195
- // Which layer of the cascade actually produced a value. Emitted only by
196
- // `getWithSource` — regular `get` hides this to keep the hot-path simple.
197
- // Use-case: Ops-debugging ("warum ist mein Wert 50 und nicht 100?") without
198
- // poking through six scope-row-lookups by hand.
199
- export type ConfigValueSource =
200
- | "user-row" // user-scoped row (only for scope:user keys)
201
- | "tenant-row" // tenant-scoped row
202
- | "system-row" // system-scoped row (tenantId = SYSTEM_TENANT_ID, userId = null)
203
- | "app-override" // from createConfigResolver({ appOverrides })
204
- | "computed" // computed resolver in the key declaration
205
- | "default" // keyDef.default
206
- | "missing"; // no row, no override, no computed, no default
207
-
208
- export type ConfigValueWithSource = {
209
- readonly value: string | number | boolean | undefined;
210
- readonly source: ConfigValueSource;
211
- };
212
-
213
- /// Full cascade for a single config key — every level the resolver
214
- /// walks through, with the winning level marked.
215
- export type ConfigCascadeLevel = {
216
- readonly label: string;
217
- readonly value: string | number | boolean | undefined;
218
- readonly source: ConfigValueSource;
219
- readonly isActive: boolean;
220
- readonly hasValue: boolean;
221
- };
222
-
223
- export type ConfigCascade = {
224
- readonly value: string | number | boolean | undefined;
225
- readonly source: ConfigValueSource;
226
- readonly levels: readonly ConfigCascadeLevel[];
227
- };
228
-
229
- // Minimal contract handlers (set/reset/values.query) call against the
230
- // resolver. Lives in the framework so SharedContextFields.configResolver
231
- // can drop the `unknown` cast — the concrete implementation in
232
- // bundled-features/config/resolver.ts implements this shape.
233
- // Read-only contract: writes flow through the config feature's
234
- // write-handlers (set / reset), which append events + let the event-store-
235
- // executor materialise the projection. The resolver is purely a read
236
- // cascade (user → tenant → system → app-override → computed → default).
237
- export type ConfigResolver = {
238
- get(
239
- qualifiedKey: string,
240
- keyDef: ConfigKeyDefinition,
241
- tenantId: TenantId,
242
- userId: string,
243
- db: DbConnection | TenantDb,
244
- secretsReader?: ConfigSecretsReader,
245
- ): Promise<string | number | boolean | undefined>;
246
-
247
- // Same cascade as get() but also reports which layer produced the value.
248
- // Intended for Ops/Support tooling — never call this from hot-path
249
- // handlers (it builds the source tag even when the caller doesn't look
250
- // at it). Row-lookup count is identical to get(); the extra work is a
251
- // small branch tag.
252
- getWithSource(
253
- qualifiedKey: string,
254
- keyDef: ConfigKeyDefinition,
255
- tenantId: TenantId,
256
- userId: string,
257
- db: DbConnection | TenantDb,
258
- secretsReader?: ConfigSecretsReader,
259
- ): Promise<ConfigValueWithSource>;
260
-
261
- getAll(
262
- tenantId: TenantId,
263
- userId: string,
264
- db: DbConnection | TenantDb,
265
- ): Promise<ReadonlyMap<string, ConfigStoredRow>>;
266
-
267
- // Like getAll() but also reports the resolution source for each key.
268
- // Use when the caller needs to display the cascade origin (e.g. the
269
- // values.query handler serves the UI's hierarchy badge). Hot-path
270
- // callers should prefer getAll() for the narrower return type.
271
- getAllWithSource(
272
- tenantId: TenantId,
273
- userId: string,
274
- db: DbConnection | TenantDb,
275
- ): Promise<ReadonlyMap<string, ConfigStoredRowWithSource>>;
276
-
277
- // Returns ALL cascade levels for a single key — not just the winner.
278
- // Each level shows its value (or undefined if not set) and whether it
279
- // is the active/winning level. Levels are ordered by specificity
280
- // descending (most specific first).
281
- getCascade(
282
- qualifiedKey: string,
283
- keyDef: ConfigKeyDefinition,
284
- tenantId: TenantId,
285
- userId: string,
286
- db: DbConnection | TenantDb,
287
- secretsReader?: ConfigSecretsReader,
288
- ): Promise<ConfigCascade>;
289
-
290
- // Batch variant: resolves cascades for N keys in one DB round-trip.
291
- // keyDefs must contain definitions for every key in the keys array.
292
- // Returns a map of qualifiedKey → ConfigCascade. backing="secrets" keys
293
- // resolve their system rung from the secrets store via secretsReader
294
- // (one read each — they are system-only and rare).
295
- getCascadeBatch(
296
- keys: readonly string[],
297
- keyDefs: ReadonlyMap<string, ConfigKeyDefinition>,
298
- tenantId: TenantId,
299
- userId: string,
300
- db: DbConnection | TenantDb,
301
- secretsReader?: ConfigSecretsReader,
302
- ): Promise<ReadonlyMap<string, ConfigCascade>>;
303
- };
304
-
305
- // --- Process-Placement (runIn) ---
306
-
307
- // Which deploy-shape a consumer / job is allowed to run in. Filtered at
308
- // entrypoint boot: createApiEntrypoint picks up "api"|"both", createWorker
309
- // Entrypoint picks up "worker"|"both", createAllInOneEntrypoint takes
310
- // everything. Default is "worker" for every async consumer/job — that's the
311
- // sensible prod default (API instances stay request-focused, heavy async
312
- // work lives on the worker fleet). Opt into "api" only for latency-
313
- // sensitive or in-memory-stateful consumers (e.g. later: SSE per-instance
314
- // push in Welle 2.7).
315
- //
316
- // Feature-hooks (r.hook preSave/postSave/…) intentionally have no runIn —
317
- // they run in-TX in whatever process handles the command. Splitting them
318
- // would break atomicity. If you want async work, use r.job or
319
- // r.multiStreamProjection.
320
- export type RunIn = "api" | "worker" | "both";
321
-
322
- // Jobs are queue-delivered via BullMQ with one dedicated queue per lane
323
- // ("kumiko-jobs-api" vs "kumiko-jobs-worker") and one dedicated event-
324
- // enqueuer consumer per lane. "both" would mean "dispatch to both queues",
325
- // which over-delivers the job; the Marten-style cursor/queue fan-out is not
326
- // free. Restrict at the type level so the boot-validator never has to
327
- // report it.
328
- export type JobRunIn = Exclude<RunIn, "both">;
329
-
330
- // --- Jobs ---
331
-
332
- export type JobHandlerFn = (payload: Record<string, unknown>, context: AppContext) => Promise<void>;
333
-
334
- export type JobTrigger =
335
- // `on` akzeptiert ein einzelnes Handler-Ref ODER eine Liste. Multi-
336
- // Trigger-Form ist DRY für Fanout-Patterns: ein Job-Body, mehrere
337
- // Trigger (z.B. webhook-fanout: incident.open / incident.update /
338
- // maintenance.start) statt N r.job-Calls mit demselben Handler-Body.
339
- // Im Handler-payload landet `_triggerName: string` damit der Code
340
- // weiß, welcher Trigger gefeuert hat.
341
- | { readonly on: import("./handlers").NameOrRef | readonly import("./handlers").NameOrRef[] }
342
- | { readonly cron: string }
343
- | { readonly manual: true };
344
-
345
- export type JobDefinition = {
346
- readonly name: string;
347
- readonly handler: JobHandlerFn;
348
- readonly trigger: JobTrigger;
349
- readonly concurrency?: ConcurrencyMode | undefined;
350
- readonly maxPerTenant?: number | undefined;
351
- readonly debounceMs?: number | undefined;
352
- readonly retries?: number | undefined;
353
- readonly backoff?: "fixed" | "exponential" | undefined;
354
- readonly timeout?: number | undefined;
355
- readonly schema?: ZodType | undefined;
356
- readonly runOnBoot?: boolean | undefined;
357
- readonly perTenant?: boolean | undefined;
358
- // Which deploy-lane runs this job. Default "worker". Set "api" only for
359
- // short CPU-light handlers (token cleanup, in-process cache warmup) that
360
- // don't justify a separate worker container — long/CPU-heavy jobs on the
361
- // API lane will starve request handlers.
362
- readonly runIn?: JobRunIn | undefined;
363
- };
364
-
365
- // --- Notifications ---
366
-
367
- export type NotificationRecipientFn = (
368
- result: import("./hooks").SaveContext,
369
- ) => string | readonly string[] | { readonly tenant: string } | null;
370
-
371
- export type NotificationDataFn = (result: import("./hooks").SaveContext) => Record<string, unknown>;
372
-
373
- // Per-channel template function: transforms raw notification data into channel-specific format.
374
- // Example: inApp gets { title, body }, email gets { subject, sections }.
375
- export type NotificationTemplateFn = (data: Record<string, unknown>) => Record<string, unknown>;
376
-
377
- export type NotificationDefinition = {
378
- readonly name: string;
379
- readonly trigger: { readonly on: string };
380
- readonly recipient: NotificationRecipientFn;
381
- readonly data: NotificationDataFn;
382
- readonly templates: Readonly<Record<string, NotificationTemplateFn>> | undefined;
383
- };
384
-
385
- // --- Translations ---
386
-
387
- export type TranslationEntry = Readonly<Record<string, string>>;
388
- export type TranslationKeys = Readonly<Record<string, TranslationEntry>>;
389
-
390
- export type TranslationsDef = {
391
- readonly keys: TranslationKeys;
392
- };
393
-
394
- // --- Registrar Extensions ---
395
-
396
- export type RegistrarExtensionHooks = {
397
- readonly preSave?: PreSaveHookFn;
398
- readonly postSave?: PostSaveHookFn;
399
- readonly preDelete?: PreDeleteHookFn;
400
- readonly postDelete?: PostDeleteHookFn;
401
- readonly preQuery?: PreQueryHookFn;
402
- };
403
-
404
- export type UiExtensionDef = {
405
- readonly editSection?: string;
406
- readonly listColumns?: string;
407
- readonly filters?: string;
408
- };
409
-
410
- export type RegistrarExtensionDef = {
411
- readonly onRegister?: (entityName: string, options?: Record<string, unknown>) => void;
412
- readonly extendSchema?: (entityName: string) => Record<string, FieldDefinition>;
413
- readonly hooks?: RegistrarExtensionHooks;
414
- readonly extendSearch?: (entityName: string) => Record<string, unknown>;
415
- readonly uiExtension?: UiExtensionDef;
416
- };
417
-
418
- export type RegistrarExtensionRegistration = {
419
- readonly extensionName: string;
420
- readonly entityName: string;
421
- readonly options?: Record<string, unknown> | undefined;
422
- // Owning feature — annotated by the registry at merge time so consumers
423
- // (readiness gating) can map a registration back to the feature's keys.
424
- readonly featureName?: string;
425
- };
426
-
427
- // Declared by the extension-point-owning foundation via r.extensionSelector:
428
- // "which provider under <extensionName> is active is chosen by <qualifiedKey>".
429
- // Readiness counts a provider-feature's required keys only when selected.
430
- export type ExtensionSelectorDef = {
431
- readonly extensionName: string;
432
- readonly qualifiedKey: string;
433
- };
434
-
435
- // --- Reference Data ---
436
-
437
- export type ReferenceDataDef = {
438
- readonly entityName: string;
439
- readonly data: readonly Record<string, unknown>[];
440
- readonly upsertKey?: string | undefined;
441
- };
442
-
443
- // --- Config Seeding ---
444
-
445
- // A deploy-time default for a config key, written via the event-store
446
- // executor at boot. Idempotent — if the stream already exists the executor
447
- // returns version_conflict and seedConfigValues counts it as skipped.
448
- // See config-seeding.md.
449
- //
450
- // `scope` is optional on the factory-output: createSeed leaves it unset
451
- // (define-feature derives it from keyDef.scope). createSystemSeed /
452
- // createTenantSeed / createUserSeed always set it explicitly.
453
- //
454
- // `tenantId` / `userId` semantics:
455
- // - system scope: both stay undefined (row stored under SYSTEM_TENANT_ID).
456
- // - tenant scope: tenantId optional (undefined → fallback row under
457
- // SYSTEM_TENANT_ID, visible to all tenants via resolver cascade).
458
- // - user scope: BOTH tenantId AND userId required, otherwise the resolver
459
- // can never match the row (user-scope cascade looks up the user's actual
460
- // tenantId, not SYSTEM_TENANT_ID).
461
- export type ConfigSeedDef = {
462
- readonly key: string; // fully-qualified config key name (set by define-feature)
463
- readonly value: string | number | boolean;
464
- readonly scope?: ConfigScope;
465
- readonly tenantId?: string;
466
- readonly userId?: string;
467
- };
468
-
469
- // Factory types for ergonomic seed creation in r.config({ seeds }).
470
-
471
- export type CreateSeedOptions = {
472
- readonly value: string | number | boolean;
473
- };
474
-
475
- export type CreateTenantSeedOptions = {
476
- readonly tenantId?: string;
477
- };
478
-
479
- export type CreateUserSeedOptions = {
480
- readonly tenantId: string;
481
- readonly userId: string;
482
- };
1
+ // Legacy path re-exported for callers still importing this module directly.
2
+ export type * from "@cosmicdrift/kumiko-types/config";
@@ -0,0 +1,2 @@
1
+ // Legacy path — re-exported for callers still importing this module directly.
2
+ export type * from "@cosmicdrift/kumiko-types/define-handler";
@@ -0,0 +1,2 @@
1
+ // Legacy path — re-exported for callers still importing this module directly.
2
+ export type * from "@cosmicdrift/kumiko-types/entity-handlers";