@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,136 @@
1
+ import type { KumikoError } from "./kumiko-error";
2
+
3
+ // Wire format every 4xx/5xx response must match. The API routes use this
4
+ // verbatim; keep it stable — the client SDK keys off these field names.
5
+ //
6
+ // `docsUrl` is the deep-link for self-service: default web/mobile renderers
7
+ // show a "Mehr erfahren →" link pointing here. Computed from `details.reason`
8
+ // (when set) or fallback to `code`. Always present, even when `details` is
9
+ // stripped from the response (e.g. internal_error in production).
10
+ export type ErrorResponseBody = {
11
+ readonly error: {
12
+ readonly code: string;
13
+ readonly i18nKey: string;
14
+ readonly i18nParams?: Readonly<Record<string, unknown>>;
15
+ readonly message: string;
16
+ readonly details?: unknown;
17
+ readonly docsUrl: string;
18
+ readonly requestId?: string;
19
+ readonly timestamp: string;
20
+ };
21
+ };
22
+
23
+ // InternalError deliberately omits `details` from the response in production,
24
+ // because that field often carries the internal cause. The log still has
25
+ // everything (see buildErrorLog).
26
+ //
27
+ // In dev/test the cause is exposed in the response body to make debugging
28
+ // possible — without this, a crash in a handler comes back as a bare
29
+ // "internal error" and developers have to add try/catch + UnprocessableError
30
+ // just to see what went wrong (real footgun, not theoretical). The exposure
31
+ // is gated on NODE_ENV !== "production" so a misconfigured prod deploy
32
+ // doesn't leak stack traces over the wire.
33
+ const CODES_WITHOUT_CLIENT_DETAILS = new Set(["internal_error"]);
34
+
35
+ const isProductionEnv = (): boolean => process.env["NODE_ENV"] === "production";
36
+
37
+ // Snapshot of the cause chain (top-most error only) suitable for inclusion
38
+ // in dev-mode error responses. Stack is truncated to keep the body small.
39
+ type DevCauseDetail = {
40
+ readonly causeName?: string;
41
+ readonly causeMessage?: string;
42
+ readonly causeStack?: string;
43
+ };
44
+
45
+ function devCauseDetail(cause: unknown): DevCauseDetail | undefined {
46
+ if (!(cause instanceof Error)) return undefined;
47
+ return {
48
+ causeName: cause.name,
49
+ causeMessage: cause.message,
50
+ ...(cause.stack && { causeStack: cause.stack.split("\n").slice(0, 8).join("\n") }),
51
+ };
52
+ }
53
+
54
+ export function serializeError(err: KumikoError, requestId?: string): ErrorResponseBody {
55
+ const stripped = CODES_WITHOUT_CLIENT_DETAILS.has(err.code) && isProductionEnv();
56
+ const exposeDeclaredDetails = err.details !== undefined && !stripped;
57
+
58
+ // For internal_error in dev/test, surface the cause chain so the
59
+ // response body has something useful instead of "internal error".
60
+ // Priority: declared details (if any) win over the cause snapshot —
61
+ // a hand-set details on InternalError should not be replaced.
62
+ const detailsForResponse = (() => {
63
+ if (exposeDeclaredDetails) return err.details;
64
+ if (CODES_WITHOUT_CLIENT_DETAILS.has(err.code) && !isProductionEnv()) {
65
+ return devCauseDetail(err.cause);
66
+ }
67
+ return undefined;
68
+ })();
69
+
70
+ return {
71
+ error: {
72
+ code: err.code,
73
+ i18nKey: err.i18nKey,
74
+ ...(err.i18nParams && { i18nParams: err.i18nParams }),
75
+ message: err.message,
76
+ ...(detailsForResponse !== undefined && { details: detailsForResponse }),
77
+ docsUrl: err.docsUrl,
78
+ ...(requestId && { requestId }),
79
+ timestamp: new Date().toISOString(),
80
+ },
81
+ };
82
+ }
83
+
84
+ // Full forensic shape — stack, cause chain, details — meant for the log
85
+ // emitter. Callers should pipe this through a sensitive-value filter before it
86
+ // leaves the process (out of scope for v1).
87
+ export type ErrorLogEntry = {
88
+ readonly name: string;
89
+ readonly code: string;
90
+ readonly httpStatus: number;
91
+ readonly message: string;
92
+ readonly i18nKey: string;
93
+ readonly details?: unknown;
94
+ readonly stack?: string;
95
+ readonly cause?: CauseSnapshot;
96
+ };
97
+
98
+ export function buildErrorLog(err: KumikoError): ErrorLogEntry {
99
+ return {
100
+ name: err.name,
101
+ code: err.code,
102
+ httpStatus: err.httpStatus,
103
+ message: err.message,
104
+ i18nKey: err.i18nKey,
105
+ ...(err.details !== undefined && { details: err.details }),
106
+ ...(err.stack && { stack: err.stack }),
107
+ ...(err.cause !== undefined && { cause: serializeCause(err.cause) }),
108
+ };
109
+ }
110
+
111
+ // Recursive cause snapshot — follows error.cause.cause... until the chain
112
+ // ends or a non-Error appears. Hard cap at 10 levels to avoid pathological
113
+ // cyclic cause chains hanging the serializer.
114
+ type CauseSnapshot = {
115
+ readonly name: string;
116
+ readonly message: string;
117
+ readonly stack?: string;
118
+ readonly cause?: CauseSnapshot;
119
+ };
120
+
121
+ const MAX_CAUSE_DEPTH = 10;
122
+
123
+ function serializeCause(cause: unknown, depth = 0): CauseSnapshot {
124
+ if (depth >= MAX_CAUSE_DEPTH) {
125
+ return { name: "CauseChainTruncated", message: `depth >= ${MAX_CAUSE_DEPTH}` };
126
+ }
127
+ if (cause instanceof Error) {
128
+ return {
129
+ name: cause.name,
130
+ message: cause.message,
131
+ ...(cause.stack && { stack: cause.stack }),
132
+ ...(cause.cause !== undefined && { cause: serializeCause(cause.cause, depth + 1) }),
133
+ };
134
+ }
135
+ return { name: "NonError", message: String(cause) };
136
+ }
@@ -0,0 +1,30 @@
1
+ // Gemeinsame Detail-Shape für invalid_transition-Errors. Beide Pfade
2
+ // (state-machine.assertTransition + failTransition) müssen identisch
3
+ // strukturierte Details liefern, sonst können HTTP-Clients den 422-Body
4
+ // nicht uniform parsen — `from`/`to`/`allowed` sind die strukturierten
5
+ // Felder, `message` ist die menschen-lesbare Form.
6
+ //
7
+ // Kein Re-Export von "validTargets" (CSV-string) mehr — wer CSV will
8
+ // baut's via `allowed.join(", ")`. Eine Shape, ein Vertrag.
9
+
10
+ export type InvalidTransitionDetails = {
11
+ readonly from: string;
12
+ readonly to: string;
13
+ readonly allowed: readonly string[];
14
+ readonly message: string;
15
+ };
16
+
17
+ export function buildInvalidTransitionDetails(
18
+ from: string,
19
+ to: string,
20
+ allowed: readonly string[],
21
+ ): InvalidTransitionDetails {
22
+ return {
23
+ from,
24
+ to,
25
+ allowed,
26
+ message: `Invalid transition: "${from}" → "${to}". Allowed from "${from}": ${
27
+ allowed.length > 0 ? allowed.join(", ") : "none"
28
+ }`,
29
+ };
30
+ }
@@ -0,0 +1,123 @@
1
+ import { NotFoundError, UnprocessableError } from "./classes";
2
+ import { KumikoError } from "./kumiko-error";
3
+ import { FrameworkReasons } from "./reasons";
4
+ import { buildInvalidTransitionDetails } from "./transition-details";
5
+
6
+ // Plain, JSON-serializable snapshot of a KumikoError for use on the write-path
7
+ // (WriteResult.error, BatchResult.error). The dispatcher stores results under
8
+ // an idempotency key — a KumikoError instance wouldn't round-trip through
9
+ // JSON, so we keep structural data only and rebuild the instance on demand
10
+ // via reraiseAsKumikoError when we need to throw upstream again.
11
+ export type WriteErrorInfo = {
12
+ readonly code: string;
13
+ readonly httpStatus: number;
14
+ readonly i18nKey: string;
15
+ readonly i18nParams?: Readonly<Record<string, unknown>>;
16
+ readonly message: string;
17
+ readonly details?: unknown;
18
+ };
19
+
20
+ // The failure half of WriteResult — `{ isSuccess: false } + error`. Named
21
+ // so the three write-failure factories below and WriteResult share one
22
+ // shape instead of restating it. Not generic: the error carries zero data,
23
+ // so there's nothing for the caller to narrow.
24
+ export type WriteFailure = {
25
+ readonly isSuccess: false;
26
+ readonly error: WriteErrorInfo;
27
+ };
28
+
29
+ // Convenience for call sites that return a failed WriteResult. Keeps the
30
+ // pattern `return writeFailure(new NotFoundError(...))` compact so handlers
31
+ // and the CrudExecutor don't need to spell out the shape each time.
32
+ export function writeFailure(err: KumikoError): WriteFailure {
33
+ return { isSuccess: false, error: toWriteErrorInfo(err) };
34
+ }
35
+
36
+ // Focused convenience for the two most common handler failures: "X not found"
37
+ // (typed 404) and "business rule violated: REASON" (typed 422 with the reason
38
+ // string surfaced in details.reason). Reach for the concrete classes when you
39
+ // need richer payload — these two cover the bulk of handler code.
40
+ export function failNotFound(entity: string, id?: number | string): WriteFailure {
41
+ return writeFailure(new NotFoundError(entity, id));
42
+ }
43
+
44
+ export function failUnprocessable(
45
+ reason: string,
46
+ details?: Readonly<Record<string, unknown>>,
47
+ ): WriteFailure {
48
+ return writeFailure(new UnprocessableError(reason, details ? { details } : undefined));
49
+ }
50
+
51
+ /**
52
+ * Convenience für State-Transition-Rejects: produces a WriteFailure mit
53
+ * reason="invalid_transition" + strukturiertem `from`/`to`/`allowed`-
54
+ * Detail-Block + lesbarer message. Pattern hat sich in publicstatus-
55
+ * Maintenance + bestehenden state-machine-Helpers wiederholt — Helper
56
+ * sammelt das in einem Aufruf statt drei manuelle Detail-Felder.
57
+ *
58
+ * `allowed` ist typisch `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
59
+ */
60
+ export function failTransition(from: string, to: string, allowed: readonly string[]): WriteFailure {
61
+ return writeFailure(
62
+ new UnprocessableError(FrameworkReasons.invalidTransition, {
63
+ i18nKey: "errors.invalidTransition",
64
+ details: buildInvalidTransitionDetails(from, to, allowed),
65
+ }),
66
+ );
67
+ }
68
+
69
+ export function toWriteErrorInfo(err: KumikoError): WriteErrorInfo {
70
+ // In dev/test surface the cause-snapshot through `details` so the
71
+ // HTTP response carries something useful. Without this, internal_error
72
+ // crashes round-trip through WriteErrorInfo (no cause field) and come
73
+ // back to the client as a bare `{ message: "internal error" }` —
74
+ // identical user-experience to a misconfigured prod, just slower
75
+ // because the dev has to add try/catch + console.log to find the
76
+ // actual stack. Same gating as serializeError: NODE_ENV !== production.
77
+ const causeDetails =
78
+ err.details === undefined && err.code === "internal_error" && err.cause instanceof Error
79
+ ? process.env["NODE_ENV"] !== "production"
80
+ ? {
81
+ causeName: err.cause.name,
82
+ causeMessage: err.cause.message,
83
+ ...(err.cause.stack && {
84
+ causeStack: err.cause.stack.split("\n").slice(0, 8).join("\n"),
85
+ }),
86
+ }
87
+ : undefined
88
+ : undefined;
89
+ const effectiveDetails = err.details ?? causeDetails;
90
+ return {
91
+ code: err.code,
92
+ httpStatus: err.httpStatus,
93
+ i18nKey: err.i18nKey,
94
+ message: err.message,
95
+ ...(err.i18nParams && { i18nParams: err.i18nParams }),
96
+ ...(effectiveDetails !== undefined && { details: effectiveDetails }),
97
+ };
98
+ }
99
+
100
+ // Reconstitutes an error from WriteErrorInfo so command() (throw-based) can
101
+ // keep raising a KumikoError after the batch returned structural data. The
102
+ // concrete subclass identity is lost here — callers can still read code /
103
+ // httpStatus / details but `instanceof NotFoundError` won't work. That's OK:
104
+ // the HTTP layer keys off code + httpStatus, not class identity.
105
+ export function reraiseAsKumikoError(info: WriteErrorInfo): KumikoError {
106
+ return new ReraisedError(info);
107
+ }
108
+
109
+ class ReraisedError extends KumikoError {
110
+ readonly code: string;
111
+ readonly httpStatus: number;
112
+
113
+ constructor(info: WriteErrorInfo) {
114
+ super({
115
+ message: info.message,
116
+ i18nKey: info.i18nKey,
117
+ ...(info.i18nParams && { i18nParams: info.i18nParams }),
118
+ ...(info.details !== undefined && { details: info.details }),
119
+ });
120
+ this.code = info.code;
121
+ this.httpStatus = info.httpStatus;
122
+ }
123
+ }
@@ -0,0 +1,49 @@
1
+ import type { ZodError, ZodIssue } from "zod";
2
+ import { ValidationError, type ValidationFieldIssue } from "./classes";
3
+
4
+ // Zod issues carry a .code and sometimes issue-specific params (min, max, etc).
5
+ // We surface those under `params` so the client can render "must be at least N"
6
+ // without re-parsing the message.
7
+ //
8
+ // Keep this list in sync with the issue-code matrix in classes.test.ts — that
9
+ // test is what catches Zod upgrades introducing new param names.
10
+ const ISSUE_PARAM_KEYS = [
11
+ "minimum",
12
+ "maximum",
13
+ "expected",
14
+ "received",
15
+ "type",
16
+ "inclusive",
17
+ "exact",
18
+ "keys",
19
+ // Zod 4 additions:
20
+ "format", // invalid_format (email, url, uuid, regex, ...)
21
+ "divisor", // not_multiple_of
22
+ "values", // invalid_value (enum / literal)
23
+ "pattern", // invalid_format with regex
24
+ ] as const;
25
+
26
+ export function validationErrorFromZod(error: ZodError): ValidationError {
27
+ const fields = error.issues.map<ValidationFieldIssue>((issue) => {
28
+ const params = extractIssueParams(issue);
29
+ return {
30
+ path: issue.path.map(String).join(".") || "(root)",
31
+ code: issue.code,
32
+ i18nKey: `errors.validation.${issue.code}`,
33
+ ...(params && { params }),
34
+ };
35
+ });
36
+ return new ValidationError({ fields }, { cause: error });
37
+ }
38
+
39
+ function extractIssueParams(issue: ZodIssue): Readonly<Record<string, unknown>> | undefined {
40
+ // ZodIssue is a discriminated union with variant-specific params (minimum,
41
+ // maximum, expected, …); reading them generically requires widening since
42
+ // the union members don't share an index signature.
43
+ const out: Record<string, unknown> = {};
44
+ const bag = issue as unknown as Record<string, unknown>; // @cast-boundary zod-issue
45
+ for (const key of ISSUE_PARAM_KEYS) {
46
+ if (bag[key] !== undefined) out[key] = bag[key];
47
+ }
48
+ return Object.keys(out).length > 0 ? out : undefined;
49
+ }