@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,37 @@
1
+ import type Redis from "ioredis";
2
+ import { generateId } from "../utils";
3
+ import { RedisKeys } from "./redis-keys";
4
+
5
+ export type DistributedLock = {
6
+ acquire(key: string, options?: { ttlSeconds?: number }): Promise<string | null>;
7
+ release(key: string, token: string): Promise<boolean>;
8
+ };
9
+
10
+ export function createDistributedLock(
11
+ redis: Redis,
12
+ prefix: string = RedisKeys.lock,
13
+ ): DistributedLock {
14
+ // Lua script for atomic check-and-delete (safe Redis server-side eval, not JS eval)
15
+ const releaseScript = `
16
+ if redis.call("get", KEYS[1]) == ARGV[1] then
17
+ return redis.call("del", KEYS[1])
18
+ else
19
+ return 0
20
+ end
21
+ `;
22
+
23
+ return {
24
+ async acquire(key, options = {}) {
25
+ const ttl = options.ttlSeconds ?? 30;
26
+ const token = generateId();
27
+ const result = await redis.set(`${prefix}${key}`, token, "EX", ttl, "NX");
28
+ return result === "OK" ? token : null;
29
+ },
30
+
31
+ async release(key, token) {
32
+ // Atomic: only release if we own the lock (compare token via Lua)
33
+ const result = (await redis.eval(releaseScript, 1, `${prefix}${key}`, token)) as number;
34
+ return result === 1;
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,113 @@
1
+ import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ import type Redis from "ioredis";
3
+ import { RedisKeys } from "./redis-keys";
4
+
5
+ // JSON.stringify turns Date into an ISO string, but DB reads return Date
6
+ // objects. Without a reviver the cache path would yield strings where the
7
+ // DB path yields Dates — consumers would silently see a type mismatch.
8
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
9
+
10
+ function dateReviver(_key: string, value: unknown): unknown {
11
+ if (typeof value === "string" && ISO_DATE.test(value)) {
12
+ const d = new Date(value);
13
+ if (!Number.isNaN(d.getTime())) return d;
14
+ }
15
+ return value;
16
+ }
17
+
18
+ function parseCached(raw: string): Record<string, unknown> | null {
19
+ try {
20
+ return JSON.parse(raw, dateReviver) as Record<string, unknown> | null; // @cast-boundary engine-payload
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export type EntityCache = {
27
+ /** Get a single cached entity. Returns null on miss. */
28
+ get(
29
+ tenantId: TenantId,
30
+ entityName: string,
31
+ id: EntityId,
32
+ ): Promise<Record<string, unknown> | null>;
33
+
34
+ /** Get multiple cached entities. Returns a Map of id → data (misses are absent). */
35
+ mget(
36
+ tenantId: TenantId,
37
+ entityName: string,
38
+ ids: readonly EntityId[],
39
+ ): Promise<Map<EntityId, Record<string, unknown>>>;
40
+
41
+ /** Cache a single entity. */
42
+ set(
43
+ tenantId: TenantId,
44
+ entityName: string,
45
+ id: EntityId,
46
+ data: Record<string, unknown>,
47
+ ): Promise<void>;
48
+
49
+ /** Cache multiple entities at once. */
50
+ mset(
51
+ tenantId: TenantId,
52
+ entityName: string,
53
+ entries: ReadonlyArray<{ id: EntityId; data: Record<string, unknown> }>,
54
+ ): Promise<void>;
55
+
56
+ /** Invalidate a single cached entity. */
57
+ del(tenantId: TenantId, entityName: string, id: EntityId): Promise<void>;
58
+ };
59
+
60
+ export type EntityCacheOptions = {
61
+ ttlSeconds?: number;
62
+ };
63
+
64
+ export function createEntityCache(redis: Redis, options: EntityCacheOptions = {}): EntityCache {
65
+ const ttl = options.ttlSeconds ?? 300;
66
+ const prefix = RedisKeys.entityCache;
67
+
68
+ function cacheKey(tenantId: TenantId, entityName: string, id: EntityId): string {
69
+ return `${prefix}${tenantId}:${entityName}:${id}`;
70
+ }
71
+
72
+ return {
73
+ async get(tenantId, entityName, id) {
74
+ const raw = await redis.get(cacheKey(tenantId, entityName, id));
75
+ if (!raw) return null;
76
+ return parseCached(raw);
77
+ },
78
+
79
+ async mget(tenantId, entityName, ids) {
80
+ if (ids.length === 0) return new Map();
81
+ const keys = ids.map((id) => cacheKey(tenantId, entityName, id));
82
+ const values = await redis.mget(...keys);
83
+
84
+ const result = new Map<EntityId, Record<string, unknown>>();
85
+ for (let i = 0; i < ids.length; i++) {
86
+ const raw = values[i];
87
+ if (raw) {
88
+ const parsed = parseCached(raw);
89
+ if (parsed) result.set(ids[i] as EntityId, parsed);
90
+ }
91
+ }
92
+ return result;
93
+ },
94
+
95
+ async set(tenantId, entityName, id, data) {
96
+ await redis.set(cacheKey(tenantId, entityName, id), JSON.stringify(data), "EX", ttl);
97
+ },
98
+
99
+ async mset(tenantId, entityName, entries) {
100
+ // skip: empty batch, nothing to cache
101
+ if (entries.length === 0) return;
102
+ const pipe = redis.pipeline();
103
+ for (const entry of entries) {
104
+ pipe.set(cacheKey(tenantId, entityName, entry.id), JSON.stringify(entry.data), "EX", ttl);
105
+ }
106
+ await pipe.exec();
107
+ },
108
+
109
+ async del(tenantId, entityName, id) {
110
+ await redis.del(cacheKey(tenantId, entityName, id));
111
+ },
112
+ };
113
+ }
@@ -0,0 +1,108 @@
1
+ import { sql } from "drizzle-orm";
2
+ import type { DbConnection } from "../db/connection";
3
+ import { bigint, index, instant, integer, table as pgTable, primaryKey, text } from "../db/dialect";
4
+ import { tableExists } from "../db/schema-inspection";
5
+ import { pushTables } from "../stack";
6
+
7
+ // Reserved sentinel used in the instance_id column for consumers whose
8
+ // delivery is "shared" — i.e. one cursor across all dispatcher instances
9
+ // (the default, pre-Welle-2.7 behaviour). Per-instance consumers store
10
+ // the concrete instanceId. Postgres PK columns can't be NULL and
11
+ // `NULL = NULL` is UNKNOWN in SQL — a nullable instance_id would break
12
+ // both uniqueness and PK constraints. The boot-validator refuses to
13
+ // start with KUMIKO_INSTANCE_ID === SHARED_INSTANCE_SENTINEL so the
14
+ // sentinel can never collide with a real instance identifier.
15
+ export const SHARED_INSTANCE_SENTINEL = "__shared__";
16
+
17
+ // Framework-level state per event-consumer-shard. A "consumer" is anything
18
+ // that walks the events-table via a persistent cursor: system consumers
19
+ // (SSE, Search) and feature multiStreamProjections (async, cross-aggregate).
20
+ //
21
+ // One row per (consumer name, instance_id) shard. Shared-delivery consumers
22
+ // have exactly one row with instance_id = SHARED_INSTANCE_SENTINEL — this
23
+ // preserves the pre-Welle-2.7 single-cursor semantic unchanged. Per-instance
24
+ // consumers get N rows (one per dispatcher instance), each with its own
25
+ // cursor — used by SSE so every API process pushes the same events to its
26
+ // own clients without a pub/sub transport. Read by the event-dispatcher
27
+ // (cursor + locking), surfaced by the CLI for ops inspection.
28
+ //
29
+ // Columns:
30
+ // - name: consumer's qualified identifier, e.g. "system:consumer:sse" or
31
+ // "my-feature:consumer:analytics". Matches the qualified-name convention
32
+ // so two features can't accidentally collide on the same consumer name.
33
+ // - instanceId: SHARED_INSTANCE_SENTINEL for shared-delivery consumers;
34
+ // concrete process-local identifier (ServerOptions.instanceId, defaults
35
+ // to KUMIKO_INSTANCE_ID or a boot-time UUID) for per-instance consumers.
36
+ // - lastProcessedEventId: bigserial `events.id` of the most recent event
37
+ // this shard finished handling. Dispatcher reads events WHERE id > this.
38
+ // - status / attempts / lastError / updatedAt — per shard.
39
+ //
40
+ // Composite PK (name, instance_id): Postgres requires NOT NULL on all PK
41
+ // columns, and `NULL = NULL` is UNKNOWN in SQL — nullable instance_id would
42
+ // break uniqueness for shared rows. Sentinel avoids both hazards with one
43
+ // uniform column shape.
44
+ //
45
+ // CAUTION (retention-guard + scale-down):
46
+ // pruneEvents refuses to delete past MIN(lastProcessedEventId) across ALL
47
+ // shards. A decommissioned instance leaves its row behind at its last
48
+ // cursor — prune is then pinned indefinitely. Before scaling down,
49
+ // delete stale per-instance shards:
50
+ // DELETE FROM kumiko_event_consumers WHERE instance_id = '<decommissioned>'
51
+ // TODO: auto-cleanup via heartbeat-liveness — follow-up, not in v1.
52
+ //
53
+ // The default(sql`0`) on lastProcessedEventId mirrors projection-state.ts:
54
+ // drizzle-kit's JSON snapshot generator can't serialise a bigint literal, so
55
+ // the server-side default is specified as raw SQL instead of .default(0n).
56
+ export const eventConsumerStateTable = pgTable(
57
+ "kumiko_event_consumers",
58
+ {
59
+ name: text("name").notNull(),
60
+ instanceId: text("instance_id").notNull().default(SHARED_INSTANCE_SENTINEL),
61
+ lastProcessedEventId: bigint("last_processed_event_id", { mode: "bigint" })
62
+ .notNull()
63
+ .default(sql`0`),
64
+ status: text("status").notNull().default("idle"),
65
+ attempts: integer("attempts").notNull().default(0),
66
+ lastError: text("last_error"),
67
+ updatedAt: instant("updated_at", { precision: 3 }).notNull().default(sql`now()`),
68
+ },
69
+ (t) => ({
70
+ pk: primaryKey({ columns: [t.name, t.instanceId] }),
71
+ statusIdx: index("kumiko_event_consumers_status_idx").on(t.status),
72
+ }),
73
+ );
74
+
75
+ // Object-const form lets call sites write `ConsumerStatuses.disabled` instead
76
+ // of the raw string, which keeps status checks refactor-safe. The runtime
77
+ // value on each field is the same string the DB stores — no mapping needed.
78
+ export const ConsumerStatuses = {
79
+ idle: "idle",
80
+ processing: "processing",
81
+ dead: "dead",
82
+ disabled: "disabled",
83
+ } as const;
84
+ export type ConsumerStatus = (typeof ConsumerStatuses)[keyof typeof ConsumerStatuses];
85
+
86
+ /**
87
+ * @deprecated Use `ConsumerStatuses` (object form) or the `ConsumerStatus`
88
+ * union type. The tuple form is kept as a back-compat alias for callers
89
+ * that were using it with `z.enum(...)` or runtime iteration — scheduled
90
+ * for removal once downstream consumers have migrated.
91
+ */
92
+ export const CONSUMER_STATUSES = [
93
+ "idle",
94
+ "processing",
95
+ "dead",
96
+ "disabled",
97
+ ] as const satisfies readonly ConsumerStatus[];
98
+
99
+ // Idempotent bootstrap. Called by setupTestStack + production boot path —
100
+ // same pattern as createProjectionStateTable / createEventsTable. If the
101
+ // table is already present (second stack in the same test DB, prod boot
102
+ // after migration), skip cleanly.
103
+ //
104
+ export async function createEventConsumerStateTable(db: DbConnection): Promise<void> {
105
+ // skip: table already exists — bootstrap is called from multiple paths
106
+ if (await tableExists(db, "public.kumiko_event_consumers")) return;
107
+ await pushTables(db, { kumikoEventConsumers: eventConsumerStateTable });
108
+ }
@@ -0,0 +1,23 @@
1
+ import type Redis from "ioredis";
2
+ import { RedisKeys } from "./redis-keys";
3
+
4
+ export type EventDedup = {
5
+ /**
6
+ * Atomically try to acquire processing rights for an eventId.
7
+ * Returns true if this is the first call (proceed), false if already processed (skip).
8
+ */
9
+ tryAcquire(eventId: string): Promise<boolean>;
10
+ };
11
+
12
+ export function createEventDedup(redis: Redis, options: { ttlSeconds?: number } = {}): EventDedup {
13
+ const ttl = options.ttlSeconds ?? 300;
14
+ const prefix = RedisKeys.eventDedup;
15
+
16
+ return {
17
+ async tryAcquire(eventId) {
18
+ // SET NX = atomic check-and-set: only succeeds if key does not exist
19
+ const result = await redis.set(`${prefix}${eventId}`, "1", "EX", ttl, "NX");
20
+ return result === "OK";
21
+ },
22
+ };
23
+ }