@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,198 @@
1
+ import { observabilityContext } from "./context";
2
+ import { generateSpanId, generateTraceId } from "./ids";
3
+ import { redactAttributes, redactValue, shouldRedactAttribute } from "./sensitive-filter";
4
+ import type {
5
+ SensitiveFilterConfig,
6
+ SerializedTraceContext,
7
+ Span,
8
+ SpanAttributes,
9
+ SpanAttributeValue,
10
+ SpanKind,
11
+ SpanStatus,
12
+ StartSpanOptions,
13
+ Tracer,
14
+ } from "./types";
15
+
16
+ // A RecordedSpan is the internal representation that provider emitters
17
+ // (console, otlp, test-collector) operate on. Every field is plain data —
18
+ // no references to the tracer or provider — so recording is cheap and the
19
+ // data can be serialized.
20
+ export type RecordedSpan = {
21
+ readonly traceId: string;
22
+ readonly spanId: string;
23
+ readonly parentSpanId: string | undefined;
24
+ readonly name: string;
25
+ readonly kind: SpanKind;
26
+ readonly startTime: number;
27
+ endTime: number | undefined;
28
+ attributes: Record<string, SpanAttributeValue>;
29
+ status: SpanStatus;
30
+ statusMessage: string | undefined;
31
+ exception: { readonly name: string; readonly message: string } | undefined;
32
+ };
33
+
34
+ type SpanEndHandler = (span: RecordedSpan) => void;
35
+
36
+ class RecordingSpan implements Span {
37
+ readonly traceId: string;
38
+ readonly spanId: string;
39
+ readonly parentSpanId: string | undefined;
40
+ readonly name: string;
41
+ private readonly record: RecordedSpan;
42
+ private readonly sensitiveConfig: SensitiveFilterConfig;
43
+ private readonly onEnd: SpanEndHandler;
44
+ private _ended = false;
45
+
46
+ constructor(args: {
47
+ record: RecordedSpan;
48
+ sensitiveConfig: SensitiveFilterConfig;
49
+ onEnd: SpanEndHandler;
50
+ }) {
51
+ this.record = args.record;
52
+ this.sensitiveConfig = args.sensitiveConfig;
53
+ this.onEnd = args.onEnd;
54
+ this.traceId = args.record.traceId;
55
+ this.spanId = args.record.spanId;
56
+ this.parentSpanId = args.record.parentSpanId;
57
+ this.name = args.record.name;
58
+ }
59
+
60
+ setAttribute(key: string, value: SpanAttributeValue): void {
61
+ // skip: span ended — mutations after end() would race the emitted snapshot
62
+ if (this._ended) return;
63
+ this.record.attributes[key] = shouldRedactAttribute(key, this.sensitiveConfig)
64
+ ? redactValue(value)
65
+ : value;
66
+ }
67
+
68
+ setAttributes(attrs: SpanAttributes): void {
69
+ // skip: span ended — see setAttribute comment
70
+ if (this._ended) return;
71
+ const safe = redactAttributes(attrs, this.sensitiveConfig);
72
+ for (const [k, v] of Object.entries(safe)) {
73
+ this.record.attributes[k] = v;
74
+ }
75
+ }
76
+
77
+ setStatus(status: SpanStatus, message?: string): void {
78
+ // skip: span ended — status is already part of the emitted snapshot
79
+ if (this._ended) return;
80
+ this.record.status = status;
81
+ this.record.statusMessage = message;
82
+ }
83
+
84
+ recordException(error: Error): void {
85
+ // skip: span ended — exception is already part of the emitted snapshot
86
+ if (this._ended) return;
87
+ this.record.exception = { name: error.name, message: error.message };
88
+ }
89
+
90
+ end(endTime?: number): void {
91
+ // skip: double-end — onEnd should fire exactly once per span
92
+ if (this._ended) return;
93
+ this._ended = true;
94
+ this.record.endTime = endTime ?? performance.now();
95
+ this.onEnd(this.record);
96
+ }
97
+
98
+ get ended(): boolean {
99
+ return this._ended;
100
+ }
101
+ }
102
+
103
+ export type RecordingTracerOptions = {
104
+ readonly sensitiveConfig: SensitiveFilterConfig;
105
+ // Called once per span after end(). Emitters (console, otlp) hook here.
106
+ readonly onSpanEnd: SpanEndHandler;
107
+ };
108
+
109
+ export class RecordingTracer implements Tracer {
110
+ private readonly sensitiveConfig: SensitiveFilterConfig;
111
+ private readonly onSpanEnd: SpanEndHandler;
112
+
113
+ constructor(opts: RecordingTracerOptions) {
114
+ this.sensitiveConfig = opts.sensitiveConfig;
115
+ this.onSpanEnd = opts.onSpanEnd;
116
+ }
117
+
118
+ startSpan(name: string, options?: StartSpanOptions): Span {
119
+ // Parent resolution: explicit parent (Span or SerializedTraceContext —
120
+ // both carry traceId+spanId so a uniform read works) or the ALS active
121
+ // span. A missing parent starts a new trace.
122
+ const explicitParent = options?.parent;
123
+ const active = explicitParent ?? this.getActiveSpan();
124
+ const traceId = active?.traceId ?? generateTraceId();
125
+ const parentSpanId = active?.spanId;
126
+
127
+ const record: RecordedSpan = {
128
+ traceId,
129
+ spanId: generateSpanId(),
130
+ parentSpanId,
131
+ name,
132
+ kind: options?.kind ?? "internal",
133
+ startTime: options?.startTime ?? performance.now(),
134
+ endTime: undefined,
135
+ attributes: options?.attributes
136
+ ? redactAttributes(options.attributes, this.sensitiveConfig)
137
+ : {},
138
+ status: "unset",
139
+ statusMessage: undefined,
140
+ exception: undefined,
141
+ };
142
+ return new RecordingSpan({
143
+ record,
144
+ sensitiveConfig: this.sensitiveConfig,
145
+ onEnd: this.onSpanEnd,
146
+ });
147
+ }
148
+
149
+ async withSpan<T>(
150
+ name: string,
151
+ optionsOrFn: StartSpanOptions | ((span: Span) => Promise<T>),
152
+ fn?: (span: Span) => Promise<T>,
153
+ ): Promise<T> {
154
+ const options = typeof optionsOrFn === "function" ? {} : (optionsOrFn ?? {});
155
+ const actualFn = typeof optionsOrFn === "function" ? optionsOrFn : fn;
156
+ if (!actualFn) {
157
+ throw new Error("withSpan called without callback");
158
+ }
159
+ const span = this.startSpan(name, options);
160
+ try {
161
+ return await observabilityContext.run({ activeSpan: span }, () => actualFn(span));
162
+ } catch (error) {
163
+ if (error instanceof Error) {
164
+ span.recordException(error);
165
+ span.setStatus("error", error.message);
166
+ } else {
167
+ span.setStatus("error", String(error));
168
+ }
169
+ throw error;
170
+ } finally {
171
+ if (!span.ended) span.end();
172
+ }
173
+ }
174
+
175
+ getActiveSpan(): Span | undefined {
176
+ return observabilityContext.getActiveSpan();
177
+ }
178
+
179
+ /**
180
+ * @deprecated Prefer `startSpan(name, { parent: context })`. Retained as a
181
+ * thin alias for call-sites that pre-date the unified StartSpanOptions.
182
+ */
183
+ startSpanFromContext(
184
+ name: string,
185
+ context: SerializedTraceContext,
186
+ options?: StartSpanOptions,
187
+ ): Span {
188
+ return this.startSpan(name, { ...options, parent: context });
189
+ }
190
+ }
191
+
192
+ // Helper to serialize an active Span into the cross-process format.
193
+ export function serializeSpanContext(span: Span): SerializedTraceContext {
194
+ return {
195
+ traceId: span.traceId,
196
+ spanId: span.spanId,
197
+ };
198
+ }
@@ -0,0 +1,132 @@
1
+ import type Redis from "ioredis";
2
+ import type { Tracer } from "./types";
3
+
4
+ // List of Redis commands we want to trace. Everything else (connection
5
+ // methods like `on`, `off`, `disconnect`, `duplicate`, plus pipeline/multi)
6
+ // falls through unwrapped. Keeping the list explicit avoids accidentally
7
+ // wrapping things that aren't commands — `connect` is a Promise, `on` is
8
+ // a listener registration — and keeps hot paths cheap.
9
+ const TRACKED_COMMANDS = new Set<string>([
10
+ // Strings / keys
11
+ "get",
12
+ "set",
13
+ "del",
14
+ "exists",
15
+ "expire",
16
+ "ttl",
17
+ "incr",
18
+ "decr",
19
+ "keys",
20
+ "scan",
21
+ "mget",
22
+ "mset",
23
+ // Hashes
24
+ "hget",
25
+ "hset",
26
+ "hmget",
27
+ "hmset",
28
+ "hgetall",
29
+ "hdel",
30
+ "hexists",
31
+ // Lists
32
+ "lpush",
33
+ "rpush",
34
+ "lpop",
35
+ "rpop",
36
+ "llen",
37
+ "lrange",
38
+ // Sets
39
+ "sadd",
40
+ "srem",
41
+ "smembers",
42
+ "sismember",
43
+ // Sorted sets
44
+ "zadd",
45
+ "zrange",
46
+ "zrem",
47
+ "zrangebyscore",
48
+ // Streams
49
+ "xadd",
50
+ "xread",
51
+ "xreadgroup",
52
+ "xack",
53
+ "xlen",
54
+ "xgroup",
55
+ "xdel",
56
+ "xrange",
57
+ "xpending",
58
+ // Pub/Sub
59
+ "publish",
60
+ "subscribe",
61
+ "unsubscribe",
62
+ "psubscribe",
63
+ "punsubscribe",
64
+ // Scripting
65
+ "eval",
66
+ "evalsha",
67
+ ]);
68
+
69
+ // Extract a redaction-safe key pattern from the first command argument.
70
+ // Actual keys often include user-generated fragments (ids, session tokens)
71
+ // that shouldn't leak into traces — we replace everything after the second
72
+ // `:` segment with `*`. Known namespace conventions (colon-separated) keep
73
+ // enough signal for grouping without leaking values.
74
+ function extractKeyPattern(arg: unknown): string | undefined {
75
+ if (typeof arg !== "string") return undefined;
76
+ const parts = arg.split(":");
77
+ if (parts.length <= 2) return arg;
78
+ return `${parts.slice(0, 2).join(":")}:*`;
79
+ }
80
+
81
+ // Wrap an ioredis client in an observability-aware proxy. Traced commands
82
+ // emit `redis.cmd` spans; everything else is passed through so pipeline,
83
+ // transaction, and connection APIs keep working.
84
+ export function wrapRedisClient(client: Redis, tracer: Tracer): Redis {
85
+ return new Proxy(client, {
86
+ get(target, prop, _receiver) {
87
+ // Symbols (e.g. internal queue) pass through unchanged.
88
+ if (typeof prop !== "string") {
89
+ return Reflect.get(target, prop, target);
90
+ }
91
+
92
+ const original = Reflect.get(target, prop, target);
93
+
94
+ if (prop === "duplicate") {
95
+ // Preserve wrapping on duplicated connections.
96
+ return (...args: unknown[]) => {
97
+ // @cast-boundary engine-bridge — Reflect.get returns unknown, narrow to ioredis-method
98
+ const dup = (original as (...args: unknown[]) => Redis).apply(target, args);
99
+ return wrapRedisClient(dup, tracer);
100
+ };
101
+ }
102
+
103
+ if (typeof original !== "function" || !TRACKED_COMMANDS.has(prop)) {
104
+ // Pass-through for non-command methods. Bind to target so `this`
105
+ // inside ioredis internals still works.
106
+ if (typeof original === "function") {
107
+ // @cast-boundary engine-bridge — Reflect.get returns unknown, narrow to ioredis-method
108
+ return (original as (...args: unknown[]) => unknown).bind(target);
109
+ }
110
+ return original;
111
+ }
112
+
113
+ return function wrappedCommand(this: unknown, ...args: unknown[]) {
114
+ const keyPattern = extractKeyPattern(args[0]);
115
+ return tracer.withSpan(
116
+ "redis.cmd",
117
+ {
118
+ kind: "client",
119
+ attributes: {
120
+ "redis.command": prop,
121
+ ...(keyPattern ? { "redis.key_pattern": keyPattern } : {}),
122
+ },
123
+ },
124
+ async () => {
125
+ // @cast-boundary engine-bridge — Reflect.get returns unknown, narrow to ioredis-method
126
+ return (original as (...args: unknown[]) => unknown).apply(target, args);
127
+ },
128
+ );
129
+ };
130
+ },
131
+ });
132
+ }
@@ -0,0 +1,108 @@
1
+ import type { SensitiveFilterConfig, SpanAttributeValue } from "./types";
2
+
3
+ export const REDACTED = "[REDACTED]";
4
+
5
+ // Defaults are intentionally conservative — easier to add a header to the
6
+ // list than to explain a PII leak. All matching is case-insensitive.
7
+ export const DEFAULT_SENSITIVE_CONFIG: SensitiveFilterConfig = {
8
+ redactedHeaders: ["authorization", "cookie", "set-cookie", "x-api-key", "proxy-authorization"],
9
+ redactedQueryParams: [
10
+ "token",
11
+ "access_token",
12
+ "refresh_token",
13
+ "password",
14
+ "secret",
15
+ "api_key",
16
+ "apikey",
17
+ ],
18
+ redactedAttributeKeyPatterns: [
19
+ /password/i,
20
+ /secret/i,
21
+ /token/i,
22
+ /apikey/i,
23
+ /privatekey/i,
24
+ /credential/i,
25
+ /session/i,
26
+ ],
27
+ };
28
+
29
+ export function mergeSensitiveConfig(
30
+ override: Partial<SensitiveFilterConfig> | undefined,
31
+ ): SensitiveFilterConfig {
32
+ if (!override) return DEFAULT_SENSITIVE_CONFIG;
33
+ return {
34
+ redactedHeaders: override.redactedHeaders ?? DEFAULT_SENSITIVE_CONFIG.redactedHeaders,
35
+ redactedQueryParams:
36
+ override.redactedQueryParams ?? DEFAULT_SENSITIVE_CONFIG.redactedQueryParams,
37
+ redactedAttributeKeyPatterns:
38
+ override.redactedAttributeKeyPatterns ??
39
+ DEFAULT_SENSITIVE_CONFIG.redactedAttributeKeyPatterns,
40
+ };
41
+ }
42
+
43
+ function lowercaseSet(names: readonly string[]): ReadonlySet<string> {
44
+ return new Set(names.map((n) => n.toLowerCase()));
45
+ }
46
+
47
+ export function redactHeaders(
48
+ headers: Readonly<Record<string, string>>,
49
+ config: SensitiveFilterConfig,
50
+ ): Record<string, string> {
51
+ const redacted = lowercaseSet(config.redactedHeaders);
52
+ const out: Record<string, string> = {};
53
+ for (const [key, value] of Object.entries(headers)) {
54
+ out[key] = redacted.has(key.toLowerCase()) ? REDACTED : value;
55
+ }
56
+ return out;
57
+ }
58
+
59
+ // Redact sensitive query params in a URL or query string. Returns a new URL
60
+ // string with the same shape — path, fragment, and ordering preserved.
61
+ export function redactQueryString(input: string, config: SensitiveFilterConfig): string {
62
+ const redacted = lowercaseSet(config.redactedQueryParams);
63
+ // URL wants absolute; handle both absolute and path-only forms.
64
+ const hasScheme = /^[a-z][a-z0-9+\-.]*:/i.test(input);
65
+ const base = hasScheme ? undefined : "http://_internal";
66
+ const url = base ? new URL(input, base) : new URL(input);
67
+ for (const key of Array.from(url.searchParams.keys())) {
68
+ if (redacted.has(key.toLowerCase())) {
69
+ url.searchParams.set(key, REDACTED);
70
+ }
71
+ }
72
+ if (!hasScheme) {
73
+ return `${url.pathname}${url.search}${url.hash}`;
74
+ }
75
+ return url.toString();
76
+ }
77
+
78
+ // Check a single attribute key against the redaction patterns.
79
+ // Used by the Span implementation when setAttribute is called.
80
+ export function shouldRedactAttribute(key: string, config: SensitiveFilterConfig): boolean {
81
+ for (const pattern of config.redactedAttributeKeyPatterns) {
82
+ if (pattern.test(key)) return true;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ // Produce a type-preserving redacted value: numbers become 0, booleans become
88
+ // false, strings become "[REDACTED]". Keeps downstream consumers (exporters,
89
+ // dashboards) from having to deal with type drift — a histogram bucket that
90
+ // expected a number never suddenly sees a string.
91
+ export function redactValue(value: SpanAttributeValue): SpanAttributeValue {
92
+ if (typeof value === "number") return 0;
93
+ if (typeof value === "boolean") return false;
94
+ return REDACTED;
95
+ }
96
+
97
+ // Filter a full attribute map, replacing matching keys with a type-preserving
98
+ // redacted value.
99
+ export function redactAttributes(
100
+ attrs: Readonly<Record<string, SpanAttributeValue>>,
101
+ config: SensitiveFilterConfig,
102
+ ): Record<string, SpanAttributeValue> {
103
+ const out: Record<string, SpanAttributeValue> = {};
104
+ for (const [key, value] of Object.entries(attrs)) {
105
+ out[key] = shouldRedactAttribute(key, config) ? redactValue(value) : value;
106
+ }
107
+ return out;
108
+ }
@@ -0,0 +1,213 @@
1
+ import type { Meter, MetricDefinition } from "./types";
2
+
3
+ // Framework-level metrics registered automatically at boot. Names match the
4
+ // Prometheus + OTel-friendly shape documented in observability-naming.md.
5
+ // Feature code never emits these — the Framework wires them from the HTTP
6
+ // middleware, dispatcher, and DB wrapper.
7
+
8
+ export const STANDARD_METRIC_DEFS: readonly MetricDefinition[] = [
9
+ {
10
+ name: "kumiko_http_requests_total",
11
+ type: "counter",
12
+ description: "HTTP requests counted by route, method, and status.",
13
+ labels: ["route", "method", "status"],
14
+ },
15
+ {
16
+ name: "kumiko_http_request_duration_seconds",
17
+ type: "histogram",
18
+ description: "HTTP request latency in seconds.",
19
+ labels: ["route", "method"],
20
+ buckets: [0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
21
+ },
22
+ {
23
+ name: "kumiko_dispatcher_handler_duration_seconds",
24
+ type: "histogram",
25
+ description: "Dispatcher handler latency in seconds.",
26
+ labels: ["handler", "success"],
27
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
28
+ },
29
+ {
30
+ name: "kumiko_dispatcher_handler_errors_total",
31
+ type: "counter",
32
+ description: "Dispatcher handler errors by class.",
33
+ labels: ["handler", "error_class"],
34
+ },
35
+ {
36
+ name: "kumiko_db_query_duration_seconds",
37
+ type: "histogram",
38
+ description: "DB query latency in seconds.",
39
+ labels: ["operation", "table"],
40
+ buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
41
+ },
42
+ // Projection-rebuild duration. Only emitted when a rebuild runs (ops-op),
43
+ // not continuously. Lag metric (continuous, live projections) is skipped
44
+ // for now — apply is synchronous, so lag is definitionally 0; a meaningful
45
+ // lag counter lands with async-apply in a future sprint.
46
+ {
47
+ name: "kumiko_projection_rebuild_duration_seconds",
48
+ type: "histogram",
49
+ description: "Duration of a full projection rebuild in seconds.",
50
+ labels: ["projection", "success"],
51
+ buckets: [0.01, 0.05, 0.1, 0.5, 1, 5, 30, 120, 600],
52
+ },
53
+ {
54
+ name: "kumiko_projection_rebuild_events_total",
55
+ type: "counter",
56
+ description: "Events replayed during a projection rebuild.",
57
+ labels: ["projection"],
58
+ },
59
+ // Event-dispatcher per-consumer metrics. Lag is the primary ops signal:
60
+ // how many events sit between the consumer's cursor and events-head.
61
+ // A growing lag means the consumer can't keep up; zero means fully caught up.
62
+ // instance_id labels each shard: SHARED_INSTANCE_SENTINEL for shared
63
+ // consumers (one cursor globally), or the process-local instanceId for
64
+ // per-instance consumers (one cursor per dispatcher). Without this,
65
+ // Prometheus collapses per-instance shards into last-writer-wins and
66
+ // per-instance lag is invisible to alerting.
67
+ {
68
+ name: "kumiko_event_consumer_lag_events",
69
+ type: "gauge",
70
+ description: "Number of events between the consumer's cursor and the events head.",
71
+ labels: ["consumer", "instance_id"],
72
+ },
73
+ {
74
+ name: "kumiko_event_consumer_events_processed_total",
75
+ type: "counter",
76
+ description: "Events successfully delivered to a consumer.",
77
+ labels: ["consumer", "instance_id"],
78
+ },
79
+ {
80
+ name: "kumiko_event_consumer_events_failed_total",
81
+ type: "counter",
82
+ description:
83
+ "Event deliveries that threw. Repeated failures on the same event lead to dead-letter.",
84
+ labels: ["consumer", "instance_id"],
85
+ },
86
+ // LISTEN-subscription health. 1 = active, 0 = dropped. Drops to 0 while
87
+ // the dispatcher is running signal that delivery latency has regressed
88
+ // from sub-millisecond (LISTEN) to pollIntervalMs (timer fallback); ops
89
+ // should look at it the moment downstream latency SLOs start slipping.
90
+ {
91
+ name: "kumiko_event_dispatcher_listen_connected",
92
+ type: "gauge",
93
+ description:
94
+ "1 if the event-dispatcher holds an active PG LISTEN subscription on the events channel, 0 otherwise.",
95
+ labels: [],
96
+ },
97
+ ] as const;
98
+
99
+ export function registerStandardMetrics(meter: Meter): void {
100
+ for (const def of STANDARD_METRIC_DEFS) {
101
+ // Guard: if already registered (e.g. buildServer called twice with the
102
+ // same meter instance — rare outside of hot-reload scenarios), skip.
103
+ if (meter.definitions().has(def.name)) continue;
104
+ meter.registerMetric(def);
105
+ }
106
+ }
107
+
108
+ // Emit helpers — read-only surface for Auto-Instrumentation call sites.
109
+ // Using these instead of raw meter.counter("...").inc() keeps the metric
110
+ // names centralised.
111
+
112
+ export function emitHttpRequest(
113
+ meter: Meter,
114
+ labels: { readonly route: string; readonly method: string; readonly status: number },
115
+ durationSeconds: number,
116
+ ): void {
117
+ meter.counter("kumiko_http_requests_total").inc(1, {
118
+ route: labels.route,
119
+ method: labels.method,
120
+ status: String(labels.status),
121
+ });
122
+ meter
123
+ .histogram("kumiko_http_request_duration_seconds")
124
+ .observe(durationSeconds, { route: labels.route, method: labels.method });
125
+ }
126
+
127
+ export function emitDispatcherHandler(
128
+ meter: Meter,
129
+ labels: { readonly handler: string; readonly success: boolean },
130
+ durationSeconds: number,
131
+ ): void {
132
+ meter.histogram("kumiko_dispatcher_handler_duration_seconds").observe(durationSeconds, {
133
+ handler: labels.handler,
134
+ success: String(labels.success),
135
+ });
136
+ }
137
+
138
+ export function emitDispatcherError(
139
+ meter: Meter,
140
+ labels: { readonly handler: string; readonly errorClass: string },
141
+ ): void {
142
+ meter.counter("kumiko_dispatcher_handler_errors_total").inc(1, {
143
+ handler: labels.handler,
144
+ error_class: labels.errorClass,
145
+ });
146
+ }
147
+
148
+ export function emitDbQuery(
149
+ meter: Meter,
150
+ labels: { readonly operation: string; readonly table: string },
151
+ durationSeconds: number,
152
+ ): void {
153
+ meter.histogram("kumiko_db_query_duration_seconds").observe(durationSeconds, {
154
+ operation: labels.operation,
155
+ table: labels.table,
156
+ });
157
+ }
158
+
159
+ export function emitProjectionRebuild(
160
+ meter: Meter,
161
+ labels: { readonly projection: string; readonly success: boolean },
162
+ durationSeconds: number,
163
+ eventsReplayed: number,
164
+ ): void {
165
+ meter.histogram("kumiko_projection_rebuild_duration_seconds").observe(durationSeconds, {
166
+ projection: labels.projection,
167
+ success: String(labels.success),
168
+ });
169
+ meter
170
+ .counter("kumiko_projection_rebuild_events_total")
171
+ .inc(eventsReplayed, { projection: labels.projection });
172
+ }
173
+
174
+ // Per-shard consumer labels. `instance_id` is the reserved sentinel
175
+ // (SHARED_INSTANCE_SENTINEL) for shared-delivery consumers, and the
176
+ // concrete dispatcher-instanceId for per-instance shards. Labelling by
177
+ // both keeps Prometheus series distinct across instances so per-instance
178
+ // lag is visible — without it, scraped gauges collapse last-writer-wins
179
+ // and a single slow shard is invisible to alerting.
180
+ export function emitEventConsumerLag(
181
+ meter: Meter,
182
+ labels: { readonly consumer: string; readonly instanceId: string },
183
+ lagEvents: number,
184
+ ): void {
185
+ meter.gauge("kumiko_event_consumer_lag_events").set(lagEvents, {
186
+ consumer: labels.consumer,
187
+ instance_id: labels.instanceId,
188
+ });
189
+ }
190
+
191
+ export function emitEventDispatcherListenConnected(meter: Meter, connected: boolean): void {
192
+ meter.gauge("kumiko_event_dispatcher_listen_connected").set(connected ? 1 : 0);
193
+ }
194
+
195
+ export function emitEventConsumerPassOutcome(
196
+ meter: Meter,
197
+ labels: { readonly consumer: string; readonly instanceId: string },
198
+ processed: number,
199
+ failed: number,
200
+ ): void {
201
+ if (processed > 0) {
202
+ meter.counter("kumiko_event_consumer_events_processed_total").inc(processed, {
203
+ consumer: labels.consumer,
204
+ instance_id: labels.instanceId,
205
+ });
206
+ }
207
+ if (failed > 0) {
208
+ meter.counter("kumiko_event_consumer_events_failed_total").inc(failed, {
209
+ consumer: labels.consumer,
210
+ instance_id: labels.instanceId,
211
+ });
212
+ }
213
+ }
@@ -0,0 +1,29 @@
1
+ // Barrel for observability types. Split into span/metric/provider files so
2
+ // each module stays focused; consumers still import from "./types".
3
+
4
+ export type {
5
+ Counter,
6
+ Gauge,
7
+ Histogram,
8
+ Meter,
9
+ MetricDefinition,
10
+ MetricLabels,
11
+ MetricsHandle,
12
+ MetricType,
13
+ } from "./metric";
14
+ export type {
15
+ ObservabilityOptions,
16
+ ObservabilityProvider,
17
+ SamplingConfig,
18
+ SensitiveFilterConfig,
19
+ } from "./provider";
20
+ export type {
21
+ SerializedTraceContext,
22
+ Span,
23
+ SpanAttributes,
24
+ SpanAttributeValue,
25
+ SpanKind,
26
+ SpanStatus,
27
+ StartSpanOptions,
28
+ Tracer,
29
+ } from "./span";