@cosmicdrift/kumiko-framework 0.1.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 (388) hide show
  1. package/README.md +159 -0
  2. package/package.json +91 -0
  3. package/src/__tests__/anonymous-access.integration.ts +325 -0
  4. package/src/__tests__/error-contract.integration.ts +435 -0
  5. package/src/__tests__/field-access.integration.ts +269 -0
  6. package/src/__tests__/full-stack.integration.ts +914 -0
  7. package/src/__tests__/ownership.integration.ts +449 -0
  8. package/src/__tests__/reference-data.integration.ts +198 -0
  9. package/src/__tests__/transition-guard.integration.ts +340 -0
  10. package/src/api/__tests__/api.test.ts +337 -0
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
  13. package/src/api/__tests__/batch.integration.ts +404 -0
  14. package/src/api/__tests__/body-limit.test.ts +88 -0
  15. package/src/api/__tests__/csrf-middleware.test.ts +97 -0
  16. package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
  17. package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
  18. package/src/api/__tests__/nested-write.integration.ts +213 -0
  19. package/src/api/__tests__/readiness.test.ts +76 -0
  20. package/src/api/__tests__/request-id-middleware.test.ts +72 -0
  21. package/src/api/__tests__/sse-broker.test.ts +58 -0
  22. package/src/api/__tests__/sse-route.test.ts +112 -0
  23. package/src/api/anonymous-cookie.ts +60 -0
  24. package/src/api/api-constants.ts +64 -0
  25. package/src/api/auth-middleware.ts +418 -0
  26. package/src/api/auth-routes.ts +982 -0
  27. package/src/api/csrf-middleware.ts +77 -0
  28. package/src/api/index.ts +31 -0
  29. package/src/api/jwt.ts +66 -0
  30. package/src/api/observability-middleware.ts +89 -0
  31. package/src/api/readiness.ts +132 -0
  32. package/src/api/request-context.ts +49 -0
  33. package/src/api/request-id-middleware.ts +50 -0
  34. package/src/api/route-registrars.ts +195 -0
  35. package/src/api/routes.ts +135 -0
  36. package/src/api/server.ts +640 -0
  37. package/src/api/sse-broker.ts +71 -0
  38. package/src/api/sse-route.ts +62 -0
  39. package/src/api/tokens.ts +16 -0
  40. package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
  41. package/src/db/__tests__/compound-types.test.ts +114 -0
  42. package/src/db/__tests__/connection-options.test.ts +68 -0
  43. package/src/db/__tests__/cursor.test.ts +41 -0
  44. package/src/db/__tests__/db-helpers.test.ts +369 -0
  45. package/src/db/__tests__/dialect-instant.test.ts +50 -0
  46. package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
  47. package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
  48. package/src/db/__tests__/encryption.test.ts +39 -0
  49. package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
  50. package/src/db/__tests__/event-store-executor.integration.ts +235 -0
  51. package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
  52. package/src/db/__tests__/located-timestamp.test.ts +184 -0
  53. package/src/db/__tests__/money.test.ts +199 -0
  54. package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
  55. package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
  56. package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
  57. package/src/db/__tests__/row-helpers.test.ts +59 -0
  58. package/src/db/__tests__/schema-migration.integration.ts +273 -0
  59. package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
  60. package/src/db/__tests__/table-builder-required.test.ts +216 -0
  61. package/src/db/__tests__/tenant-db.integration.ts +606 -0
  62. package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
  63. package/src/db/apply-entity-event.ts +188 -0
  64. package/src/db/assert-exists-in.ts +59 -0
  65. package/src/db/compound-types.ts +47 -0
  66. package/src/db/connection.ts +104 -0
  67. package/src/db/cursor.ts +83 -0
  68. package/src/db/dialect.ts +109 -0
  69. package/src/db/eagerload.ts +174 -0
  70. package/src/db/encryption.ts +39 -0
  71. package/src/db/event-store-executor.ts +906 -0
  72. package/src/db/index.ts +55 -0
  73. package/src/db/located-timestamp.ts +114 -0
  74. package/src/db/money.ts +120 -0
  75. package/src/db/pg-error.ts +46 -0
  76. package/src/db/reference-data.ts +77 -0
  77. package/src/db/row-helpers.ts +53 -0
  78. package/src/db/schema-inspection.ts +25 -0
  79. package/src/db/table-builder.ts +475 -0
  80. package/src/db/tenant-db.ts +434 -0
  81. package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
  82. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
  83. package/src/engine/__tests__/boot-validator.test.ts +1865 -0
  84. package/src/engine/__tests__/build-app-schema.test.ts +154 -0
  85. package/src/engine/__tests__/claim-keys.test.ts +274 -0
  86. package/src/engine/__tests__/config-helpers.test.ts +236 -0
  87. package/src/engine/__tests__/effective-features.test.ts +86 -0
  88. package/src/engine/__tests__/engine.test.ts +1461 -0
  89. package/src/engine/__tests__/entity-handlers.test.ts +274 -0
  90. package/src/engine/__tests__/event-helpers.test.ts +68 -0
  91. package/src/engine/__tests__/extends-registrar.test.ts +159 -0
  92. package/src/engine/__tests__/factories-long-text.test.ts +84 -0
  93. package/src/engine/__tests__/factories-time.test.ts +158 -0
  94. package/src/engine/__tests__/field-predicates.test.ts +48 -0
  95. package/src/engine/__tests__/hook-phases.test.ts +132 -0
  96. package/src/engine/__tests__/identifiers.test.ts +35 -0
  97. package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
  98. package/src/engine/__tests__/nav.test.ts +267 -0
  99. package/src/engine/__tests__/ownership.test.ts +421 -0
  100. package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
  101. package/src/engine/__tests__/projection-helpers.test.ts +62 -0
  102. package/src/engine/__tests__/projection.test.ts +191 -0
  103. package/src/engine/__tests__/qualified-name.test.ts +264 -0
  104. package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
  105. package/src/engine/__tests__/run-in.test.ts +38 -0
  106. package/src/engine/__tests__/schema-builder.test.ts +380 -0
  107. package/src/engine/__tests__/screen.test.ts +408 -0
  108. package/src/engine/__tests__/state-machine.test.ts +148 -0
  109. package/src/engine/__tests__/system-user.test.ts +57 -0
  110. package/src/engine/__tests__/validation-hooks.test.ts +71 -0
  111. package/src/engine/access.ts +23 -0
  112. package/src/engine/boot-validator.ts +1528 -0
  113. package/src/engine/build-app-schema.ts +125 -0
  114. package/src/engine/config-helpers.ts +115 -0
  115. package/src/engine/constants.ts +85 -0
  116. package/src/engine/create-app.ts +98 -0
  117. package/src/engine/define-feature.ts +702 -0
  118. package/src/engine/define-handler.ts +78 -0
  119. package/src/engine/define-roles.ts +19 -0
  120. package/src/engine/effective-features.ts +87 -0
  121. package/src/engine/entity-handlers.ts +364 -0
  122. package/src/engine/event-helpers.ts +73 -0
  123. package/src/engine/factories.ts +328 -0
  124. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
  125. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
  126. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
  127. package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
  128. package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
  129. package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
  130. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
  131. package/src/engine/feature-ast/extractors.ts +2562 -0
  132. package/src/engine/feature-ast/index.ts +105 -0
  133. package/src/engine/feature-ast/parse.ts +369 -0
  134. package/src/engine/feature-ast/patch.ts +525 -0
  135. package/src/engine/feature-ast/patcher.ts +518 -0
  136. package/src/engine/feature-ast/patterns.ts +434 -0
  137. package/src/engine/feature-ast/render.ts +602 -0
  138. package/src/engine/feature-ast/source-location.ts +45 -0
  139. package/src/engine/field-access.ts +120 -0
  140. package/src/engine/index.ts +254 -0
  141. package/src/engine/ownership.ts +337 -0
  142. package/src/engine/parse-ref-target.ts +22 -0
  143. package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
  144. package/src/engine/pattern-library/index.ts +24 -0
  145. package/src/engine/pattern-library/library.ts +1117 -0
  146. package/src/engine/pattern-library/types.ts +255 -0
  147. package/src/engine/projection-helpers.ts +85 -0
  148. package/src/engine/qualified-name.ts +122 -0
  149. package/src/engine/read-claim.ts +31 -0
  150. package/src/engine/registry.ts +1325 -0
  151. package/src/engine/resolve-config-or-param.ts +153 -0
  152. package/src/engine/run-in.ts +29 -0
  153. package/src/engine/schema-builder.ts +175 -0
  154. package/src/engine/screen-filter-ops.ts +51 -0
  155. package/src/engine/state-machine.ts +70 -0
  156. package/src/engine/system-user.ts +32 -0
  157. package/src/engine/types/config.ts +306 -0
  158. package/src/engine/types/event-type-map.ts +37 -0
  159. package/src/engine/types/feature.ts +574 -0
  160. package/src/engine/types/fields.ts +422 -0
  161. package/src/engine/types/handlers.ts +742 -0
  162. package/src/engine/types/hooks.ts +142 -0
  163. package/src/engine/types/http-route.ts +54 -0
  164. package/src/engine/types/identifiers.ts +47 -0
  165. package/src/engine/types/index.ts +208 -0
  166. package/src/engine/types/nav.ts +46 -0
  167. package/src/engine/types/projection.ts +132 -0
  168. package/src/engine/types/relations.ts +51 -0
  169. package/src/engine/types/screen.ts +452 -0
  170. package/src/engine/types/workspace.ts +42 -0
  171. package/src/engine/validation.ts +33 -0
  172. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
  173. package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
  174. package/src/entrypoint/index.ts +442 -0
  175. package/src/errors/__tests__/classes.test.ts +371 -0
  176. package/src/errors/__tests__/write-failures.test.ts +109 -0
  177. package/src/errors/classes.ts +249 -0
  178. package/src/errors/i18n/de.yaml +83 -0
  179. package/src/errors/i18n/en.yaml +80 -0
  180. package/src/errors/index.ts +41 -0
  181. package/src/errors/kumiko-error.ts +67 -0
  182. package/src/errors/reasons.ts +36 -0
  183. package/src/errors/serialize.ts +136 -0
  184. package/src/errors/transition-details.ts +30 -0
  185. package/src/errors/write-error-info.ts +123 -0
  186. package/src/errors/zod-bridge.ts +49 -0
  187. package/src/event-store/__tests__/admin-api.integration.ts +361 -0
  188. package/src/event-store/__tests__/event-store.integration.ts +584 -0
  189. package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
  190. package/src/event-store/__tests__/perf.integration.ts +255 -0
  191. package/src/event-store/__tests__/snapshot.integration.ts +267 -0
  192. package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
  193. package/src/event-store/__tests__/upcaster.integration.ts +460 -0
  194. package/src/event-store/admin-api.ts +257 -0
  195. package/src/event-store/archive.ts +106 -0
  196. package/src/event-store/errors.ts +35 -0
  197. package/src/event-store/event-store.ts +405 -0
  198. package/src/event-store/events-schema.ts +90 -0
  199. package/src/event-store/index.ts +50 -0
  200. package/src/event-store/snapshot.ts +210 -0
  201. package/src/event-store/upcaster-dead-letter.ts +119 -0
  202. package/src/event-store/upcaster.ts +147 -0
  203. package/src/files/__tests__/content-disposition.test.ts +123 -0
  204. package/src/files/__tests__/file-field-column.integration.ts +103 -0
  205. package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
  206. package/src/files/__tests__/file-handle.test.ts +122 -0
  207. package/src/files/__tests__/files.integration.ts +830 -0
  208. package/src/files/__tests__/storage-tracking.integration.ts +153 -0
  209. package/src/files/content-disposition.ts +55 -0
  210. package/src/files/file-handle.ts +63 -0
  211. package/src/files/file-ref-table.ts +22 -0
  212. package/src/files/file-routes.ts +353 -0
  213. package/src/files/in-memory-provider.ts +62 -0
  214. package/src/files/index.ts +29 -0
  215. package/src/files/local-provider.ts +35 -0
  216. package/src/files/storage-tracking.ts +60 -0
  217. package/src/files/types.ts +118 -0
  218. package/src/i18n/__tests__/i18n.test.ts +72 -0
  219. package/src/i18n/index.ts +29 -0
  220. package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
  221. package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
  222. package/src/jobs/__tests__/jobs.integration.ts +566 -0
  223. package/src/jobs/index.ts +2 -0
  224. package/src/jobs/job-runner.ts +574 -0
  225. package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
  226. package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
  227. package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
  228. package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
  229. package/src/lifecycle/index.ts +13 -0
  230. package/src/lifecycle/lifecycle.ts +160 -0
  231. package/src/lifecycle/signal-handlers.ts +62 -0
  232. package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
  233. package/src/logging/index.ts +3 -0
  234. package/src/logging/pino-logger.ts +64 -0
  235. package/src/logging/types.ts +7 -0
  236. package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
  237. package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
  238. package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
  239. package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
  240. package/src/migrations/index.ts +28 -0
  241. package/src/migrations/projection-detection.ts +149 -0
  242. package/src/migrations/rebuild-marker.ts +64 -0
  243. package/src/migrations/schema-drift.ts +395 -0
  244. package/src/observability/__tests__/console-provider.test.ts +67 -0
  245. package/src/observability/__tests__/metric-validator.test.ts +87 -0
  246. package/src/observability/__tests__/noop-provider.test.ts +82 -0
  247. package/src/observability/__tests__/observability.integration.ts +559 -0
  248. package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
  249. package/src/observability/__tests__/recording-meter.test.ts +101 -0
  250. package/src/observability/__tests__/recording-tracer.test.ts +110 -0
  251. package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
  252. package/src/observability/console-provider.ts +130 -0
  253. package/src/observability/context.ts +26 -0
  254. package/src/observability/fallback.ts +34 -0
  255. package/src/observability/ids.ts +25 -0
  256. package/src/observability/index.ts +79 -0
  257. package/src/observability/metric-validator.ts +86 -0
  258. package/src/observability/metrics-handle.ts +56 -0
  259. package/src/observability/noop-provider.ts +146 -0
  260. package/src/observability/prometheus-meter.ts +284 -0
  261. package/src/observability/recording-meter.ts +156 -0
  262. package/src/observability/recording-tracer.ts +198 -0
  263. package/src/observability/redis-wrapper.ts +132 -0
  264. package/src/observability/sensitive-filter.ts +108 -0
  265. package/src/observability/standard-metrics.ts +213 -0
  266. package/src/observability/types/index.ts +29 -0
  267. package/src/observability/types/metric.ts +56 -0
  268. package/src/observability/types/provider.ts +32 -0
  269. package/src/observability/types/span.ts +64 -0
  270. package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
  271. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
  272. package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
  273. package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
  274. package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
  275. package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
  276. package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
  277. package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
  278. package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
  279. package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
  280. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
  281. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
  282. package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
  283. package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
  284. package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
  285. package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
  286. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
  287. package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
  288. package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
  289. package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
  290. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
  291. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
  292. package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
  293. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
  294. package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
  295. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
  296. package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
  297. package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
  298. package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
  299. package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
  300. package/src/pipeline/append-event-core.ts +117 -0
  301. package/src/pipeline/auth-claims-resolver.ts +103 -0
  302. package/src/pipeline/cascade-handler.ts +113 -0
  303. package/src/pipeline/dispatcher.ts +1585 -0
  304. package/src/pipeline/distributed-lock.ts +37 -0
  305. package/src/pipeline/entity-cache.ts +113 -0
  306. package/src/pipeline/event-consumer-state.ts +108 -0
  307. package/src/pipeline/event-dedup.ts +23 -0
  308. package/src/pipeline/event-dispatcher.ts +1016 -0
  309. package/src/pipeline/event-retention.ts +154 -0
  310. package/src/pipeline/idempotency.ts +76 -0
  311. package/src/pipeline/index.ts +66 -0
  312. package/src/pipeline/lifecycle-pipeline.ts +409 -0
  313. package/src/pipeline/msp-rebuild.ts +242 -0
  314. package/src/pipeline/multi-stream-apply-context.ts +115 -0
  315. package/src/pipeline/projection-rebuild.ts +334 -0
  316. package/src/pipeline/projection-state.ts +72 -0
  317. package/src/pipeline/projections-runner.ts +56 -0
  318. package/src/pipeline/redis-keys.ts +11 -0
  319. package/src/pipeline/system-hooks.ts +190 -0
  320. package/src/random/__tests__/generate.test.ts +149 -0
  321. package/src/random/generate.ts +141 -0
  322. package/src/random/index.ts +8 -0
  323. package/src/random/words.ts +392 -0
  324. package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
  325. package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
  326. package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
  327. package/src/rate-limit/bucket.ts +36 -0
  328. package/src/rate-limit/index.ts +14 -0
  329. package/src/rate-limit/middleware.ts +152 -0
  330. package/src/rate-limit/resolver.ts +267 -0
  331. package/src/redis/__tests__/redis-options.test.ts +54 -0
  332. package/src/redis/index.ts +74 -0
  333. package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
  334. package/src/search/__tests__/search-adapter.test.ts +256 -0
  335. package/src/search/in-memory-adapter.ts +123 -0
  336. package/src/search/index.ts +12 -0
  337. package/src/search/meilisearch-adapter.ts +106 -0
  338. package/src/search/types.ts +39 -0
  339. package/src/secrets/__tests__/dek-cache.test.ts +213 -0
  340. package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
  341. package/src/secrets/__tests__/envelope.test.ts +74 -0
  342. package/src/secrets/__tests__/leak-guard.test.ts +92 -0
  343. package/src/secrets/__tests__/rotation.test.ts +149 -0
  344. package/src/secrets/dek-cache.ts +116 -0
  345. package/src/secrets/env-master-key-provider.ts +162 -0
  346. package/src/secrets/envelope.ts +55 -0
  347. package/src/secrets/index.ts +19 -0
  348. package/src/secrets/leak-guard.ts +87 -0
  349. package/src/secrets/rotation.ts +34 -0
  350. package/src/secrets/types.ts +107 -0
  351. package/src/stack/db.ts +104 -0
  352. package/src/stack/event-collector.ts +23 -0
  353. package/src/stack/index.ts +32 -0
  354. package/src/stack/redis.ts +44 -0
  355. package/src/stack/request-helper.ts +168 -0
  356. package/src/stack/table-helpers.ts +104 -0
  357. package/src/stack/test-stack.ts +357 -0
  358. package/src/stack/test-users.ts +37 -0
  359. package/src/testing/__tests__/e2e-generator.test.ts +230 -0
  360. package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
  361. package/src/testing/access-assertions.ts +15 -0
  362. package/src/testing/assertions.ts +35 -0
  363. package/src/testing/e2e-generator.ts +465 -0
  364. package/src/testing/expect-error.ts +25 -0
  365. package/src/testing/handler-context.ts +125 -0
  366. package/src/testing/http-cookies.ts +52 -0
  367. package/src/testing/index.ts +41 -0
  368. package/src/testing/late-bound.ts +39 -0
  369. package/src/testing/mutable-master-key-provider.ts +31 -0
  370. package/src/testing/observability-recorder.ts +54 -0
  371. package/src/testing/shared-entities.ts +49 -0
  372. package/src/testing/utils.ts +1 -0
  373. package/src/testing/wait-for.ts +31 -0
  374. package/src/time/__tests__/polyfill.test.ts +73 -0
  375. package/src/time/__tests__/tz-context.test.ts +121 -0
  376. package/src/time/index.ts +21 -0
  377. package/src/time/polyfill.ts +70 -0
  378. package/src/time/tz-context.ts +107 -0
  379. package/src/ui-types/app-schema.ts +57 -0
  380. package/src/ui-types/index.ts +65 -0
  381. package/src/utils/__tests__/assert.test.ts +17 -0
  382. package/src/utils/__tests__/env-parse.test.ts +54 -0
  383. package/src/utils/assert.ts +18 -0
  384. package/src/utils/env-parse.ts +16 -0
  385. package/src/utils/ids.ts +16 -0
  386. package/src/utils/index.ts +5 -0
  387. package/src/utils/safe-json.ts +30 -0
  388. package/src/utils/serialization.ts +7 -0
@@ -0,0 +1,142 @@
1
+ import type { StoredEvent } from "../../event-store/event-store";
2
+ import type { AppContext } from "./handlers";
3
+ import type { EntityId } from "./identifiers";
4
+
5
+ // --- Validation ---
6
+
7
+ export type ValidationError = {
8
+ readonly field: string;
9
+ readonly error: string;
10
+ };
11
+
12
+ export type ValidationHookFn = (
13
+ data: Readonly<Record<string, unknown>>,
14
+ ) => readonly ValidationError[] | null;
15
+
16
+ // --- Save/Delete Context (what hooks receive) ---
17
+
18
+ export type SaveContext = {
19
+ readonly kind: "save";
20
+ readonly id: EntityId;
21
+ readonly data: Readonly<Record<string, unknown>>;
22
+ readonly changes: Readonly<Record<string, unknown>>;
23
+ readonly previous: Readonly<Record<string, unknown>>;
24
+ readonly isNew: boolean;
25
+ readonly entityName?: string | undefined;
26
+ // The event that produced this save. Populated by the event-store-executor;
27
+ // the pipeline uses it to drive projections inside the same transaction.
28
+ // Optional because hand-crafted SaveContexts (tests, custom executors) may
29
+ // not have an event — projections just skip in that case.
30
+ readonly event?: StoredEvent | undefined;
31
+ };
32
+
33
+ export type DeleteContext = {
34
+ readonly kind: "delete";
35
+ readonly id: EntityId;
36
+ readonly data: Readonly<Record<string, unknown>>;
37
+ readonly entityName?: string | undefined;
38
+ // See SaveContext.event — same semantics.
39
+ readonly event?: StoredEvent | undefined;
40
+ };
41
+
42
+ export type LifecycleResult = SaveContext | DeleteContext;
43
+
44
+ // --- Lifecycle Hooks ---
45
+
46
+ export type PreSaveHookFn = (
47
+ changes: Record<string, unknown>,
48
+ context: AppContext & {
49
+ readonly previous: Readonly<Record<string, unknown>>;
50
+ readonly isNew: boolean;
51
+ },
52
+ ) => Promise<Record<string, unknown>>;
53
+
54
+ export type PostSaveHookFn = (result: SaveContext, context: AppContext) => Promise<void>;
55
+
56
+ // Batch-variant: called once at the end of a dispatcher batch with every
57
+ // successful SaveContext. The per-save PostSaveHookFn still fires for
58
+ // side-effects that need per-entity semantics (SSE); PostSaveBatch exists
59
+ // for adapters that can amortise work across the whole batch (e.g. search
60
+ // index batch-writes, bulk webhook fanout).
61
+ export type PostSaveBatchHookFn = (
62
+ results: readonly SaveContext[],
63
+ context: AppContext,
64
+ ) => Promise<void>;
65
+
66
+ export type PreDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
67
+
68
+ export type PostDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
69
+
70
+ export type PostDeleteBatchHookFn = (
71
+ payloads: readonly DeleteContext[],
72
+ context: AppContext,
73
+ ) => Promise<void>;
74
+
75
+ export type PreQueryHookFn = (
76
+ payload: Record<string, unknown>,
77
+ context: AppContext,
78
+ ) => Promise<Record<string, unknown>>;
79
+
80
+ export type LifecycleHookFn =
81
+ | PreSaveHookFn
82
+ | PostSaveHookFn
83
+ | PreDeleteHookFn
84
+ | PostDeleteHookFn
85
+ | PreQueryHookFn;
86
+
87
+ // --- Hook Phases ---
88
+ //
89
+ // inTransaction: Hook runs inside the DB transaction. Failures roll back
90
+ // the entire write. Use for: DB-based side-effects (counter updates,
91
+ // dependent entity writes).
92
+ //
93
+ // afterCommit (default): Hook runs after the transaction commits. Failures
94
+ // are logged but don't affect the write. Use for: external systems
95
+ // (SSE broadcast, search index, email, webhooks).
96
+
97
+ export const HookPhases = {
98
+ inTransaction: "inTransaction",
99
+ afterCommit: "afterCommit",
100
+ } as const;
101
+
102
+ export type HookPhase = (typeof HookPhases)[keyof typeof HookPhases];
103
+
104
+ // Owner-tag shared across every hook structure. The lifecycle pipeline uses
105
+ // it to skip hooks whose owning feature is globally disabled:
106
+ // - A concrete feature name like "orders" → subject to the feature-toggle
107
+ // filter (skipped when "orders" is disabled).
108
+ // - "*" (star) → invariant plumbing, never filtered. Reserved for
109
+ // extension-provided hooks and framework-internal hooks that belong to
110
+ // the pipeline itself, not a feature.
111
+ // - Omitted (undefined) → treated as "*". Supports tests that hand-build
112
+ // HookMap objects without caring about ownership.
113
+ export type HookOwner = { readonly featureName?: string };
114
+
115
+ export type PhasedHook<TFn> = {
116
+ readonly fn: TFn;
117
+ readonly phase: HookPhase;
118
+ } & HookOwner;
119
+
120
+ // Flat (non-phased) hook — preSave, preQuery. Same owner contract, no
121
+ // phase semantics because these hooks run exactly once per handler pass
122
+ // before/around the DB transaction.
123
+ export type OwnedFn<TFn> = {
124
+ readonly fn: TFn;
125
+ } & HookOwner;
126
+
127
+ // --- Hook Maps ---
128
+
129
+ export type HookMap = {
130
+ readonly validation: Readonly<Record<string, ValidationHookFn>>;
131
+ readonly preSave: Readonly<Record<string, readonly OwnedFn<PreSaveHookFn>[]>>;
132
+ readonly postSave: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
133
+ readonly preDelete: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
134
+ readonly postDelete: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
135
+ readonly preQuery: Readonly<Record<string, readonly OwnedFn<PreQueryHookFn>[]>>;
136
+ };
137
+
138
+ export type EntityHookMap = {
139
+ readonly postSave: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
140
+ readonly preDelete: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
141
+ readonly postDelete: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
142
+ };
@@ -0,0 +1,54 @@
1
+ // HTTP-Route-Definition — feature-deklarierte HTTP-Endpoints außerhalb
2
+ // der /api/write|query|batch-Pipeline. Use-Case: RSS/Atom-Feeds, OpenAPI-
3
+ // Specs, OG-Image-Generators, Webhook-Receiver — alles wo der Feature-
4
+ // Author das Wire-Format selbst kontrolliert.
5
+ //
6
+ // Pattern symmetrisch zu r.queryHandler / r.writeHandler: Definition als
7
+ // Teil des Features (nicht des App-Bootstrapping). Phase-3 Multi-Tenant
8
+ // wird trivial weil tenant-context via host-resolution greift.
9
+ //
10
+ // Escape-hatch bleibt: runProdApp.extraRoutes für hand-rolled Routes die
11
+ // nichts mit einem Feature zu tun haben (z.B. plattform-spezifische
12
+ // Static-Serving-Logic).
13
+
14
+ import type { Context } from "hono";
15
+
16
+ /** Subset von HTTP-Methoden den wir aktiv unterstützen. Hono spricht
17
+ * alle, aber das hier sind die einzigen die ein Feature-Author
18
+ * realistisch deklariert. */
19
+ export type HttpRouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
20
+
21
+ /** Dependencies die der Handler vom Framework bekommt. App-Author kann
22
+ * die App selbst aufrufen (`deps.app.fetch(...)` für intern-call) oder
23
+ * direkt per dispatcher Daten ziehen. Db/Redis sind die rohen Connections
24
+ * — wer Tenant-Scope braucht muss durch dispatcher.query gehen.
25
+ *
26
+ * Hono-typing: `Context<any, any>` weil das Hono-Type-Param-Setup nur
27
+ * intern relevant ist. Concrete Hono-app wird im Boot-Path zugewiesen. */
28
+ export type HttpRouteHandlerDeps = {
29
+ /** Die Hono-app — Handler kann via app.fetch(...) interne Routes
30
+ * ansprechen (z.B. /api/query mit der vollen Auth-/Anonymous-Chain). */
31
+ // biome-ignore lint/suspicious/noExplicitAny: Hono's generic-Param ist im Framework-Boundary unsichtbar
32
+ readonly app: import("hono").Hono<any, any>;
33
+ };
34
+
35
+ export type HttpRouteHandler = (
36
+ // biome-ignore lint/suspicious/noExplicitAny: Hono Context-Generics sind im Framework-Boundary unsichtbar
37
+ c: Context<any, any>,
38
+ deps: HttpRouteHandlerDeps,
39
+ ) => Response | Promise<Response>;
40
+
41
+ export type HttpRouteDefinition = {
42
+ /** HTTP-Methode — bei Hono-Mount via app.{get,post,...}(path). */
43
+ readonly method: HttpRouteMethod;
44
+ /** URL-Pfad (Hono-Pattern, z.B. "/feed.xml" oder "/og/:tenantId.png"). */
45
+ readonly path: string;
46
+ /** Wenn true, bypasses die /api/*-Auth-Middleware. Default false —
47
+ * Routes liegen außerhalb /api/* und sehen die Auth-Middleware
48
+ * ohnehin nicht; das Flag ist semantisch (= "diese Route ist
49
+ * bewusst öffentlich") für Boot-Validator + Doku. */
50
+ readonly anonymous?: boolean;
51
+ /** Hono-Handler. Bekommt Hono-Context + Framework-Deps; returnt
52
+ * Response (sync oder async). */
53
+ readonly handler: HttpRouteHandler;
54
+ };
@@ -0,0 +1,47 @@
1
+ // Domain-identifier type aliases. Used everywhere a tenantId/userId/aggregateId
2
+ // travels through the framework. One declaration per concept so future
3
+ // representation changes (branded types, UUID validation, opaque wrappers)
4
+ // land in a single place.
5
+
6
+ // Tenant identifier — UUID string today. May become branded/opaque later
7
+ // without touching call sites.
8
+ export type TenantId = string;
9
+
10
+ // Lowercase UUID (any RFC-4122 variant). Strict enough to keep client-
11
+ // supplied junk (e.g. SQL fragments, path-traversal probes) out of the
12
+ // pipeline; loose enough that v4 / v7 / nil all match. Any caller that
13
+ // already holds a TenantId from a trusted source (JWT payload, server
14
+ // config) skips this — the helper is for **untrusted input** crossing
15
+ // the system boundary.
16
+ const TENANT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
17
+
18
+ // Validates a candidate string against the tenantId format and returns it
19
+ // as a TenantId, or `null` when it doesn't match. Use at every system
20
+ // boundary that admits untrusted input (HTTP headers, cookies, query
21
+ // params). Returning null instead of throwing keeps the caller in charge
22
+ // of the rejection shape — middleware returns 400, batch jobs may filter
23
+ // + log, and unit tests don't need a try/catch.
24
+ export function parseTenantId(value: unknown): TenantId | null {
25
+ if (typeof value !== "string") return null;
26
+ if (!TENANT_ID_REGEX.test(value)) return null;
27
+ return value;
28
+ }
29
+
30
+ // "System-scope" tenant marker: handlers carry this tenantId when the event
31
+ // doesn't belong to any particular tenant (reference data, cross-tenant
32
+ // jobs, global config). The concrete UUID is a valid v4 (not all-zeroes —
33
+ // Postgres' UUID type rejects invalid variants), chosen to be easy to
34
+ // eyeball in logs. Central constant so call sites don't re-type the string
35
+ // and the isSystemTenant() check stays in sync.
36
+ export const SYSTEM_TENANT_ID: TenantId = "00000000-0000-4000-8000-000000000000";
37
+
38
+ export function isSystemTenant(tenantId: TenantId | null | undefined): boolean {
39
+ return !tenantId || tenantId === SYSTEM_TENANT_ID;
40
+ }
41
+
42
+ // Primary-key identifier for any entity row. Two shapes coexist because of
43
+ // the entity-def `idType` switch: classic CRUD entities keep `serial` (number),
44
+ // while tenant + ES aggregates run on `uuid` (string). Call sites that pass
45
+ // the id through to the DB layer stay agnostic; only code that formats ids
46
+ // for URLs, logs, or cache keys needs `String(id)` — JS coerces both safely.
47
+ export type EntityId = number | string;
@@ -0,0 +1,208 @@
1
+ // Barrel: re-exports all types from logical modules
2
+ // Duplicate types (OnDeleteStrategy, ConfigScope, ConcurrencyMode, LifecycleHookType)
3
+ // are defined ONLY in constants.ts — re-exported here for backwards compatibility.
4
+
5
+ // Re-export types that were duplicated in types.ts but are canonical in constants.ts
6
+ export type {
7
+ ConcurrencyMode,
8
+ ConfigScope,
9
+ LifecycleHookType,
10
+ OnDeleteStrategy,
11
+ } from "../constants";
12
+ export type {
13
+ ConfigAccessor,
14
+ ConfigAccessorFactory,
15
+ ConfigBounds,
16
+ ConfigComputedContext,
17
+ ConfigComputedFn,
18
+ ConfigDefinition,
19
+ ConfigKeyAccess,
20
+ ConfigKeyDefinition,
21
+ ConfigKeyHandle,
22
+ ConfigKeyType,
23
+ ConfigResolver,
24
+ ConfigStoredRow,
25
+ ConfigValue,
26
+ ConfigValueSource,
27
+ ConfigValueWithSource,
28
+ JobDefinition,
29
+ JobHandlerFn,
30
+ JobRunIn,
31
+ JobTrigger,
32
+ NotificationDataFn,
33
+ NotificationDefinition,
34
+ NotificationRecipientFn,
35
+ NotificationTemplateFn,
36
+ ReferenceDataDef,
37
+ RegistrarExtensionDef,
38
+ RegistrarExtensionHooks,
39
+ RegistrarExtensionRegistration,
40
+ RunIn,
41
+ TranslationEntry,
42
+ TranslationKeys,
43
+ TranslationsDef,
44
+ UiExtensionDef,
45
+ } from "./config";
46
+ // Cross-Feature Compile-Time-Type-Map — features extend per declare-module.
47
+ export type {
48
+ KumikoEntityTypeMap,
49
+ KumikoEventTypeMap,
50
+ KumikoHandlerPayloadMap,
51
+ KumikoHandlerResultMap,
52
+ } from "./event-type-map";
53
+ export type {
54
+ FeatureDefinition,
55
+ FeatureMetricDef,
56
+ FeatureMetricType,
57
+ FeatureRegistrar,
58
+ MetricOptions,
59
+ Registry,
60
+ SecretKeyDefinition,
61
+ SecretKeyHandle,
62
+ SecretOptions,
63
+ } from "./feature";
64
+ export type {
65
+ AnyFileFieldDef,
66
+ BooleanFieldDef,
67
+ DateFieldDef,
68
+ DefaultCurrency,
69
+ EmbeddedFieldDef,
70
+ EmbeddedSubFieldDef,
71
+ EntityDefinition,
72
+ EntityIndexDef,
73
+ FieldAccess,
74
+ FieldDefinition,
75
+ FieldsMap,
76
+ FileFieldDef,
77
+ FilesFieldDef,
78
+ ImageFieldDef,
79
+ ImagesFieldDef,
80
+ LocatedTimestampFieldDef,
81
+ LongTextFieldDef,
82
+ MoneyFieldDef,
83
+ MultiSelectFieldDef,
84
+ NumberFieldDef,
85
+ ReferenceFieldDef,
86
+ SelectFieldDef,
87
+ TextFieldDef,
88
+ TimestampFieldDef,
89
+ TransitionMap,
90
+ TzFieldDef,
91
+ } from "./fields";
92
+ export { DEFAULT_CURRENCIES, isFileField } from "./fields";
93
+ export type {
94
+ AccessRule,
95
+ AggregateStreamHandle,
96
+ AppContext,
97
+ AppendEventArgs,
98
+ AppendEventFn,
99
+ AppendEventUnsafeFn,
100
+ AuthClaimsContext,
101
+ AuthClaimsFn,
102
+ AuthClaimsHookDef,
103
+ CamelToKebab,
104
+ ClaimKeyDefinition,
105
+ ClaimKeyHandle,
106
+ ClaimKeyJsType,
107
+ ClaimKeyType,
108
+ EntityRef,
109
+ EventDef,
110
+ EventMigrationDef,
111
+ EventUpcastCtx,
112
+ EventUpcastFn,
113
+ FetchForWritingArgs,
114
+ HandlerContext,
115
+ HandlerRef,
116
+ JobContext,
117
+ JobRunnerRef,
118
+ NameOrRef,
119
+ NotifyFactory,
120
+ NotifyFn,
121
+ NotifyOptions,
122
+ NotifyPriority,
123
+ QualifiedEventName,
124
+ QueryEvent,
125
+ QueryHandlerDef,
126
+ QueryHandlerFn,
127
+ RateLimitOption,
128
+ RateLimitPer,
129
+ SessionUser,
130
+ WriteEvent,
131
+ WriteHandlerDef,
132
+ WriteHandlerFn,
133
+ WriteResult,
134
+ } from "./handlers";
135
+ export { resolveName, withResponseData } from "./handlers";
136
+ export type {
137
+ DeleteContext,
138
+ EntityHookMap,
139
+ HookMap,
140
+ HookOwner,
141
+ HookPhase,
142
+ LifecycleHookFn,
143
+ LifecycleResult,
144
+ OwnedFn,
145
+ PhasedHook,
146
+ PostDeleteBatchHookFn,
147
+ PostDeleteHookFn,
148
+ PostSaveBatchHookFn,
149
+ PostSaveHookFn,
150
+ PreDeleteHookFn,
151
+ PreQueryHookFn,
152
+ PreSaveHookFn,
153
+ SaveContext,
154
+ ValidationError,
155
+ ValidationHookFn,
156
+ } from "./hooks";
157
+ export { HookPhases } from "./hooks";
158
+ export type {
159
+ HttpRouteDefinition,
160
+ HttpRouteHandler,
161
+ HttpRouteHandlerDeps,
162
+ HttpRouteMethod,
163
+ } from "./http-route";
164
+ // Domain-identifier type aliases — see identifiers.ts for rationale.
165
+ export type { EntityId, TenantId } from "./identifiers";
166
+ export { isSystemTenant, SYSTEM_TENANT_ID } from "./identifiers";
167
+ export type { NavDefinition } from "./nav";
168
+ export type {
169
+ MspErrorMode,
170
+ MspErrorPolicy,
171
+ MultiStreamApplyFn,
172
+ MultiStreamProjectionDefinition,
173
+ ProjectionDefinition,
174
+ ProjectionTable,
175
+ SingleStreamApplyFn,
176
+ } from "./projection";
177
+ export type {
178
+ BelongsToRelation,
179
+ EntityRelations,
180
+ HasManyRelation,
181
+ ManyToManyRelation,
182
+ RelationDefinition,
183
+ } from "./relations";
184
+ export type {
185
+ ActionFormScreenDefinition,
186
+ ConfigEditScreenDefinition,
187
+ CustomScreenDefinition,
188
+ CustomScreenRoute,
189
+ EditFieldSpec,
190
+ EditLayout,
191
+ EditSectionSpec,
192
+ EntityEditScreenDefinition,
193
+ EntityListScreenDefinition,
194
+ FieldCondition,
195
+ FieldRenderer,
196
+ ListColumnSpec,
197
+ PlatformComponent,
198
+ RowAction,
199
+ RowActionNavigate,
200
+ RowActionWriteHandler,
201
+ ScreenDefinition,
202
+ ScreenFilter,
203
+ ScreenFilterOp,
204
+ ScreenSlots,
205
+ ToolbarAction,
206
+ } from "./screen";
207
+ export { normalizeEditField, normalizeListColumn } from "./screen";
208
+ export type { WorkspaceDefinition } from "./workspace";
@@ -0,0 +1,46 @@
1
+ import type { AccessRule } from "./handlers";
2
+
3
+ // Nav entry declaration. Every feature that wants to appear in the app's
4
+ // navigation tree registers one or more entries via r.nav(). The engine
5
+ // keeps the list flat — ui-core's resolveNavigation assembles the parent/
6
+ // child tree at render time, so changes (toggles, access-gating) don't
7
+ // require re-indexing a tree shape server-side.
8
+ //
9
+ // Cross-feature references are allowed: `screen` may point at any
10
+ // registered screen QN, `parent` at any registered nav QN. The boot
11
+ // validator checks both references exist + rejects parent cycles.
12
+ export type NavDefinition = {
13
+ // Feature author writes the feature-local short id ("catalog"); the
14
+ // registry overwrites `id` with the qualified name ("shop:nav:catalog")
15
+ // in its stored copy. Callers of `registry.getNav(qn)` /
16
+ // `getTopLevelNavs()` / `getNavsByParent(...)` always see the qualified
17
+ // id — no parallel reverse index needed. `feature.navs[shortId]` on the
18
+ // unregistered FeatureDefinition keeps the short form.
19
+ readonly id: string;
20
+ // i18n translation key. Resolved at render time by the renderer's
21
+ // useTranslation hook; engine keeps it opaque.
22
+ readonly label: string;
23
+ // Icon key — whatever the icon registry of the active renderer understands.
24
+ // Engine doesn't validate; unknown icons surface as a missing icon on screen,
25
+ // not a boot failure.
26
+ readonly icon?: string;
27
+ // Qualified name of a parent nav entry ("<feature>:nav:<id>"). Omit for
28
+ // top-level entries. Boot-validator rejects cycles + dangling refs.
29
+ readonly parent?: string;
30
+ // Sort weight within the parent's children (lower = earlier). Ties are
31
+ // broken by registration order — features registered later appear lower.
32
+ readonly order?: number;
33
+ // Qualified name of the screen this entry navigates to
34
+ // ("<feature>:screen:<id>"). Omit for pure grouping entries (a parent-only
35
+ // nav node that renders a sub-tree but has no target screen itself).
36
+ readonly screen?: string;
37
+ // Role / openToAll gate. The nav resolver hides entries the user can't
38
+ // reach; leave unset to always show (engine stays un-opinionated about
39
+ // who sees what — apps that need default-deny can set { roles: [] }).
40
+ readonly access?: AccessRule;
41
+ // Workspace QNs this entry self-assigns to. Merged at boot with any
42
+ // r.workspace({ nav: [...] }) explicit lists. Omit to leave workspace
43
+ // membership decided solely by the workspace's nav list (or both empty
44
+ // → entry belongs to no workspace).
45
+ readonly workspaces?: readonly string[];
46
+ };
@@ -0,0 +1,132 @@
1
+ import type { DbRunner } from "../../db/connection";
2
+ import type { TableColumns } from "../../db/dialect";
3
+ import type { StoredEvent } from "../../event-store/event-store";
4
+ import type { MultiStreamApplyContext } from "../../pipeline/multi-stream-apply-context";
5
+ import type { RunIn } from "./config";
6
+
7
+ // Drizzle pgTable shape — projections hand their table through to apply() so
8
+ // user code writes upserts/updates directly instead of going through a
9
+ // framework-managed state reducer. Using Drizzle's own `PgTableWithColumns<any>`
10
+ // (re-exported as TableColumns) keeps typing honest: drizzle's typed paths work
11
+ // inside apply(), but the column union is erased so framework code doesn't need
12
+ // to know the schema shape of every user table.
13
+ // biome-ignore lint/suspicious/noExplicitAny: Drizzle's PgTable generic needs a concrete row shape; we erase it on purpose because the framework does not know user-defined column types.
14
+ export type ProjectionTable = TableColumns<any>;
15
+
16
+ // Single-stream projection apply: runs inline in the write-TX of the event
17
+ // it projects. Gets the event + TX-scoped DbRunner — that's it. Inline
18
+ // projections must not spawn further events (no ctx) because they run
19
+ // inside the command's transaction and the framework guarantees a single
20
+ // commit boundary per command.
21
+ //
22
+ // Generic über payload-shape. Default = Record<string, unknown> behält
23
+ // rückwärtskompatibles Verhalten; Konkrete Apply-Handler annotieren
24
+ // `SingleStreamApplyFn<MyPayload>` für typed event.payload-Access.
25
+ export type SingleStreamApplyFn<TPayload = Record<string, unknown>> = (
26
+ event: StoredEvent<TPayload>,
27
+ tx: DbRunner,
28
+ ) => Promise<void>;
29
+
30
+ // Multi-stream projection apply: runs asynchronously via the event-dispatcher
31
+ // with its own cursor. Gets the event, tx, and a ctx surface for emitting
32
+ // follow-up events (saga / process-manager pattern). ctx.appendEvent +
33
+ // ctx.loadAggregate are the Marten-equivalent of IProjectionSession — write
34
+ // cross-aggregate reactions here, not in single-stream projections.
35
+ export type MultiStreamApplyFn<TPayload = Record<string, unknown>> = (
36
+ event: StoredEvent<TPayload>,
37
+ tx: DbRunner,
38
+ ctx: MultiStreamApplyContext,
39
+ ) => Promise<void>;
40
+
41
+ export type ProjectionDefinition = {
42
+ readonly name: string;
43
+ // One or more entity names whose events feed this projection. Event-types
44
+ // are matched in `apply` (e.g. "unit.created") — `source` is only used to
45
+ // index projections so the executor doesn't scan all projections on every
46
+ // write.
47
+ readonly source: string | readonly string[];
48
+ // Drizzle-table the projection materializes into. User owns the schema —
49
+ // framework just guarantees the TX and event delivery.
50
+ readonly table: ProjectionTable;
51
+ // Keyed by fully-qualified event type ("<aggregate>.<verb>", e.g. "unit.created").
52
+ // Missing keys are silently skipped — a projection declares only the events it
53
+ // cares about.
54
+ readonly apply: Readonly<Record<string, SingleStreamApplyFn>>;
55
+ // Auto-registered projection (one per r.entity) that exists ONLY to
56
+ // make rebuildProjection work for entity-tables. Live writes go through
57
+ // the EventStoreExecutor directly — firing the implicit apply inline
58
+ // would double-write into the same table. The inline-projection-runner
59
+ // skips entries with this flag; rebuildProjection treats them
60
+ // identically to explicit projections.
61
+ readonly isImplicit?: boolean;
62
+ };
63
+
64
+ // Per-lifecycle error policy for a MultiStreamProjection. Mirrors Marten's
65
+ // Projections.Errors / Projections.RebuildErrors split — a projection can
66
+ // be lenient during steady-state delivery but strict during rebuild (or
67
+ // vice versa).
68
+ export type MspErrorPolicy = {
69
+ // When the apply handler throws: log the error, advance the cursor past
70
+ // the offending event, and keep delivering. Default false — current
71
+ // strict behaviour: retry up to maxAttempts, then mark the consumer
72
+ // status="dead" and pause delivery. Use for best-effort sinks
73
+ // (notifications, webhooks) where a single bad event should not stall
74
+ // the whole consumer.
75
+ readonly skipApplyErrors?: boolean;
76
+ };
77
+
78
+ export type MspErrorMode = {
79
+ // Applied during steady-state dispatcher delivery.
80
+ readonly continuous?: MspErrorPolicy;
81
+ // Applied during rebuildProjection() / backfill passes. When omitted,
82
+ // rebuild inherits continuous — explicit override common for "strict
83
+ // during rebuild, lenient in production" patterns.
84
+ readonly rebuild?: MspErrorPolicy;
85
+ };
86
+
87
+ // Marten-style MultiStreamProjection: aggregates events from many streams
88
+ // into one cross-cutting read-model. Unlike ProjectionDefinition (single-
89
+ // source, inline in the write-TX), an MSP is ASYNC — the event-dispatcher
90
+ // picks events off the log via its own cursor. Handlers MUST be idempotent
91
+ // because the dispatcher guarantees at-least-once delivery.
92
+ //
93
+ // Use for Sagas / process managers, customer-centric views that span
94
+ // multiple aggregate types, cross-feature aggregations, audit logs. With
95
+ // `table` omitted, the MSP becomes a pure side-effect consumer — sending
96
+ // notifications, posting webhooks, updating an external system. Marten's
97
+ // equivalent of a subscription / event listener, without a separate API.
98
+ export type MultiStreamProjectionDefinition = {
99
+ readonly name: string;
100
+ // Optional: omit for side-effect-only handlers (notifications, external
101
+ // system sync). When present, setupTestStack auto-pushes the table.
102
+ readonly table?: ProjectionTable;
103
+ // Keyed by fully-qualified event type. Unlike a single-stream projection,
104
+ // there is no source-entity hint — the MSP declares the event types it
105
+ // cares about directly. Extract the identity/grouping key inside the
106
+ // apply handler from the event payload.
107
+ readonly apply: Readonly<Record<string, MultiStreamApplyFn>>;
108
+ // How the dispatcher handles apply-throws. Default strict (retry + dead).
109
+ readonly errorMode?: MspErrorMode;
110
+ // Which deploy-lane runs this MSP's dispatcher. Default "worker". MSPs
111
+ // share a single consumer-row per MSP name with SKIP LOCKED, so "both"
112
+ // is safe semantically (API + Worker race for each event; exactly one
113
+ // wins). Use "api" for MSPs that need in-process state on the API
114
+ // (rare); use "both" only when genuinely load-balancing is helpful.
115
+ readonly runIn?: RunIn;
116
+ // Delivery semantics across multi-instance deploys:
117
+ // "shared" (default) — one cursor across all dispatcher instances,
118
+ // SKIP LOCKED serialises; each event delivered exactly
119
+ // once globally. The right choice for side-effects with
120
+ // any downstream state: notifications, external APIs,
121
+ // projection tables, audit rows.
122
+ // "per-instance" — one cursor PER dispatcher instance, so every process
123
+ // delivers every event. Required for push-to-local-
124
+ // subscribers (SSE, in-memory caches): a split-deploy
125
+ // where API instance B emits an event that API instance
126
+ // A's clients also need to see. Handler MUST be
127
+ // side-effect-free relative to the DB — it only reaches
128
+ // in-process structures — otherwise each instance
129
+ // writes duplicate rows. Misuse = duplicated side
130
+ // effects, not a safety property.
131
+ readonly delivery?: "shared" | "per-instance";
132
+ };
@@ -0,0 +1,51 @@
1
+ import type { OnDeleteStrategy } from "../constants";
2
+
3
+ // --- Relations ---
4
+
5
+ export type BelongsToRelation = {
6
+ readonly type: "belongsTo";
7
+ readonly target: string;
8
+ readonly foreignKey: string;
9
+ readonly searchInclude?: readonly string[];
10
+ // onDelete is declared on the parent-side (hasMany / manyToMany) because
11
+ // that's where the "what happens to my children?" decision lives. A
12
+ // belongsTo node just points at a parent — the parent's onDelete drives
13
+ // the cleanup.
14
+ };
15
+
16
+ export type HasManyRelation = {
17
+ readonly type: "hasMany";
18
+ readonly target: string;
19
+ readonly foreignKey: string;
20
+ readonly onDelete?: OnDeleteStrategy;
21
+ // When true, a nested payload under this relation's key (e.g.
22
+ // `{ tasks: [{ ... }] }` on a `project:create` write) is auto-expanded
23
+ // into child writes: parent first, then one child-write per entry with
24
+ // the foreign key set to the parent's new id — all in the same TX.
25
+ // Opt-in (default false) so legacy hasMany relations that were declared
26
+ // purely for cascade-delete or UI-nav semantics don't silently gain a
27
+ // client-writable path. Children are never inferred from payload-shape
28
+ // alone; only relations with this flag unlock nested-write.
29
+ //
30
+ // Scope v1: depth=1, create-only, hasMany-only. Update-nested,
31
+ // delete-nested, and belongsTo/m2m auto-expansion are explicit future
32
+ // work — when they arrive, they'll take the same flag so the opt-in
33
+ // stays a single, consistent surface.
34
+ readonly nestedWrite?: boolean;
35
+ };
36
+
37
+ export type ManyToManyRelation = {
38
+ readonly type: "manyToMany";
39
+ readonly target: string;
40
+ readonly through: {
41
+ readonly table: string;
42
+ readonly sourceKey: string;
43
+ readonly targetKey: string;
44
+ };
45
+ readonly searchInclude?: readonly string[];
46
+ readonly onDelete?: OnDeleteStrategy;
47
+ };
48
+
49
+ export type RelationDefinition = BelongsToRelation | HasManyRelation | ManyToManyRelation;
50
+
51
+ export type EntityRelations = Readonly<Record<string, RelationDefinition>>;