@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.
- package/README.md +159 -0
- package/package.json +91 -0
- package/src/__tests__/anonymous-access.integration.ts +325 -0
- package/src/__tests__/error-contract.integration.ts +435 -0
- package/src/__tests__/field-access.integration.ts +269 -0
- package/src/__tests__/full-stack.integration.ts +914 -0
- package/src/__tests__/ownership.integration.ts +449 -0
- package/src/__tests__/reference-data.integration.ts +198 -0
- package/src/__tests__/transition-guard.integration.ts +340 -0
- package/src/api/__tests__/api.test.ts +337 -0
- package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
- package/src/api/__tests__/batch.integration.ts +404 -0
- package/src/api/__tests__/body-limit.test.ts +88 -0
- package/src/api/__tests__/csrf-middleware.test.ts +97 -0
- package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
- package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
- package/src/api/__tests__/nested-write.integration.ts +213 -0
- package/src/api/__tests__/readiness.test.ts +76 -0
- package/src/api/__tests__/request-id-middleware.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +58 -0
- package/src/api/__tests__/sse-route.test.ts +112 -0
- package/src/api/anonymous-cookie.ts +60 -0
- package/src/api/api-constants.ts +64 -0
- package/src/api/auth-middleware.ts +418 -0
- package/src/api/auth-routes.ts +982 -0
- package/src/api/csrf-middleware.ts +77 -0
- package/src/api/index.ts +31 -0
- package/src/api/jwt.ts +66 -0
- package/src/api/observability-middleware.ts +89 -0
- package/src/api/readiness.ts +132 -0
- package/src/api/request-context.ts +49 -0
- package/src/api/request-id-middleware.ts +50 -0
- package/src/api/route-registrars.ts +195 -0
- package/src/api/routes.ts +135 -0
- package/src/api/server.ts +640 -0
- package/src/api/sse-broker.ts +71 -0
- package/src/api/sse-route.ts +62 -0
- package/src/api/tokens.ts +16 -0
- package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
- package/src/db/__tests__/compound-types.test.ts +114 -0
- package/src/db/__tests__/connection-options.test.ts +68 -0
- package/src/db/__tests__/cursor.test.ts +41 -0
- package/src/db/__tests__/db-helpers.test.ts +369 -0
- package/src/db/__tests__/dialect-instant.test.ts +50 -0
- package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
- package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
- package/src/db/__tests__/encryption.test.ts +39 -0
- package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
- package/src/db/__tests__/event-store-executor.integration.ts +235 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
- package/src/db/__tests__/located-timestamp.test.ts +184 -0
- package/src/db/__tests__/money.test.ts +199 -0
- package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
- package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
- package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
- package/src/db/__tests__/row-helpers.test.ts +59 -0
- package/src/db/__tests__/schema-migration.integration.ts +273 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
- package/src/db/__tests__/table-builder-required.test.ts +216 -0
- package/src/db/__tests__/tenant-db.integration.ts +606 -0
- package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
- package/src/db/apply-entity-event.ts +188 -0
- package/src/db/assert-exists-in.ts +59 -0
- package/src/db/compound-types.ts +47 -0
- package/src/db/connection.ts +104 -0
- package/src/db/cursor.ts +83 -0
- package/src/db/dialect.ts +109 -0
- package/src/db/eagerload.ts +174 -0
- package/src/db/encryption.ts +39 -0
- package/src/db/event-store-executor.ts +906 -0
- package/src/db/index.ts +55 -0
- package/src/db/located-timestamp.ts +114 -0
- package/src/db/money.ts +120 -0
- package/src/db/pg-error.ts +46 -0
- package/src/db/reference-data.ts +77 -0
- package/src/db/row-helpers.ts +53 -0
- package/src/db/schema-inspection.ts +25 -0
- package/src/db/table-builder.ts +475 -0
- package/src/db/tenant-db.ts +434 -0
- package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
- package/src/engine/__tests__/boot-validator.test.ts +1865 -0
- package/src/engine/__tests__/build-app-schema.test.ts +154 -0
- package/src/engine/__tests__/claim-keys.test.ts +274 -0
- package/src/engine/__tests__/config-helpers.test.ts +236 -0
- package/src/engine/__tests__/effective-features.test.ts +86 -0
- package/src/engine/__tests__/engine.test.ts +1461 -0
- package/src/engine/__tests__/entity-handlers.test.ts +274 -0
- package/src/engine/__tests__/event-helpers.test.ts +68 -0
- package/src/engine/__tests__/extends-registrar.test.ts +159 -0
- package/src/engine/__tests__/factories-long-text.test.ts +84 -0
- package/src/engine/__tests__/factories-time.test.ts +158 -0
- package/src/engine/__tests__/field-predicates.test.ts +48 -0
- package/src/engine/__tests__/hook-phases.test.ts +132 -0
- package/src/engine/__tests__/identifiers.test.ts +35 -0
- package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
- package/src/engine/__tests__/nav.test.ts +267 -0
- package/src/engine/__tests__/ownership.test.ts +421 -0
- package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
- package/src/engine/__tests__/projection-helpers.test.ts +62 -0
- package/src/engine/__tests__/projection.test.ts +191 -0
- package/src/engine/__tests__/qualified-name.test.ts +264 -0
- package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
- package/src/engine/__tests__/run-in.test.ts +38 -0
- package/src/engine/__tests__/schema-builder.test.ts +380 -0
- package/src/engine/__tests__/screen.test.ts +408 -0
- package/src/engine/__tests__/state-machine.test.ts +148 -0
- package/src/engine/__tests__/system-user.test.ts +57 -0
- package/src/engine/__tests__/validation-hooks.test.ts +71 -0
- package/src/engine/access.ts +23 -0
- package/src/engine/boot-validator.ts +1528 -0
- package/src/engine/build-app-schema.ts +125 -0
- package/src/engine/config-helpers.ts +115 -0
- package/src/engine/constants.ts +85 -0
- package/src/engine/create-app.ts +98 -0
- package/src/engine/define-feature.ts +702 -0
- package/src/engine/define-handler.ts +78 -0
- package/src/engine/define-roles.ts +19 -0
- package/src/engine/effective-features.ts +87 -0
- package/src/engine/entity-handlers.ts +364 -0
- package/src/engine/event-helpers.ts +73 -0
- package/src/engine/factories.ts +328 -0
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
- package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
- package/src/engine/feature-ast/extractors.ts +2562 -0
- package/src/engine/feature-ast/index.ts +105 -0
- package/src/engine/feature-ast/parse.ts +369 -0
- package/src/engine/feature-ast/patch.ts +525 -0
- package/src/engine/feature-ast/patcher.ts +518 -0
- package/src/engine/feature-ast/patterns.ts +434 -0
- package/src/engine/feature-ast/render.ts +602 -0
- package/src/engine/feature-ast/source-location.ts +45 -0
- package/src/engine/field-access.ts +120 -0
- package/src/engine/index.ts +254 -0
- package/src/engine/ownership.ts +337 -0
- package/src/engine/parse-ref-target.ts +22 -0
- package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
- package/src/engine/pattern-library/index.ts +24 -0
- package/src/engine/pattern-library/library.ts +1117 -0
- package/src/engine/pattern-library/types.ts +255 -0
- package/src/engine/projection-helpers.ts +85 -0
- package/src/engine/qualified-name.ts +122 -0
- package/src/engine/read-claim.ts +31 -0
- package/src/engine/registry.ts +1325 -0
- package/src/engine/resolve-config-or-param.ts +153 -0
- package/src/engine/run-in.ts +29 -0
- package/src/engine/schema-builder.ts +175 -0
- package/src/engine/screen-filter-ops.ts +51 -0
- package/src/engine/state-machine.ts +70 -0
- package/src/engine/system-user.ts +32 -0
- package/src/engine/types/config.ts +306 -0
- package/src/engine/types/event-type-map.ts +37 -0
- package/src/engine/types/feature.ts +574 -0
- package/src/engine/types/fields.ts +422 -0
- package/src/engine/types/handlers.ts +742 -0
- package/src/engine/types/hooks.ts +142 -0
- package/src/engine/types/http-route.ts +54 -0
- package/src/engine/types/identifiers.ts +47 -0
- package/src/engine/types/index.ts +208 -0
- package/src/engine/types/nav.ts +46 -0
- package/src/engine/types/projection.ts +132 -0
- package/src/engine/types/relations.ts +51 -0
- package/src/engine/types/screen.ts +452 -0
- package/src/engine/types/workspace.ts +42 -0
- package/src/engine/validation.ts +33 -0
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
- package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
- package/src/entrypoint/index.ts +442 -0
- package/src/errors/__tests__/classes.test.ts +371 -0
- package/src/errors/__tests__/write-failures.test.ts +109 -0
- package/src/errors/classes.ts +249 -0
- package/src/errors/i18n/de.yaml +83 -0
- package/src/errors/i18n/en.yaml +80 -0
- package/src/errors/index.ts +41 -0
- package/src/errors/kumiko-error.ts +67 -0
- package/src/errors/reasons.ts +36 -0
- package/src/errors/serialize.ts +136 -0
- package/src/errors/transition-details.ts +30 -0
- package/src/errors/write-error-info.ts +123 -0
- package/src/errors/zod-bridge.ts +49 -0
- package/src/event-store/__tests__/admin-api.integration.ts +361 -0
- package/src/event-store/__tests__/event-store.integration.ts +584 -0
- package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
- package/src/event-store/__tests__/perf.integration.ts +255 -0
- package/src/event-store/__tests__/snapshot.integration.ts +267 -0
- package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
- package/src/event-store/__tests__/upcaster.integration.ts +460 -0
- package/src/event-store/admin-api.ts +257 -0
- package/src/event-store/archive.ts +106 -0
- package/src/event-store/errors.ts +35 -0
- package/src/event-store/event-store.ts +405 -0
- package/src/event-store/events-schema.ts +90 -0
- package/src/event-store/index.ts +50 -0
- package/src/event-store/snapshot.ts +210 -0
- package/src/event-store/upcaster-dead-letter.ts +119 -0
- package/src/event-store/upcaster.ts +147 -0
- package/src/files/__tests__/content-disposition.test.ts +123 -0
- package/src/files/__tests__/file-field-column.integration.ts +103 -0
- package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
- package/src/files/__tests__/file-handle.test.ts +122 -0
- package/src/files/__tests__/files.integration.ts +830 -0
- package/src/files/__tests__/storage-tracking.integration.ts +153 -0
- package/src/files/content-disposition.ts +55 -0
- package/src/files/file-handle.ts +63 -0
- package/src/files/file-ref-table.ts +22 -0
- package/src/files/file-routes.ts +353 -0
- package/src/files/in-memory-provider.ts +62 -0
- package/src/files/index.ts +29 -0
- package/src/files/local-provider.ts +35 -0
- package/src/files/storage-tracking.ts +60 -0
- package/src/files/types.ts +118 -0
- package/src/i18n/__tests__/i18n.test.ts +72 -0
- package/src/i18n/index.ts +29 -0
- package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
- package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
- package/src/jobs/__tests__/jobs.integration.ts +566 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-runner.ts +574 -0
- package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
- package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
- package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
- package/src/lifecycle/index.ts +13 -0
- package/src/lifecycle/lifecycle.ts +160 -0
- package/src/lifecycle/signal-handlers.ts +62 -0
- package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/pino-logger.ts +64 -0
- package/src/logging/types.ts +7 -0
- package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
- package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
- package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
- package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
- package/src/migrations/index.ts +28 -0
- package/src/migrations/projection-detection.ts +149 -0
- package/src/migrations/rebuild-marker.ts +64 -0
- package/src/migrations/schema-drift.ts +395 -0
- package/src/observability/__tests__/console-provider.test.ts +67 -0
- package/src/observability/__tests__/metric-validator.test.ts +87 -0
- package/src/observability/__tests__/noop-provider.test.ts +82 -0
- package/src/observability/__tests__/observability.integration.ts +559 -0
- package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
- package/src/observability/__tests__/recording-meter.test.ts +101 -0
- package/src/observability/__tests__/recording-tracer.test.ts +110 -0
- package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
- package/src/observability/console-provider.ts +130 -0
- package/src/observability/context.ts +26 -0
- package/src/observability/fallback.ts +34 -0
- package/src/observability/ids.ts +25 -0
- package/src/observability/index.ts +79 -0
- package/src/observability/metric-validator.ts +86 -0
- package/src/observability/metrics-handle.ts +56 -0
- package/src/observability/noop-provider.ts +146 -0
- package/src/observability/prometheus-meter.ts +284 -0
- package/src/observability/recording-meter.ts +156 -0
- package/src/observability/recording-tracer.ts +198 -0
- package/src/observability/redis-wrapper.ts +132 -0
- package/src/observability/sensitive-filter.ts +108 -0
- package/src/observability/standard-metrics.ts +213 -0
- package/src/observability/types/index.ts +29 -0
- package/src/observability/types/metric.ts +56 -0
- package/src/observability/types/provider.ts +32 -0
- package/src/observability/types/span.ts +64 -0
- package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
- package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
- package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
- package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
- package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
- package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
- package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
- package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
- package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
- package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
- package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
- package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
- package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
- package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
- package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
- package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
- package/src/pipeline/append-event-core.ts +117 -0
- package/src/pipeline/auth-claims-resolver.ts +103 -0
- package/src/pipeline/cascade-handler.ts +113 -0
- package/src/pipeline/dispatcher.ts +1585 -0
- package/src/pipeline/distributed-lock.ts +37 -0
- package/src/pipeline/entity-cache.ts +113 -0
- package/src/pipeline/event-consumer-state.ts +108 -0
- package/src/pipeline/event-dedup.ts +23 -0
- package/src/pipeline/event-dispatcher.ts +1016 -0
- package/src/pipeline/event-retention.ts +154 -0
- package/src/pipeline/idempotency.ts +76 -0
- package/src/pipeline/index.ts +66 -0
- package/src/pipeline/lifecycle-pipeline.ts +409 -0
- package/src/pipeline/msp-rebuild.ts +242 -0
- package/src/pipeline/multi-stream-apply-context.ts +115 -0
- package/src/pipeline/projection-rebuild.ts +334 -0
- package/src/pipeline/projection-state.ts +72 -0
- package/src/pipeline/projections-runner.ts +56 -0
- package/src/pipeline/redis-keys.ts +11 -0
- package/src/pipeline/system-hooks.ts +190 -0
- package/src/random/__tests__/generate.test.ts +149 -0
- package/src/random/generate.ts +141 -0
- package/src/random/index.ts +8 -0
- package/src/random/words.ts +392 -0
- package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
- package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
- package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
- package/src/rate-limit/bucket.ts +36 -0
- package/src/rate-limit/index.ts +14 -0
- package/src/rate-limit/middleware.ts +152 -0
- package/src/rate-limit/resolver.ts +267 -0
- package/src/redis/__tests__/redis-options.test.ts +54 -0
- package/src/redis/index.ts +74 -0
- package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
- package/src/search/__tests__/search-adapter.test.ts +256 -0
- package/src/search/in-memory-adapter.ts +123 -0
- package/src/search/index.ts +12 -0
- package/src/search/meilisearch-adapter.ts +106 -0
- package/src/search/types.ts +39 -0
- package/src/secrets/__tests__/dek-cache.test.ts +213 -0
- package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
- package/src/secrets/__tests__/envelope.test.ts +74 -0
- package/src/secrets/__tests__/leak-guard.test.ts +92 -0
- package/src/secrets/__tests__/rotation.test.ts +149 -0
- package/src/secrets/dek-cache.ts +116 -0
- package/src/secrets/env-master-key-provider.ts +162 -0
- package/src/secrets/envelope.ts +55 -0
- package/src/secrets/index.ts +19 -0
- package/src/secrets/leak-guard.ts +87 -0
- package/src/secrets/rotation.ts +34 -0
- package/src/secrets/types.ts +107 -0
- package/src/stack/db.ts +104 -0
- package/src/stack/event-collector.ts +23 -0
- package/src/stack/index.ts +32 -0
- package/src/stack/redis.ts +44 -0
- package/src/stack/request-helper.ts +168 -0
- package/src/stack/table-helpers.ts +104 -0
- package/src/stack/test-stack.ts +357 -0
- package/src/stack/test-users.ts +37 -0
- package/src/testing/__tests__/e2e-generator.test.ts +230 -0
- package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
- package/src/testing/access-assertions.ts +15 -0
- package/src/testing/assertions.ts +35 -0
- package/src/testing/e2e-generator.ts +465 -0
- package/src/testing/expect-error.ts +25 -0
- package/src/testing/handler-context.ts +125 -0
- package/src/testing/http-cookies.ts +52 -0
- package/src/testing/index.ts +41 -0
- package/src/testing/late-bound.ts +39 -0
- package/src/testing/mutable-master-key-provider.ts +31 -0
- package/src/testing/observability-recorder.ts +54 -0
- package/src/testing/shared-entities.ts +49 -0
- package/src/testing/utils.ts +1 -0
- package/src/testing/wait-for.ts +31 -0
- package/src/time/__tests__/polyfill.test.ts +73 -0
- package/src/time/__tests__/tz-context.test.ts +121 -0
- package/src/time/index.ts +21 -0
- package/src/time/polyfill.ts +70 -0
- package/src/time/tz-context.ts +107 -0
- package/src/ui-types/app-schema.ts +57 -0
- package/src/ui-types/index.ts +65 -0
- package/src/utils/__tests__/assert.test.ts +17 -0
- package/src/utils/__tests__/env-parse.test.ts +54 -0
- package/src/utils/assert.ts +18 -0
- package/src/utils/env-parse.ts +16 -0
- package/src/utils/ids.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/safe-json.ts +30 -0
- package/src/utils/serialization.ts +7 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Route-level wiring that buildServer used to inline. Extracted so
|
|
2
|
+
// server.ts stays a composition-of-named-steps rather than 500 lines of
|
|
3
|
+
// branches. Every registrar takes the Hono app + whatever state it
|
|
4
|
+
// actually reads — no monolithic `options` parameter, because these
|
|
5
|
+
// helpers exist to make each step's dependencies visible at the call-site.
|
|
6
|
+
|
|
7
|
+
import type { Hono } from "hono";
|
|
8
|
+
import { bodyLimit } from "hono/body-limit";
|
|
9
|
+
import type { DbConnection } from "../db/connection";
|
|
10
|
+
import type { Lifecycle } from "../lifecycle";
|
|
11
|
+
import type { Meter, PrometheusMeter } from "../observability";
|
|
12
|
+
import { serializeOpenMetrics } from "../observability";
|
|
13
|
+
import type { EventConsumer } from "../pipeline/event-dispatcher";
|
|
14
|
+
import { Routes } from "./api-constants";
|
|
15
|
+
import {
|
|
16
|
+
createReadinessProbe,
|
|
17
|
+
dbPingCheck,
|
|
18
|
+
dispatcherLagCheck,
|
|
19
|
+
type ReadinessCheck,
|
|
20
|
+
redisPingCheck,
|
|
21
|
+
} from "./readiness";
|
|
22
|
+
|
|
23
|
+
// --- Body size limit ------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const BODY_LIMIT_PATHS = [
|
|
26
|
+
`/api${Routes.write}`,
|
|
27
|
+
`/api${Routes.batch}`,
|
|
28
|
+
`/api${Routes.query}`,
|
|
29
|
+
`/api${Routes.command}`,
|
|
30
|
+
`/api${Routes.auth}/*`,
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_MAX_REQUEST_BYTES = 1_048_576;
|
|
34
|
+
|
|
35
|
+
// Cap JSON bodies on /api/write + /api/batch + /api/query + /api/command
|
|
36
|
+
// + /api/auth/*. File uploads keep their own per-field maxSize. `0`
|
|
37
|
+
// disables the limit entirely — only useful when a reverse-proxy caps
|
|
38
|
+
// upstream or tests want raw passthrough.
|
|
39
|
+
export function registerBodyLimit(app: Hono, maxBytes: number): void {
|
|
40
|
+
// skip: opt-out path — caller passed `maxBytes: 0`, so no middleware
|
|
41
|
+
// is attached (upstream cap via reverse-proxy is expected). Not a bug
|
|
42
|
+
// suppression, an intentional disable.
|
|
43
|
+
if (maxBytes <= 0) return;
|
|
44
|
+
const limit = bodyLimit({ maxSize: maxBytes });
|
|
45
|
+
for (const path of BODY_LIMIT_PATHS) app.use(path, limit);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --- /metrics (Prometheus scrape) -----------------------------------------
|
|
49
|
+
|
|
50
|
+
export type MetricsRouteOptions = {
|
|
51
|
+
readonly token?: string;
|
|
52
|
+
readonly path?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Timing-safe string compare — equal-length strings compare every byte
|
|
56
|
+
// regardless of where they diverge; different lengths return false without
|
|
57
|
+
// iterating. Prevents side-channel leaks on the Bearer-token check.
|
|
58
|
+
function constantTimeEqual(a: string, b: string): boolean {
|
|
59
|
+
if (a.length !== b.length) return false;
|
|
60
|
+
let diff = 0;
|
|
61
|
+
for (let i = 0; i < a.length; i++) {
|
|
62
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
63
|
+
}
|
|
64
|
+
return diff === 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Type-guard: duck-types the meter on `snapshot()`. An alternative
|
|
68
|
+
// Prometheus-compatible meter (future OTLP bridge) works without an
|
|
69
|
+
// explicit union — if it exposes `snapshot()` it's serialisable.
|
|
70
|
+
function isPrometheusMeter(m: Meter): m is PrometheusMeter {
|
|
71
|
+
return typeof (m as { snapshot?: unknown }).snapshot === "function";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Mount `/metrics` (or the caller-supplied path). Takes just the Meter —
|
|
75
|
+
// not the whole ObservabilityProvider — because that's the only field
|
|
76
|
+
// this route reads. Bearer-token optional — without one, scraping is
|
|
77
|
+
// open, fine for a private cluster.
|
|
78
|
+
export function registerMetricsRoute(app: Hono, meter: Meter, options: MetricsRouteOptions): void {
|
|
79
|
+
const metricsPath = options.path ?? "/metrics";
|
|
80
|
+
const expectedToken = options.token;
|
|
81
|
+
app.get(metricsPath, async (c) => {
|
|
82
|
+
if (expectedToken !== undefined) {
|
|
83
|
+
const header = c.req.header("authorization") ?? "";
|
|
84
|
+
const prefix = "Bearer ";
|
|
85
|
+
if (!header.startsWith(prefix)) return c.text("unauthorized", 401);
|
|
86
|
+
const provided = header.slice(prefix.length);
|
|
87
|
+
if (!constantTimeEqual(provided, expectedToken)) return c.text("unauthorized", 401);
|
|
88
|
+
}
|
|
89
|
+
if (!isPrometheusMeter(meter)) {
|
|
90
|
+
return c.text(
|
|
91
|
+
"metrics endpoint requires a PrometheusMeter — wrap the observability provider around createPrometheusMeter()",
|
|
92
|
+
503,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const body = serializeOpenMetrics(meter);
|
|
96
|
+
c.header("Content-Type", "application/openmetrics-text; version=1.0.0; charset=utf-8");
|
|
97
|
+
return c.body(body);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- /version ---------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
// Anonymous endpoint that returns build-identity. Used by ops-tooling
|
|
104
|
+
// (prod-version.sh) und Telegram-deploy-Notification damit man nicht
|
|
105
|
+
// kubectl + crictl auf den Master braucht um die deployed-Version zu
|
|
106
|
+
// sehen.
|
|
107
|
+
//
|
|
108
|
+
// BUILD_VERSION + BUILD_TIME werden vom Dockerfile (ARG → ENV)
|
|
109
|
+
// durchgereicht — fallen zurück auf "dev" / "unknown" wenn lokal ohne
|
|
110
|
+
// Build-args gebaut.
|
|
111
|
+
export function registerVersionRoute(app: Hono): void {
|
|
112
|
+
const version = process.env["BUILD_VERSION"] ?? "dev";
|
|
113
|
+
const buildTime = process.env["BUILD_TIME"] ?? "unknown";
|
|
114
|
+
app.get(Routes.version, (c) =>
|
|
115
|
+
c.json({
|
|
116
|
+
version,
|
|
117
|
+
buildTime,
|
|
118
|
+
node: process.env["NODE_ENV"] ?? "development",
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- /health + /health/ready ----------------------------------------------
|
|
124
|
+
|
|
125
|
+
// Readiness is one concept — what to check (db/redis/consumers) AND how
|
|
126
|
+
// to run the probe (timeout, lag-threshold). Keep the pair grouped so
|
|
127
|
+
// callers see "this is my readiness setup" as a single struct, not as
|
|
128
|
+
// four half-related top-level fields.
|
|
129
|
+
export type HealthRoutesOptions = {
|
|
130
|
+
readonly lifecycle?: Lifecycle;
|
|
131
|
+
readonly readiness?: {
|
|
132
|
+
readonly db?: DbConnection;
|
|
133
|
+
readonly redis?: import("ioredis").default;
|
|
134
|
+
readonly consumers?: readonly EventConsumer[];
|
|
135
|
+
readonly timeoutMs?: number;
|
|
136
|
+
// Opt-in dispatcher-lag gate. Off by default — a default threshold
|
|
137
|
+
// would false-503 small deployments that legitimately lag during
|
|
138
|
+
// bursts. Requires `db` + `consumers` to be set too; otherwise silently
|
|
139
|
+
// skipped (nothing to diff against).
|
|
140
|
+
readonly maxDispatcherLag?: bigint;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Mount both health probes:
|
|
145
|
+
// /health — always, trivial 200 with `{status:"ok"}` (liveness)
|
|
146
|
+
// /health/ready — only when a lifecycle is wired. Short-circuits to
|
|
147
|
+
// 503 during drain; otherwise runs dependency checks
|
|
148
|
+
// (DB/Redis/Dispatcher-lag) in parallel with a per-check
|
|
149
|
+
// timeout.
|
|
150
|
+
export function registerHealthRoutes(app: Hono, options: HealthRoutesOptions): void {
|
|
151
|
+
app.get(Routes.health, (c) => c.json({ status: "ok" }));
|
|
152
|
+
|
|
153
|
+
// skip: no lifecycle wired → /health/ready stays absent by design.
|
|
154
|
+
// Without lifecycle we'd have no way to flip the probe to 503 on
|
|
155
|
+
// drain — `/health` alone is enough for a test/dev process.
|
|
156
|
+
if (!options.lifecycle) return;
|
|
157
|
+
const lifecycle = options.lifecycle;
|
|
158
|
+
const readiness = options.readiness;
|
|
159
|
+
|
|
160
|
+
const readinessChecks: ReadinessCheck[] = [];
|
|
161
|
+
if (readiness?.db) readinessChecks.push(dbPingCheck(readiness.db));
|
|
162
|
+
if (readiness?.redis) readinessChecks.push(redisPingCheck(readiness.redis));
|
|
163
|
+
if (
|
|
164
|
+
readiness?.maxDispatcherLag !== undefined &&
|
|
165
|
+
readiness.db &&
|
|
166
|
+
readiness.consumers &&
|
|
167
|
+
readiness.consumers.length > 0
|
|
168
|
+
) {
|
|
169
|
+
readinessChecks.push(
|
|
170
|
+
dispatcherLagCheck(
|
|
171
|
+
readiness.db,
|
|
172
|
+
readiness.consumers.map((c) => c.name),
|
|
173
|
+
readiness.maxDispatcherLag,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const probe = createReadinessProbe(readinessChecks, { timeoutMs: readiness?.timeoutMs });
|
|
178
|
+
|
|
179
|
+
app.get(Routes.healthReady, async (c) => {
|
|
180
|
+
const state = lifecycle.state();
|
|
181
|
+
if (state !== "ready") {
|
|
182
|
+
return c.json({ status: "not_ready", state, uptimeSec: lifecycle.uptimeSec() }, 503);
|
|
183
|
+
}
|
|
184
|
+
const result = await probe();
|
|
185
|
+
return c.json(
|
|
186
|
+
{
|
|
187
|
+
status: result.ok ? "ready" : "not_ready",
|
|
188
|
+
state,
|
|
189
|
+
uptimeSec: lifecycle.uptimeSec(),
|
|
190
|
+
checks: result.checks,
|
|
191
|
+
},
|
|
192
|
+
result.ok ? 200 : 503,
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type Context, Hono } from "hono";
|
|
2
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
3
|
+
import {
|
|
4
|
+
InternalError,
|
|
5
|
+
isKumikoError,
|
|
6
|
+
type KumikoError,
|
|
7
|
+
reraiseAsKumikoError,
|
|
8
|
+
serializeError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
} from "../errors";
|
|
11
|
+
import type { Dispatcher } from "../pipeline/dispatcher";
|
|
12
|
+
import { Routes } from "./api-constants";
|
|
13
|
+
import { getUser } from "./auth-middleware";
|
|
14
|
+
import { requestContext } from "./request-context";
|
|
15
|
+
|
|
16
|
+
export function createApiRoutes(dispatcher: Dispatcher) {
|
|
17
|
+
const api = new Hono();
|
|
18
|
+
|
|
19
|
+
api.post(Routes.write, async (c) => {
|
|
20
|
+
const user = getUser(c);
|
|
21
|
+
const body = await c.req.json<{ type: string; payload: unknown; requestId?: string }>();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const result = await dispatcher.write(body.type, body.payload, user, body.requestId);
|
|
25
|
+
if (!result.isSuccess) {
|
|
26
|
+
return writeErrorResponse(c, reraiseAsKumikoError(result.error));
|
|
27
|
+
}
|
|
28
|
+
return c.json(result);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return writeErrorResponse(c, toKumiko(e));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
api.post(Routes.batch, async (c) => {
|
|
35
|
+
const user = getUser(c);
|
|
36
|
+
const body = await c.req.json<{
|
|
37
|
+
commands: Array<{ type: string; payload: unknown }>;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
if (!Array.isArray(body.commands)) {
|
|
42
|
+
// Client-shape violation → ValidationError (400, code=validation_error)
|
|
43
|
+
// matches what a Zod-level schema failure would produce if /batch had
|
|
44
|
+
// one. Client SDKs can key off the uniform validation contract.
|
|
45
|
+
return writeErrorResponse(
|
|
46
|
+
c,
|
|
47
|
+
new ValidationError({
|
|
48
|
+
fields: [
|
|
49
|
+
{
|
|
50
|
+
path: "commands",
|
|
51
|
+
code: "invalid_type",
|
|
52
|
+
i18nKey: "errors.validation.invalid_type",
|
|
53
|
+
params: { expected: "array", received: typeof body.commands },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await dispatcher.batch(body.commands, user, body.requestId);
|
|
62
|
+
if (!result.isSuccess) {
|
|
63
|
+
const err = reraiseAsKumikoError(result.error);
|
|
64
|
+
const requestId = requestContext.get()?.requestId;
|
|
65
|
+
const { error } = serializeError(err, requestId);
|
|
66
|
+
// Keep failedIndex + results alongside the error envelope so callers
|
|
67
|
+
// can tell which command in the batch failed and inspect the partial
|
|
68
|
+
// results from the successful commands before the rollback.
|
|
69
|
+
return c.json(
|
|
70
|
+
{
|
|
71
|
+
isSuccess: false,
|
|
72
|
+
error,
|
|
73
|
+
failedIndex: result.failedIndex,
|
|
74
|
+
results: result.results,
|
|
75
|
+
},
|
|
76
|
+
err.httpStatus as ContentfulStatusCode,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return c.json(result);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return writeErrorResponse(c, toKumiko(e));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
api.post(Routes.query, async (c) => {
|
|
86
|
+
const user = getUser(c);
|
|
87
|
+
const body = await c.req.json<{ type: string; payload: unknown }>();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const result = await dispatcher.query(body.type, body.payload, user);
|
|
91
|
+
return c.json({ data: result });
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return queryErrorResponse(c, toKumiko(e));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
api.post(Routes.command, async (c) => {
|
|
98
|
+
const user = getUser(c);
|
|
99
|
+
const body = await c.req.json<{ type: string; payload: unknown }>();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await dispatcher.command(body.type, body.payload, user);
|
|
103
|
+
return c.json({ ok: true }, 202);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return queryErrorResponse(c, toKumiko(e));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return api;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toKumiko(e: unknown): KumikoError {
|
|
113
|
+
if (isKumikoError(e)) return e;
|
|
114
|
+
if (e instanceof Error) return new InternalError({ cause: e });
|
|
115
|
+
return new InternalError({ message: String(e) });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For /write + /batch: keep the isSuccess flag so clients can flip on a single
|
|
119
|
+
// boolean (mirrors the success shape). The actual error body is the
|
|
120
|
+
// error-contract payload nested under .error.
|
|
121
|
+
function writeErrorResponse(c: Context, err: KumikoError, statusOverride?: number) {
|
|
122
|
+
const requestId = requestContext.get()?.requestId;
|
|
123
|
+
const { error } = serializeError(err, requestId);
|
|
124
|
+
const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode;
|
|
125
|
+
return c.json({ isSuccess: false, error }, status);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// For /query + /command: no isSuccess on success (just { data } / {ok}), so we
|
|
129
|
+
// keep the same lean shape on failure — only the `error` key.
|
|
130
|
+
function queryErrorResponse(c: Context, err: KumikoError, statusOverride?: number) {
|
|
131
|
+
const requestId = requestContext.get()?.requestId;
|
|
132
|
+
const body = serializeError(err, requestId);
|
|
133
|
+
const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode;
|
|
134
|
+
return c.json(body, status);
|
|
135
|
+
}
|