@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,136 @@
|
|
|
1
|
+
import type { KumikoError } from "./kumiko-error";
|
|
2
|
+
|
|
3
|
+
// Wire format every 4xx/5xx response must match. The API routes use this
|
|
4
|
+
// verbatim; keep it stable — the client SDK keys off these field names.
|
|
5
|
+
//
|
|
6
|
+
// `docsUrl` is the deep-link for self-service: default web/mobile renderers
|
|
7
|
+
// show a "Mehr erfahren →" link pointing here. Computed from `details.reason`
|
|
8
|
+
// (when set) or fallback to `code`. Always present, even when `details` is
|
|
9
|
+
// stripped from the response (e.g. internal_error in production).
|
|
10
|
+
export type ErrorResponseBody = {
|
|
11
|
+
readonly error: {
|
|
12
|
+
readonly code: string;
|
|
13
|
+
readonly i18nKey: string;
|
|
14
|
+
readonly i18nParams?: Readonly<Record<string, unknown>>;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
readonly details?: unknown;
|
|
17
|
+
readonly docsUrl: string;
|
|
18
|
+
readonly requestId?: string;
|
|
19
|
+
readonly timestamp: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// InternalError deliberately omits `details` from the response in production,
|
|
24
|
+
// because that field often carries the internal cause. The log still has
|
|
25
|
+
// everything (see buildErrorLog).
|
|
26
|
+
//
|
|
27
|
+
// In dev/test the cause is exposed in the response body to make debugging
|
|
28
|
+
// possible — without this, a crash in a handler comes back as a bare
|
|
29
|
+
// "internal error" and developers have to add try/catch + UnprocessableError
|
|
30
|
+
// just to see what went wrong (real footgun, not theoretical). The exposure
|
|
31
|
+
// is gated on NODE_ENV !== "production" so a misconfigured prod deploy
|
|
32
|
+
// doesn't leak stack traces over the wire.
|
|
33
|
+
const CODES_WITHOUT_CLIENT_DETAILS = new Set(["internal_error"]);
|
|
34
|
+
|
|
35
|
+
const isProductionEnv = (): boolean => process.env["NODE_ENV"] === "production";
|
|
36
|
+
|
|
37
|
+
// Snapshot of the cause chain (top-most error only) suitable for inclusion
|
|
38
|
+
// in dev-mode error responses. Stack is truncated to keep the body small.
|
|
39
|
+
type DevCauseDetail = {
|
|
40
|
+
readonly causeName?: string;
|
|
41
|
+
readonly causeMessage?: string;
|
|
42
|
+
readonly causeStack?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function devCauseDetail(cause: unknown): DevCauseDetail | undefined {
|
|
46
|
+
if (!(cause instanceof Error)) return undefined;
|
|
47
|
+
return {
|
|
48
|
+
causeName: cause.name,
|
|
49
|
+
causeMessage: cause.message,
|
|
50
|
+
...(cause.stack && { causeStack: cause.stack.split("\n").slice(0, 8).join("\n") }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function serializeError(err: KumikoError, requestId?: string): ErrorResponseBody {
|
|
55
|
+
const stripped = CODES_WITHOUT_CLIENT_DETAILS.has(err.code) && isProductionEnv();
|
|
56
|
+
const exposeDeclaredDetails = err.details !== undefined && !stripped;
|
|
57
|
+
|
|
58
|
+
// For internal_error in dev/test, surface the cause chain so the
|
|
59
|
+
// response body has something useful instead of "internal error".
|
|
60
|
+
// Priority: declared details (if any) win over the cause snapshot —
|
|
61
|
+
// a hand-set details on InternalError should not be replaced.
|
|
62
|
+
const detailsForResponse = (() => {
|
|
63
|
+
if (exposeDeclaredDetails) return err.details;
|
|
64
|
+
if (CODES_WITHOUT_CLIENT_DETAILS.has(err.code) && !isProductionEnv()) {
|
|
65
|
+
return devCauseDetail(err.cause);
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
})();
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
error: {
|
|
72
|
+
code: err.code,
|
|
73
|
+
i18nKey: err.i18nKey,
|
|
74
|
+
...(err.i18nParams && { i18nParams: err.i18nParams }),
|
|
75
|
+
message: err.message,
|
|
76
|
+
...(detailsForResponse !== undefined && { details: detailsForResponse }),
|
|
77
|
+
docsUrl: err.docsUrl,
|
|
78
|
+
...(requestId && { requestId }),
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Full forensic shape — stack, cause chain, details — meant for the log
|
|
85
|
+
// emitter. Callers should pipe this through a sensitive-value filter before it
|
|
86
|
+
// leaves the process (out of scope for v1).
|
|
87
|
+
export type ErrorLogEntry = {
|
|
88
|
+
readonly name: string;
|
|
89
|
+
readonly code: string;
|
|
90
|
+
readonly httpStatus: number;
|
|
91
|
+
readonly message: string;
|
|
92
|
+
readonly i18nKey: string;
|
|
93
|
+
readonly details?: unknown;
|
|
94
|
+
readonly stack?: string;
|
|
95
|
+
readonly cause?: CauseSnapshot;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export function buildErrorLog(err: KumikoError): ErrorLogEntry {
|
|
99
|
+
return {
|
|
100
|
+
name: err.name,
|
|
101
|
+
code: err.code,
|
|
102
|
+
httpStatus: err.httpStatus,
|
|
103
|
+
message: err.message,
|
|
104
|
+
i18nKey: err.i18nKey,
|
|
105
|
+
...(err.details !== undefined && { details: err.details }),
|
|
106
|
+
...(err.stack && { stack: err.stack }),
|
|
107
|
+
...(err.cause !== undefined && { cause: serializeCause(err.cause) }),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Recursive cause snapshot — follows error.cause.cause... until the chain
|
|
112
|
+
// ends or a non-Error appears. Hard cap at 10 levels to avoid pathological
|
|
113
|
+
// cyclic cause chains hanging the serializer.
|
|
114
|
+
type CauseSnapshot = {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
readonly message: string;
|
|
117
|
+
readonly stack?: string;
|
|
118
|
+
readonly cause?: CauseSnapshot;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const MAX_CAUSE_DEPTH = 10;
|
|
122
|
+
|
|
123
|
+
function serializeCause(cause: unknown, depth = 0): CauseSnapshot {
|
|
124
|
+
if (depth >= MAX_CAUSE_DEPTH) {
|
|
125
|
+
return { name: "CauseChainTruncated", message: `depth >= ${MAX_CAUSE_DEPTH}` };
|
|
126
|
+
}
|
|
127
|
+
if (cause instanceof Error) {
|
|
128
|
+
return {
|
|
129
|
+
name: cause.name,
|
|
130
|
+
message: cause.message,
|
|
131
|
+
...(cause.stack && { stack: cause.stack }),
|
|
132
|
+
...(cause.cause !== undefined && { cause: serializeCause(cause.cause, depth + 1) }),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { name: "NonError", message: String(cause) };
|
|
136
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Gemeinsame Detail-Shape für invalid_transition-Errors. Beide Pfade
|
|
2
|
+
// (state-machine.assertTransition + failTransition) müssen identisch
|
|
3
|
+
// strukturierte Details liefern, sonst können HTTP-Clients den 422-Body
|
|
4
|
+
// nicht uniform parsen — `from`/`to`/`allowed` sind die strukturierten
|
|
5
|
+
// Felder, `message` ist die menschen-lesbare Form.
|
|
6
|
+
//
|
|
7
|
+
// Kein Re-Export von "validTargets" (CSV-string) mehr — wer CSV will
|
|
8
|
+
// baut's via `allowed.join(", ")`. Eine Shape, ein Vertrag.
|
|
9
|
+
|
|
10
|
+
export type InvalidTransitionDetails = {
|
|
11
|
+
readonly from: string;
|
|
12
|
+
readonly to: string;
|
|
13
|
+
readonly allowed: readonly string[];
|
|
14
|
+
readonly message: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildInvalidTransitionDetails(
|
|
18
|
+
from: string,
|
|
19
|
+
to: string,
|
|
20
|
+
allowed: readonly string[],
|
|
21
|
+
): InvalidTransitionDetails {
|
|
22
|
+
return {
|
|
23
|
+
from,
|
|
24
|
+
to,
|
|
25
|
+
allowed,
|
|
26
|
+
message: `Invalid transition: "${from}" → "${to}". Allowed from "${from}": ${
|
|
27
|
+
allowed.length > 0 ? allowed.join(", ") : "none"
|
|
28
|
+
}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { NotFoundError, UnprocessableError } from "./classes";
|
|
2
|
+
import { KumikoError } from "./kumiko-error";
|
|
3
|
+
import { FrameworkReasons } from "./reasons";
|
|
4
|
+
import { buildInvalidTransitionDetails } from "./transition-details";
|
|
5
|
+
|
|
6
|
+
// Plain, JSON-serializable snapshot of a KumikoError for use on the write-path
|
|
7
|
+
// (WriteResult.error, BatchResult.error). The dispatcher stores results under
|
|
8
|
+
// an idempotency key — a KumikoError instance wouldn't round-trip through
|
|
9
|
+
// JSON, so we keep structural data only and rebuild the instance on demand
|
|
10
|
+
// via reraiseAsKumikoError when we need to throw upstream again.
|
|
11
|
+
export type WriteErrorInfo = {
|
|
12
|
+
readonly code: string;
|
|
13
|
+
readonly httpStatus: number;
|
|
14
|
+
readonly i18nKey: string;
|
|
15
|
+
readonly i18nParams?: Readonly<Record<string, unknown>>;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly details?: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// The failure half of WriteResult — `{ isSuccess: false } + error`. Named
|
|
21
|
+
// so the three write-failure factories below and WriteResult share one
|
|
22
|
+
// shape instead of restating it. Not generic: the error carries zero data,
|
|
23
|
+
// so there's nothing for the caller to narrow.
|
|
24
|
+
export type WriteFailure = {
|
|
25
|
+
readonly isSuccess: false;
|
|
26
|
+
readonly error: WriteErrorInfo;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Convenience for call sites that return a failed WriteResult. Keeps the
|
|
30
|
+
// pattern `return writeFailure(new NotFoundError(...))` compact so handlers
|
|
31
|
+
// and the CrudExecutor don't need to spell out the shape each time.
|
|
32
|
+
export function writeFailure(err: KumikoError): WriteFailure {
|
|
33
|
+
return { isSuccess: false, error: toWriteErrorInfo(err) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Focused convenience for the two most common handler failures: "X not found"
|
|
37
|
+
// (typed 404) and "business rule violated: REASON" (typed 422 with the reason
|
|
38
|
+
// string surfaced in details.reason). Reach for the concrete classes when you
|
|
39
|
+
// need richer payload — these two cover the bulk of handler code.
|
|
40
|
+
export function failNotFound(entity: string, id?: number | string): WriteFailure {
|
|
41
|
+
return writeFailure(new NotFoundError(entity, id));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function failUnprocessable(
|
|
45
|
+
reason: string,
|
|
46
|
+
details?: Readonly<Record<string, unknown>>,
|
|
47
|
+
): WriteFailure {
|
|
48
|
+
return writeFailure(new UnprocessableError(reason, details ? { details } : undefined));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convenience für State-Transition-Rejects: produces a WriteFailure mit
|
|
53
|
+
* reason="invalid_transition" + strukturiertem `from`/`to`/`allowed`-
|
|
54
|
+
* Detail-Block + lesbarer message. Pattern hat sich in publicstatus-
|
|
55
|
+
* Maintenance + bestehenden state-machine-Helpers wiederholt — Helper
|
|
56
|
+
* sammelt das in einem Aufruf statt drei manuelle Detail-Felder.
|
|
57
|
+
*
|
|
58
|
+
* `allowed` ist typisch `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
|
|
59
|
+
*/
|
|
60
|
+
export function failTransition(from: string, to: string, allowed: readonly string[]): WriteFailure {
|
|
61
|
+
return writeFailure(
|
|
62
|
+
new UnprocessableError(FrameworkReasons.invalidTransition, {
|
|
63
|
+
i18nKey: "errors.invalidTransition",
|
|
64
|
+
details: buildInvalidTransitionDetails(from, to, allowed),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function toWriteErrorInfo(err: KumikoError): WriteErrorInfo {
|
|
70
|
+
// In dev/test surface the cause-snapshot through `details` so the
|
|
71
|
+
// HTTP response carries something useful. Without this, internal_error
|
|
72
|
+
// crashes round-trip through WriteErrorInfo (no cause field) and come
|
|
73
|
+
// back to the client as a bare `{ message: "internal error" }` —
|
|
74
|
+
// identical user-experience to a misconfigured prod, just slower
|
|
75
|
+
// because the dev has to add try/catch + console.log to find the
|
|
76
|
+
// actual stack. Same gating as serializeError: NODE_ENV !== production.
|
|
77
|
+
const causeDetails =
|
|
78
|
+
err.details === undefined && err.code === "internal_error" && err.cause instanceof Error
|
|
79
|
+
? process.env["NODE_ENV"] !== "production"
|
|
80
|
+
? {
|
|
81
|
+
causeName: err.cause.name,
|
|
82
|
+
causeMessage: err.cause.message,
|
|
83
|
+
...(err.cause.stack && {
|
|
84
|
+
causeStack: err.cause.stack.split("\n").slice(0, 8).join("\n"),
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
: undefined
|
|
88
|
+
: undefined;
|
|
89
|
+
const effectiveDetails = err.details ?? causeDetails;
|
|
90
|
+
return {
|
|
91
|
+
code: err.code,
|
|
92
|
+
httpStatus: err.httpStatus,
|
|
93
|
+
i18nKey: err.i18nKey,
|
|
94
|
+
message: err.message,
|
|
95
|
+
...(err.i18nParams && { i18nParams: err.i18nParams }),
|
|
96
|
+
...(effectiveDetails !== undefined && { details: effectiveDetails }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Reconstitutes an error from WriteErrorInfo so command() (throw-based) can
|
|
101
|
+
// keep raising a KumikoError after the batch returned structural data. The
|
|
102
|
+
// concrete subclass identity is lost here — callers can still read code /
|
|
103
|
+
// httpStatus / details but `instanceof NotFoundError` won't work. That's OK:
|
|
104
|
+
// the HTTP layer keys off code + httpStatus, not class identity.
|
|
105
|
+
export function reraiseAsKumikoError(info: WriteErrorInfo): KumikoError {
|
|
106
|
+
return new ReraisedError(info);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class ReraisedError extends KumikoError {
|
|
110
|
+
readonly code: string;
|
|
111
|
+
readonly httpStatus: number;
|
|
112
|
+
|
|
113
|
+
constructor(info: WriteErrorInfo) {
|
|
114
|
+
super({
|
|
115
|
+
message: info.message,
|
|
116
|
+
i18nKey: info.i18nKey,
|
|
117
|
+
...(info.i18nParams && { i18nParams: info.i18nParams }),
|
|
118
|
+
...(info.details !== undefined && { details: info.details }),
|
|
119
|
+
});
|
|
120
|
+
this.code = info.code;
|
|
121
|
+
this.httpStatus = info.httpStatus;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ZodError, ZodIssue } from "zod";
|
|
2
|
+
import { ValidationError, type ValidationFieldIssue } from "./classes";
|
|
3
|
+
|
|
4
|
+
// Zod issues carry a .code and sometimes issue-specific params (min, max, etc).
|
|
5
|
+
// We surface those under `params` so the client can render "must be at least N"
|
|
6
|
+
// without re-parsing the message.
|
|
7
|
+
//
|
|
8
|
+
// Keep this list in sync with the issue-code matrix in classes.test.ts — that
|
|
9
|
+
// test is what catches Zod upgrades introducing new param names.
|
|
10
|
+
const ISSUE_PARAM_KEYS = [
|
|
11
|
+
"minimum",
|
|
12
|
+
"maximum",
|
|
13
|
+
"expected",
|
|
14
|
+
"received",
|
|
15
|
+
"type",
|
|
16
|
+
"inclusive",
|
|
17
|
+
"exact",
|
|
18
|
+
"keys",
|
|
19
|
+
// Zod 4 additions:
|
|
20
|
+
"format", // invalid_format (email, url, uuid, regex, ...)
|
|
21
|
+
"divisor", // not_multiple_of
|
|
22
|
+
"values", // invalid_value (enum / literal)
|
|
23
|
+
"pattern", // invalid_format with regex
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export function validationErrorFromZod(error: ZodError): ValidationError {
|
|
27
|
+
const fields = error.issues.map<ValidationFieldIssue>((issue) => {
|
|
28
|
+
const params = extractIssueParams(issue);
|
|
29
|
+
return {
|
|
30
|
+
path: issue.path.map(String).join(".") || "(root)",
|
|
31
|
+
code: issue.code,
|
|
32
|
+
i18nKey: `errors.validation.${issue.code}`,
|
|
33
|
+
...(params && { params }),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
return new ValidationError({ fields }, { cause: error });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractIssueParams(issue: ZodIssue): Readonly<Record<string, unknown>> | undefined {
|
|
40
|
+
// ZodIssue is a discriminated union with variant-specific params (minimum,
|
|
41
|
+
// maximum, expected, …); reading them generically requires widening since
|
|
42
|
+
// the union members don't share an index signature.
|
|
43
|
+
const out: Record<string, unknown> = {};
|
|
44
|
+
const bag = issue as unknown as Record<string, unknown>; // @cast-boundary zod-issue
|
|
45
|
+
for (const key of ISSUE_PARAM_KEYS) {
|
|
46
|
+
if (bag[key] !== undefined) out[key] = bag[key];
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
49
|
+
}
|