@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,422 @@
1
+ // --- Field Types ---
2
+
3
+ // OwnershipMap is declared in engine/ownership.ts — field-access maps to
4
+ // per-role ownership rules. A legacy `readonly string[]` form is still
5
+ // accepted at the type layer during migration: features that pass an
6
+ // array are auto-normalized to { [role]: "all" } at registry build.
7
+ // Long-term: string[] disappears.
8
+ import type { OwnershipMap } from "../ownership";
9
+
10
+ export type FieldAccess = {
11
+ readonly read?: OwnershipMap | readonly string[];
12
+ readonly write?: OwnershipMap | readonly string[];
13
+ };
14
+
15
+ // `sensitive: true` — the field's value is excluded from event payloads
16
+ // (create data, update changes/previous, delete/restore previous). The entity
17
+ // row still stores it; only the immutable event-log won't. Use for data that
18
+ // must never land in permanent history: password hashes, API tokens,
19
+ // unhashed PII, bank details, tax IDs. The trade-off: event-replay and
20
+ // custom projections cannot read sensitive field values. See
21
+ // docs/plans/architecture/projections.md.
22
+
23
+ export type TextFieldDef = {
24
+ readonly type: "text";
25
+ readonly maxLength?: number;
26
+ readonly required?: boolean;
27
+ readonly searchable?: boolean;
28
+ readonly sortable?: boolean;
29
+ /** Author erlaubt Screen-Filter auf diesem Feld (Tier 2.7c).
30
+ * Boot-Validator weist Filter mit `filterable !== true` zurück.
31
+ * Default: false — analog zu `sortable`, opt-in. */
32
+ readonly filterable?: boolean;
33
+ readonly encrypted?: boolean;
34
+ readonly sensitive?: boolean;
35
+ readonly format?: "email" | "url" | "phone";
36
+ readonly default?: string;
37
+ readonly access?: FieldAccess;
38
+ /** Mehrzeiliger Text — DefaultInput rendert dann ein <textarea> statt
39
+ * <input type="text">. `true` = Default 4 Zeilen, `{ rows: N }` =
40
+ * explizite Höhe. Search/sort/encrypt verhalten sich unverändert
41
+ * identisch zu single-line — nur die Render-Surface wechselt. */
42
+ readonly multiline?: boolean | { readonly rows?: number };
43
+ };
44
+
45
+ /**
46
+ * Long-form text content — source-code, markdown, blog-posts, email-
47
+ * templates, anything that can be megabytes large. Bewusst MINIMALE
48
+ * Surface gegen `text`:
49
+ *
50
+ * - **Kein `sortable`**: ORDER BY auf 100 KB-Strings kostet I/O ohne
51
+ * sinnvolles UX-Outcome (lex-Sortierung von Code ist Nonsense).
52
+ * - **Kein `searchable`**: ILIKE/Substring-Suche auf langen Texten
53
+ * skaliert nicht. Wer wirklich Volltextsuche will, nimmt den
54
+ * SearchAdapter (Meilisearch) — der hat eine eigene Pipeline mit
55
+ * Tokenizer + Index, NICHT diesen field-flag.
56
+ * - **Kein `filterable`**: WHERE auf langen Strings same Story wie
57
+ * sortable.
58
+ * - **Kein `format`**: email/url/phone sind kurz definierte Inputs,
59
+ * longText ist per Definition unstrukturiert.
60
+ *
61
+ * Type-level enforcement statt convention: wer sortable/searchable
62
+ * braucht, nimmt `text` (mit den entsprechenden Skalierungs-Trade-offs).
63
+ * DB-mapping ist identisch zu text (Postgres `text` ist unbounded).
64
+ */
65
+ export type LongTextFieldDef = {
66
+ readonly type: "longText";
67
+ /** Optionale soft-Cap. Default unbounded (= Postgres-text-limit, 1 GB).
68
+ * Nützlich für defensive Caps wie 1 MB damit ein verirrter Browser-
69
+ * Paste nicht die DB sprengt. */
70
+ readonly maxLength?: number;
71
+ readonly required?: boolean;
72
+ readonly encrypted?: boolean;
73
+ readonly sensitive?: boolean;
74
+ readonly default?: string;
75
+ readonly access?: FieldAccess;
76
+ readonly multiline?: boolean | { readonly rows?: number };
77
+ };
78
+
79
+ export type BooleanFieldDef = {
80
+ readonly type: "boolean";
81
+ readonly required?: boolean;
82
+ readonly sortable?: boolean;
83
+ readonly filterable?: boolean;
84
+ readonly sensitive?: boolean;
85
+ readonly default?: boolean;
86
+ readonly access?: FieldAccess;
87
+ };
88
+
89
+ export type SelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
90
+ readonly type: "select";
91
+ readonly options: TOptions;
92
+ readonly required?: boolean;
93
+ readonly sortable?: boolean;
94
+ readonly filterable?: boolean;
95
+ readonly sensitive?: boolean;
96
+ readonly default?: TOptions[number];
97
+ readonly access?: FieldAccess;
98
+ };
99
+
100
+ // Mehrere Werte aus einer festen Options-Liste — UI rendert als
101
+ // Checkbox-/Multi-Select-Kontrolle. Storage: jsonb-Array<string>;
102
+ // jeder Eintrag muss in `options` enthalten sein.
103
+ //
104
+ // Wann statt `select`: wenn der User mehr als einen Wert gleichzeitig
105
+ // auswählen darf (Führerscheinklassen, Tags, Sprachen, Skills).
106
+ // Wann statt `embedded` mit Booleans: wenn die Option-Liste nicht
107
+ // hardcoded sein soll oder bei mehr als ~5 Optionen — sonst explodiert
108
+ // das embedded-Schema.
109
+ //
110
+ // Ordering: das Array bewahrt die Caller-Reihenfolge (jsonb-array, nicht
111
+ // set). Das Framework dedupliziert beim Schreiben nicht — Validator
112
+ // rejected Duplikate erst wenn Bedarf da ist.
113
+ export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
114
+ readonly type: "multiSelect";
115
+ readonly options: TOptions;
116
+ readonly required?: boolean;
117
+ readonly filterable?: boolean;
118
+ readonly sensitive?: boolean;
119
+ /** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
120
+ readonly default?: readonly TOptions[number][];
121
+ readonly access?: FieldAccess;
122
+ };
123
+
124
+ export type NumberFieldDef = {
125
+ readonly type: "number";
126
+ readonly required?: boolean;
127
+ readonly sortable?: boolean;
128
+ readonly filterable?: boolean;
129
+ readonly sensitive?: boolean;
130
+ readonly default?: number;
131
+ readonly access?: FieldAccess;
132
+ };
133
+
134
+ export type MoneyFieldDef = {
135
+ readonly type: "money";
136
+ readonly required?: boolean;
137
+ readonly sortable?: boolean;
138
+ readonly filterable?: boolean;
139
+ readonly sensitive?: boolean;
140
+ readonly access?: FieldAccess;
141
+ };
142
+
143
+ // Reference-Field (Tier 2.7e-3) — FK-Style Verweis auf eine andere
144
+ // Entity. Gespeichert als UUID-Spalte (uuid type), Read-Side liefert
145
+ // optional die referenced Row mit (Tier 2.7e-4 eagerload).
146
+ //
147
+ // `entity` akzeptiert zwei Formen:
148
+ // - kurz ("customer") — same-feature reference, Default-Pfad.
149
+ // - qualifiziert ("users:user") — cross-feature, Format
150
+ // "<featureName>:<entityName>". Renderer baut die Lookup-Query-QN
151
+ // gegen das angegebene Feature (`users:query:user:list`).
152
+ //
153
+ // `labelField` (optional) — welches Feld der referenced Entity wird
154
+ // im Select-Dropdown als Label gezeigt. Default: "id". Best practice
155
+ // ist ein menschlich-lesbares Feld wie "name", "title", "email".
156
+ export type ReferenceFieldDef = {
157
+ readonly type: "reference";
158
+ readonly entity: string;
159
+ readonly required?: boolean;
160
+ readonly filterable?: boolean;
161
+ readonly sensitive?: boolean;
162
+ readonly access?: FieldAccess;
163
+ /** Welches Feld der referenced Entity als Display-Label im
164
+ * Select-Dropdown erscheint. Default: "id". Boot-Validator pinst
165
+ * dass das Feld auf der referenced Entity existiert. */
166
+ readonly labelField?: string;
167
+ /** Multi-Reference (Tier 2.7e-Multi): Wert ist ein Array von UUIDs
168
+ * statt single UUID. Storage als jsonb-Array<uuid>. UI rendert
169
+ * Multi-Select-Combobox mit Tag-Anzeige der gewählten Items. */
170
+ readonly multiple?: boolean;
171
+ };
172
+
173
+ // --- Currency ---
174
+
175
+ export const DEFAULT_CURRENCIES = [
176
+ "EUR",
177
+ "USD",
178
+ "GBP",
179
+ "CHF",
180
+ "JPY",
181
+ "SEK",
182
+ "NOK",
183
+ "DKK",
184
+ "PLN",
185
+ "CZK",
186
+ "CAD",
187
+ "AUD",
188
+ "NZD",
189
+ "CNY",
190
+ "INR",
191
+ ] as const;
192
+
193
+ export type DefaultCurrency = (typeof DEFAULT_CURRENCIES)[number];
194
+
195
+ // --- Embedded Object ---
196
+
197
+ export type EmbeddedSubFieldDef = {
198
+ readonly type: "text" | "number" | "boolean" | "date";
199
+ readonly required?: boolean;
200
+ readonly searchable?: boolean;
201
+ readonly access?: FieldAccess;
202
+ };
203
+
204
+ export type EmbeddedFieldDef = {
205
+ readonly type: "embedded";
206
+ readonly required?: boolean;
207
+ readonly sensitive?: boolean;
208
+ readonly schema: Readonly<Record<string, EmbeddedSubFieldDef>>;
209
+ readonly access?: FieldAccess;
210
+ };
211
+
212
+ // Legacy "date" — JS-Date-Object, semantisch unklar (Wall-Clock vs Instant).
213
+ // Für neue Felder bevorzuge:
214
+ // - `timestamp` für UTC-Instant ("wann ist das passiert")
215
+ // - `locatedTimestamp(name)` Helper für Termine die an einem Ort
216
+ // stattfinden ("Pickup um 10:00 in Lissabon")
217
+ // - (kommt) `plainDate` für Kalender-Daten ohne Uhrzeit (z.B. Geburtstag)
218
+ // Siehe docs/plans/architecture/timezones.md
219
+ export type DateFieldDef = {
220
+ readonly type: "date";
221
+ readonly required?: boolean;
222
+ readonly sortable?: boolean;
223
+ readonly filterable?: boolean;
224
+ readonly sensitive?: boolean;
225
+ readonly access?: FieldAccess;
226
+ };
227
+
228
+ // UTC-Instant (Temporal.Instant). Für Ereignisse die zu einem bestimmten
229
+ // Augenblick passieren, ohne Location-Bezug: createdAt, loginAt, actualPickupAt.
230
+ // JSON-Form: ISO-UTC-String "2026-04-18T10:00:00Z" via .toJSON().
231
+ //
232
+ // Mit `locatedBy: "<name>Tz"` markiert: bildet ein Wall-Clock+TZ-Pair mit dem
233
+ // referenzierten tz-Feld. JSON-Form wird dann zwei Felder ({ at, tz }), DB
234
+ // speichert Wall-Clock+tz und konvertiert transparent (siehe DB-Wrapper,
235
+ // kommt in einer späteren Iteration).
236
+ //
237
+ // Verwendung über den `locatedTimestamp(name)` Helper, der das Pair atomar
238
+ // erzeugt und die Marker korrekt verdrahtet.
239
+ export type TimestampFieldDef = {
240
+ readonly type: "timestamp";
241
+ readonly required?: boolean;
242
+ readonly sortable?: boolean;
243
+ readonly filterable?: boolean;
244
+ readonly sensitive?: boolean;
245
+ readonly access?: FieldAccess;
246
+ /**
247
+ * Marker: dieses Timestamp-Feld ist Wall-Clock-Zeit an einem Ort.
248
+ * Wert ist der Name des begleitenden tz-Felds (IANA-Zone).
249
+ *
250
+ * Beispiel: `locatedTimestamp("pickup")` erzeugt
251
+ * { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
252
+ */
253
+ readonly locatedBy?: string;
254
+ };
255
+
256
+ // IANA-Zonenname (z.B. "Europe/Berlin", "America/Los_Angeles").
257
+ // Wird via `Intl.supportedValuesOf("timeZone")` validiert (kommt im
258
+ // Zod-Validator-Schritt). Eigener Field-Typ damit Type-Safety + Storage
259
+ // (TEXT-Spalte) korrekt sind und der `locatedBy`-Marker eindeutig auflöst.
260
+ export type TzFieldDef = {
261
+ readonly type: "tz";
262
+ readonly required?: boolean;
263
+ readonly sensitive?: boolean;
264
+ readonly access?: FieldAccess;
265
+ };
266
+
267
+ // Wall-Clock-Termin an einem Ort als ATOMARES Konzept.
268
+ // EIN Feld in der Schema-Definition, ZWEI Spalten in der DB
269
+ // (`<name>_utc TIMESTAMPTZ` + `<name>_tz TEXT`), DREI Felder im API-Object
270
+ // ({ at, tz, utc }). Drizzle-Wrapper macht die Konvertierung transparent —
271
+ // Feature-Code sieht das 3-Felder-Object beim Read und schreibt
272
+ // { at, tz } beim Insert (utc wird berechnet).
273
+ //
274
+ // API-Form:
275
+ // Write: { at: "2026-04-15T10:00:00", tz: "Europe/Lisbon" }
276
+ // Read: { at: "2026-04-15T10:00:00", tz: "Europe/Lisbon", utc: "2026-04-15T09:00:00Z" }
277
+ //
278
+ // Default-Sicht für `at`: Wall-Clock am Ort (`tz`). Wer User-lokale Sicht
279
+ // will, projeziert `utc` separat per ctx.tz.fromInstantInZone(utc, userTz).
280
+ //
281
+ // Ersetzt das alte `locatedTimestamp(name)` Helper-Pattern (zwei separate
282
+ // Pair-Felder). Sauberer Single-Field-Typ + Auto-Convert-Logik.
283
+ //
284
+ // Siehe docs/plans/architecture/timezones.md.
285
+ export type LocatedTimestampFieldDef = {
286
+ readonly type: "locatedTimestamp";
287
+ readonly required?: boolean;
288
+ readonly sortable?: boolean;
289
+ readonly filterable?: boolean;
290
+ readonly sensitive?: boolean;
291
+ readonly access?: FieldAccess;
292
+ };
293
+
294
+ export type FileFieldDef = {
295
+ readonly type: "file";
296
+ readonly required?: boolean;
297
+ readonly maxSize?: string;
298
+ readonly accept?: readonly string[];
299
+ readonly access?: FieldAccess;
300
+ };
301
+
302
+ export type ImageFieldDef = {
303
+ readonly type: "image";
304
+ readonly required?: boolean;
305
+ readonly maxSize?: string;
306
+ readonly accept?: readonly string[];
307
+ readonly thumbnails?: boolean;
308
+ readonly access?: FieldAccess;
309
+ };
310
+
311
+ export type FilesFieldDef = {
312
+ readonly type: "files";
313
+ readonly maxSize?: string;
314
+ readonly accept?: readonly string[];
315
+ readonly maxCount?: number;
316
+ readonly access?: FieldAccess;
317
+ };
318
+
319
+ export type ImagesFieldDef = {
320
+ readonly type: "images";
321
+ readonly maxSize?: string;
322
+ readonly accept?: readonly string[];
323
+ readonly maxCount?: number;
324
+ readonly thumbnails?: boolean;
325
+ readonly access?: FieldAccess;
326
+ };
327
+
328
+ export type FieldDefinition =
329
+ | TextFieldDef
330
+ | LongTextFieldDef
331
+ | BooleanFieldDef
332
+ | SelectFieldDef
333
+ | MultiSelectFieldDef
334
+ | NumberFieldDef
335
+ | MoneyFieldDef
336
+ | ReferenceFieldDef
337
+ | EmbeddedFieldDef
338
+ | DateFieldDef
339
+ | TimestampFieldDef
340
+ | TzFieldDef
341
+ | LocatedTimestampFieldDef
342
+ | FileFieldDef
343
+ | ImageFieldDef
344
+ | FilesFieldDef
345
+ | ImagesFieldDef;
346
+
347
+ // Union of all field variants that represent uploaded files. They share
348
+ // `maxSize` and `accept`, which is what upload validation cares about.
349
+ export type AnyFileFieldDef = FileFieldDef | ImageFieldDef | FilesFieldDef | ImagesFieldDef;
350
+
351
+ export function isFileField(field: FieldDefinition | undefined): field is AnyFileFieldDef {
352
+ if (!field) return false;
353
+ return (
354
+ field.type === "file" ||
355
+ field.type === "image" ||
356
+ field.type === "files" ||
357
+ field.type === "images"
358
+ );
359
+ }
360
+
361
+ // --- Entity ---
362
+
363
+ // --- State Transitions ---
364
+
365
+ export type TransitionMap = Readonly<Record<string, readonly string[]>>;
366
+
367
+ /** Composite-Index auf einer Entity. Spalten werden via field-Name
368
+ * referenziert (camelCase). buildDrizzleTable mapped sie auf snake_case-
369
+ * Spaltennamen und benennt den Index nach Convention:
370
+ *
371
+ * <table>_<col1>_<col2>_idx (non-unique)
372
+ * <table>_<col1>_<col2>_unique (unique)
373
+ *
374
+ * Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
375
+ * vermeidet Migration-Churn beim Refactor.
376
+ *
377
+ * Single-column indices über `tenantId` sind redundant (buildDrizzleTable
378
+ * legt die immer automatisch an); die Boot-Validation warnt. */
379
+ export type EntityIndexDef = {
380
+ readonly columns: readonly [string, ...string[]];
381
+ readonly unique?: boolean;
382
+ readonly name?: string;
383
+ };
384
+
385
+ export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
386
+
387
+ export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
388
+ readonly table?: string;
389
+ readonly fields: F;
390
+ readonly softDelete?: boolean;
391
+ readonly searchWeight?: number;
392
+ readonly defaultCurrency?: string;
393
+ /** Allowed state transitions per field. Boot validates against select options. */
394
+ readonly transitions?: Readonly<Record<string, TransitionMap>>;
395
+ /** Composite-Indices über mehrere Felder. Single-column FK-Indices und
396
+ * der tenant_id-Index werden weiterhin automatisch von buildDrizzleTable
397
+ * angelegt — diese Liste ist nur für Custom-Indices die der Author
398
+ * explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
399
+ readonly indexes?: readonly EntityIndexDef[];
400
+ /**
401
+ * PK-Typ der Entity.
402
+ * - `"serial"` (default): bigserial integer — schneller, kompakter, perfekt für klassische CRUD-Entities.
403
+ * - `"uuid"`: uuid mit `gen_random_uuid()` default — verpflichtend für Entities deren `id` als
404
+ * Foreign-Key-Wert in multi-tenant Kontexten reist (z.B. `tenant.id` IS der `tenantId`). Auch für
405
+ * ES-Aggregate (Phase 2+) notwendig, da Events per UUID aggregiert werden.
406
+ */
407
+ readonly idType?: "serial" | "uuid";
408
+ /**
409
+ * Row-level ownership rules (H.2). read runs as WHERE-predicate on list/
410
+ * detail/queryProjection, scoping which rows the caller sees. write runs
411
+ * pre-save on create/update/delete, scoping which rows the caller may
412
+ * modify (Straddle-safe, multi-role atomic — see engine/ownership.ts).
413
+ *
414
+ * Keys are role names; rules use the `from()` helper or `{ where }`
415
+ * escape hatch. Entity-level ownership is AND-ed with tenant isolation —
416
+ * a user's tenant filter still applies first.
417
+ */
418
+ readonly access?: {
419
+ readonly read?: OwnershipMap;
420
+ readonly write?: OwnershipMap;
421
+ };
422
+ };