@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,255 @@
|
|
|
1
|
+
// Forms-Schema metadata — describes how a FeaturePattern is rendered as
|
|
2
|
+
// a Designer form, fed to an LLM as a JSON-Schema, or surfaced in the
|
|
3
|
+
// MCP-Server tool list. One schema, three consumers — that's the whole
|
|
4
|
+
// point of factoring this out of the hand-rolled C5 skeleton.
|
|
5
|
+
//
|
|
6
|
+
// **NOT a DSL.** The Source-of-Truth for a feature stays
|
|
7
|
+
// `defineFeature.ts` — the AST-visitor (feature-ast/parse.ts) reads it
|
|
8
|
+
// and the renderer (feature-ast/render.ts) writes it. This file is
|
|
9
|
+
// pure metadata for UI / LLM-prompt rendering: how to display the
|
|
10
|
+
// already-parsed FeaturePattern in a form. No alternative parser, no
|
|
11
|
+
// alternative syntax, no second canonical representation.
|
|
12
|
+
//
|
|
13
|
+
// **Vokabular** (12 input-types):
|
|
14
|
+
//
|
|
15
|
+
// text — single-line string (name / label / id)
|
|
16
|
+
// textarea — multi-line string (description / SQL / readme)
|
|
17
|
+
// number — numeric (version / fromVersion / rateLimit.limit)
|
|
18
|
+
// boolean — checkbox (softDelete / default / openToAll)
|
|
19
|
+
// select — closed enum (idType / hookType / method)
|
|
20
|
+
// string-list — readonly string[] (featureNames / roles)
|
|
21
|
+
// code-block — opaque TS source-span, read-only in the form
|
|
22
|
+
// (schemaSource / handlerBody / fnBody)
|
|
23
|
+
// entity-fields-editor — per-row editor for EntityDefinition.fields
|
|
24
|
+
// key-value-map — generic { [key]: structured-value } editor
|
|
25
|
+
// (config.keys / translations.keys / applyBodies)
|
|
26
|
+
// discriminated-union — tagged sub-form (AccessRule: roles vs openToAll)
|
|
27
|
+
// entity-ref — string with autocomplete against the registry
|
|
28
|
+
// json-readonly — opaque pretty-printed JSON (Unknown / extension)
|
|
29
|
+
//
|
|
30
|
+
// **Path notation:** `path` references the value's location inside the
|
|
31
|
+
// FeaturePattern object using dot-separated keys (`definition.fields`,
|
|
32
|
+
// `access.roles`, `templates.email`). Numeric indices use the bracket
|
|
33
|
+
// form (`columns.0`). The Designer/LLM uses paths for read & write —
|
|
34
|
+
// the patcher (C2) consumes pattern-level updates only, so paths are
|
|
35
|
+
// purely a UI concern at this stage.
|
|
36
|
+
|
|
37
|
+
import type { FeaturePatternKind } from "../feature-ast/patterns";
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Field input types
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
export type FormInputType =
|
|
44
|
+
| "text"
|
|
45
|
+
| "textarea"
|
|
46
|
+
| "number"
|
|
47
|
+
| "boolean"
|
|
48
|
+
| "select"
|
|
49
|
+
| "string-list"
|
|
50
|
+
| "code-block"
|
|
51
|
+
| "entity-fields-editor"
|
|
52
|
+
| "key-value-map"
|
|
53
|
+
| "discriminated-union"
|
|
54
|
+
| "entity-ref"
|
|
55
|
+
| "json-readonly";
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Field spec — one entry per editable property of a pattern
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Bilingual labels — Designer renders the user's locale, falls back to
|
|
63
|
+
* `en`. AI-Builder uses `en` exclusively (cheaper prompts, single
|
|
64
|
+
* grounding language for the model).
|
|
65
|
+
*/
|
|
66
|
+
export type FormFieldLabel = {
|
|
67
|
+
readonly en: string;
|
|
68
|
+
readonly de?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Common shape every field carries — the input-specific fields are added
|
|
73
|
+
* by the discriminated subtypes below.
|
|
74
|
+
*/
|
|
75
|
+
type FormFieldBase = {
|
|
76
|
+
/**
|
|
77
|
+
* Dot/bracket-path inside the FeaturePattern. e.g. "entityName",
|
|
78
|
+
* "definition.fields", "access.roles", "templates.email".
|
|
79
|
+
*/
|
|
80
|
+
readonly path: string;
|
|
81
|
+
readonly label: FormFieldLabel;
|
|
82
|
+
/**
|
|
83
|
+
* Short hint shown below the input. Optional — keep terse, the
|
|
84
|
+
* Designer / LLM is the doc consumer, not the casual reader.
|
|
85
|
+
*/
|
|
86
|
+
readonly hint?: FormFieldLabel;
|
|
87
|
+
/**
|
|
88
|
+
* Whether the form should refuse to submit when this field is empty.
|
|
89
|
+
* Defaults to `false`. Validation runs *before* the patch hits the
|
|
90
|
+
* file — caller-side guard.
|
|
91
|
+
*/
|
|
92
|
+
readonly required?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Read-only fields are surfaced for context but never edited (e.g.
|
|
95
|
+
* `kind` discriminator, opaque source-spans on mixed patterns).
|
|
96
|
+
*/
|
|
97
|
+
readonly readOnly?: boolean;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type TextField = FormFieldBase & {
|
|
101
|
+
readonly input: "text";
|
|
102
|
+
readonly placeholder?: string;
|
|
103
|
+
readonly maxLength?: number;
|
|
104
|
+
readonly pattern?: string;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type TextareaField = FormFieldBase & {
|
|
108
|
+
readonly input: "textarea";
|
|
109
|
+
readonly placeholder?: string;
|
|
110
|
+
readonly rows?: number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type NumberField = FormFieldBase & {
|
|
114
|
+
readonly input: "number";
|
|
115
|
+
readonly min?: number;
|
|
116
|
+
readonly max?: number;
|
|
117
|
+
readonly step?: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type BooleanField = FormFieldBase & {
|
|
121
|
+
readonly input: "boolean";
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type SelectOption = {
|
|
125
|
+
readonly value: string;
|
|
126
|
+
readonly label: FormFieldLabel;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type SelectField = FormFieldBase & {
|
|
130
|
+
readonly input: "select";
|
|
131
|
+
readonly options: readonly SelectOption[];
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type StringListField = FormFieldBase & {
|
|
135
|
+
readonly input: "string-list";
|
|
136
|
+
/** Per-item placeholder, e.g. "feature-name". */
|
|
137
|
+
readonly itemPlaceholder?: string;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type CodeBlockField = FormFieldBase & {
|
|
141
|
+
readonly input: "code-block";
|
|
142
|
+
/**
|
|
143
|
+
* The source language for syntax highlighting. The skeleton renders
|
|
144
|
+
* everything as plain TS; later iterations can branch on `zod` /
|
|
145
|
+
* `tsx` for dedicated highlighters.
|
|
146
|
+
*/
|
|
147
|
+
readonly language: "typescript" | "tsx" | "zod";
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Editor for `EntityDefinition.fields` — a row per field with name,
|
|
152
|
+
* type-discriminator, plus type-specific knobs. Modeled as a single
|
|
153
|
+
* input-type because the structure is too rich for a generic
|
|
154
|
+
* key-value-map (each row's right-hand side is itself a discriminated
|
|
155
|
+
* union over FieldDefinition variants).
|
|
156
|
+
*/
|
|
157
|
+
export type EntityFieldsEditorField = FormFieldBase & {
|
|
158
|
+
readonly input: "entity-fields-editor";
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generic `{ [key]: value }` editor — value-shape is described by an
|
|
163
|
+
* inner FormSchema applied to each entry. Used for config-keys,
|
|
164
|
+
* translations, projection.applyBodies, notification.templates.
|
|
165
|
+
*/
|
|
166
|
+
export type KeyValueMapField = FormFieldBase & {
|
|
167
|
+
readonly input: "key-value-map";
|
|
168
|
+
/** Placeholder for new keys. */
|
|
169
|
+
readonly keyPlaceholder?: string;
|
|
170
|
+
/** What input renders for each value. Recursive — keep it simple. */
|
|
171
|
+
readonly valueInput: FormInputType;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export type DiscriminatedUnionField = FormFieldBase & {
|
|
175
|
+
readonly input: "discriminated-union";
|
|
176
|
+
/** Tag-property name (e.g. "type" for hook-target, "kind" for relation). */
|
|
177
|
+
readonly discriminator: string;
|
|
178
|
+
/** Each branch carries its own field-list. */
|
|
179
|
+
readonly variants: ReadonlyArray<{
|
|
180
|
+
readonly tag: string;
|
|
181
|
+
readonly label: FormFieldLabel;
|
|
182
|
+
readonly fields: readonly FormFieldSpec[];
|
|
183
|
+
}>;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export type EntityRefField = FormFieldBase & {
|
|
187
|
+
readonly input: "entity-ref";
|
|
188
|
+
/**
|
|
189
|
+
* If true, the input also accepts cross-feature references like
|
|
190
|
+
* `auth:event:loggedIn`. Default is feature-local only.
|
|
191
|
+
*/
|
|
192
|
+
readonly allowQualified?: boolean;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export type JsonReadonlyField = FormFieldBase & {
|
|
196
|
+
readonly input: "json-readonly";
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type FormFieldSpec =
|
|
200
|
+
| TextField
|
|
201
|
+
| TextareaField
|
|
202
|
+
| NumberField
|
|
203
|
+
| BooleanField
|
|
204
|
+
| SelectField
|
|
205
|
+
| StringListField
|
|
206
|
+
| CodeBlockField
|
|
207
|
+
| EntityFieldsEditorField
|
|
208
|
+
| KeyValueMapField
|
|
209
|
+
| DiscriminatedUnionField
|
|
210
|
+
| EntityRefField
|
|
211
|
+
| JsonReadonlyField;
|
|
212
|
+
|
|
213
|
+
// =============================================================================
|
|
214
|
+
// Pattern schema — top-level metadata for one FeaturePattern kind
|
|
215
|
+
// =============================================================================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* High-level grouping the Designer uses for navigation: data shapes
|
|
219
|
+
* (entity / relation), behaviour (handler / hook), UI (screen / nav),
|
|
220
|
+
* meta (config / metric / secret), background (job / projection),
|
|
221
|
+
* cross-cutting (notification / authClaims / httpRoute), advanced
|
|
222
|
+
* (extendsRegistrar / unknown). Order in the Designer panel is alpha
|
|
223
|
+
* inside a category.
|
|
224
|
+
*/
|
|
225
|
+
export type PatternCategory =
|
|
226
|
+
| "data"
|
|
227
|
+
| "behaviour"
|
|
228
|
+
| "ui"
|
|
229
|
+
| "meta"
|
|
230
|
+
| "background"
|
|
231
|
+
| "cross-cutting"
|
|
232
|
+
| "advanced";
|
|
233
|
+
|
|
234
|
+
export type PatternFormSchema = {
|
|
235
|
+
readonly kind: FeaturePatternKind;
|
|
236
|
+
readonly label: FormFieldLabel;
|
|
237
|
+
/** Short blurb shown in the Designer's "add new pattern" dialog. */
|
|
238
|
+
readonly summary: FormFieldLabel;
|
|
239
|
+
readonly category: PatternCategory;
|
|
240
|
+
/**
|
|
241
|
+
* Editability matches feature-ast's `getEditability()`:
|
|
242
|
+
* - "static" → fully form-driven, no closures
|
|
243
|
+
* - "mixed" → header is form, body is opaque code-block
|
|
244
|
+
* - "opaque" → entire pattern is read-only
|
|
245
|
+
*/
|
|
246
|
+
readonly editability: "static" | "mixed" | "opaque";
|
|
247
|
+
/**
|
|
248
|
+
* Singleton kinds appear at most once per feature (requires,
|
|
249
|
+
* toggleable, config, …). The Designer hides "Add" once the
|
|
250
|
+
* singleton is present.
|
|
251
|
+
*/
|
|
252
|
+
readonly singleton?: boolean;
|
|
253
|
+
/** Ordered list of editable fields. */
|
|
254
|
+
readonly fields: readonly FormFieldSpec[];
|
|
255
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import type { DbRunner } from "../db/connection";
|
|
3
|
+
import type { StoredEvent } from "../event-store/event-store";
|
|
4
|
+
import type { MultiStreamApplyContext } from "../pipeline/multi-stream-apply-context";
|
|
5
|
+
import type { MultiStreamApplyFn, ProjectionTable, SingleStreamApplyFn } from "./types/projection";
|
|
6
|
+
|
|
7
|
+
// Typed-Apply-Helper für r.projection.apply: erlaubt per-event-type
|
|
8
|
+
// typed event.payload-Access ohne SingleStreamApplyFn-Generic durch die
|
|
9
|
+
// ganze ProjectionDefinition propagieren zu müssen.
|
|
10
|
+
//
|
|
11
|
+
// Der Helper ist ein purer Type-Vehikel — zur Laufzeit identitäts-fn:
|
|
12
|
+
//
|
|
13
|
+
// apply: {
|
|
14
|
+
// "user.created": defineApply<UserCreatedPayload>(async (event, tx) => {
|
|
15
|
+
// // event.payload ist UserCreatedPayload, nicht Record<string, unknown>
|
|
16
|
+
// await tx.insert(usersTable).values({ id: event.aggregateId, ...event.payload });
|
|
17
|
+
// }),
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// Default-Generic = Record<string, unknown> behält rückwärtskompatibles
|
|
21
|
+
// Verhalten für Apply-Handler die ohne Type-Argument geschrieben sind.
|
|
22
|
+
export function defineApply<TPayload = Record<string, unknown>>(
|
|
23
|
+
fn: (event: StoredEvent<TPayload>, tx: DbRunner) => Promise<void>,
|
|
24
|
+
): SingleStreamApplyFn {
|
|
25
|
+
// @cast-boundary engine-bridge — typed user-fn → erased internal storage
|
|
26
|
+
return fn as SingleStreamApplyFn;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Pendant für r.multiStreamProjection.apply — bekommt zusätzlich ctx.
|
|
30
|
+
export function defineMspApply<TPayload = Record<string, unknown>>(
|
|
31
|
+
fn: (event: StoredEvent<TPayload>, tx: DbRunner, ctx: MultiStreamApplyContext) => Promise<void>,
|
|
32
|
+
): MultiStreamApplyFn {
|
|
33
|
+
// @cast-boundary engine-bridge — typed user-fn → erased internal storage
|
|
34
|
+
return fn as MultiStreamApplyFn;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// UPDATE <projection-table> SET <fields> WHERE id = event.aggregateId.
|
|
38
|
+
// The "event drives one row by aggregate id" shape is what 90 % of state-
|
|
39
|
+
// machine projections look like: every status-change event maps to a single
|
|
40
|
+
// column update on the projection's own row. Pass a literal object for
|
|
41
|
+
// fixed values, or a reducer-style function when the new value comes from
|
|
42
|
+
// the event payload.
|
|
43
|
+
//
|
|
44
|
+
// r.projection({
|
|
45
|
+
// name: "invoice-status",
|
|
46
|
+
// source: "invoice",
|
|
47
|
+
// table: invoiceTable,
|
|
48
|
+
// apply: {
|
|
49
|
+
// [INVOICE.sent]: setFields(invoiceTable, { status: "sent" }),
|
|
50
|
+
// [INVOICE.statusForced]: setFields(invoiceTable, (e) => {
|
|
51
|
+
// const p = e.payload as { newStatus: InvoiceStatus };
|
|
52
|
+
// return { status: p.newStatus };
|
|
53
|
+
// }),
|
|
54
|
+
// },
|
|
55
|
+
// });
|
|
56
|
+
//
|
|
57
|
+
// The projection table must expose an `id` column typed as the aggregate's
|
|
58
|
+
// stream id — every executor-managed aggregate table does. Use a raw apply
|
|
59
|
+
// function when the update targets a different key or needs JOIN/SET logic.
|
|
60
|
+
export function setFields(
|
|
61
|
+
table: ProjectionTable,
|
|
62
|
+
fields:
|
|
63
|
+
| Record<string, unknown>
|
|
64
|
+
| ((event: Parameters<SingleStreamApplyFn>[0]) => Record<string, unknown>),
|
|
65
|
+
): SingleStreamApplyFn {
|
|
66
|
+
const idCol = (table as Record<string, unknown>)["id"]; // @cast-boundary dynamic-key
|
|
67
|
+
if (!idCol) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"setFields: projection table has no 'id' column — pass a custom apply function for tables keyed on another column.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return async (event, tx) => {
|
|
73
|
+
const values = typeof fields === "function" ? fields(event) : fields;
|
|
74
|
+
// ProjectionTable erases its column shape on purpose (the framework
|
|
75
|
+
// does not know user table shapes). Drizzle's tx.update().set() is
|
|
76
|
+
// strict about the concrete row, so we feed it the erased value; the
|
|
77
|
+
// type-safety guarantee for `values` lives at the setFields call-site.
|
|
78
|
+
// biome-ignore lint/suspicious/noExplicitAny: see note above.
|
|
79
|
+
const set = values as any;
|
|
80
|
+
await tx
|
|
81
|
+
.update(table)
|
|
82
|
+
.set(set)
|
|
83
|
+
.where(eq(idCol as never, event.aggregateId)); // @cast-boundary db-operator
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Qualified Name (QN) system: unified naming pattern for all framework identifiers.
|
|
2
|
+
// Pattern: "scope:type:name" — colon-separated, kebab-case segments.
|
|
3
|
+
//
|
|
4
|
+
// The name part can contain colons for sub-structure (e.g. "task:create").
|
|
5
|
+
// Scope and type are always single segments. Name is everything after the second colon.
|
|
6
|
+
//
|
|
7
|
+
// Examples:
|
|
8
|
+
// "tasks:write:task:create" → scope=tasks, type=write, name=task:create
|
|
9
|
+
// "system:hook:search-index" → scope=system, type=hook, name=search-index
|
|
10
|
+
// "billing:notify:invoice-sent" → scope=billing, type=notify, name=invoice-sent
|
|
11
|
+
|
|
12
|
+
// Built-in QN types used by the framework.
|
|
13
|
+
// Features can define additional types — the type segment is validated for format only, not membership.
|
|
14
|
+
export const QnTypes = {
|
|
15
|
+
write: "write",
|
|
16
|
+
query: "query",
|
|
17
|
+
hook: "hook",
|
|
18
|
+
job: "job",
|
|
19
|
+
notify: "notify",
|
|
20
|
+
event: "event",
|
|
21
|
+
channel: "channel",
|
|
22
|
+
config: "config",
|
|
23
|
+
secret: "secret",
|
|
24
|
+
screen: "screen",
|
|
25
|
+
nav: "nav",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export type BuiltinQnType = (typeof QnTypes)[keyof typeof QnTypes];
|
|
29
|
+
|
|
30
|
+
// QnType is string — framework types are predefined, but features can use custom types.
|
|
31
|
+
export type QnType = string;
|
|
32
|
+
|
|
33
|
+
const QN_SEGMENT = /^[a-z][a-z0-9-]*$/;
|
|
34
|
+
|
|
35
|
+
export type ParsedQn = {
|
|
36
|
+
scope: string;
|
|
37
|
+
type: string;
|
|
38
|
+
name: string; // may contain colons (e.g. "task:create")
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function validateSegment(value: string, label: string, context?: string): void {
|
|
42
|
+
if (!QN_SEGMENT.test(value)) {
|
|
43
|
+
const suffix = context ? ` in "${context}"` : "";
|
|
44
|
+
throw new Error(`Invalid QN ${label} "${value}"${suffix}: must match ${QN_SEGMENT}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Build a qualified name from parts. Validates all segments.
|
|
49
|
+
// The name can contain colons for sub-structure (e.g. "task:create").
|
|
50
|
+
export function qn(scope: string, type: QnType, name: string): string {
|
|
51
|
+
validateSegment(scope, "scope");
|
|
52
|
+
validateSegment(type, "type");
|
|
53
|
+
for (const part of name.split(":")) {
|
|
54
|
+
validateSegment(part, "name");
|
|
55
|
+
}
|
|
56
|
+
return `${scope}:${type}:${name}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse a qualified name string into its parts.
|
|
60
|
+
// Splits on the first two colons — everything after is the name (which may contain colons).
|
|
61
|
+
export function parseQn(value: string): ParsedQn {
|
|
62
|
+
const first = value.indexOf(":");
|
|
63
|
+
if (first < 0)
|
|
64
|
+
throw new Error(`Invalid QN "${value}": expected at least 3 colon-separated segments`);
|
|
65
|
+
const second = value.indexOf(":", first + 1);
|
|
66
|
+
if (second < 0)
|
|
67
|
+
throw new Error(`Invalid QN "${value}": expected at least 3 colon-separated segments`);
|
|
68
|
+
|
|
69
|
+
const scope = value.slice(0, first);
|
|
70
|
+
const type = value.slice(first + 1, second);
|
|
71
|
+
const name = value.slice(second + 1);
|
|
72
|
+
|
|
73
|
+
validateSegment(scope, "scope", value);
|
|
74
|
+
validateSegment(type, "type", value);
|
|
75
|
+
for (const part of name.split(":")) {
|
|
76
|
+
validateSegment(part, "name", value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { scope, type, name };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if a string is a valid qualified name.
|
|
83
|
+
export function isValidQn(value: string): boolean {
|
|
84
|
+
try {
|
|
85
|
+
parseQn(value);
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// True if `name` is a valid QN segment (lowercase letters, digits, dashes;
|
|
93
|
+
// starts with a letter). Same rule as `QN_SEGMENT` — kept public so feature
|
|
94
|
+
// registration can reject bad names at the source instead of at registry-boot.
|
|
95
|
+
export function isKebabSegment(name: string): boolean {
|
|
96
|
+
return QN_SEGMENT.test(name);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Build a fully-qualified entity name from a feature name + QN type + short
|
|
100
|
+
// name, running both names through toKebab first. This is the canonical
|
|
101
|
+
// "how the registry qualifies things" helper — both createRegistry and
|
|
102
|
+
// validateBoot need it, and they previously duplicated the literal
|
|
103
|
+
// `qn(toKebab(feature), type, toKebab(short))` expression. Keeping it here
|
|
104
|
+
// means a single place to change if the qualification rule ever evolves.
|
|
105
|
+
export function qualifyEntityName(featureName: string, type: QnType, shortName: string): string {
|
|
106
|
+
return qn(toKebab(featureName), type, toKebab(shortName));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Convert camelCase or dot.separated strings to kebab-case.
|
|
110
|
+
// "task.create" → "task-create"
|
|
111
|
+
// "ticketAssigned" → "ticket-assigned"
|
|
112
|
+
// "billing-period.create" → "billing-period-create"
|
|
113
|
+
// "monthlyReport" → "monthly-report"
|
|
114
|
+
// Already kebab-case → unchanged
|
|
115
|
+
// Colons are preserved: "task:create" → "task:create"
|
|
116
|
+
export function toKebab(input: string): string {
|
|
117
|
+
return input
|
|
118
|
+
.replace(/\./g, "-") // dots → dashes
|
|
119
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2") // consecutive uppercase: SSEBroadcast → SSE-Broadcast
|
|
120
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2") // camelCase boundaries: ticketAssigned → ticket-Assigned
|
|
121
|
+
.toLowerCase();
|
|
122
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, SessionUser } from "./types";
|
|
2
|
+
|
|
3
|
+
// Read a feature-declared claim from a SessionUser.
|
|
4
|
+
//
|
|
5
|
+
// The generic is inferred from the handle's `type` literal, so call sites
|
|
6
|
+
// get the right narrowed return type without a cast:
|
|
7
|
+
//
|
|
8
|
+
// const DriverClaims = r.claimKeys(...);
|
|
9
|
+
// const teamId = readClaim(user, DriverClaims.teamId); // string | undefined
|
|
10
|
+
// const regionId = readClaim(user, DriverClaims.regionId); // number | undefined
|
|
11
|
+
//
|
|
12
|
+
// Returns undefined when:
|
|
13
|
+
// - user.claims is absent entirely (hook system didn't populate anything)
|
|
14
|
+
// - the specific handle's qualified name isn't in user.claims (feature's
|
|
15
|
+
// r.authClaims hook didn't return this inner-key for this user)
|
|
16
|
+
//
|
|
17
|
+
// The cast is unchecked. The declared handle type is a contract between the
|
|
18
|
+
// feature's r.claimKey + r.authClaims return; the resolver's runtime check
|
|
19
|
+
// flags drift with a warning, but readClaim itself trusts the declaration.
|
|
20
|
+
// If you need schema-level validation of a claim value, wrap it with zod
|
|
21
|
+
// at the call-site.
|
|
22
|
+
export function readClaim<T extends ClaimKeyType>(
|
|
23
|
+
user: SessionUser,
|
|
24
|
+
handle: ClaimKeyHandle<T>,
|
|
25
|
+
): ClaimKeyJsType<T> | undefined {
|
|
26
|
+
const claims = user.claims;
|
|
27
|
+
if (!claims) return undefined;
|
|
28
|
+
const raw = claims[handle.name];
|
|
29
|
+
if (raw === undefined || raw === null) return undefined;
|
|
30
|
+
return raw as ClaimKeyJsType<T>;
|
|
31
|
+
}
|