@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,142 @@
|
|
|
1
|
+
import type { StoredEvent } from "../../event-store/event-store";
|
|
2
|
+
import type { AppContext } from "./handlers";
|
|
3
|
+
import type { EntityId } from "./identifiers";
|
|
4
|
+
|
|
5
|
+
// --- Validation ---
|
|
6
|
+
|
|
7
|
+
export type ValidationError = {
|
|
8
|
+
readonly field: string;
|
|
9
|
+
readonly error: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ValidationHookFn = (
|
|
13
|
+
data: Readonly<Record<string, unknown>>,
|
|
14
|
+
) => readonly ValidationError[] | null;
|
|
15
|
+
|
|
16
|
+
// --- Save/Delete Context (what hooks receive) ---
|
|
17
|
+
|
|
18
|
+
export type SaveContext = {
|
|
19
|
+
readonly kind: "save";
|
|
20
|
+
readonly id: EntityId;
|
|
21
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
22
|
+
readonly changes: Readonly<Record<string, unknown>>;
|
|
23
|
+
readonly previous: Readonly<Record<string, unknown>>;
|
|
24
|
+
readonly isNew: boolean;
|
|
25
|
+
readonly entityName?: string | undefined;
|
|
26
|
+
// The event that produced this save. Populated by the event-store-executor;
|
|
27
|
+
// the pipeline uses it to drive projections inside the same transaction.
|
|
28
|
+
// Optional because hand-crafted SaveContexts (tests, custom executors) may
|
|
29
|
+
// not have an event — projections just skip in that case.
|
|
30
|
+
readonly event?: StoredEvent | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type DeleteContext = {
|
|
34
|
+
readonly kind: "delete";
|
|
35
|
+
readonly id: EntityId;
|
|
36
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
37
|
+
readonly entityName?: string | undefined;
|
|
38
|
+
// See SaveContext.event — same semantics.
|
|
39
|
+
readonly event?: StoredEvent | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type LifecycleResult = SaveContext | DeleteContext;
|
|
43
|
+
|
|
44
|
+
// --- Lifecycle Hooks ---
|
|
45
|
+
|
|
46
|
+
export type PreSaveHookFn = (
|
|
47
|
+
changes: Record<string, unknown>,
|
|
48
|
+
context: AppContext & {
|
|
49
|
+
readonly previous: Readonly<Record<string, unknown>>;
|
|
50
|
+
readonly isNew: boolean;
|
|
51
|
+
},
|
|
52
|
+
) => Promise<Record<string, unknown>>;
|
|
53
|
+
|
|
54
|
+
export type PostSaveHookFn = (result: SaveContext, context: AppContext) => Promise<void>;
|
|
55
|
+
|
|
56
|
+
// Batch-variant: called once at the end of a dispatcher batch with every
|
|
57
|
+
// successful SaveContext. The per-save PostSaveHookFn still fires for
|
|
58
|
+
// side-effects that need per-entity semantics (SSE); PostSaveBatch exists
|
|
59
|
+
// for adapters that can amortise work across the whole batch (e.g. search
|
|
60
|
+
// index batch-writes, bulk webhook fanout).
|
|
61
|
+
export type PostSaveBatchHookFn = (
|
|
62
|
+
results: readonly SaveContext[],
|
|
63
|
+
context: AppContext,
|
|
64
|
+
) => Promise<void>;
|
|
65
|
+
|
|
66
|
+
export type PreDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
|
|
67
|
+
|
|
68
|
+
export type PostDeleteHookFn = (payload: DeleteContext, context: AppContext) => Promise<void>;
|
|
69
|
+
|
|
70
|
+
export type PostDeleteBatchHookFn = (
|
|
71
|
+
payloads: readonly DeleteContext[],
|
|
72
|
+
context: AppContext,
|
|
73
|
+
) => Promise<void>;
|
|
74
|
+
|
|
75
|
+
export type PreQueryHookFn = (
|
|
76
|
+
payload: Record<string, unknown>,
|
|
77
|
+
context: AppContext,
|
|
78
|
+
) => Promise<Record<string, unknown>>;
|
|
79
|
+
|
|
80
|
+
export type LifecycleHookFn =
|
|
81
|
+
| PreSaveHookFn
|
|
82
|
+
| PostSaveHookFn
|
|
83
|
+
| PreDeleteHookFn
|
|
84
|
+
| PostDeleteHookFn
|
|
85
|
+
| PreQueryHookFn;
|
|
86
|
+
|
|
87
|
+
// --- Hook Phases ---
|
|
88
|
+
//
|
|
89
|
+
// inTransaction: Hook runs inside the DB transaction. Failures roll back
|
|
90
|
+
// the entire write. Use for: DB-based side-effects (counter updates,
|
|
91
|
+
// dependent entity writes).
|
|
92
|
+
//
|
|
93
|
+
// afterCommit (default): Hook runs after the transaction commits. Failures
|
|
94
|
+
// are logged but don't affect the write. Use for: external systems
|
|
95
|
+
// (SSE broadcast, search index, email, webhooks).
|
|
96
|
+
|
|
97
|
+
export const HookPhases = {
|
|
98
|
+
inTransaction: "inTransaction",
|
|
99
|
+
afterCommit: "afterCommit",
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
102
|
+
export type HookPhase = (typeof HookPhases)[keyof typeof HookPhases];
|
|
103
|
+
|
|
104
|
+
// Owner-tag shared across every hook structure. The lifecycle pipeline uses
|
|
105
|
+
// it to skip hooks whose owning feature is globally disabled:
|
|
106
|
+
// - A concrete feature name like "orders" → subject to the feature-toggle
|
|
107
|
+
// filter (skipped when "orders" is disabled).
|
|
108
|
+
// - "*" (star) → invariant plumbing, never filtered. Reserved for
|
|
109
|
+
// extension-provided hooks and framework-internal hooks that belong to
|
|
110
|
+
// the pipeline itself, not a feature.
|
|
111
|
+
// - Omitted (undefined) → treated as "*". Supports tests that hand-build
|
|
112
|
+
// HookMap objects without caring about ownership.
|
|
113
|
+
export type HookOwner = { readonly featureName?: string };
|
|
114
|
+
|
|
115
|
+
export type PhasedHook<TFn> = {
|
|
116
|
+
readonly fn: TFn;
|
|
117
|
+
readonly phase: HookPhase;
|
|
118
|
+
} & HookOwner;
|
|
119
|
+
|
|
120
|
+
// Flat (non-phased) hook — preSave, preQuery. Same owner contract, no
|
|
121
|
+
// phase semantics because these hooks run exactly once per handler pass
|
|
122
|
+
// before/around the DB transaction.
|
|
123
|
+
export type OwnedFn<TFn> = {
|
|
124
|
+
readonly fn: TFn;
|
|
125
|
+
} & HookOwner;
|
|
126
|
+
|
|
127
|
+
// --- Hook Maps ---
|
|
128
|
+
|
|
129
|
+
export type HookMap = {
|
|
130
|
+
readonly validation: Readonly<Record<string, ValidationHookFn>>;
|
|
131
|
+
readonly preSave: Readonly<Record<string, readonly OwnedFn<PreSaveHookFn>[]>>;
|
|
132
|
+
readonly postSave: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
|
|
133
|
+
readonly preDelete: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
|
|
134
|
+
readonly postDelete: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
|
|
135
|
+
readonly preQuery: Readonly<Record<string, readonly OwnedFn<PreQueryHookFn>[]>>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type EntityHookMap = {
|
|
139
|
+
readonly postSave: Readonly<Record<string, readonly PhasedHook<PostSaveHookFn>[]>>;
|
|
140
|
+
readonly preDelete: Readonly<Record<string, readonly PhasedHook<PreDeleteHookFn>[]>>;
|
|
141
|
+
readonly postDelete: Readonly<Record<string, readonly PhasedHook<PostDeleteHookFn>[]>>;
|
|
142
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// HTTP-Route-Definition — feature-deklarierte HTTP-Endpoints außerhalb
|
|
2
|
+
// der /api/write|query|batch-Pipeline. Use-Case: RSS/Atom-Feeds, OpenAPI-
|
|
3
|
+
// Specs, OG-Image-Generators, Webhook-Receiver — alles wo der Feature-
|
|
4
|
+
// Author das Wire-Format selbst kontrolliert.
|
|
5
|
+
//
|
|
6
|
+
// Pattern symmetrisch zu r.queryHandler / r.writeHandler: Definition als
|
|
7
|
+
// Teil des Features (nicht des App-Bootstrapping). Phase-3 Multi-Tenant
|
|
8
|
+
// wird trivial weil tenant-context via host-resolution greift.
|
|
9
|
+
//
|
|
10
|
+
// Escape-hatch bleibt: runProdApp.extraRoutes für hand-rolled Routes die
|
|
11
|
+
// nichts mit einem Feature zu tun haben (z.B. plattform-spezifische
|
|
12
|
+
// Static-Serving-Logic).
|
|
13
|
+
|
|
14
|
+
import type { Context } from "hono";
|
|
15
|
+
|
|
16
|
+
/** Subset von HTTP-Methoden den wir aktiv unterstützen. Hono spricht
|
|
17
|
+
* alle, aber das hier sind die einzigen die ein Feature-Author
|
|
18
|
+
* realistisch deklariert. */
|
|
19
|
+
export type HttpRouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
20
|
+
|
|
21
|
+
/** Dependencies die der Handler vom Framework bekommt. App-Author kann
|
|
22
|
+
* die App selbst aufrufen (`deps.app.fetch(...)` für intern-call) oder
|
|
23
|
+
* direkt per dispatcher Daten ziehen. Db/Redis sind die rohen Connections
|
|
24
|
+
* — wer Tenant-Scope braucht muss durch dispatcher.query gehen.
|
|
25
|
+
*
|
|
26
|
+
* Hono-typing: `Context<any, any>` weil das Hono-Type-Param-Setup nur
|
|
27
|
+
* intern relevant ist. Concrete Hono-app wird im Boot-Path zugewiesen. */
|
|
28
|
+
export type HttpRouteHandlerDeps = {
|
|
29
|
+
/** Die Hono-app — Handler kann via app.fetch(...) interne Routes
|
|
30
|
+
* ansprechen (z.B. /api/query mit der vollen Auth-/Anonymous-Chain). */
|
|
31
|
+
// biome-ignore lint/suspicious/noExplicitAny: Hono's generic-Param ist im Framework-Boundary unsichtbar
|
|
32
|
+
readonly app: import("hono").Hono<any, any>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type HttpRouteHandler = (
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: Hono Context-Generics sind im Framework-Boundary unsichtbar
|
|
37
|
+
c: Context<any, any>,
|
|
38
|
+
deps: HttpRouteHandlerDeps,
|
|
39
|
+
) => Response | Promise<Response>;
|
|
40
|
+
|
|
41
|
+
export type HttpRouteDefinition = {
|
|
42
|
+
/** HTTP-Methode — bei Hono-Mount via app.{get,post,...}(path). */
|
|
43
|
+
readonly method: HttpRouteMethod;
|
|
44
|
+
/** URL-Pfad (Hono-Pattern, z.B. "/feed.xml" oder "/og/:tenantId.png"). */
|
|
45
|
+
readonly path: string;
|
|
46
|
+
/** Wenn true, bypasses die /api/*-Auth-Middleware. Default false —
|
|
47
|
+
* Routes liegen außerhalb /api/* und sehen die Auth-Middleware
|
|
48
|
+
* ohnehin nicht; das Flag ist semantisch (= "diese Route ist
|
|
49
|
+
* bewusst öffentlich") für Boot-Validator + Doku. */
|
|
50
|
+
readonly anonymous?: boolean;
|
|
51
|
+
/** Hono-Handler. Bekommt Hono-Context + Framework-Deps; returnt
|
|
52
|
+
* Response (sync oder async). */
|
|
53
|
+
readonly handler: HttpRouteHandler;
|
|
54
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Domain-identifier type aliases. Used everywhere a tenantId/userId/aggregateId
|
|
2
|
+
// travels through the framework. One declaration per concept so future
|
|
3
|
+
// representation changes (branded types, UUID validation, opaque wrappers)
|
|
4
|
+
// land in a single place.
|
|
5
|
+
|
|
6
|
+
// Tenant identifier — UUID string today. May become branded/opaque later
|
|
7
|
+
// without touching call sites.
|
|
8
|
+
export type TenantId = string;
|
|
9
|
+
|
|
10
|
+
// Lowercase UUID (any RFC-4122 variant). Strict enough to keep client-
|
|
11
|
+
// supplied junk (e.g. SQL fragments, path-traversal probes) out of the
|
|
12
|
+
// pipeline; loose enough that v4 / v7 / nil all match. Any caller that
|
|
13
|
+
// already holds a TenantId from a trusted source (JWT payload, server
|
|
14
|
+
// config) skips this — the helper is for **untrusted input** crossing
|
|
15
|
+
// the system boundary.
|
|
16
|
+
const TENANT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
17
|
+
|
|
18
|
+
// Validates a candidate string against the tenantId format and returns it
|
|
19
|
+
// as a TenantId, or `null` when it doesn't match. Use at every system
|
|
20
|
+
// boundary that admits untrusted input (HTTP headers, cookies, query
|
|
21
|
+
// params). Returning null instead of throwing keeps the caller in charge
|
|
22
|
+
// of the rejection shape — middleware returns 400, batch jobs may filter
|
|
23
|
+
// + log, and unit tests don't need a try/catch.
|
|
24
|
+
export function parseTenantId(value: unknown): TenantId | null {
|
|
25
|
+
if (typeof value !== "string") return null;
|
|
26
|
+
if (!TENANT_ID_REGEX.test(value)) return null;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// "System-scope" tenant marker: handlers carry this tenantId when the event
|
|
31
|
+
// doesn't belong to any particular tenant (reference data, cross-tenant
|
|
32
|
+
// jobs, global config). The concrete UUID is a valid v4 (not all-zeroes —
|
|
33
|
+
// Postgres' UUID type rejects invalid variants), chosen to be easy to
|
|
34
|
+
// eyeball in logs. Central constant so call sites don't re-type the string
|
|
35
|
+
// and the isSystemTenant() check stays in sync.
|
|
36
|
+
export const SYSTEM_TENANT_ID: TenantId = "00000000-0000-4000-8000-000000000000";
|
|
37
|
+
|
|
38
|
+
export function isSystemTenant(tenantId: TenantId | null | undefined): boolean {
|
|
39
|
+
return !tenantId || tenantId === SYSTEM_TENANT_ID;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Primary-key identifier for any entity row. Two shapes coexist because of
|
|
43
|
+
// the entity-def `idType` switch: classic CRUD entities keep `serial` (number),
|
|
44
|
+
// while tenant + ES aggregates run on `uuid` (string). Call sites that pass
|
|
45
|
+
// the id through to the DB layer stay agnostic; only code that formats ids
|
|
46
|
+
// for URLs, logs, or cache keys needs `String(id)` — JS coerces both safely.
|
|
47
|
+
export type EntityId = number | string;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Barrel: re-exports all types from logical modules
|
|
2
|
+
// Duplicate types (OnDeleteStrategy, ConfigScope, ConcurrencyMode, LifecycleHookType)
|
|
3
|
+
// are defined ONLY in constants.ts — re-exported here for backwards compatibility.
|
|
4
|
+
|
|
5
|
+
// Re-export types that were duplicated in types.ts but are canonical in constants.ts
|
|
6
|
+
export type {
|
|
7
|
+
ConcurrencyMode,
|
|
8
|
+
ConfigScope,
|
|
9
|
+
LifecycleHookType,
|
|
10
|
+
OnDeleteStrategy,
|
|
11
|
+
} from "../constants";
|
|
12
|
+
export type {
|
|
13
|
+
ConfigAccessor,
|
|
14
|
+
ConfigAccessorFactory,
|
|
15
|
+
ConfigBounds,
|
|
16
|
+
ConfigComputedContext,
|
|
17
|
+
ConfigComputedFn,
|
|
18
|
+
ConfigDefinition,
|
|
19
|
+
ConfigKeyAccess,
|
|
20
|
+
ConfigKeyDefinition,
|
|
21
|
+
ConfigKeyHandle,
|
|
22
|
+
ConfigKeyType,
|
|
23
|
+
ConfigResolver,
|
|
24
|
+
ConfigStoredRow,
|
|
25
|
+
ConfigValue,
|
|
26
|
+
ConfigValueSource,
|
|
27
|
+
ConfigValueWithSource,
|
|
28
|
+
JobDefinition,
|
|
29
|
+
JobHandlerFn,
|
|
30
|
+
JobRunIn,
|
|
31
|
+
JobTrigger,
|
|
32
|
+
NotificationDataFn,
|
|
33
|
+
NotificationDefinition,
|
|
34
|
+
NotificationRecipientFn,
|
|
35
|
+
NotificationTemplateFn,
|
|
36
|
+
ReferenceDataDef,
|
|
37
|
+
RegistrarExtensionDef,
|
|
38
|
+
RegistrarExtensionHooks,
|
|
39
|
+
RegistrarExtensionRegistration,
|
|
40
|
+
RunIn,
|
|
41
|
+
TranslationEntry,
|
|
42
|
+
TranslationKeys,
|
|
43
|
+
TranslationsDef,
|
|
44
|
+
UiExtensionDef,
|
|
45
|
+
} from "./config";
|
|
46
|
+
// Cross-Feature Compile-Time-Type-Map — features extend per declare-module.
|
|
47
|
+
export type {
|
|
48
|
+
KumikoEntityTypeMap,
|
|
49
|
+
KumikoEventTypeMap,
|
|
50
|
+
KumikoHandlerPayloadMap,
|
|
51
|
+
KumikoHandlerResultMap,
|
|
52
|
+
} from "./event-type-map";
|
|
53
|
+
export type {
|
|
54
|
+
FeatureDefinition,
|
|
55
|
+
FeatureMetricDef,
|
|
56
|
+
FeatureMetricType,
|
|
57
|
+
FeatureRegistrar,
|
|
58
|
+
MetricOptions,
|
|
59
|
+
Registry,
|
|
60
|
+
SecretKeyDefinition,
|
|
61
|
+
SecretKeyHandle,
|
|
62
|
+
SecretOptions,
|
|
63
|
+
} from "./feature";
|
|
64
|
+
export type {
|
|
65
|
+
AnyFileFieldDef,
|
|
66
|
+
BooleanFieldDef,
|
|
67
|
+
DateFieldDef,
|
|
68
|
+
DefaultCurrency,
|
|
69
|
+
EmbeddedFieldDef,
|
|
70
|
+
EmbeddedSubFieldDef,
|
|
71
|
+
EntityDefinition,
|
|
72
|
+
EntityIndexDef,
|
|
73
|
+
FieldAccess,
|
|
74
|
+
FieldDefinition,
|
|
75
|
+
FieldsMap,
|
|
76
|
+
FileFieldDef,
|
|
77
|
+
FilesFieldDef,
|
|
78
|
+
ImageFieldDef,
|
|
79
|
+
ImagesFieldDef,
|
|
80
|
+
LocatedTimestampFieldDef,
|
|
81
|
+
LongTextFieldDef,
|
|
82
|
+
MoneyFieldDef,
|
|
83
|
+
MultiSelectFieldDef,
|
|
84
|
+
NumberFieldDef,
|
|
85
|
+
ReferenceFieldDef,
|
|
86
|
+
SelectFieldDef,
|
|
87
|
+
TextFieldDef,
|
|
88
|
+
TimestampFieldDef,
|
|
89
|
+
TransitionMap,
|
|
90
|
+
TzFieldDef,
|
|
91
|
+
} from "./fields";
|
|
92
|
+
export { DEFAULT_CURRENCIES, isFileField } from "./fields";
|
|
93
|
+
export type {
|
|
94
|
+
AccessRule,
|
|
95
|
+
AggregateStreamHandle,
|
|
96
|
+
AppContext,
|
|
97
|
+
AppendEventArgs,
|
|
98
|
+
AppendEventFn,
|
|
99
|
+
AppendEventUnsafeFn,
|
|
100
|
+
AuthClaimsContext,
|
|
101
|
+
AuthClaimsFn,
|
|
102
|
+
AuthClaimsHookDef,
|
|
103
|
+
CamelToKebab,
|
|
104
|
+
ClaimKeyDefinition,
|
|
105
|
+
ClaimKeyHandle,
|
|
106
|
+
ClaimKeyJsType,
|
|
107
|
+
ClaimKeyType,
|
|
108
|
+
EntityRef,
|
|
109
|
+
EventDef,
|
|
110
|
+
EventMigrationDef,
|
|
111
|
+
EventUpcastCtx,
|
|
112
|
+
EventUpcastFn,
|
|
113
|
+
FetchForWritingArgs,
|
|
114
|
+
HandlerContext,
|
|
115
|
+
HandlerRef,
|
|
116
|
+
JobContext,
|
|
117
|
+
JobRunnerRef,
|
|
118
|
+
NameOrRef,
|
|
119
|
+
NotifyFactory,
|
|
120
|
+
NotifyFn,
|
|
121
|
+
NotifyOptions,
|
|
122
|
+
NotifyPriority,
|
|
123
|
+
QualifiedEventName,
|
|
124
|
+
QueryEvent,
|
|
125
|
+
QueryHandlerDef,
|
|
126
|
+
QueryHandlerFn,
|
|
127
|
+
RateLimitOption,
|
|
128
|
+
RateLimitPer,
|
|
129
|
+
SessionUser,
|
|
130
|
+
WriteEvent,
|
|
131
|
+
WriteHandlerDef,
|
|
132
|
+
WriteHandlerFn,
|
|
133
|
+
WriteResult,
|
|
134
|
+
} from "./handlers";
|
|
135
|
+
export { resolveName, withResponseData } from "./handlers";
|
|
136
|
+
export type {
|
|
137
|
+
DeleteContext,
|
|
138
|
+
EntityHookMap,
|
|
139
|
+
HookMap,
|
|
140
|
+
HookOwner,
|
|
141
|
+
HookPhase,
|
|
142
|
+
LifecycleHookFn,
|
|
143
|
+
LifecycleResult,
|
|
144
|
+
OwnedFn,
|
|
145
|
+
PhasedHook,
|
|
146
|
+
PostDeleteBatchHookFn,
|
|
147
|
+
PostDeleteHookFn,
|
|
148
|
+
PostSaveBatchHookFn,
|
|
149
|
+
PostSaveHookFn,
|
|
150
|
+
PreDeleteHookFn,
|
|
151
|
+
PreQueryHookFn,
|
|
152
|
+
PreSaveHookFn,
|
|
153
|
+
SaveContext,
|
|
154
|
+
ValidationError,
|
|
155
|
+
ValidationHookFn,
|
|
156
|
+
} from "./hooks";
|
|
157
|
+
export { HookPhases } from "./hooks";
|
|
158
|
+
export type {
|
|
159
|
+
HttpRouteDefinition,
|
|
160
|
+
HttpRouteHandler,
|
|
161
|
+
HttpRouteHandlerDeps,
|
|
162
|
+
HttpRouteMethod,
|
|
163
|
+
} from "./http-route";
|
|
164
|
+
// Domain-identifier type aliases — see identifiers.ts for rationale.
|
|
165
|
+
export type { EntityId, TenantId } from "./identifiers";
|
|
166
|
+
export { isSystemTenant, SYSTEM_TENANT_ID } from "./identifiers";
|
|
167
|
+
export type { NavDefinition } from "./nav";
|
|
168
|
+
export type {
|
|
169
|
+
MspErrorMode,
|
|
170
|
+
MspErrorPolicy,
|
|
171
|
+
MultiStreamApplyFn,
|
|
172
|
+
MultiStreamProjectionDefinition,
|
|
173
|
+
ProjectionDefinition,
|
|
174
|
+
ProjectionTable,
|
|
175
|
+
SingleStreamApplyFn,
|
|
176
|
+
} from "./projection";
|
|
177
|
+
export type {
|
|
178
|
+
BelongsToRelation,
|
|
179
|
+
EntityRelations,
|
|
180
|
+
HasManyRelation,
|
|
181
|
+
ManyToManyRelation,
|
|
182
|
+
RelationDefinition,
|
|
183
|
+
} from "./relations";
|
|
184
|
+
export type {
|
|
185
|
+
ActionFormScreenDefinition,
|
|
186
|
+
ConfigEditScreenDefinition,
|
|
187
|
+
CustomScreenDefinition,
|
|
188
|
+
CustomScreenRoute,
|
|
189
|
+
EditFieldSpec,
|
|
190
|
+
EditLayout,
|
|
191
|
+
EditSectionSpec,
|
|
192
|
+
EntityEditScreenDefinition,
|
|
193
|
+
EntityListScreenDefinition,
|
|
194
|
+
FieldCondition,
|
|
195
|
+
FieldRenderer,
|
|
196
|
+
ListColumnSpec,
|
|
197
|
+
PlatformComponent,
|
|
198
|
+
RowAction,
|
|
199
|
+
RowActionNavigate,
|
|
200
|
+
RowActionWriteHandler,
|
|
201
|
+
ScreenDefinition,
|
|
202
|
+
ScreenFilter,
|
|
203
|
+
ScreenFilterOp,
|
|
204
|
+
ScreenSlots,
|
|
205
|
+
ToolbarAction,
|
|
206
|
+
} from "./screen";
|
|
207
|
+
export { normalizeEditField, normalizeListColumn } from "./screen";
|
|
208
|
+
export type { WorkspaceDefinition } from "./workspace";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AccessRule } from "./handlers";
|
|
2
|
+
|
|
3
|
+
// Nav entry declaration. Every feature that wants to appear in the app's
|
|
4
|
+
// navigation tree registers one or more entries via r.nav(). The engine
|
|
5
|
+
// keeps the list flat — ui-core's resolveNavigation assembles the parent/
|
|
6
|
+
// child tree at render time, so changes (toggles, access-gating) don't
|
|
7
|
+
// require re-indexing a tree shape server-side.
|
|
8
|
+
//
|
|
9
|
+
// Cross-feature references are allowed: `screen` may point at any
|
|
10
|
+
// registered screen QN, `parent` at any registered nav QN. The boot
|
|
11
|
+
// validator checks both references exist + rejects parent cycles.
|
|
12
|
+
export type NavDefinition = {
|
|
13
|
+
// Feature author writes the feature-local short id ("catalog"); the
|
|
14
|
+
// registry overwrites `id` with the qualified name ("shop:nav:catalog")
|
|
15
|
+
// in its stored copy. Callers of `registry.getNav(qn)` /
|
|
16
|
+
// `getTopLevelNavs()` / `getNavsByParent(...)` always see the qualified
|
|
17
|
+
// id — no parallel reverse index needed. `feature.navs[shortId]` on the
|
|
18
|
+
// unregistered FeatureDefinition keeps the short form.
|
|
19
|
+
readonly id: string;
|
|
20
|
+
// i18n translation key. Resolved at render time by the renderer's
|
|
21
|
+
// useTranslation hook; engine keeps it opaque.
|
|
22
|
+
readonly label: string;
|
|
23
|
+
// Icon key — whatever the icon registry of the active renderer understands.
|
|
24
|
+
// Engine doesn't validate; unknown icons surface as a missing icon on screen,
|
|
25
|
+
// not a boot failure.
|
|
26
|
+
readonly icon?: string;
|
|
27
|
+
// Qualified name of a parent nav entry ("<feature>:nav:<id>"). Omit for
|
|
28
|
+
// top-level entries. Boot-validator rejects cycles + dangling refs.
|
|
29
|
+
readonly parent?: string;
|
|
30
|
+
// Sort weight within the parent's children (lower = earlier). Ties are
|
|
31
|
+
// broken by registration order — features registered later appear lower.
|
|
32
|
+
readonly order?: number;
|
|
33
|
+
// Qualified name of the screen this entry navigates to
|
|
34
|
+
// ("<feature>:screen:<id>"). Omit for pure grouping entries (a parent-only
|
|
35
|
+
// nav node that renders a sub-tree but has no target screen itself).
|
|
36
|
+
readonly screen?: string;
|
|
37
|
+
// Role / openToAll gate. The nav resolver hides entries the user can't
|
|
38
|
+
// reach; leave unset to always show (engine stays un-opinionated about
|
|
39
|
+
// who sees what — apps that need default-deny can set { roles: [] }).
|
|
40
|
+
readonly access?: AccessRule;
|
|
41
|
+
// Workspace QNs this entry self-assigns to. Merged at boot with any
|
|
42
|
+
// r.workspace({ nav: [...] }) explicit lists. Omit to leave workspace
|
|
43
|
+
// membership decided solely by the workspace's nav list (or both empty
|
|
44
|
+
// → entry belongs to no workspace).
|
|
45
|
+
readonly workspaces?: readonly string[];
|
|
46
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { DbRunner } from "../../db/connection";
|
|
2
|
+
import type { TableColumns } from "../../db/dialect";
|
|
3
|
+
import type { StoredEvent } from "../../event-store/event-store";
|
|
4
|
+
import type { MultiStreamApplyContext } from "../../pipeline/multi-stream-apply-context";
|
|
5
|
+
import type { RunIn } from "./config";
|
|
6
|
+
|
|
7
|
+
// Drizzle pgTable shape — projections hand their table through to apply() so
|
|
8
|
+
// user code writes upserts/updates directly instead of going through a
|
|
9
|
+
// framework-managed state reducer. Using Drizzle's own `PgTableWithColumns<any>`
|
|
10
|
+
// (re-exported as TableColumns) keeps typing honest: drizzle's typed paths work
|
|
11
|
+
// inside apply(), but the column union is erased so framework code doesn't need
|
|
12
|
+
// to know the schema shape of every user table.
|
|
13
|
+
// biome-ignore lint/suspicious/noExplicitAny: Drizzle's PgTable generic needs a concrete row shape; we erase it on purpose because the framework does not know user-defined column types.
|
|
14
|
+
export type ProjectionTable = TableColumns<any>;
|
|
15
|
+
|
|
16
|
+
// Single-stream projection apply: runs inline in the write-TX of the event
|
|
17
|
+
// it projects. Gets the event + TX-scoped DbRunner — that's it. Inline
|
|
18
|
+
// projections must not spawn further events (no ctx) because they run
|
|
19
|
+
// inside the command's transaction and the framework guarantees a single
|
|
20
|
+
// commit boundary per command.
|
|
21
|
+
//
|
|
22
|
+
// Generic über payload-shape. Default = Record<string, unknown> behält
|
|
23
|
+
// rückwärtskompatibles Verhalten; Konkrete Apply-Handler annotieren
|
|
24
|
+
// `SingleStreamApplyFn<MyPayload>` für typed event.payload-Access.
|
|
25
|
+
export type SingleStreamApplyFn<TPayload = Record<string, unknown>> = (
|
|
26
|
+
event: StoredEvent<TPayload>,
|
|
27
|
+
tx: DbRunner,
|
|
28
|
+
) => Promise<void>;
|
|
29
|
+
|
|
30
|
+
// Multi-stream projection apply: runs asynchronously via the event-dispatcher
|
|
31
|
+
// with its own cursor. Gets the event, tx, and a ctx surface for emitting
|
|
32
|
+
// follow-up events (saga / process-manager pattern). ctx.appendEvent +
|
|
33
|
+
// ctx.loadAggregate are the Marten-equivalent of IProjectionSession — write
|
|
34
|
+
// cross-aggregate reactions here, not in single-stream projections.
|
|
35
|
+
export type MultiStreamApplyFn<TPayload = Record<string, unknown>> = (
|
|
36
|
+
event: StoredEvent<TPayload>,
|
|
37
|
+
tx: DbRunner,
|
|
38
|
+
ctx: MultiStreamApplyContext,
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
|
|
41
|
+
export type ProjectionDefinition = {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
// One or more entity names whose events feed this projection. Event-types
|
|
44
|
+
// are matched in `apply` (e.g. "unit.created") — `source` is only used to
|
|
45
|
+
// index projections so the executor doesn't scan all projections on every
|
|
46
|
+
// write.
|
|
47
|
+
readonly source: string | readonly string[];
|
|
48
|
+
// Drizzle-table the projection materializes into. User owns the schema —
|
|
49
|
+
// framework just guarantees the TX and event delivery.
|
|
50
|
+
readonly table: ProjectionTable;
|
|
51
|
+
// Keyed by fully-qualified event type ("<aggregate>.<verb>", e.g. "unit.created").
|
|
52
|
+
// Missing keys are silently skipped — a projection declares only the events it
|
|
53
|
+
// cares about.
|
|
54
|
+
readonly apply: Readonly<Record<string, SingleStreamApplyFn>>;
|
|
55
|
+
// Auto-registered projection (one per r.entity) that exists ONLY to
|
|
56
|
+
// make rebuildProjection work for entity-tables. Live writes go through
|
|
57
|
+
// the EventStoreExecutor directly — firing the implicit apply inline
|
|
58
|
+
// would double-write into the same table. The inline-projection-runner
|
|
59
|
+
// skips entries with this flag; rebuildProjection treats them
|
|
60
|
+
// identically to explicit projections.
|
|
61
|
+
readonly isImplicit?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Per-lifecycle error policy for a MultiStreamProjection. Mirrors Marten's
|
|
65
|
+
// Projections.Errors / Projections.RebuildErrors split — a projection can
|
|
66
|
+
// be lenient during steady-state delivery but strict during rebuild (or
|
|
67
|
+
// vice versa).
|
|
68
|
+
export type MspErrorPolicy = {
|
|
69
|
+
// When the apply handler throws: log the error, advance the cursor past
|
|
70
|
+
// the offending event, and keep delivering. Default false — current
|
|
71
|
+
// strict behaviour: retry up to maxAttempts, then mark the consumer
|
|
72
|
+
// status="dead" and pause delivery. Use for best-effort sinks
|
|
73
|
+
// (notifications, webhooks) where a single bad event should not stall
|
|
74
|
+
// the whole consumer.
|
|
75
|
+
readonly skipApplyErrors?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type MspErrorMode = {
|
|
79
|
+
// Applied during steady-state dispatcher delivery.
|
|
80
|
+
readonly continuous?: MspErrorPolicy;
|
|
81
|
+
// Applied during rebuildProjection() / backfill passes. When omitted,
|
|
82
|
+
// rebuild inherits continuous — explicit override common for "strict
|
|
83
|
+
// during rebuild, lenient in production" patterns.
|
|
84
|
+
readonly rebuild?: MspErrorPolicy;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Marten-style MultiStreamProjection: aggregates events from many streams
|
|
88
|
+
// into one cross-cutting read-model. Unlike ProjectionDefinition (single-
|
|
89
|
+
// source, inline in the write-TX), an MSP is ASYNC — the event-dispatcher
|
|
90
|
+
// picks events off the log via its own cursor. Handlers MUST be idempotent
|
|
91
|
+
// because the dispatcher guarantees at-least-once delivery.
|
|
92
|
+
//
|
|
93
|
+
// Use for Sagas / process managers, customer-centric views that span
|
|
94
|
+
// multiple aggregate types, cross-feature aggregations, audit logs. With
|
|
95
|
+
// `table` omitted, the MSP becomes a pure side-effect consumer — sending
|
|
96
|
+
// notifications, posting webhooks, updating an external system. Marten's
|
|
97
|
+
// equivalent of a subscription / event listener, without a separate API.
|
|
98
|
+
export type MultiStreamProjectionDefinition = {
|
|
99
|
+
readonly name: string;
|
|
100
|
+
// Optional: omit for side-effect-only handlers (notifications, external
|
|
101
|
+
// system sync). When present, setupTestStack auto-pushes the table.
|
|
102
|
+
readonly table?: ProjectionTable;
|
|
103
|
+
// Keyed by fully-qualified event type. Unlike a single-stream projection,
|
|
104
|
+
// there is no source-entity hint — the MSP declares the event types it
|
|
105
|
+
// cares about directly. Extract the identity/grouping key inside the
|
|
106
|
+
// apply handler from the event payload.
|
|
107
|
+
readonly apply: Readonly<Record<string, MultiStreamApplyFn>>;
|
|
108
|
+
// How the dispatcher handles apply-throws. Default strict (retry + dead).
|
|
109
|
+
readonly errorMode?: MspErrorMode;
|
|
110
|
+
// Which deploy-lane runs this MSP's dispatcher. Default "worker". MSPs
|
|
111
|
+
// share a single consumer-row per MSP name with SKIP LOCKED, so "both"
|
|
112
|
+
// is safe semantically (API + Worker race for each event; exactly one
|
|
113
|
+
// wins). Use "api" for MSPs that need in-process state on the API
|
|
114
|
+
// (rare); use "both" only when genuinely load-balancing is helpful.
|
|
115
|
+
readonly runIn?: RunIn;
|
|
116
|
+
// Delivery semantics across multi-instance deploys:
|
|
117
|
+
// "shared" (default) — one cursor across all dispatcher instances,
|
|
118
|
+
// SKIP LOCKED serialises; each event delivered exactly
|
|
119
|
+
// once globally. The right choice for side-effects with
|
|
120
|
+
// any downstream state: notifications, external APIs,
|
|
121
|
+
// projection tables, audit rows.
|
|
122
|
+
// "per-instance" — one cursor PER dispatcher instance, so every process
|
|
123
|
+
// delivers every event. Required for push-to-local-
|
|
124
|
+
// subscribers (SSE, in-memory caches): a split-deploy
|
|
125
|
+
// where API instance B emits an event that API instance
|
|
126
|
+
// A's clients also need to see. Handler MUST be
|
|
127
|
+
// side-effect-free relative to the DB — it only reaches
|
|
128
|
+
// in-process structures — otherwise each instance
|
|
129
|
+
// writes duplicate rows. Misuse = duplicated side
|
|
130
|
+
// effects, not a safety property.
|
|
131
|
+
readonly delivery?: "shared" | "per-instance";
|
|
132
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { OnDeleteStrategy } from "../constants";
|
|
2
|
+
|
|
3
|
+
// --- Relations ---
|
|
4
|
+
|
|
5
|
+
export type BelongsToRelation = {
|
|
6
|
+
readonly type: "belongsTo";
|
|
7
|
+
readonly target: string;
|
|
8
|
+
readonly foreignKey: string;
|
|
9
|
+
readonly searchInclude?: readonly string[];
|
|
10
|
+
// onDelete is declared on the parent-side (hasMany / manyToMany) because
|
|
11
|
+
// that's where the "what happens to my children?" decision lives. A
|
|
12
|
+
// belongsTo node just points at a parent — the parent's onDelete drives
|
|
13
|
+
// the cleanup.
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type HasManyRelation = {
|
|
17
|
+
readonly type: "hasMany";
|
|
18
|
+
readonly target: string;
|
|
19
|
+
readonly foreignKey: string;
|
|
20
|
+
readonly onDelete?: OnDeleteStrategy;
|
|
21
|
+
// When true, a nested payload under this relation's key (e.g.
|
|
22
|
+
// `{ tasks: [{ ... }] }` on a `project:create` write) is auto-expanded
|
|
23
|
+
// into child writes: parent first, then one child-write per entry with
|
|
24
|
+
// the foreign key set to the parent's new id — all in the same TX.
|
|
25
|
+
// Opt-in (default false) so legacy hasMany relations that were declared
|
|
26
|
+
// purely for cascade-delete or UI-nav semantics don't silently gain a
|
|
27
|
+
// client-writable path. Children are never inferred from payload-shape
|
|
28
|
+
// alone; only relations with this flag unlock nested-write.
|
|
29
|
+
//
|
|
30
|
+
// Scope v1: depth=1, create-only, hasMany-only. Update-nested,
|
|
31
|
+
// delete-nested, and belongsTo/m2m auto-expansion are explicit future
|
|
32
|
+
// work — when they arrive, they'll take the same flag so the opt-in
|
|
33
|
+
// stays a single, consistent surface.
|
|
34
|
+
readonly nestedWrite?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ManyToManyRelation = {
|
|
38
|
+
readonly type: "manyToMany";
|
|
39
|
+
readonly target: string;
|
|
40
|
+
readonly through: {
|
|
41
|
+
readonly table: string;
|
|
42
|
+
readonly sourceKey: string;
|
|
43
|
+
readonly targetKey: string;
|
|
44
|
+
};
|
|
45
|
+
readonly searchInclude?: readonly string[];
|
|
46
|
+
readonly onDelete?: OnDeleteStrategy;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type RelationDefinition = BelongsToRelation | HasManyRelation | ManyToManyRelation;
|
|
50
|
+
|
|
51
|
+
export type EntityRelations = Readonly<Record<string, RelationDefinition>>;
|