@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,315 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { createTenantConfig, createUserConfig } from "../config-helpers";
3
+ import { type ClampInfo, resolveConfigOrParam } from "../resolve-config-or-param";
4
+ import type {
5
+ ConfigAccessor,
6
+ ConfigKeyDefinition,
7
+ ConfigKeyHandle,
8
+ ConfigKeyType,
9
+ ConfigValue,
10
+ Registry,
11
+ } from "../types";
12
+
13
+ // Tests build keydefs through the public factories (createTenantConfig etc.)
14
+ // — identical to what a feature-dev writes in r.config. Hand-rolled
15
+ // ConfigKeyDefinition literals are only used where we deliberately bypass
16
+ // the factory's compile-time guards (defence-in-depth tests).
17
+ type KeyEntry = ConfigKeyDefinition<ConfigKeyType>;
18
+
19
+ // Minimal ctx stub: just enough for resolveConfigOrParam. The real ctx has
20
+ // a ConfigAccessor that hits the DB; here we pass a mock that returns a
21
+ // deterministic fallback value so we can distinguish "param used" from
22
+ // "config fallback used" in assertions.
23
+ function makeCtx(entries: Record<string, { def: KeyEntry; fallback: unknown }>) {
24
+ const registry = {
25
+ getConfigKey: (key: string) => entries[key]?.def,
26
+ } as unknown as Registry;
27
+
28
+ const configFn = vi.fn(async <T extends ConfigKeyType>(handle: ConfigKeyHandle<T>) => {
29
+ return entries[handle.name]?.fallback as ConfigValue<T> | undefined;
30
+ });
31
+
32
+ // Cast to the overloaded ConfigAccessor — the test only ever calls the
33
+ // handle-overload, so the missing string-overload on the mock is moot.
34
+ // The double-cast keeps the runtime mock untouched while satisfying the
35
+ // structural check.
36
+ const config = configFn as unknown as ConfigAccessor;
37
+
38
+ return {
39
+ ctx: { config, registry },
40
+ configFn,
41
+ };
42
+ }
43
+
44
+ function handleFor<T extends ConfigKeyType>(name: string, type: T): ConfigKeyHandle<T> {
45
+ return { name, type };
46
+ }
47
+
48
+ describe("resolveConfigOrParam — number with bounds", () => {
49
+ const numberDef = createTenantConfig("number", {
50
+ default: 10,
51
+ bounds: { min: 1, max: 100 },
52
+ allowPerRequest: true,
53
+ });
54
+
55
+ test("paramValue inside bounds returns param as-is", async () => {
56
+ const { ctx } = makeCtx({ k: { def: numberDef, fallback: 10 } });
57
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 50)).toBe(50);
58
+ });
59
+
60
+ test("paramValue below min is clamped up to min", async () => {
61
+ const { ctx } = makeCtx({ k: { def: numberDef, fallback: 10 } });
62
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), -5)).toBe(1);
63
+ });
64
+
65
+ test("paramValue above max is clamped down to max", async () => {
66
+ const { ctx } = makeCtx({ k: { def: numberDef, fallback: 10 } });
67
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999)).toBe(100);
68
+ });
69
+
70
+ test("string numbers are coerced and clamped", async () => {
71
+ const { ctx } = makeCtx({ k: { def: numberDef, fallback: 10 } });
72
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), "42")).toBe(42);
73
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), "9999")).toBe(100);
74
+ });
75
+
76
+ test("NaN / Infinity / non-numeric strings fall back to config", async () => {
77
+ const { ctx, configFn } = makeCtx({ k: { def: numberDef, fallback: 10 } });
78
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), "abc")).toBe(10);
79
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), NaN)).toBe(10);
80
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), Infinity)).toBe(10);
81
+ expect(configFn).toHaveBeenCalled();
82
+ });
83
+
84
+ test("undefined / null / empty string → config fallback", async () => {
85
+ const { ctx, configFn } = makeCtx({ k: { def: numberDef, fallback: 10 } });
86
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), undefined)).toBe(10);
87
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), null)).toBe(10);
88
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), "")).toBe(10);
89
+ expect(configFn).toHaveBeenCalledTimes(3);
90
+ });
91
+
92
+ test("number without bounds is passed through unchanged", async () => {
93
+ const { bounds: _bounds, ...noBoundsDef } = numberDef;
94
+ const { ctx } = makeCtx({ k: { def: noBoundsDef, fallback: 10 } });
95
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 999_999)).toBe(999_999);
96
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), -999)).toBe(-999);
97
+ });
98
+ });
99
+
100
+ describe("resolveConfigOrParam — boolean", () => {
101
+ const boolDef = createUserConfig("boolean", {
102
+ default: false,
103
+ allowPerRequest: true,
104
+ });
105
+
106
+ test("boolean passed through", async () => {
107
+ const { ctx } = makeCtx({ k: { def: boolDef, fallback: false } });
108
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), true)).toBe(true);
109
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), false)).toBe(false);
110
+ });
111
+
112
+ test("'true'/'1' parsed as true, anything else as false", async () => {
113
+ const { ctx } = makeCtx({ k: { def: boolDef, fallback: false } });
114
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), "true")).toBe(true);
115
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), "TRUE")).toBe(true);
116
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), "1")).toBe(true);
117
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), "false")).toBe(false);
118
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), "nonsense")).toBe(false);
119
+ });
120
+
121
+ test("undefined → config fallback", async () => {
122
+ const { ctx } = makeCtx({ k: { def: boolDef, fallback: true } });
123
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "boolean"), undefined)).toBe(true);
124
+ });
125
+ });
126
+
127
+ describe("resolveConfigOrParam — select (option whitelist)", () => {
128
+ const selectDef = createTenantConfig("select", {
129
+ default: "light",
130
+ options: ["light", "dark", "auto"],
131
+ allowPerRequest: true,
132
+ });
133
+
134
+ test("valid option returns the option", async () => {
135
+ const { ctx } = makeCtx({ k: { def: selectDef, fallback: "light" } });
136
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "select"), "dark")).toBe("dark");
137
+ });
138
+
139
+ test("invalid option falls back to configured value", async () => {
140
+ const { ctx, configFn } = makeCtx({ k: { def: selectDef, fallback: "light" } });
141
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "select"), "purple")).toBe("light");
142
+ expect(configFn).toHaveBeenCalled();
143
+ });
144
+ });
145
+
146
+ describe("resolveConfigOrParam — text (defence-in-depth lock)", () => {
147
+ // Two layers protect text keys:
148
+ // 1. allowPerRequest can never be true for text (type-level + boot-check).
149
+ // 2. Even if someone smuggles in allowPerRequest=true via hand-rolled
150
+ // config, the resolver's text-case throws as a second barrier.
151
+
152
+ const textDefNoOptIn = createTenantConfig("text", { default: "default" });
153
+
154
+ test("undefined paramValue returns config value (normal read still works)", async () => {
155
+ const { ctx } = makeCtx({ k: { def: textDefNoOptIn, fallback: "default" } });
156
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "text"), undefined)).toBe("default");
157
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "text"), null)).toBe("default");
158
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "text"), "")).toBe("default");
159
+ });
160
+
161
+ test("paramValue on text key without opt-in throws (layer 1: allowPerRequest gate)", async () => {
162
+ const { ctx } = makeCtx({ k: { def: textDefNoOptIn, fallback: "default" } });
163
+ await expect(resolveConfigOrParam(ctx, handleFor("k", "text"), "custom")).rejects.toThrow(
164
+ /per-request override not enabled/i,
165
+ );
166
+ });
167
+
168
+ test("hand-rolled text key with allowPerRequest=true still throws (layer 2: text-specific lock)", async () => {
169
+ // Type-level guard rejects this declaration; boot-validator would too.
170
+ // But if someone force-casts past both, the resolver must still refuse.
171
+ // Hand-rolled spread is the only way to get this shape past the
172
+ // factory's compile-time never-type for allowPerRequest on text.
173
+ const forcedTextDef: KeyEntry = { ...textDefNoOptIn, allowPerRequest: true };
174
+ const { ctx } = makeCtx({ k: { def: forcedTextDef, fallback: "default" } });
175
+ await expect(resolveConfigOrParam(ctx, handleFor("k", "text"), "custom")).rejects.toThrow(
176
+ /not allowed for type="text"/i,
177
+ );
178
+ });
179
+
180
+ test("attack-like strings are always rejected (documents threat model)", async () => {
181
+ const { ctx } = makeCtx({ k: { def: textDefNoOptIn, fallback: "default" } });
182
+ await expect(
183
+ resolveConfigOrParam(ctx, handleFor("k", "text"), "<script>alert(1)</script>"),
184
+ ).rejects.toThrow();
185
+ await expect(
186
+ resolveConfigOrParam(ctx, handleFor("k", "text"), "'; DROP TABLE users; --"),
187
+ ).rejects.toThrow();
188
+ });
189
+ });
190
+
191
+ describe("resolveConfigOrParam — allowPerRequest opt-in (deny-by-default)", () => {
192
+ // No allowPerRequest → any paramValue should be rejected.
193
+ const numberDefNoOptIn = createTenantConfig("number", {
194
+ default: 10,
195
+ bounds: { min: 1, max: 100 },
196
+ });
197
+
198
+ test("paramValue on a key WITHOUT allowPerRequest throws", async () => {
199
+ const { ctx } = makeCtx({ k: { def: numberDefNoOptIn, fallback: 10 } });
200
+ await expect(resolveConfigOrParam(ctx, handleFor("k", "number"), 42)).rejects.toThrow(
201
+ /per-request override not enabled.*allowPerRequest/i,
202
+ );
203
+ });
204
+
205
+ test("undefined paramValue is OK even without opt-in (normal config read still works)", async () => {
206
+ const { ctx } = makeCtx({ k: { def: numberDefNoOptIn, fallback: 10 } });
207
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), undefined)).toBe(10);
208
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), null)).toBe(10);
209
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), "")).toBe(10);
210
+ });
211
+
212
+ test("allowPerRequest=false throws (explicit denial, same as omitted)", async () => {
213
+ // Spread on the factory-produced def to flip the flag explicitly — the
214
+ // factory's own guard (omits allowPerRequest when not true) means we
215
+ // can't express "false" through the factory alone.
216
+ const explicitDeny: KeyEntry = { ...numberDefNoOptIn, allowPerRequest: false };
217
+ const { ctx } = makeCtx({ k: { def: explicitDeny, fallback: 10 } });
218
+ await expect(resolveConfigOrParam(ctx, handleFor("k", "number"), 42)).rejects.toThrow(
219
+ /per-request override not enabled/i,
220
+ );
221
+ });
222
+
223
+ test("error message includes the key name so debugging is quick", async () => {
224
+ const { ctx } = makeCtx({ "orders:config:some-key": { def: numberDefNoOptIn, fallback: 10 } });
225
+ await expect(
226
+ resolveConfigOrParam(ctx, handleFor("orders:config:some-key", "number"), 42),
227
+ ).rejects.toThrow(/orders:config:some-key/);
228
+ });
229
+ });
230
+
231
+ describe("resolveConfigOrParam — edge cases", () => {
232
+ test("handle whose key is missing from registry falls back to ctx.config", async () => {
233
+ // Registry returns undefined for this handle — helper gracefully degrades.
234
+ const { ctx, configFn } = makeCtx({});
235
+ configFn.mockResolvedValue(42 as never);
236
+ expect(await resolveConfigOrParam(ctx, handleFor("missing", "number"), 999)).toBe(42);
237
+ expect(configFn).toHaveBeenCalled();
238
+ });
239
+ });
240
+
241
+ describe("resolveConfigOrParam — onClamp audit hook", () => {
242
+ const boundedDef = createTenantConfig("number", {
243
+ default: 10,
244
+ bounds: { min: 1, max: 100 },
245
+ allowPerRequest: true,
246
+ });
247
+
248
+ test("onClamp fires when value is clamped down to max", async () => {
249
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
250
+ const clamps: ClampInfo[] = [];
251
+ const result = await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999, {
252
+ onClamp: (info) => clamps.push(info),
253
+ });
254
+ expect(result).toBe(100);
255
+ expect(clamps).toHaveLength(1);
256
+ expect(clamps[0]).toMatchObject({
257
+ key: "k",
258
+ original: 9999,
259
+ clamped: 100,
260
+ min: 1,
261
+ max: 100,
262
+ });
263
+ });
264
+
265
+ test("onClamp fires when value is clamped up to min", async () => {
266
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
267
+ const clamps: ClampInfo[] = [];
268
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), -5, {
269
+ onClamp: (info) => clamps.push(info),
270
+ });
271
+ expect(clamps[0]).toMatchObject({ original: -5, clamped: 1 });
272
+ });
273
+
274
+ test("onClamp does NOT fire when value is within bounds", async () => {
275
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
276
+ const onClamp = vi.fn();
277
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), 50, { onClamp });
278
+ expect(onClamp).not.toHaveBeenCalled();
279
+ });
280
+
281
+ test("onClamp does NOT fire on exact boundary values", async () => {
282
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
283
+ const onClamp = vi.fn();
284
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), 1, { onClamp });
285
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), 100, { onClamp });
286
+ expect(onClamp).not.toHaveBeenCalled();
287
+ });
288
+
289
+ test("onClamp does NOT fire when value is coerced to NaN (no clamp happens, config fallback used)", async () => {
290
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
291
+ const onClamp = vi.fn();
292
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), "abc", { onClamp });
293
+ expect(onClamp).not.toHaveBeenCalled();
294
+ });
295
+
296
+ test("absent options → no callback infrastructure, still works (backward-compat with callers pre-audit)", async () => {
297
+ const { ctx } = makeCtx({ k: { def: boundedDef, fallback: 10 } });
298
+ // Three variants: no 4th arg, empty options, options without onClamp.
299
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999)).toBe(100);
300
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999, {})).toBe(100);
301
+ expect(await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999, {})).toBe(100);
302
+ });
303
+
304
+ test("clamp info omits min when bounds has only max (and vice versa)", async () => {
305
+ // Spread on factory def to get a max-only bounds.
306
+ const maxOnly: KeyEntry = { ...boundedDef, bounds: { max: 100 } };
307
+ const { ctx } = makeCtx({ k: { def: maxOnly, fallback: 10 } });
308
+ const clamps: ClampInfo[] = [];
309
+ await resolveConfigOrParam(ctx, handleFor("k", "number"), 9999, {
310
+ onClamp: (info) => clamps.push(info),
311
+ });
312
+ expect(clamps[0]).toMatchObject({ clamped: 100, max: 100 });
313
+ expect(clamps[0]).not.toHaveProperty("min");
314
+ });
315
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { runsInLane } from "../run-in";
3
+
4
+ describe("runsInLane", () => {
5
+ test("undefined runIn defaults to worker", () => {
6
+ expect(runsInLane(undefined, "worker")).toBe(true);
7
+ expect(runsInLane(undefined, "api")).toBe(false);
8
+ // All-in-one accepts everything, defaults included.
9
+ expect(runsInLane(undefined, "both")).toBe(true);
10
+ });
11
+
12
+ test("runIn='both' runs on any single-lane process", () => {
13
+ expect(runsInLane("both", "api")).toBe(true);
14
+ expect(runsInLane("both", "worker")).toBe(true);
15
+ expect(runsInLane("both", "both")).toBe(true);
16
+ });
17
+
18
+ test("runIn='api' runs only on api (or all-in-one)", () => {
19
+ expect(runsInLane("api", "api")).toBe(true);
20
+ expect(runsInLane("api", "worker")).toBe(false);
21
+ expect(runsInLane("api", "both")).toBe(true);
22
+ });
23
+
24
+ test("runIn='worker' runs only on worker (or all-in-one)", () => {
25
+ expect(runsInLane("worker", "worker")).toBe(true);
26
+ expect(runsInLane("worker", "api")).toBe(false);
27
+ expect(runsInLane("worker", "both")).toBe(true);
28
+ });
29
+
30
+ test("processLane='both' disables filtering entirely", () => {
31
+ // All-in-one must run every consumer regardless of pin — it's the
32
+ // single process filling every role.
33
+ expect(runsInLane(undefined, "both")).toBe(true);
34
+ expect(runsInLane("api", "both")).toBe(true);
35
+ expect(runsInLane("worker", "both")).toBe(true);
36
+ expect(runsInLane("both", "both")).toBe(true);
37
+ });
38
+ });