@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,134 @@
1
+ // Integration-Test für detectProjectionsToRebuild — die Brücke zwischen
2
+ // Snapshot-Diff (Welle 2) und ImplicitProjection (Sprint G). Beweist dass
3
+ // `kumiko migrate generate` das richtige Marker-File schreibt: ein
4
+ // Spalten-Add auf einer r.entity-Tabelle muss als
5
+ // `<feature>:projection:<entity>-entity` rebuild-Kandidat erkannt werden.
6
+ //
7
+ // Production-Behavior: ohne diese Brücke würden die Welle-2- und
8
+ // Sprint-G-Pieces nebeneinander leben aber sich nicht treffen — Marker
9
+ // wäre leer, kein Rebuild würde ausgelöst.
10
+
11
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
15
+ import { createBooleanField, createEntity, createTextField, defineFeature } from "../../engine";
16
+ import { createRegistry } from "../../engine/registry";
17
+ import { detectProjectionsToRebuild } from "../projection-detection";
18
+
19
+ let migrationsDir: string;
20
+
21
+ beforeEach(() => {
22
+ migrationsDir = mkdtempSync(join(tmpdir(), "kumiko-detect-"));
23
+ mkdirSync(join(migrationsDir, "meta"), { recursive: true });
24
+ });
25
+
26
+ afterEach(() => {
27
+ rmSync(migrationsDir, { recursive: true, force: true });
28
+ });
29
+
30
+ function writeJournal(entries: { idx: number; tag: string }[]): void {
31
+ writeFileSync(
32
+ join(migrationsDir, "meta/_journal.json"),
33
+ JSON.stringify({
34
+ version: "7",
35
+ dialect: "postgresql",
36
+ entries: entries.map((e) => ({
37
+ idx: e.idx,
38
+ version: "7",
39
+ when: 1700000000000 + e.idx,
40
+ tag: e.tag,
41
+ breakpoints: true,
42
+ })),
43
+ }),
44
+ );
45
+ }
46
+
47
+ function writeSnapshot(idx: number, tableName: string, columnNames: string[]): void {
48
+ const columns: Record<string, unknown> = {};
49
+ for (const name of columnNames) {
50
+ columns[name] = { name, type: "text" };
51
+ }
52
+ // Plus base-columns die jede Entity-Tabelle hat, damit wir nicht mit
53
+ // dem Test-Compare versehentlich kompletten neue Tabellen markieren.
54
+ columns["id"] = { name: "id", type: "uuid", primaryKey: true, notNull: true };
55
+ columns["tenant_id"] = { name: "tenant_id", type: "uuid", notNull: true };
56
+ columns["version"] = { name: "version", type: "integer", default: 1, notNull: true };
57
+ writeFileSync(
58
+ join(migrationsDir, "meta", `${String(idx).padStart(4, "0")}_snapshot.json`),
59
+ JSON.stringify({
60
+ tables: {
61
+ [`public.${tableName}`]: {
62
+ schema: "",
63
+ name: tableName,
64
+ columns,
65
+ },
66
+ },
67
+ }),
68
+ );
69
+ }
70
+
71
+ const widgetEntity = createEntity({
72
+ table: "test_widgets",
73
+ fields: {
74
+ name: createTextField({ required: true }),
75
+ isEnabled: createBooleanField({ default: true }),
76
+ },
77
+ });
78
+
79
+ const widgetFeature = defineFeature("detecttest", (r) => {
80
+ r.entity("widget", widgetEntity);
81
+ });
82
+
83
+ describe("detectProjectionsToRebuild", () => {
84
+ test("Spalten-Add auf r.entity-Tabelle → ImplicitProjection als Rebuild-Kandidat", () => {
85
+ // Initial-Migration: 2 Spalten
86
+ writeSnapshot(0, "test_widgets", ["name", "is_enabled"]);
87
+ // Folge-Migration: 3 Spalten (description dazu)
88
+ writeSnapshot(1, "test_widgets", ["name", "is_enabled", "description"]);
89
+ writeJournal([
90
+ { idx: 0, tag: "0000_init" },
91
+ { idx: 1, tag: "0001_add_description" },
92
+ ]);
93
+
94
+ const registry = createRegistry([widgetFeature]);
95
+ const projections = detectProjectionsToRebuild(registry, migrationsDir);
96
+
97
+ expect(projections).toEqual(["detecttest:projection:widget-entity"]);
98
+ });
99
+
100
+ test("identische Snapshots → keine Rebuild-Kandidaten", () => {
101
+ writeSnapshot(0, "test_widgets", ["name", "is_enabled"]);
102
+ writeSnapshot(1, "test_widgets", ["name", "is_enabled"]);
103
+ writeJournal([
104
+ { idx: 0, tag: "0000_init" },
105
+ { idx: 1, tag: "0001_no_op" },
106
+ ]);
107
+
108
+ const registry = createRegistry([widgetFeature]);
109
+ expect(detectProjectionsToRebuild(registry, migrationsDir)).toEqual([]);
110
+ });
111
+
112
+ test("Initial-Migration (nur ein Snapshot) → leer (keine historischen Events)", () => {
113
+ writeSnapshot(0, "test_widgets", ["name"]);
114
+ writeJournal([{ idx: 0, tag: "0000_init" }]);
115
+
116
+ const registry = createRegistry([widgetFeature]);
117
+ expect(detectProjectionsToRebuild(registry, migrationsDir)).toEqual([]);
118
+ });
119
+
120
+ test("Spalten-Add auf einer Tabelle die KEINE Projection ist → leer", () => {
121
+ // Tabelle "unrelated" ist nicht als Projection registriert (kein
122
+ // r.entity, keine r.projection). Schema-Change soll keinen
123
+ // Rebuild-Marker erzeugen.
124
+ writeSnapshot(0, "unrelated", ["a"]);
125
+ writeSnapshot(1, "unrelated", ["a", "b"]);
126
+ writeJournal([
127
+ { idx: 0, tag: "0000_init" },
128
+ { idx: 1, tag: "0001_add_b" },
129
+ ]);
130
+
131
+ const registry = createRegistry([widgetFeature]);
132
+ expect(detectProjectionsToRebuild(registry, migrationsDir)).toEqual([]);
133
+ });
134
+ });
@@ -0,0 +1,79 @@
1
+ // Unit-Tests für die Marker-File-IO. Production-Behavior:
2
+ // - Generate-Step schreibt Marker mit kanonischer Struktur
3
+ // - Apply-Step liest Marker zurück (oder null wenn keiner)
4
+ // - schemaVersion-Mismatch wirft (verhindert dass alte Markers gegen
5
+ // neue Lese-Logik fahren)
6
+
7
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
11
+ import { readRebuildMarker, writeRebuildMarker } from "../rebuild-marker";
12
+
13
+ let tmpDir: string;
14
+
15
+ beforeEach(() => {
16
+ tmpDir = mkdtempSync(join(tmpdir(), "kumiko-marker-"));
17
+ });
18
+
19
+ afterEach(() => {
20
+ rmSync(tmpDir, { recursive: true, force: true });
21
+ });
22
+
23
+ describe("writeRebuildMarker / readRebuildMarker round-trip", () => {
24
+ test("schreibt File mit kanonischer Struktur", () => {
25
+ writeRebuildMarker(tmpDir, "0042_brave_taskmaster", [
26
+ "publicstatus:projection:incident-entity",
27
+ "publicstatus:projection:component-entity",
28
+ ]);
29
+ const raw = readFileSync(join(tmpDir, "0042_brave_taskmaster__rebuild.json"), "utf-8");
30
+ const parsed = JSON.parse(raw);
31
+ expect(parsed).toEqual({
32
+ schemaVersion: 1,
33
+ migrationTag: "0042_brave_taskmaster",
34
+ // sortiert — Reihenfolge der projections im Output ist deterministisch
35
+ projections: [
36
+ "publicstatus:projection:component-entity",
37
+ "publicstatus:projection:incident-entity",
38
+ ],
39
+ });
40
+ });
41
+
42
+ test("read returns parsed marker", () => {
43
+ writeRebuildMarker(tmpDir, "0001_foo", ["app:projection:bar-entity"]);
44
+ const marker = readRebuildMarker(tmpDir, "0001_foo");
45
+ expect(marker?.migrationTag).toBe("0001_foo");
46
+ expect(marker?.projections).toEqual(["app:projection:bar-entity"]);
47
+ });
48
+
49
+ test("leere Projection-Liste schreibt KEIN File (Noise-Reduktion)", () => {
50
+ writeRebuildMarker(tmpDir, "0003_only_index", []);
51
+ expect(readRebuildMarker(tmpDir, "0003_only_index")).toBeNull();
52
+ });
53
+
54
+ test("read returns null wenn File nicht existiert", () => {
55
+ expect(readRebuildMarker(tmpDir, "0099_never_written")).toBeNull();
56
+ });
57
+
58
+ test("schemaVersion-Mismatch wirft mit klarer Message", () => {
59
+ const path = join(tmpDir, "0042_future__rebuild.json");
60
+ writeFileSync(
61
+ path,
62
+ JSON.stringify({ schemaVersion: 999, migrationTag: "0042_future", projections: [] }),
63
+ );
64
+ expect(() => readRebuildMarker(tmpDir, "0042_future")).toThrow(/schemaVersion/);
65
+ });
66
+
67
+ test("korrupte JSON wirft (kein silent-null)", () => {
68
+ const path = join(tmpDir, "0050_corrupt__rebuild.json");
69
+ writeFileSync(path, "{ this is not json");
70
+ expect(() => readRebuildMarker(tmpDir, "0050_corrupt")).toThrow();
71
+ });
72
+
73
+ test("Idempotenz: zweiter write überschreibt", () => {
74
+ writeRebuildMarker(tmpDir, "0010_x", ["a:projection:one-entity"]);
75
+ writeRebuildMarker(tmpDir, "0010_x", ["a:projection:two-entity"]);
76
+ const marker = readRebuildMarker(tmpDir, "0010_x");
77
+ expect(marker?.projections).toEqual(["a:projection:two-entity"]);
78
+ });
79
+ });
@@ -0,0 +1,28 @@
1
+ export {
2
+ buildProjectionTableIndex,
3
+ type ChangedTable,
4
+ compareSnapshots,
5
+ detectProjectionsToRebuild,
6
+ latestMigrationTag,
7
+ projectionsFromChanges,
8
+ } from "./projection-detection";
9
+ export { type RebuildMarker, readRebuildMarker, writeRebuildMarker } from "./rebuild-marker";
10
+ export {
11
+ type AppliedMigration,
12
+ assertSchemaCurrent,
13
+ type ColumnIssue,
14
+ type ColumnSpec,
15
+ type DriftReport,
16
+ detectDrift,
17
+ formatDriftReport,
18
+ type Journal,
19
+ type JournalEntry,
20
+ loadAppliedMigrations,
21
+ loadJournal,
22
+ loadLatestSnapshot,
23
+ loadPreviousSnapshot,
24
+ loadSnapshot,
25
+ SchemaDriftError,
26
+ type Snapshot,
27
+ type SnapshotTable,
28
+ } from "./schema-drift";
@@ -0,0 +1,149 @@
1
+ // Snapshot-Diff + Projection-Lookup für die Welle-2-Migration-Pipeline.
2
+ //
3
+ // Wenn `kumiko migrate generate` ein neues Drizzle-Snapshot-File erzeugt,
4
+ // vergleichen wir es mit dem vorherigen. Tabellen die schema-changes
5
+ // haben (Spalten dazu/weg, Spalten-Type-Änderung) sind Kandidaten für
6
+ // einen Projection-Rebuild — vorausgesetzt sie gehören zu einer
7
+ // registrierten Projection.
8
+ //
9
+ // Der Lookup geht über getTableName(projection.table) — die Drizzle-
10
+ // public-API für den physischen Tabellen-Namen einer pgTable-Definition.
11
+ // Damit muss niemand Tabellen-Namen doppelt pflegen (Truth liegt in der
12
+ // Projection-Definition).
13
+
14
+ import { getTableName } from "drizzle-orm";
15
+ import type { Registry } from "../engine/types/feature";
16
+ import {
17
+ type ColumnSpec,
18
+ loadJournal,
19
+ loadLatestSnapshot,
20
+ loadPreviousSnapshot,
21
+ type Snapshot,
22
+ } from "./schema-drift";
23
+
24
+ /** Welche Tabellen haben sich zwischen prev und current geändert?
25
+ * Reine Tabellen-Existenz: in current aber nicht in prev → "added".
26
+ * Spalten-Veränderungen: identische Tabelle aber Spalten unterscheiden. */
27
+ export type ChangedTable = {
28
+ readonly fullName: string; // "schema.name" oder einfach "name" wenn empty schema
29
+ readonly tableName: string; // nur "name" für tableName-Lookup
30
+ readonly kind: "added" | "modified" | "removed";
31
+ };
32
+
33
+ export function compareSnapshots(
34
+ prev: Snapshot | null,
35
+ current: Snapshot,
36
+ ): readonly ChangedTable[] {
37
+ const changes: ChangedTable[] = [];
38
+ const prevKeys = new Set(prev ? Object.keys(prev.tables) : []);
39
+ const currentKeys = new Set(Object.keys(current.tables));
40
+
41
+ for (const key of currentKeys) {
42
+ const cur = current.tables[key];
43
+ if (!cur) continue;
44
+ const fullName = cur.schema && cur.schema.length > 0 ? `${cur.schema}.${cur.name}` : cur.name;
45
+ if (!prevKeys.has(key)) {
46
+ changes.push({ fullName, tableName: cur.name, kind: "added" });
47
+ continue;
48
+ }
49
+ const prevTable = prev?.tables[key];
50
+ if (!prevTable) continue;
51
+ if (!sameColumns(prevTable.columns, cur.columns)) {
52
+ changes.push({ fullName, tableName: cur.name, kind: "modified" });
53
+ }
54
+ }
55
+
56
+ for (const key of prevKeys) {
57
+ if (!currentKeys.has(key)) {
58
+ const prevTable = prev?.tables[key];
59
+ if (!prevTable) continue;
60
+ const fullName =
61
+ prevTable.schema && prevTable.schema.length > 0
62
+ ? `${prevTable.schema}.${prevTable.name}`
63
+ : prevTable.name;
64
+ changes.push({ fullName, tableName: prevTable.name, kind: "removed" });
65
+ }
66
+ }
67
+
68
+ return changes;
69
+ }
70
+
71
+ function sameColumns(
72
+ a: Readonly<Record<string, ColumnSpec>>,
73
+ b: Readonly<Record<string, ColumnSpec>>,
74
+ ): boolean {
75
+ const aKeys = Object.keys(a);
76
+ const bKeys = Object.keys(b);
77
+ if (aKeys.length !== bKeys.length) return false;
78
+ for (const key of aKeys) {
79
+ const colA = a[key];
80
+ const colB = b[key];
81
+ if (!colA || !colB) return false;
82
+ if (colA.name !== colB.name) return false;
83
+ if (colA.type !== colB.type) return false;
84
+ if (Boolean(colA.notNull) !== Boolean(colB.notNull)) return false;
85
+ if (Boolean(colA.primaryKey) !== Boolean(colB.primaryKey)) return false;
86
+ // Default-Vergleich bewusst per JSON — Drizzle serialisiert default-
87
+ // expressions als String, das passt für CREATE TABLE-Zwecke.
88
+ if (JSON.stringify(colA.default) !== JSON.stringify(colB.default)) return false;
89
+ }
90
+ return true;
91
+ }
92
+
93
+ /** Index `tableName → projection-name` aus der Registry. Nur Projections
94
+ * mit table-Definition (single-stream + multi-stream-with-table) zählen.
95
+ * Side-effect-only MSPs (table omitted) haben keinen Rebuild-Sinn. */
96
+ export function buildProjectionTableIndex(registry: Registry): ReadonlyMap<string, string> {
97
+ const index = new Map<string, string>();
98
+ for (const [name, def] of registry.getAllProjections()) {
99
+ index.set(getTableName(def.table), name);
100
+ }
101
+ for (const [name, def] of registry.getAllMultiStreamProjections()) {
102
+ if (def.table) index.set(getTableName(def.table), name);
103
+ }
104
+ return index;
105
+ }
106
+
107
+ /** Aus einer Liste geänderter Tabellen die Projection-Namen extrahieren.
108
+ * "removed" ignoriert: gelöschte Projection-Tabellen → die Projection
109
+ * ist auch weg, kein Rebuild-Bedarf. "added" wird zurückgegeben — beim
110
+ * ersten Migrate aus einer leeren DB sind das keine echten Rebuilds
111
+ * (keine historischen Events), aber der Apply-Step filtert das selbst
112
+ * über event-count > 0 heraus. */
113
+ export function projectionsFromChanges(
114
+ changes: readonly ChangedTable[],
115
+ index: ReadonlyMap<string, string>,
116
+ ): readonly string[] {
117
+ const names = new Set<string>();
118
+ for (const change of changes) {
119
+ if (change.kind === "removed") continue;
120
+ const projection = index.get(change.tableName);
121
+ if (projection) names.add(projection);
122
+ }
123
+ return [...names].sort();
124
+ }
125
+
126
+ /** Convenience: gibt für die letzte Migration zurück welche Projections
127
+ * rebuild brauchen würden. Empty wenn das gerade die erste Migration ist
128
+ * (kein vorheriger Snapshot, alle Tabellen "added", aber keine Events). */
129
+ export function detectProjectionsToRebuild(
130
+ registry: Registry,
131
+ migrationsDir: string,
132
+ ): readonly string[] {
133
+ const prev = loadPreviousSnapshot(migrationsDir);
134
+ // Initial migration: nur "added"-Changes, keine historischen Events
135
+ // zum Replayen → kein Rebuild nötig.
136
+ if (prev === null) return [];
137
+ const current = loadLatestSnapshot(migrationsDir);
138
+ const changes = compareSnapshots(prev, current);
139
+ const index = buildProjectionTableIndex(registry);
140
+ return projectionsFromChanges(changes, index);
141
+ }
142
+
143
+ /** Tag des letzten journal-Eintrags — nutzen wir als Marker-File-Name. */
144
+ export function latestMigrationTag(migrationsDir: string): string {
145
+ const journal = loadJournal(migrationsDir);
146
+ const last = journal.entries[journal.entries.length - 1];
147
+ if (!last) throw new Error(`latestMigrationTag: empty journal in ${migrationsDir}`);
148
+ return last.tag;
149
+ }
@@ -0,0 +1,64 @@
1
+ // Rebuild-Marker-File: zur generate-Zeit schreibt der Migration-Generator
2
+ // ein Side-File `<tag>__rebuild.json` neben das SQL-File. Beim apply liest
3
+ // der Apply-Step die Marker für alle neu-applied Migrations und ruft
4
+ // rebuildProjection() für jede gelistete Projection.
5
+ //
6
+ // Format:
7
+ // {
8
+ // "schemaVersion": 1,
9
+ // "migrationTag": "0042_brave_taskmaster",
10
+ // "projections": ["publicstatus:projection:incident-state", ...]
11
+ // }
12
+ //
13
+ // Das File wird zum Migration-File committed und durchläuft Code-Review
14
+ // — die Projection-Rebuild-Liste ist damit sichtbar im PR.
15
+
16
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { resolve } from "node:path";
18
+ import { parseJsonOrThrow } from "../utils/safe-json";
19
+
20
+ const MARKER_VERSION = 1 as const;
21
+
22
+ export type RebuildMarker = {
23
+ readonly schemaVersion: typeof MARKER_VERSION;
24
+ readonly migrationTag: string;
25
+ readonly projections: readonly string[];
26
+ };
27
+
28
+ function markerPath(migrationsDir: string, migrationTag: string): string {
29
+ return resolve(migrationsDir, `${migrationTag}__rebuild.json`);
30
+ }
31
+
32
+ export function writeRebuildMarker(
33
+ migrationsDir: string,
34
+ migrationTag: string,
35
+ projections: readonly string[],
36
+ ): void {
37
+ // skip: leere Projections-Liste → kein Marker-File. Reduziert Noise
38
+ // bei Migrations die keine Projection berühren (Infra-Tabellen, pure
39
+ // Index-Adds). Caller braucht keinen Confirm — File-Existenz ist die
40
+ // Truth-Quelle.
41
+ if (projections.length === 0) return;
42
+ const marker: RebuildMarker = {
43
+ schemaVersion: MARKER_VERSION,
44
+ migrationTag,
45
+ projections: [...projections].sort(),
46
+ };
47
+ writeFileSync(markerPath(migrationsDir, migrationTag), `${JSON.stringify(marker, null, 2)}\n`);
48
+ }
49
+
50
+ export function readRebuildMarker(
51
+ migrationsDir: string,
52
+ migrationTag: string,
53
+ ): RebuildMarker | null {
54
+ const path = markerPath(migrationsDir, migrationTag);
55
+ if (!existsSync(path)) return null;
56
+ const parsed = parseJsonOrThrow<RebuildMarker>(readFileSync(path, "utf-8"), `marker at ${path}`);
57
+ if (parsed.schemaVersion !== MARKER_VERSION) {
58
+ throw new Error(
59
+ `readRebuildMarker: ${path} hat schemaVersion ${parsed.schemaVersion}, ` +
60
+ `erwartet ${MARKER_VERSION}. Kumiko-Version-Mismatch?`,
61
+ );
62
+ }
63
+ return parsed;
64
+ }