@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,452 @@
|
|
|
1
|
+
import type { FieldDefinition } from "./fields";
|
|
2
|
+
import type { AccessRule } from "./handlers";
|
|
3
|
+
|
|
4
|
+
// Screen definitions describe how a feature surfaces data to the user.
|
|
5
|
+
// Pure data — the engine stores these verbatim and ui-core / the renderer
|
|
6
|
+
// packages decide what to do with them. The framework must not import
|
|
7
|
+
// React / react-native from here; renderer components stay opaque so
|
|
8
|
+
// `engine/` imports don't pull the whole UI toolchain into every bundle.
|
|
9
|
+
//
|
|
10
|
+
// Note on `id`: feature authors write the short form ("product-list"); the
|
|
11
|
+
// registry overwrites `id` with the qualified name ("shop:screen:product-list")
|
|
12
|
+
// in its stored copies. Callers of `registry.getScreen(qn)` /
|
|
13
|
+
// `getAllScreens()` / `getScreensByEntity(...)` always see the qualified id.
|
|
14
|
+
// `feature.screens[shortId]` on the unregistered FeatureDefinition keeps
|
|
15
|
+
// the short form.
|
|
16
|
+
|
|
17
|
+
// A per-platform component pair — used anywhere a feature attaches a
|
|
18
|
+
// rendered component (screens, slots, routes, field renderers, future
|
|
19
|
+
// r.uiComponent). Both fields are `unknown` so the engine doesn't depend
|
|
20
|
+
// on React types; ui-core resolves the correct platform at mount-time.
|
|
21
|
+
// Framework code only checks structural presence.
|
|
22
|
+
export type PlatformComponent = {
|
|
23
|
+
readonly react?: unknown;
|
|
24
|
+
readonly native?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Level-2 field renderer (ui-architecture.md §Renderer Customization):
|
|
28
|
+
// - PlatformComponent → platform-specific component from the same feature
|
|
29
|
+
// - string → cross-feature QN reference (resolved by the renderer)
|
|
30
|
+
// - function → inline value formatter (e.g. `v => `${v} €``)
|
|
31
|
+
// Function-Form bekommt optional die ganze Row als 2. Argument —
|
|
32
|
+
// nützlich für context-aware Renderer (Tier 2.7e-Eagerload nutzt das
|
|
33
|
+
// um aus row._refs den resolved Display-Wert zu lesen). Renderer die
|
|
34
|
+
// nur den value brauchen ignorieren das Argument einfach.
|
|
35
|
+
export type FieldRenderer =
|
|
36
|
+
| PlatformComponent
|
|
37
|
+
| string
|
|
38
|
+
| ((value: unknown, row?: Readonly<Record<string, unknown>>) => string);
|
|
39
|
+
|
|
40
|
+
// Conditional field-state evaluator. `data` is the current form row and
|
|
41
|
+
// `ctx` carries user / session info — the form-controller in ui-core passes
|
|
42
|
+
// both at evaluation time. Engine-side defaults are `unknown` because the
|
|
43
|
+
// framework has nothing to assert about the shapes; feature code can narrow
|
|
44
|
+
// them by passing type args (e.g. `FieldCondition<OrderRow>`) to skip the
|
|
45
|
+
// cast at call sites.
|
|
46
|
+
export type FieldCondition<TData = unknown, TCtx = unknown> = (data: TData, ctx: TCtx) => boolean;
|
|
47
|
+
|
|
48
|
+
// --- entityList ---
|
|
49
|
+
|
|
50
|
+
// `string` shorthand when the column only needs its field name; the object
|
|
51
|
+
// form carries renderer / display overrides. normalizeListColumn() below
|
|
52
|
+
// collapses both into the object form for downstream consumers.
|
|
53
|
+
export type ListColumnSpec =
|
|
54
|
+
| string
|
|
55
|
+
| {
|
|
56
|
+
readonly field: string;
|
|
57
|
+
readonly renderer?: FieldRenderer;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Pagination-Modi für entityList:
|
|
61
|
+
// - "pages": klassischer Pager (← 1 2 ... N →) — bookmarkable
|
|
62
|
+
// via ?page=N in der URL. Server liefert `total`
|
|
63
|
+
// damit der Pager "Page X of Y" rendern kann.
|
|
64
|
+
// Default für CRUD-/Admin-Listen.
|
|
65
|
+
// - "infinite": IntersectionObserver am Bottom — beim Sichtbar
|
|
66
|
+
// werden lädt cursor-basiert die nächste Page und
|
|
67
|
+
// appended an `rows`. Kein page-State in URL (Scroll-
|
|
68
|
+
// Position ist Browser-eigen).
|
|
69
|
+
// - false: Pagination aus — `executor.list()` lädt alles
|
|
70
|
+
// was zur Tenant-Sicht passt. Sinnvoll für kleine
|
|
71
|
+
// Lookup-/Master-Daten (≤ ~200 Rows).
|
|
72
|
+
export type ListPaginationMode = "pages" | "infinite" | false;
|
|
73
|
+
|
|
74
|
+
// Sort-State auf der Wire — Field-Name muss zur Entity-Definition
|
|
75
|
+
// passen UND als sortable: true markiert sein. Validator lehnt sonst
|
|
76
|
+
// beim Boot ab.
|
|
77
|
+
export type ListSortDir = "asc" | "desc";
|
|
78
|
+
export type ListSortSpec = {
|
|
79
|
+
readonly field: string;
|
|
80
|
+
readonly dir: ListSortDir;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Screen-Level Filter — Author deklariert pro List-Screen einen festen
|
|
84
|
+
// Filter der bei jedem Query angewendet wird. Use-case: drei Buckets
|
|
85
|
+
// derselben Entity ("Upcoming Maintenance" / "Active Maintenance" /
|
|
86
|
+
// "Past Maintenance") ohne drei Custom-Pages — Filter pro Screen
|
|
87
|
+
// unterscheidet sie.
|
|
88
|
+
//
|
|
89
|
+
// Operatoren (Drizzle-konsistent):
|
|
90
|
+
// eq/ne → field = value | field != value
|
|
91
|
+
// lt/gt → field < value | field > value (numerisch / temporal)
|
|
92
|
+
// in → field IN (...values), value muss readonly array sein
|
|
93
|
+
//
|
|
94
|
+
// Field muss in der Entity existieren UND `filterable: true` haben
|
|
95
|
+
// (Boot-Validator pinned beides). `lt`/`gt` nur auf vergleichbaren
|
|
96
|
+
// Field-Types (number/money/date/timestamp/locatedTimestamp); auf
|
|
97
|
+
// text/boolean/select/multiSelect lehnt der Validator das ab.
|
|
98
|
+
//
|
|
99
|
+
// Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
|
|
100
|
+
// Server appliziert den filter aus dem Payload — der Client kann ihn
|
|
101
|
+
// weglassen oder durch einen anderen ersetzen. Boundary bleiben
|
|
102
|
+
// access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
|
|
103
|
+
// restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
|
|
104
|
+
export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "in";
|
|
105
|
+
export type ScreenFilter = {
|
|
106
|
+
readonly field: string;
|
|
107
|
+
readonly op: ScreenFilterOp;
|
|
108
|
+
readonly value: unknown;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// RowAction — per-Row Button/Dropdown-Item das einen Write-Handler
|
|
112
|
+
// triggert. Lebt im Schema (Author deklariert pro List-Screen welche
|
|
113
|
+
// Aktionen möglich sind), Caller liefert die handler-QN + optional
|
|
114
|
+
// payload-Builder + Confirm-Prompt.
|
|
115
|
+
//
|
|
116
|
+
// Pattern: row-level Lifecycle-Operations (Maintenance start/cancel/
|
|
117
|
+
// complete, Incident resolve, Order ship etc.) — Sachen die in einem
|
|
118
|
+
// CRUD-update kein passendes Verb haben aber als WriteHandler existieren.
|
|
119
|
+
//
|
|
120
|
+
// ⚠️ Function-Props (`payload`, `params`, `visible`) leben nur im
|
|
121
|
+
// Monolith-Bundle-Pattern (Server + Client teilen Source-Bundle, wie
|
|
122
|
+
// Showcase mit dev-server). In setups mit JSON-injected window.__
|
|
123
|
+
// KUMIKO_SCHEMA__ werden Functions silent gedroppt (`buildAppSchema`
|
|
124
|
+
// whitelist-projeziert + JSON.stringify entfernt sie). Für solche Apps:
|
|
125
|
+
// - `payload`/`params` weglassen → Default `{ id: row.id }` greift.
|
|
126
|
+
// - `visible` über server-side Filter im Handler statt Client-side
|
|
127
|
+
// Visibility lösen.
|
|
128
|
+
// Declarative Alternative kommt wenn ein konkreter Use-Case das fordert.
|
|
129
|
+
//
|
|
130
|
+
// Discriminated Union mit `kind`:
|
|
131
|
+
// - "writeHandler" (default für Backwards-Compat): dispatched einen
|
|
132
|
+
// Write-Handler mit Payload pro Row.
|
|
133
|
+
// - "navigate" (Tier 2.7e): navigiert zu einem anderen Screen,
|
|
134
|
+
// optional mit URL-Search-Params aus `params(row)`. Use-case:
|
|
135
|
+
// "Edit", "View Audit-Log", "Open in actionForm" etc.
|
|
136
|
+
export type RowAction = RowActionWriteHandler | RowActionNavigate;
|
|
137
|
+
|
|
138
|
+
export type RowActionWriteHandler = {
|
|
139
|
+
/** Default für RowActions ohne explizit gesetzten `kind` —
|
|
140
|
+
* Backwards-kompatible Form. Kann auch explizit gesetzt werden. */
|
|
141
|
+
readonly kind?: "writeHandler";
|
|
142
|
+
/** Stable id pro Screen — kebab-case, eindeutig im Action-Set. */
|
|
143
|
+
readonly id: string;
|
|
144
|
+
/** Anzeige-Text (i18n-Key). */
|
|
145
|
+
readonly label: string;
|
|
146
|
+
/** Qualified-Name des Server-Handlers, z.B.
|
|
147
|
+
* "publicstatus:write:maintenance:start". Wird via useDispatcher
|
|
148
|
+
* dispatcht. */
|
|
149
|
+
readonly handler: string;
|
|
150
|
+
/** Payload-Builder pro Row. Default = `{ id: row.id }`. ⚠️ Function-
|
|
151
|
+
* Form nur im Monolith-Bundle-Pattern — siehe Type-Header. */
|
|
152
|
+
readonly payload?: (row: Readonly<Record<string, unknown>>) => Record<string, unknown>;
|
|
153
|
+
/** i18n-Key für die Confirm-Dialog-Description. Wenn gesetzt, öffnet
|
|
154
|
+
* ein Modal vor der Ausführung — der User muss explizit bestätigen.
|
|
155
|
+
* Zusammen mit `style: "danger"` ist das die Standard-Sicherheits-
|
|
156
|
+
* Garde für destruktive Aktionen. */
|
|
157
|
+
readonly confirm?: string;
|
|
158
|
+
/** i18n-Key für den Confirm-Button-Text im Dialog. Default = `label`
|
|
159
|
+
* (also "Delete" → "Delete"-Button im Confirm). Setzen wenn die
|
|
160
|
+
* Action einen langen Namen hat ("Mark Subscription as Cancelled")
|
|
161
|
+
* und der Button kürzer sein soll ("Cancel Subscription"). */
|
|
162
|
+
readonly confirmLabel?: string;
|
|
163
|
+
/** Conditional Visibility pro Row — Action erscheint nur wenn die
|
|
164
|
+
* Bedingung true returnt. Beispiel: nur "Start" zeigen wenn
|
|
165
|
+
* status === "scheduled". ⚠️ Function-Form nur im Monolith-Bundle-
|
|
166
|
+
* Pattern — siehe Type-Header. */
|
|
167
|
+
readonly visible?: FieldCondition;
|
|
168
|
+
/** Visual-Style. "danger" rendert rot + erzwingt einen Confirm-
|
|
169
|
+
* Dialog (auch ohne expliziten `confirm`-Key). */
|
|
170
|
+
readonly style?: "primary" | "secondary" | "danger";
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type RowActionNavigate = {
|
|
174
|
+
readonly kind: "navigate";
|
|
175
|
+
readonly id: string;
|
|
176
|
+
readonly label: string;
|
|
177
|
+
/** Screen-id (kurz, unqualified) zu dem navigiert wird. Boot-
|
|
178
|
+
* Validator prüft Existenz im selben Feature. */
|
|
179
|
+
readonly screen: string;
|
|
180
|
+
/** Optional: URL-Search-Params aus row-Context. Wird in actionForm-
|
|
181
|
+
* Targets als initial values gelesen ("Edit Customer X" → URL hat
|
|
182
|
+
* `?customerId=row-uuid`, actionForm initial values pre-fillen).
|
|
183
|
+
* ⚠️ Function-Form nur im Monolith-Bundle-Pattern. */
|
|
184
|
+
readonly params?: (row: Readonly<Record<string, unknown>>) => Record<string, unknown>;
|
|
185
|
+
/** Conditional Visibility pro Row — analog zu writeHandler-Variante. */
|
|
186
|
+
readonly visible?: FieldCondition;
|
|
187
|
+
readonly style?: "primary" | "secondary";
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// ToolbarAction — Button im List-Header. Zwei Varianten: navigate auf
|
|
191
|
+
// einen anderen Screen (z.B. zu einem actionForm) oder direkt einen
|
|
192
|
+
// Handler dispatchen (z.B. "Sync All" ohne Form).
|
|
193
|
+
//
|
|
194
|
+
// ⚠️ Function-Props (`payload`) leben nur im Monolith-Bundle-Pattern
|
|
195
|
+
// (siehe RowAction-JSDoc) — JSON-injected Schemas droppen sie silent.
|
|
196
|
+
// Für solche Apps payload weglassen oder im writeHandler server-side
|
|
197
|
+
// die Tenant-/Session-Kontext-Werte ableiten.
|
|
198
|
+
export type ToolbarAction =
|
|
199
|
+
| {
|
|
200
|
+
readonly kind: "navigate";
|
|
201
|
+
readonly id: string;
|
|
202
|
+
readonly label: string;
|
|
203
|
+
/** Screen-id (kurz, unqualified) zu dem navigiert wird. */
|
|
204
|
+
readonly screen: string;
|
|
205
|
+
readonly style?: "primary" | "secondary";
|
|
206
|
+
}
|
|
207
|
+
| {
|
|
208
|
+
readonly kind: "writeHandler";
|
|
209
|
+
readonly id: string;
|
|
210
|
+
readonly label: string;
|
|
211
|
+
readonly handler: string;
|
|
212
|
+
/** Optional: Payload-Builder ohne row-Context. Default = `{}`. */
|
|
213
|
+
readonly payload?: () => Record<string, unknown>;
|
|
214
|
+
/** i18n-Key für Confirm-Dialog-Description. Wenn gesetzt UND/ODER
|
|
215
|
+
* style="danger": Modal vor der Ausführung. */
|
|
216
|
+
readonly confirm?: string;
|
|
217
|
+
/** i18n-Key für Confirm-Button-Text im Dialog. Default = `label`. */
|
|
218
|
+
readonly confirmLabel?: string;
|
|
219
|
+
readonly style?: "primary" | "secondary" | "danger";
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export type EntityListScreenDefinition = {
|
|
223
|
+
readonly id: string;
|
|
224
|
+
readonly type: "entityList";
|
|
225
|
+
readonly entity: string;
|
|
226
|
+
readonly columns: readonly ListColumnSpec[];
|
|
227
|
+
// Row renderer (Desktop) — when omitted, renderer draws the default table
|
|
228
|
+
// from `columns`. cardRenderer fills the same role on compact layouts.
|
|
229
|
+
readonly rowRenderer?: PlatformComponent;
|
|
230
|
+
readonly cardRenderer?: PlatformComponent;
|
|
231
|
+
/** Per-Row-Aktionen — rendert eine Actions-Spalte rechts in der Tabelle.
|
|
232
|
+
* Bis zu 2 actions als inline-Buttons; >2 als Kebab-Dropdown.
|
|
233
|
+
* Reihenfolge im Array = Reihenfolge in der UI. */
|
|
234
|
+
readonly rowActions?: readonly RowAction[];
|
|
235
|
+
/** Toolbar-Aktionen (List-Header). "Open Incident", "Schedule Maintenance"
|
|
236
|
+
* etc. — neben "+ Neu" wenn vorhanden. Reihenfolge im Array = UI-
|
|
237
|
+
* Reihenfolge, primary-style links. */
|
|
238
|
+
readonly toolbarActions?: readonly ToolbarAction[];
|
|
239
|
+
/** Server-side Filter, fest am Screen — drei Buckets derselben
|
|
240
|
+
* Entity ohne Custom-Pages (z.B. "Upcoming" / "Active" / "Past"
|
|
241
|
+
* Maintenance). User-side q-Search läuft AUF dem gefilterten Set
|
|
242
|
+
* oben drauf. Boot-Validator pinst dass field in der Entity existiert. */
|
|
243
|
+
readonly filter?: ScreenFilter;
|
|
244
|
+
// Pagination-Modus (Default "pages"). Bestimmt UI (Pager vs Scroll-
|
|
245
|
+
// Sentinel) und ob der Server `total` mitliefern muss.
|
|
246
|
+
readonly pagination?: ListPaginationMode;
|
|
247
|
+
// Page-Größe. Default 50 — guter Kompromiss zwischen "viel sichtbar"
|
|
248
|
+
// und "DB liefert schnell". Apps mit teurem Read (Joins, Computed-
|
|
249
|
+
// Fields) gehen runter; Power-User-Listen (z.B. internal Analytics)
|
|
250
|
+
// gehen hoch.
|
|
251
|
+
readonly pageSize?: number;
|
|
252
|
+
// Default-Sortierung beim Erst-Mount. Wenn URL-Param `?sort=…`
|
|
253
|
+
// gesetzt ist, gewinnt der; sonst nutzt RenderList diesen Default.
|
|
254
|
+
// `field` muss in der Entity sortable: true sein — Boot-Validator
|
|
255
|
+
// pinnt das.
|
|
256
|
+
readonly defaultSort?: ListSortSpec;
|
|
257
|
+
// Search-Toolbar im UI an/aus. Server-Search geht IMMER über den
|
|
258
|
+
// SearchAdapter (Meilisearch) — kein DB-ILIKE-Drift. Default true
|
|
259
|
+
// wenn die Entity searchable Felder hat, sonst false.
|
|
260
|
+
readonly searchable?: boolean;
|
|
261
|
+
readonly slots?: ScreenSlots;
|
|
262
|
+
readonly access?: AccessRule;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// --- entityEdit ---
|
|
266
|
+
|
|
267
|
+
// camelCase `readOnly` instead of the spec's lowercase `readonly`: TS's
|
|
268
|
+
// `readonly` modifier on the same line would make the declaration read
|
|
269
|
+
// `readonly readonly?: FieldCondition`, which is legal but a real lese-knick.
|
|
270
|
+
// Mirrors React's `readOnly` prop so the ergonomic cost of the divergence
|
|
271
|
+
// from ui-architecture.md is minimal.
|
|
272
|
+
export type EditFieldSpec =
|
|
273
|
+
| string
|
|
274
|
+
| {
|
|
275
|
+
readonly field: string;
|
|
276
|
+
readonly span?: number;
|
|
277
|
+
readonly visible?: FieldCondition;
|
|
278
|
+
readonly readOnly?: FieldCondition;
|
|
279
|
+
readonly required?: FieldCondition;
|
|
280
|
+
readonly renderer?: FieldRenderer;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export type EditSectionSpec = {
|
|
284
|
+
readonly title: string;
|
|
285
|
+
readonly columns?: number;
|
|
286
|
+
readonly fields: readonly EditFieldSpec[];
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export type EditLayout = {
|
|
290
|
+
readonly sections: readonly EditSectionSpec[];
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type EntityEditScreenDefinition = {
|
|
294
|
+
readonly id: string;
|
|
295
|
+
readonly type: "entityEdit";
|
|
296
|
+
readonly entity: string;
|
|
297
|
+
readonly layout: EditLayout;
|
|
298
|
+
readonly slots?: ScreenSlots;
|
|
299
|
+
readonly access?: AccessRule;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// --- actionForm ---
|
|
303
|
+
|
|
304
|
+
// Form-Screen für non-CRUD Write-Handler. Wird gerendert wie ein
|
|
305
|
+
// EntityEditScreen (sections + fields), aber:
|
|
306
|
+
// - kein detail-fetch beim Mount (initial-state = field-defaults)
|
|
307
|
+
// - kein CRUD-verb-mapping ("create"/"update") — Author gibt
|
|
308
|
+
// explizit die Write-Handler-QN an
|
|
309
|
+
// - Form-Object landet 1:1 als payload beim Handler; sein Zod-Schema
|
|
310
|
+
// validiert weiter
|
|
311
|
+
// - optional `redirect` zu einem anderen Screen nach Submit-Success
|
|
312
|
+
//
|
|
313
|
+
// Beispiele: "Send invitation" (mit recipient-email + role), "Approve
|
|
314
|
+
// invoice" (mit notes), "Bulk-import" (mit CSV-string + mode).
|
|
315
|
+
//
|
|
316
|
+
// Field-Shape: inline am Screen statt entity-Reference. Author hat
|
|
317
|
+
// explizite Kontrolle was die Form rendert ohne eine ganze Entity
|
|
318
|
+
// dafür anzulegen. Die FieldDefinitions sind dieselben wie auf
|
|
319
|
+
// Entities (text/select/number/...) — alle DefaultInput-Renderer
|
|
320
|
+
// greifen unverändert.
|
|
321
|
+
export type ActionFormScreenDefinition = {
|
|
322
|
+
readonly id: string;
|
|
323
|
+
readonly type: "actionForm";
|
|
324
|
+
/** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
|
|
325
|
+
* 1:1 als payload — Handler-Schema (Zod) validiert weiter. */
|
|
326
|
+
readonly handler: string;
|
|
327
|
+
/** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
|
|
328
|
+
* wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
|
|
329
|
+
* Validator). */
|
|
330
|
+
readonly fields: Readonly<Record<string, FieldDefinition>>;
|
|
331
|
+
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
332
|
+
* fields-Map oben. */
|
|
333
|
+
readonly layout: EditLayout;
|
|
334
|
+
/** i18n-key für den Submit-Button. Default: i18n-Default des
|
|
335
|
+
* Renderers (typischerweise "actions.submit"). */
|
|
336
|
+
readonly submitLabel?: string;
|
|
337
|
+
/** Nach erfolgreichem Submit zu dieser Screen-ID navigieren (kurze
|
|
338
|
+
* ID, z.B. "item-list" — gleiche Feature, der nav-Router resolved
|
|
339
|
+
* zum vollen Pfad). Cross-Feature-Redirect ist nicht supported.
|
|
340
|
+
* Wenn nicht gesetzt, bleibt der User auf dem Form-Screen. Boot-
|
|
341
|
+
* Validator prüft dass die ID einen registrierten Screen meint. */
|
|
342
|
+
readonly redirect?: string;
|
|
343
|
+
readonly slots?: ScreenSlots;
|
|
344
|
+
readonly access?: AccessRule;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// --- custom ---
|
|
348
|
+
|
|
349
|
+
// Sub-route declared by a custom screen (Expo Router / URL-routing use).
|
|
350
|
+
// `path` is the route-segment appended to the screen's own path; the
|
|
351
|
+
// framework owns the outer routing. Components stay opaque.
|
|
352
|
+
export type CustomScreenRoute = {
|
|
353
|
+
readonly path: string;
|
|
354
|
+
readonly component: PlatformComponent;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export type CustomScreenDefinition = {
|
|
358
|
+
readonly id: string;
|
|
359
|
+
readonly type: "custom";
|
|
360
|
+
readonly renderer: PlatformComponent;
|
|
361
|
+
readonly routes?: readonly CustomScreenRoute[];
|
|
362
|
+
readonly access?: AccessRule;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// --- configEdit ---
|
|
366
|
+
|
|
367
|
+
// Form-Screen der Tenant-/User-/System-Settings aus dem bundled
|
|
368
|
+
// config-Feature liest und schreibt. Wird gerendert wie ein
|
|
369
|
+
// EntityEditScreen (sections + fields), aber:
|
|
370
|
+
// - Detail-Load via `config:query:values` (statt `<entity>:detail`)
|
|
371
|
+
// - Pre-Fill nutzt `configKeys[shortName]` → qualifizierter Key, dann
|
|
372
|
+
// `values[qualifiedKey].value`
|
|
373
|
+
// - Submit feuert pro geändertem Feld einen `config:write:set` mit
|
|
374
|
+
// {key, value, scope}; das config-feature behandelt jeden Key als
|
|
375
|
+
// eigenes Aggregate (configValue.<keyHash>) und alle N Writes laufen
|
|
376
|
+
// parallel (Promise.all). Per-Key idempotent → Retry safe.
|
|
377
|
+
// - kein Singleton-Hack nötig: pro (key+tenantId) gibt's by-design
|
|
378
|
+
// genau eine Row, der Bridge-Pattern aus dem Branding-MVP fällt weg
|
|
379
|
+
//
|
|
380
|
+
// Partial-Failure-Semantik: wenn von N parallelen Writes einer scheitert,
|
|
381
|
+
// bleiben die anderen committed (pro-Aggregate, kein Multi-Stream-Rollback).
|
|
382
|
+
// Das Form bleibt dirty bis der User retried — die schon erfolgreichen
|
|
383
|
+
// Writes feuern dann nochmal mit demselben Wert. Für `text` / `number` /
|
|
384
|
+
// `boolean` Keys ist das idempotent. Wer einen ConfigKey mit nicht-
|
|
385
|
+
// idempotentem Setter baut (Counter, append-only-list o.ä.) muss die
|
|
386
|
+
// Idempotenz im Setter sicherstellen.
|
|
387
|
+
//
|
|
388
|
+
// Field-Shape: inline am Screen wie bei `actionForm`. Author hat damit
|
|
389
|
+
// explizite Kontrolle über Input-Type (text/number/select/...) ohne
|
|
390
|
+
// Resolve-Ceremony — die FieldDefinitions sind dieselben wie auf
|
|
391
|
+
// Entities, alle DefaultInput-Renderer greifen unverändert. Field-
|
|
392
|
+
// Labels gehen über bestehende i18n-Konventionen (`<feature>:entity:
|
|
393
|
+
// <namespace>:field:<name>` o.ä. — der Author wählt den Namespace).
|
|
394
|
+
//
|
|
395
|
+
// scope MUSS mit der `createTenantConfig`/`createSystemConfig`/
|
|
396
|
+
// `createUserConfig`-Deklaration der referenzierten Keys
|
|
397
|
+
// übereinstimmen — der Boot-Validator pinnt das.
|
|
398
|
+
export type ConfigEditScreenDefinition = {
|
|
399
|
+
readonly id: string;
|
|
400
|
+
readonly type: "configEdit";
|
|
401
|
+
/** scope für config:write:set Calls. Muss zur Scope-Deklaration der
|
|
402
|
+
* in `configKeys` referenzierten Keys passen — Boot-Validator
|
|
403
|
+
* prüft das gegen die Registry. */
|
|
404
|
+
readonly scope: "tenant" | "system" | "user";
|
|
405
|
+
/** Map: form-field-name (kurz, wie im Layout referenziert) → voll-
|
|
406
|
+
* qualifizierter Config-Key (`<feature>:config:<short>`). Boot-
|
|
407
|
+
* Validator prüft dass jeder qualifizierte Key in der Registry
|
|
408
|
+
* bekannt ist. */
|
|
409
|
+
readonly configKeys: Readonly<Record<string, string>>;
|
|
410
|
+
/** Form-Shape pro Field-Name. Selbe FieldDefinitions wie auf
|
|
411
|
+
* Entities/ActionForm. Field-Names matchen die Keys in `configKeys`
|
|
412
|
+
* — Boot-Validator pinnt das. */
|
|
413
|
+
readonly fields: Readonly<Record<string, FieldDefinition>>;
|
|
414
|
+
/** Layout: Sections mit Field-Refs. Identisch zu entityEdit/
|
|
415
|
+
* actionForm. */
|
|
416
|
+
readonly layout: EditLayout;
|
|
417
|
+
/** i18n-key für den Submit-Button. Default: "kumiko.actions.save". */
|
|
418
|
+
readonly submitLabel?: string;
|
|
419
|
+
readonly slots?: ScreenSlots;
|
|
420
|
+
readonly access?: AccessRule;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// --- shared slots (Level 4 from ui-architecture.md) ---
|
|
424
|
+
|
|
425
|
+
export type ScreenSlots = {
|
|
426
|
+
readonly header?: PlatformComponent;
|
|
427
|
+
readonly beforeForm?: PlatformComponent;
|
|
428
|
+
readonly afterForm?: PlatformComponent;
|
|
429
|
+
readonly sidebar?: PlatformComponent;
|
|
430
|
+
readonly footer?: PlatformComponent;
|
|
431
|
+
readonly toolbar?: PlatformComponent;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// --- discriminated union ---
|
|
435
|
+
|
|
436
|
+
export type ScreenDefinition =
|
|
437
|
+
| EntityListScreenDefinition
|
|
438
|
+
| EntityEditScreenDefinition
|
|
439
|
+
| ActionFormScreenDefinition
|
|
440
|
+
| ConfigEditScreenDefinition
|
|
441
|
+
| CustomScreenDefinition;
|
|
442
|
+
|
|
443
|
+
// Collapse the string-shorthand into the object form. Both the boot-validator
|
|
444
|
+
// and (later) ui-core's view-model builder iterate over fields/columns — the
|
|
445
|
+
// helper keeps that loop from growing two branches everywhere.
|
|
446
|
+
export function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string> {
|
|
447
|
+
return typeof c === "string" ? { field: c } : c;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string> {
|
|
451
|
+
return typeof f === "string" ? { field: f } : f;
|
|
452
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AccessRule } from "./handlers";
|
|
2
|
+
|
|
3
|
+
// Workspace declaration. A workspace is a persona-/role-scoped UI surface:
|
|
4
|
+
// pure UI composition with no backend, DB or auth impact. The engine stores
|
|
5
|
+
// these verbatim and the active web shell (shellWorkspaces) renders the
|
|
6
|
+
// switcher and filters the nav tree by membership + access.
|
|
7
|
+
//
|
|
8
|
+
// Membership is computed from two sources, merged at boot:
|
|
9
|
+
// 1. r.workspace({ nav: [...] }) — explicit list of nav QNs
|
|
10
|
+
// 2. r.nav({ workspaces: [...] }) — nav entry self-assigns to workspaces
|
|
11
|
+
// A nav entry that appears in neither source belongs to no workspace and
|
|
12
|
+
// only shows up when no workspace is active (legacy / non-workspace apps).
|
|
13
|
+
//
|
|
14
|
+
// Cross-feature references are allowed: `nav` may point at any registered
|
|
15
|
+
// nav QN. The boot validator checks references exist and that workspace
|
|
16
|
+
// IDs referenced from r.nav are real.
|
|
17
|
+
export type WorkspaceDefinition = {
|
|
18
|
+
// Feature author writes the short id ("disposition"); the registry
|
|
19
|
+
// overwrites `id` with the qualified name ("bmc:workspace:disposition")
|
|
20
|
+
// in its stored copy. Same pattern as NavDefinition / ScreenDefinition.
|
|
21
|
+
readonly id: string;
|
|
22
|
+
// i18n translation key. Resolved at render time by the renderer's
|
|
23
|
+
// useTranslation hook; engine keeps it opaque.
|
|
24
|
+
readonly label: string;
|
|
25
|
+
// Icon key — whatever the icon registry of the active renderer understands.
|
|
26
|
+
// Engine doesn't validate; unknown icons surface as a missing icon on
|
|
27
|
+
// screen, not a boot failure (mirrors NavDefinition.icon).
|
|
28
|
+
readonly icon?: string;
|
|
29
|
+
// Sort weight in the workspace switcher (lower = earlier). Ties broken
|
|
30
|
+
// by registration order — features registered later appear lower.
|
|
31
|
+
readonly order?: number;
|
|
32
|
+
// Role / openToAll gate. Only users matching this rule see the workspace
|
|
33
|
+
// in their switcher. Mirrors NavDefinition.access — same semantics across
|
|
34
|
+
// the UI surface, so a default-deny app can do `{ roles: [] }`.
|
|
35
|
+
readonly access?: AccessRule;
|
|
36
|
+
// Explicit nav QNs that belong to this workspace. Merged with any nav
|
|
37
|
+
// entries that self-assign via r.nav({ workspaces: [...] }).
|
|
38
|
+
readonly nav?: readonly string[];
|
|
39
|
+
// Default workspace at login when the user has access to multiple. Boot
|
|
40
|
+
// validator rejects more than one default per app.
|
|
41
|
+
readonly default?: boolean;
|
|
42
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseQn, toKebab } from "./qualified-name";
|
|
2
|
+
import type { Registry, ValidationError } from "./types";
|
|
3
|
+
|
|
4
|
+
export type { ValidationError };
|
|
5
|
+
|
|
6
|
+
export function runValidation(
|
|
7
|
+
registry: Registry,
|
|
8
|
+
hookName: string,
|
|
9
|
+
data: Readonly<Record<string, unknown>>,
|
|
10
|
+
): readonly ValidationError[] | null {
|
|
11
|
+
const errors: ValidationError[] = [];
|
|
12
|
+
|
|
13
|
+
// hookName is a qualified name like "feature:write:task:create".
|
|
14
|
+
// Validation hooks are stored with the unqualified short name in the feature definition.
|
|
15
|
+
const parsed = parseQn(hookName);
|
|
16
|
+
|
|
17
|
+
for (const [featureName, feature] of registry.features) {
|
|
18
|
+
if (toKebab(featureName) !== parsed.scope) continue;
|
|
19
|
+
|
|
20
|
+
const validationHooks = feature.hooks.validation;
|
|
21
|
+
if (!validationHooks) continue;
|
|
22
|
+
|
|
23
|
+
// Find the hook by matching the QN name segment against the stored short name.
|
|
24
|
+
// Both use colon convention (e.g. "task:create"), so direct match works.
|
|
25
|
+
const hook = validationHooks[parsed.name];
|
|
26
|
+
if (hook) {
|
|
27
|
+
const result = hook(data);
|
|
28
|
+
if (result) errors.push(...result);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return errors.length > 0 ? errors : null;
|
|
33
|
+
}
|