@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,255 @@
1
+ // Forms-Schema metadata — describes how a FeaturePattern is rendered as
2
+ // a Designer form, fed to an LLM as a JSON-Schema, or surfaced in the
3
+ // MCP-Server tool list. One schema, three consumers — that's the whole
4
+ // point of factoring this out of the hand-rolled C5 skeleton.
5
+ //
6
+ // **NOT a DSL.** The Source-of-Truth for a feature stays
7
+ // `defineFeature.ts` — the AST-visitor (feature-ast/parse.ts) reads it
8
+ // and the renderer (feature-ast/render.ts) writes it. This file is
9
+ // pure metadata for UI / LLM-prompt rendering: how to display the
10
+ // already-parsed FeaturePattern in a form. No alternative parser, no
11
+ // alternative syntax, no second canonical representation.
12
+ //
13
+ // **Vokabular** (12 input-types):
14
+ //
15
+ // text — single-line string (name / label / id)
16
+ // textarea — multi-line string (description / SQL / readme)
17
+ // number — numeric (version / fromVersion / rateLimit.limit)
18
+ // boolean — checkbox (softDelete / default / openToAll)
19
+ // select — closed enum (idType / hookType / method)
20
+ // string-list — readonly string[] (featureNames / roles)
21
+ // code-block — opaque TS source-span, read-only in the form
22
+ // (schemaSource / handlerBody / fnBody)
23
+ // entity-fields-editor — per-row editor for EntityDefinition.fields
24
+ // key-value-map — generic { [key]: structured-value } editor
25
+ // (config.keys / translations.keys / applyBodies)
26
+ // discriminated-union — tagged sub-form (AccessRule: roles vs openToAll)
27
+ // entity-ref — string with autocomplete against the registry
28
+ // json-readonly — opaque pretty-printed JSON (Unknown / extension)
29
+ //
30
+ // **Path notation:** `path` references the value's location inside the
31
+ // FeaturePattern object using dot-separated keys (`definition.fields`,
32
+ // `access.roles`, `templates.email`). Numeric indices use the bracket
33
+ // form (`columns.0`). The Designer/LLM uses paths for read & write —
34
+ // the patcher (C2) consumes pattern-level updates only, so paths are
35
+ // purely a UI concern at this stage.
36
+
37
+ import type { FeaturePatternKind } from "../feature-ast/patterns";
38
+
39
+ // =============================================================================
40
+ // Field input types
41
+ // =============================================================================
42
+
43
+ export type FormInputType =
44
+ | "text"
45
+ | "textarea"
46
+ | "number"
47
+ | "boolean"
48
+ | "select"
49
+ | "string-list"
50
+ | "code-block"
51
+ | "entity-fields-editor"
52
+ | "key-value-map"
53
+ | "discriminated-union"
54
+ | "entity-ref"
55
+ | "json-readonly";
56
+
57
+ // =============================================================================
58
+ // Field spec — one entry per editable property of a pattern
59
+ // =============================================================================
60
+
61
+ /**
62
+ * Bilingual labels — Designer renders the user's locale, falls back to
63
+ * `en`. AI-Builder uses `en` exclusively (cheaper prompts, single
64
+ * grounding language for the model).
65
+ */
66
+ export type FormFieldLabel = {
67
+ readonly en: string;
68
+ readonly de?: string;
69
+ };
70
+
71
+ /**
72
+ * Common shape every field carries — the input-specific fields are added
73
+ * by the discriminated subtypes below.
74
+ */
75
+ type FormFieldBase = {
76
+ /**
77
+ * Dot/bracket-path inside the FeaturePattern. e.g. "entityName",
78
+ * "definition.fields", "access.roles", "templates.email".
79
+ */
80
+ readonly path: string;
81
+ readonly label: FormFieldLabel;
82
+ /**
83
+ * Short hint shown below the input. Optional — keep terse, the
84
+ * Designer / LLM is the doc consumer, not the casual reader.
85
+ */
86
+ readonly hint?: FormFieldLabel;
87
+ /**
88
+ * Whether the form should refuse to submit when this field is empty.
89
+ * Defaults to `false`. Validation runs *before* the patch hits the
90
+ * file — caller-side guard.
91
+ */
92
+ readonly required?: boolean;
93
+ /**
94
+ * Read-only fields are surfaced for context but never edited (e.g.
95
+ * `kind` discriminator, opaque source-spans on mixed patterns).
96
+ */
97
+ readonly readOnly?: boolean;
98
+ };
99
+
100
+ export type TextField = FormFieldBase & {
101
+ readonly input: "text";
102
+ readonly placeholder?: string;
103
+ readonly maxLength?: number;
104
+ readonly pattern?: string;
105
+ };
106
+
107
+ export type TextareaField = FormFieldBase & {
108
+ readonly input: "textarea";
109
+ readonly placeholder?: string;
110
+ readonly rows?: number;
111
+ };
112
+
113
+ export type NumberField = FormFieldBase & {
114
+ readonly input: "number";
115
+ readonly min?: number;
116
+ readonly max?: number;
117
+ readonly step?: number;
118
+ };
119
+
120
+ export type BooleanField = FormFieldBase & {
121
+ readonly input: "boolean";
122
+ };
123
+
124
+ export type SelectOption = {
125
+ readonly value: string;
126
+ readonly label: FormFieldLabel;
127
+ };
128
+
129
+ export type SelectField = FormFieldBase & {
130
+ readonly input: "select";
131
+ readonly options: readonly SelectOption[];
132
+ };
133
+
134
+ export type StringListField = FormFieldBase & {
135
+ readonly input: "string-list";
136
+ /** Per-item placeholder, e.g. "feature-name". */
137
+ readonly itemPlaceholder?: string;
138
+ };
139
+
140
+ export type CodeBlockField = FormFieldBase & {
141
+ readonly input: "code-block";
142
+ /**
143
+ * The source language for syntax highlighting. The skeleton renders
144
+ * everything as plain TS; later iterations can branch on `zod` /
145
+ * `tsx` for dedicated highlighters.
146
+ */
147
+ readonly language: "typescript" | "tsx" | "zod";
148
+ };
149
+
150
+ /**
151
+ * Editor for `EntityDefinition.fields` — a row per field with name,
152
+ * type-discriminator, plus type-specific knobs. Modeled as a single
153
+ * input-type because the structure is too rich for a generic
154
+ * key-value-map (each row's right-hand side is itself a discriminated
155
+ * union over FieldDefinition variants).
156
+ */
157
+ export type EntityFieldsEditorField = FormFieldBase & {
158
+ readonly input: "entity-fields-editor";
159
+ };
160
+
161
+ /**
162
+ * Generic `{ [key]: value }` editor — value-shape is described by an
163
+ * inner FormSchema applied to each entry. Used for config-keys,
164
+ * translations, projection.applyBodies, notification.templates.
165
+ */
166
+ export type KeyValueMapField = FormFieldBase & {
167
+ readonly input: "key-value-map";
168
+ /** Placeholder for new keys. */
169
+ readonly keyPlaceholder?: string;
170
+ /** What input renders for each value. Recursive — keep it simple. */
171
+ readonly valueInput: FormInputType;
172
+ };
173
+
174
+ export type DiscriminatedUnionField = FormFieldBase & {
175
+ readonly input: "discriminated-union";
176
+ /** Tag-property name (e.g. "type" for hook-target, "kind" for relation). */
177
+ readonly discriminator: string;
178
+ /** Each branch carries its own field-list. */
179
+ readonly variants: ReadonlyArray<{
180
+ readonly tag: string;
181
+ readonly label: FormFieldLabel;
182
+ readonly fields: readonly FormFieldSpec[];
183
+ }>;
184
+ };
185
+
186
+ export type EntityRefField = FormFieldBase & {
187
+ readonly input: "entity-ref";
188
+ /**
189
+ * If true, the input also accepts cross-feature references like
190
+ * `auth:event:loggedIn`. Default is feature-local only.
191
+ */
192
+ readonly allowQualified?: boolean;
193
+ };
194
+
195
+ export type JsonReadonlyField = FormFieldBase & {
196
+ readonly input: "json-readonly";
197
+ };
198
+
199
+ export type FormFieldSpec =
200
+ | TextField
201
+ | TextareaField
202
+ | NumberField
203
+ | BooleanField
204
+ | SelectField
205
+ | StringListField
206
+ | CodeBlockField
207
+ | EntityFieldsEditorField
208
+ | KeyValueMapField
209
+ | DiscriminatedUnionField
210
+ | EntityRefField
211
+ | JsonReadonlyField;
212
+
213
+ // =============================================================================
214
+ // Pattern schema — top-level metadata for one FeaturePattern kind
215
+ // =============================================================================
216
+
217
+ /**
218
+ * High-level grouping the Designer uses for navigation: data shapes
219
+ * (entity / relation), behaviour (handler / hook), UI (screen / nav),
220
+ * meta (config / metric / secret), background (job / projection),
221
+ * cross-cutting (notification / authClaims / httpRoute), advanced
222
+ * (extendsRegistrar / unknown). Order in the Designer panel is alpha
223
+ * inside a category.
224
+ */
225
+ export type PatternCategory =
226
+ | "data"
227
+ | "behaviour"
228
+ | "ui"
229
+ | "meta"
230
+ | "background"
231
+ | "cross-cutting"
232
+ | "advanced";
233
+
234
+ export type PatternFormSchema = {
235
+ readonly kind: FeaturePatternKind;
236
+ readonly label: FormFieldLabel;
237
+ /** Short blurb shown in the Designer's "add new pattern" dialog. */
238
+ readonly summary: FormFieldLabel;
239
+ readonly category: PatternCategory;
240
+ /**
241
+ * Editability matches feature-ast's `getEditability()`:
242
+ * - "static" → fully form-driven, no closures
243
+ * - "mixed" → header is form, body is opaque code-block
244
+ * - "opaque" → entire pattern is read-only
245
+ */
246
+ readonly editability: "static" | "mixed" | "opaque";
247
+ /**
248
+ * Singleton kinds appear at most once per feature (requires,
249
+ * toggleable, config, …). The Designer hides "Add" once the
250
+ * singleton is present.
251
+ */
252
+ readonly singleton?: boolean;
253
+ /** Ordered list of editable fields. */
254
+ readonly fields: readonly FormFieldSpec[];
255
+ };
@@ -0,0 +1,85 @@
1
+ import { eq } from "drizzle-orm";
2
+ import type { DbRunner } from "../db/connection";
3
+ import type { StoredEvent } from "../event-store/event-store";
4
+ import type { MultiStreamApplyContext } from "../pipeline/multi-stream-apply-context";
5
+ import type { MultiStreamApplyFn, ProjectionTable, SingleStreamApplyFn } from "./types/projection";
6
+
7
+ // Typed-Apply-Helper für r.projection.apply: erlaubt per-event-type
8
+ // typed event.payload-Access ohne SingleStreamApplyFn-Generic durch die
9
+ // ganze ProjectionDefinition propagieren zu müssen.
10
+ //
11
+ // Der Helper ist ein purer Type-Vehikel — zur Laufzeit identitäts-fn:
12
+ //
13
+ // apply: {
14
+ // "user.created": defineApply<UserCreatedPayload>(async (event, tx) => {
15
+ // // event.payload ist UserCreatedPayload, nicht Record<string, unknown>
16
+ // await tx.insert(usersTable).values({ id: event.aggregateId, ...event.payload });
17
+ // }),
18
+ // }
19
+ //
20
+ // Default-Generic = Record<string, unknown> behält rückwärtskompatibles
21
+ // Verhalten für Apply-Handler die ohne Type-Argument geschrieben sind.
22
+ export function defineApply<TPayload = Record<string, unknown>>(
23
+ fn: (event: StoredEvent<TPayload>, tx: DbRunner) => Promise<void>,
24
+ ): SingleStreamApplyFn {
25
+ // @cast-boundary engine-bridge — typed user-fn → erased internal storage
26
+ return fn as SingleStreamApplyFn;
27
+ }
28
+
29
+ // Pendant für r.multiStreamProjection.apply — bekommt zusätzlich ctx.
30
+ export function defineMspApply<TPayload = Record<string, unknown>>(
31
+ fn: (event: StoredEvent<TPayload>, tx: DbRunner, ctx: MultiStreamApplyContext) => Promise<void>,
32
+ ): MultiStreamApplyFn {
33
+ // @cast-boundary engine-bridge — typed user-fn → erased internal storage
34
+ return fn as MultiStreamApplyFn;
35
+ }
36
+
37
+ // UPDATE <projection-table> SET <fields> WHERE id = event.aggregateId.
38
+ // The "event drives one row by aggregate id" shape is what 90 % of state-
39
+ // machine projections look like: every status-change event maps to a single
40
+ // column update on the projection's own row. Pass a literal object for
41
+ // fixed values, or a reducer-style function when the new value comes from
42
+ // the event payload.
43
+ //
44
+ // r.projection({
45
+ // name: "invoice-status",
46
+ // source: "invoice",
47
+ // table: invoiceTable,
48
+ // apply: {
49
+ // [INVOICE.sent]: setFields(invoiceTable, { status: "sent" }),
50
+ // [INVOICE.statusForced]: setFields(invoiceTable, (e) => {
51
+ // const p = e.payload as { newStatus: InvoiceStatus };
52
+ // return { status: p.newStatus };
53
+ // }),
54
+ // },
55
+ // });
56
+ //
57
+ // The projection table must expose an `id` column typed as the aggregate's
58
+ // stream id — every executor-managed aggregate table does. Use a raw apply
59
+ // function when the update targets a different key or needs JOIN/SET logic.
60
+ export function setFields(
61
+ table: ProjectionTable,
62
+ fields:
63
+ | Record<string, unknown>
64
+ | ((event: Parameters<SingleStreamApplyFn>[0]) => Record<string, unknown>),
65
+ ): SingleStreamApplyFn {
66
+ const idCol = (table as Record<string, unknown>)["id"]; // @cast-boundary dynamic-key
67
+ if (!idCol) {
68
+ throw new Error(
69
+ "setFields: projection table has no 'id' column — pass a custom apply function for tables keyed on another column.",
70
+ );
71
+ }
72
+ return async (event, tx) => {
73
+ const values = typeof fields === "function" ? fields(event) : fields;
74
+ // ProjectionTable erases its column shape on purpose (the framework
75
+ // does not know user table shapes). Drizzle's tx.update().set() is
76
+ // strict about the concrete row, so we feed it the erased value; the
77
+ // type-safety guarantee for `values` lives at the setFields call-site.
78
+ // biome-ignore lint/suspicious/noExplicitAny: see note above.
79
+ const set = values as any;
80
+ await tx
81
+ .update(table)
82
+ .set(set)
83
+ .where(eq(idCol as never, event.aggregateId)); // @cast-boundary db-operator
84
+ };
85
+ }
@@ -0,0 +1,122 @@
1
+ // Qualified Name (QN) system: unified naming pattern for all framework identifiers.
2
+ // Pattern: "scope:type:name" — colon-separated, kebab-case segments.
3
+ //
4
+ // The name part can contain colons for sub-structure (e.g. "task:create").
5
+ // Scope and type are always single segments. Name is everything after the second colon.
6
+ //
7
+ // Examples:
8
+ // "tasks:write:task:create" → scope=tasks, type=write, name=task:create
9
+ // "system:hook:search-index" → scope=system, type=hook, name=search-index
10
+ // "billing:notify:invoice-sent" → scope=billing, type=notify, name=invoice-sent
11
+
12
+ // Built-in QN types used by the framework.
13
+ // Features can define additional types — the type segment is validated for format only, not membership.
14
+ export const QnTypes = {
15
+ write: "write",
16
+ query: "query",
17
+ hook: "hook",
18
+ job: "job",
19
+ notify: "notify",
20
+ event: "event",
21
+ channel: "channel",
22
+ config: "config",
23
+ secret: "secret",
24
+ screen: "screen",
25
+ nav: "nav",
26
+ } as const;
27
+
28
+ export type BuiltinQnType = (typeof QnTypes)[keyof typeof QnTypes];
29
+
30
+ // QnType is string — framework types are predefined, but features can use custom types.
31
+ export type QnType = string;
32
+
33
+ const QN_SEGMENT = /^[a-z][a-z0-9-]*$/;
34
+
35
+ export type ParsedQn = {
36
+ scope: string;
37
+ type: string;
38
+ name: string; // may contain colons (e.g. "task:create")
39
+ };
40
+
41
+ function validateSegment(value: string, label: string, context?: string): void {
42
+ if (!QN_SEGMENT.test(value)) {
43
+ const suffix = context ? ` in "${context}"` : "";
44
+ throw new Error(`Invalid QN ${label} "${value}"${suffix}: must match ${QN_SEGMENT}`);
45
+ }
46
+ }
47
+
48
+ // Build a qualified name from parts. Validates all segments.
49
+ // The name can contain colons for sub-structure (e.g. "task:create").
50
+ export function qn(scope: string, type: QnType, name: string): string {
51
+ validateSegment(scope, "scope");
52
+ validateSegment(type, "type");
53
+ for (const part of name.split(":")) {
54
+ validateSegment(part, "name");
55
+ }
56
+ return `${scope}:${type}:${name}`;
57
+ }
58
+
59
+ // Parse a qualified name string into its parts.
60
+ // Splits on the first two colons — everything after is the name (which may contain colons).
61
+ export function parseQn(value: string): ParsedQn {
62
+ const first = value.indexOf(":");
63
+ if (first < 0)
64
+ throw new Error(`Invalid QN "${value}": expected at least 3 colon-separated segments`);
65
+ const second = value.indexOf(":", first + 1);
66
+ if (second < 0)
67
+ throw new Error(`Invalid QN "${value}": expected at least 3 colon-separated segments`);
68
+
69
+ const scope = value.slice(0, first);
70
+ const type = value.slice(first + 1, second);
71
+ const name = value.slice(second + 1);
72
+
73
+ validateSegment(scope, "scope", value);
74
+ validateSegment(type, "type", value);
75
+ for (const part of name.split(":")) {
76
+ validateSegment(part, "name", value);
77
+ }
78
+
79
+ return { scope, type, name };
80
+ }
81
+
82
+ // Check if a string is a valid qualified name.
83
+ export function isValidQn(value: string): boolean {
84
+ try {
85
+ parseQn(value);
86
+ return true;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ // True if `name` is a valid QN segment (lowercase letters, digits, dashes;
93
+ // starts with a letter). Same rule as `QN_SEGMENT` — kept public so feature
94
+ // registration can reject bad names at the source instead of at registry-boot.
95
+ export function isKebabSegment(name: string): boolean {
96
+ return QN_SEGMENT.test(name);
97
+ }
98
+
99
+ // Build a fully-qualified entity name from a feature name + QN type + short
100
+ // name, running both names through toKebab first. This is the canonical
101
+ // "how the registry qualifies things" helper — both createRegistry and
102
+ // validateBoot need it, and they previously duplicated the literal
103
+ // `qn(toKebab(feature), type, toKebab(short))` expression. Keeping it here
104
+ // means a single place to change if the qualification rule ever evolves.
105
+ export function qualifyEntityName(featureName: string, type: QnType, shortName: string): string {
106
+ return qn(toKebab(featureName), type, toKebab(shortName));
107
+ }
108
+
109
+ // Convert camelCase or dot.separated strings to kebab-case.
110
+ // "task.create" → "task-create"
111
+ // "ticketAssigned" → "ticket-assigned"
112
+ // "billing-period.create" → "billing-period-create"
113
+ // "monthlyReport" → "monthly-report"
114
+ // Already kebab-case → unchanged
115
+ // Colons are preserved: "task:create" → "task:create"
116
+ export function toKebab(input: string): string {
117
+ return input
118
+ .replace(/\./g, "-") // dots → dashes
119
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2") // consecutive uppercase: SSEBroadcast → SSE-Broadcast
120
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2") // camelCase boundaries: ticketAssigned → ticket-Assigned
121
+ .toLowerCase();
122
+ }
@@ -0,0 +1,31 @@
1
+ import type { ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, SessionUser } from "./types";
2
+
3
+ // Read a feature-declared claim from a SessionUser.
4
+ //
5
+ // The generic is inferred from the handle's `type` literal, so call sites
6
+ // get the right narrowed return type without a cast:
7
+ //
8
+ // const DriverClaims = r.claimKeys(...);
9
+ // const teamId = readClaim(user, DriverClaims.teamId); // string | undefined
10
+ // const regionId = readClaim(user, DriverClaims.regionId); // number | undefined
11
+ //
12
+ // Returns undefined when:
13
+ // - user.claims is absent entirely (hook system didn't populate anything)
14
+ // - the specific handle's qualified name isn't in user.claims (feature's
15
+ // r.authClaims hook didn't return this inner-key for this user)
16
+ //
17
+ // The cast is unchecked. The declared handle type is a contract between the
18
+ // feature's r.claimKey + r.authClaims return; the resolver's runtime check
19
+ // flags drift with a warning, but readClaim itself trusts the declaration.
20
+ // If you need schema-level validation of a claim value, wrap it with zod
21
+ // at the call-site.
22
+ export function readClaim<T extends ClaimKeyType>(
23
+ user: SessionUser,
24
+ handle: ClaimKeyHandle<T>,
25
+ ): ClaimKeyJsType<T> | undefined {
26
+ const claims = user.claims;
27
+ if (!claims) return undefined;
28
+ const raw = claims[handle.name];
29
+ if (raw === undefined || raw === null) return undefined;
30
+ return raw as ClaimKeyJsType<T>;
31
+ }