@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,103 @@
|
|
|
1
|
+
import type { AuthClaimsContext, AuthClaimsHookDef, SessionUser } from "../engine/types";
|
|
2
|
+
import type { Logger } from "../logging/types";
|
|
3
|
+
|
|
4
|
+
// Shape the dispatcher (or a test harness) has to hand in: a way to build the
|
|
5
|
+
// per-hook context and the list of registered hooks. Staying off the
|
|
6
|
+
// Dispatcher/Registry types here keeps this utility trivially unit-testable —
|
|
7
|
+
// a fake `hooks` array + a stub `contextFactory` and you can assert the merge
|
|
8
|
+
// policy without spinning up Drizzle, Redis, or anything else.
|
|
9
|
+
export type ResolveAuthClaimsArgs = {
|
|
10
|
+
readonly user: SessionUser;
|
|
11
|
+
readonly hooks: readonly AuthClaimsHookDef[];
|
|
12
|
+
readonly contextFactory: (user: SessionUser) => AuthClaimsContext;
|
|
13
|
+
readonly log?: Logger;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Run every registered r.authClaims() hook in parallel and merge the results
|
|
17
|
+
// into a single claim record.
|
|
18
|
+
//
|
|
19
|
+
// Key policy:
|
|
20
|
+
// - Auto-prefix: each key a hook returns is stored as `"<featureName>:<key>"`.
|
|
21
|
+
// Cross-feature collisions are impossible by construction.
|
|
22
|
+
// - Same-feature duplicate key (two hooks in one feature both return the same
|
|
23
|
+
// inner key, or one hook returns the same key twice): last-wins. Matches
|
|
24
|
+
// the `{ ...payload.claims }` spread the JWT layer already does.
|
|
25
|
+
// - Reserved separator: we skip any key returned by a hook that itself
|
|
26
|
+
// contains `":"` and log a warning. The separator is the framework's —
|
|
27
|
+
// claim-returning code must own the *inner* name, not the prefix.
|
|
28
|
+
//
|
|
29
|
+
// Error policy: best-effort. If a hook throws, the error is logged and that
|
|
30
|
+
// feature's claims just don't end up in the record. Login still succeeds.
|
|
31
|
+
// Rationale: claims are convenience identity-facts, not access-gates.
|
|
32
|
+
// Security decisions must go through `roles` + field-access rules, both of
|
|
33
|
+
// which are unaffected by a missing claim.
|
|
34
|
+
export async function resolveAuthClaims(
|
|
35
|
+
args: ResolveAuthClaimsArgs,
|
|
36
|
+
): Promise<Record<string, unknown>> {
|
|
37
|
+
if (args.hooks.length === 0) return {};
|
|
38
|
+
|
|
39
|
+
const ctx = args.contextFactory(args.user);
|
|
40
|
+
|
|
41
|
+
const results = await Promise.allSettled(
|
|
42
|
+
args.hooks.map((h) => runSingleHook(h, args.user, ctx, args.log)),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const merged: Record<string, unknown> = {};
|
|
46
|
+
for (let i = 0; i < results.length; i++) {
|
|
47
|
+
const hook = args.hooks[i];
|
|
48
|
+
if (!hook) continue;
|
|
49
|
+
const result = results[i];
|
|
50
|
+
if (!result) continue;
|
|
51
|
+
if (result.status === "rejected") {
|
|
52
|
+
// Already logged in runSingleHook's catch — Promise.allSettled just
|
|
53
|
+
// surfaces it here so the rest of the merge keeps going.
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const claims = result.value;
|
|
57
|
+
for (const [innerKey, value] of Object.entries(claims)) {
|
|
58
|
+
if (innerKey.includes(":")) {
|
|
59
|
+
args.log?.warn("r.authClaims return key contains reserved separator ':' — dropping", {
|
|
60
|
+
featureName: hook.featureName,
|
|
61
|
+
rejectedKey: innerKey,
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Typo / rename drift check: when the feature declared its claim
|
|
66
|
+
// vocabulary via r.claimKey(), any hook return-key outside that
|
|
67
|
+
// vocabulary is almost certainly a mistake (forgot to declare,
|
|
68
|
+
// renamed one side but not the other). We still merge it — best-
|
|
69
|
+
// effort matches the error policy — but log a warning so it surfaces.
|
|
70
|
+
//
|
|
71
|
+
// `declaredKeys` is undefined when the feature never called
|
|
72
|
+
// r.claimKey(), keeping legacy hooks silent (opt-in to the check).
|
|
73
|
+
if (hook.declaredKeys && !hook.declaredKeys.has(innerKey)) {
|
|
74
|
+
args.log?.warn(
|
|
75
|
+
"r.authClaims returned an inner-key that was not declared via r.claimKey()",
|
|
76
|
+
{ featureName: hook.featureName, undeclaredKey: innerKey },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const prefixed = `${hook.featureName}:${innerKey}`;
|
|
80
|
+
merged[prefixed] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Isolate the try/catch so the calling Promise.allSettled only sees success
|
|
87
|
+
// values — keeps the merge loop straightforward.
|
|
88
|
+
async function runSingleHook(
|
|
89
|
+
hook: AuthClaimsHookDef,
|
|
90
|
+
user: SessionUser,
|
|
91
|
+
ctx: AuthClaimsContext,
|
|
92
|
+
log: Logger | undefined,
|
|
93
|
+
): Promise<Record<string, unknown>> {
|
|
94
|
+
try {
|
|
95
|
+
return await hook.fn(user, ctx);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
log?.warn("r.authClaims hook threw — dropping this feature's claims for this login", {
|
|
98
|
+
featureName: hook.featureName,
|
|
99
|
+
err: err instanceof Error ? err.message : String(err),
|
|
100
|
+
});
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import type { TableColumns } from "../db/dialect";
|
|
3
|
+
import { OnDeleteStrategies, SystemHookNames, SystemHookPriorities } from "../engine/constants";
|
|
4
|
+
import type { PreDeleteHookFn, Registry } from "../engine/types";
|
|
5
|
+
import { ConflictError, FrameworkReasons } from "../errors";
|
|
6
|
+
import type { SystemHookDef } from "./lifecycle-pipeline";
|
|
7
|
+
|
|
8
|
+
// biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables
|
|
9
|
+
type TableMap = ReadonlyMap<string, TableColumns<any>>;
|
|
10
|
+
|
|
11
|
+
export function createCascadeDeleteHook(
|
|
12
|
+
registry: Registry,
|
|
13
|
+
tables: TableMap,
|
|
14
|
+
): SystemHookDef<PreDeleteHookFn> {
|
|
15
|
+
return {
|
|
16
|
+
name: SystemHookNames.cascadeDelete,
|
|
17
|
+
priority: SystemHookPriorities.cascadeDelete,
|
|
18
|
+
fn: async (payload, ctx) => {
|
|
19
|
+
const entityName = payload.entityName;
|
|
20
|
+
if (!entityName || !ctx.db) {
|
|
21
|
+
ctx.log?.debug(
|
|
22
|
+
`cascadeDelete: skipping — ${!entityName ? "no entityName" : "no db"} on payload ${payload.id}`,
|
|
23
|
+
);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const db = ctx.db;
|
|
27
|
+
|
|
28
|
+
// Cascade applies to outgoing hasMany / manyToMany relations only —
|
|
29
|
+
// the parent side of the link is where `onDelete` lives (see
|
|
30
|
+
// relations.ts). Incoming belongsTo edges are covered because their
|
|
31
|
+
// counterpart hasMany declares the strategy.
|
|
32
|
+
const outgoing = registry.getRelations(entityName);
|
|
33
|
+
|
|
34
|
+
// Outgoing: e.g. department.users (hasMany target: user) — users have FK to department
|
|
35
|
+
for (const [, relation] of Object.entries(outgoing)) {
|
|
36
|
+
// Only hasMany / manyToMany carry cascade semantics. belongsTo points
|
|
37
|
+
// at a parent; the parent's onDelete decides what happens to this node.
|
|
38
|
+
if (relation.type !== "hasMany" && relation.type !== "manyToMany") continue;
|
|
39
|
+
const strategy = relation.onDelete ?? OnDeleteStrategies.nothing;
|
|
40
|
+
if (strategy === OnDeleteStrategies.nothing) continue;
|
|
41
|
+
|
|
42
|
+
if (relation.type === "hasMany" && relation.foreignKey) {
|
|
43
|
+
const targetTable = tables.get(relation.target);
|
|
44
|
+
if (!targetTable) continue;
|
|
45
|
+
|
|
46
|
+
if (strategy === OnDeleteStrategies.restrict) {
|
|
47
|
+
const rows = await db
|
|
48
|
+
.select({ id: targetTable["id"] })
|
|
49
|
+
.from(targetTable)
|
|
50
|
+
.where(eq(targetTable[relation.foreignKey], payload.id))
|
|
51
|
+
.limit(1);
|
|
52
|
+
if (rows.length > 0) {
|
|
53
|
+
throw new ConflictError({
|
|
54
|
+
message: `${relation.target} has records referencing ${entityName}#${payload.id}`,
|
|
55
|
+
i18nKey: "errors.deleteRestricted",
|
|
56
|
+
details: {
|
|
57
|
+
reason: FrameworkReasons.deleteRestricted,
|
|
58
|
+
blockingEntity: relation.target,
|
|
59
|
+
entity: entityName,
|
|
60
|
+
entityId: payload.id,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (strategy === OnDeleteStrategies.cascade) {
|
|
67
|
+
await db.delete(targetTable).where(eq(targetTable[relation.foreignKey], payload.id));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (strategy === OnDeleteStrategies.setNull) {
|
|
71
|
+
await db
|
|
72
|
+
.update(targetTable)
|
|
73
|
+
.set({ [relation.foreignKey]: null })
|
|
74
|
+
.where(eq(targetTable[relation.foreignKey], payload.id));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (relation.type === "manyToMany" && relation.through) {
|
|
79
|
+
const throughTable = tables.get(relation.through.table);
|
|
80
|
+
if (!throughTable) continue;
|
|
81
|
+
// sourceKey points at the owner side (the entity being deleted).
|
|
82
|
+
// targetKey would point at the other side — filtering by it here
|
|
83
|
+
// would miss every through-row for this entity.
|
|
84
|
+
const sourceKey = relation.through.sourceKey;
|
|
85
|
+
|
|
86
|
+
if (strategy === OnDeleteStrategies.restrict) {
|
|
87
|
+
const rows = await db
|
|
88
|
+
.select({ id: throughTable["id"] })
|
|
89
|
+
.from(throughTable)
|
|
90
|
+
.where(eq(throughTable[sourceKey], payload.id))
|
|
91
|
+
.limit(1);
|
|
92
|
+
if (rows.length > 0) {
|
|
93
|
+
throw new ConflictError({
|
|
94
|
+
message: `${relation.through.table} has records referencing ${entityName}#${payload.id}`,
|
|
95
|
+
i18nKey: "errors.deleteRestricted",
|
|
96
|
+
details: {
|
|
97
|
+
reason: FrameworkReasons.deleteRestricted,
|
|
98
|
+
blockingEntity: relation.through.table,
|
|
99
|
+
entity: entityName,
|
|
100
|
+
entityId: payload.id,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (strategy === OnDeleteStrategies.cascade) {
|
|
107
|
+
await db.delete(throughTable).where(eq(throughTable[sourceKey], payload.id));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|