@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 +9 -38
- package/package.json +21 -2
- package/src/__tests__/anonymous-access.integration.test.ts +185 -0
- package/src/__tests__/consumer-cli.integration.test.ts +172 -0
- package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
- package/src/__tests__/full-stack.integration.test.ts +1 -1
- package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
- package/src/__tests__/schema-cli.integration.test.ts +50 -0
- package/src/__tests__/store-table.integration.test.ts +93 -0
- package/src/api/__tests__/api.test.ts +373 -0
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +101 -11
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +200 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
- package/src/api/__tests__/pat-scope.test.ts +36 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
- package/src/api/__tests__/request-id-middleware.test.ts +51 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/api-constants.ts +11 -0
- package/src/api/auth-middleware.ts +248 -40
- package/src/api/auth-routes.ts +576 -95
- package/src/api/index.ts +13 -4
- package/src/api/jwt.ts +170 -11
- package/src/api/pat-scope.ts +14 -0
- package/src/api/pii-leak-guard.ts +47 -0
- package/src/api/request-context.ts +3 -0
- package/src/api/request-id-middleware.ts +2 -0
- package/src/api/routes.ts +169 -2
- package/src/api/server.ts +112 -16
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/__tests__/PATTERN.md +0 -1
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/__tests__/write-brand.test.ts +21 -0
- package/src/bun-db/connection.ts +3 -3
- package/src/bun-db/index.ts +2 -0
- package/src/bun-db/query.ts +105 -32
- package/src/consumer-cli.ts +134 -0
- package/src/crypto/__tests__/blind-index.test.ts +130 -0
- package/src/crypto/__tests__/event-pii.test.ts +161 -0
- package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
- package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
- package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
- package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
- package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
- package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
- package/src/crypto/blind-index.ts +118 -0
- package/src/crypto/event-pii.ts +70 -0
- package/src/crypto/in-memory-kms-adapter.ts +50 -0
- package/src/crypto/index.ts +67 -0
- package/src/crypto/kms-adapter.ts +2 -0
- package/src/crypto/pg-kms-adapter.ts +295 -0
- package/src/crypto/pii-field-encryption.ts +248 -0
- package/src/crypto/request-kms-cache.ts +38 -0
- package/src/crypto/subject-resolver.ts +91 -0
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
- package/src/db/__tests__/blind-index.integration.test.ts +248 -0
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/collect-table-metas.test.ts +10 -10
- package/src/db/__tests__/config-seed.integration.test.ts +13 -5
- package/src/db/__tests__/dialect-instant.test.ts +1 -4
- package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
- package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
- package/src/db/__tests__/feature-table-sources.test.ts +34 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
- package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-generator.test.ts +21 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
- package/src/db/__tests__/rebuild-marker.test.ts +13 -3
- package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
- package/src/db/__tests__/schema-inspection.test.ts +7 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
- package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
- package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/apply-entity-event.ts +18 -14
- package/src/db/blind-index-cleanup.ts +55 -0
- package/src/db/bun-provider.ts +2 -2
- package/src/db/collect-table-metas.ts +6 -7
- package/src/db/config-seed.ts +9 -9
- package/src/db/connection.ts +7 -12
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +20 -26
- package/src/db/entity-field-encryption.ts +81 -24
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +63 -90
- package/src/db/event-store-executor-context.ts +404 -0
- package/src/db/event-store-executor-read.ts +275 -0
- package/src/db/event-store-executor-write.ts +644 -0
- package/src/db/event-store-executor.ts +28 -1155
- package/src/db/feature-table-sources.ts +6 -5
- package/src/db/index.ts +20 -3
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-generator.ts +39 -6
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +9 -1
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/backfill-pii.ts +277 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/queries/event-store.ts +126 -19
- package/src/db/queries/projection-rebuild.ts +22 -8
- package/src/db/queries/shadow-swap.ts +183 -0
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/query-api.ts +1 -0
- package/src/db/query.ts +1 -0
- package/src/db/rebuild-marker.ts +18 -2
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +257 -0
- package/src/db/schema-inspection.ts +1 -1
- package/src/db/table-builder.ts +102 -71
- package/src/db/tenant-db.ts +15 -53
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
- package/src/engine/__tests__/boot-validator.test.ts +219 -15
- package/src/engine/__tests__/build-app-schema.test.ts +82 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
- package/src/engine/__tests__/build-target.test.ts +3 -11
- package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
- package/src/engine/__tests__/config-helpers.test.ts +16 -0
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +163 -1
- package/src/engine/__tests__/entity-handlers.test.ts +80 -0
- package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
- package/src/engine/__tests__/factories-time.test.ts +2 -66
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
- package/src/engine/__tests__/feature-manifest.test.ts +31 -1
- package/src/engine/__tests__/field-access.test.ts +23 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/membership-roles.test.ts +4 -10
- package/src/engine/__tests__/nav.test.ts +86 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
- package/src/engine/__tests__/post-query-hook.test.ts +7 -7
- package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
- package/src/engine/__tests__/registry.test.ts +144 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/screen.test.ts +147 -1
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
- package/src/engine/__tests__/store-table.test.ts +229 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
- package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
- package/src/engine/boot-validator/action-wiring.ts +149 -0
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/config-deps.ts +40 -4
- package/src/engine/boot-validator/entity-handler.ts +20 -21
- package/src/engine/boot-validator/entity-list-screens.ts +88 -0
- package/src/engine/boot-validator/gdpr-storage.ts +0 -20
- package/src/engine/boot-validator/i18n-keys.ts +58 -4
- package/src/engine/boot-validator/index.ts +33 -12
- package/src/engine/boot-validator/nav.ts +125 -0
- package/src/engine/boot-validator/pii-retention.ts +95 -10
- package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
- package/src/engine/boot-validator/workspaces.ts +68 -0
- package/src/engine/build-app-schema.ts +16 -0
- package/src/engine/build-config-feature-schema.ts +44 -7
- package/src/engine/codemod/pipeline-codemod.ts +5 -5
- package/src/engine/config-helpers.ts +15 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/define-feature.ts +95 -947
- package/src/engine/define-handler.ts +28 -94
- package/src/engine/define-workflow.ts +1 -1
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/entity-handlers.ts +76 -11
- package/src/engine/extensions/tenant-data.ts +19 -0
- package/src/engine/extensions/user-data.ts +29 -2
- package/src/engine/factories.ts +8 -49
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
- package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
- package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
- package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
- package/src/engine/feature-ast/extractors/events.ts +322 -0
- package/src/engine/feature-ast/extractors/handlers.ts +234 -0
- package/src/engine/feature-ast/extractors/hooks.ts +243 -0
- package/src/engine/feature-ast/extractors/index.ts +34 -31
- package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
- package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
- package/src/engine/feature-ast/extractors/round1.ts +3 -3
- package/src/engine/feature-ast/extractors/round5.ts +3 -3
- package/src/engine/feature-ast/extractors/round6.ts +2 -36
- package/src/engine/feature-ast/extractors/shared.ts +64 -6
- package/src/engine/feature-ast/index.ts +2 -4
- package/src/engine/feature-ast/parse.ts +130 -23
- package/src/engine/feature-ast/patch.ts +39 -50
- package/src/engine/feature-ast/patcher.ts +37 -38
- package/src/engine/feature-ast/patterns.ts +50 -62
- package/src/engine/feature-ast/render.ts +67 -44
- package/src/engine/feature-builder-state.ts +168 -0
- package/src/engine/feature-config-events-jobs.ts +403 -0
- package/src/engine/feature-entity-handlers.ts +208 -0
- package/src/engine/feature-manifest.ts +2 -1
- package/src/engine/feature-ui-extensions.ts +500 -0
- package/src/engine/field-access.ts +13 -2
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +16 -0
- package/src/engine/index.ts +23 -7
- package/src/engine/membership-roles.ts +13 -0
- package/src/engine/object-form.ts +27 -0
- package/src/engine/ownership.ts +26 -79
- package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
- package/src/engine/pattern-library/library.ts +44 -1152
- package/src/engine/pattern-library/mixed-schemas.ts +450 -0
- package/src/engine/pattern-library/opaque-schemas.ts +124 -0
- package/src/engine/pattern-library/shared-fields.ts +75 -0
- package/src/engine/pattern-library/static-schemas.ts +456 -0
- package/src/engine/pipeline.ts +6 -11
- package/src/engine/registry-facade.ts +362 -0
- package/src/engine/registry-ingest.ts +478 -0
- package/src/engine/registry-state.ts +388 -0
- package/src/engine/registry-validate.ts +642 -0
- package/src/engine/registry.ts +78 -1658
- package/src/engine/run-pipeline.ts +1 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/soft-delete-cleanup.ts +6 -2
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -482
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -972
- package/src/engine/types/fields.ts +2 -675
- package/src/engine/types/handlers.ts +2 -774
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -54
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +86 -40
- package/src/engine/types/nav.ts +2 -63
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -138
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -574
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -132
- package/src/engine/types/workspace.ts +2 -49
- package/src/engine/validate-projection-allowlist.ts +6 -6
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
- package/src/entrypoint/index.ts +49 -6
- package/src/errors/classes.ts +22 -1
- package/src/errors/field-issue.ts +0 -3
- package/src/errors/index.ts +1 -1
- package/src/errors/write-error-info.ts +10 -22
- package/src/es-ops/README.md +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
- package/src/es-ops/context.ts +4 -2
- package/src/es-ops/types.ts +8 -1
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
- package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
- package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
- package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
- package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +64 -78
- package/src/event-store/events-schema.ts +11 -13
- package/src/event-store/index.ts +18 -3
- package/src/event-store/rebuild-dead-letter.ts +111 -0
- package/src/event-store/snapshot.ts +63 -40
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/file-ref-entity.test.ts +8 -0
- package/src/files/__tests__/files.integration.test.ts +24 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/provider-resolver.test.ts +70 -0
- package/src/files/__tests__/write-stream.test.ts +13 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/file-ref-entity.ts +2 -2
- package/src/files/file-routes.ts +13 -0
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +27 -8
- package/src/files/provider-resolver.ts +31 -20
- package/src/files/types.ts +13 -55
- package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
- package/src/i18n/required-surface-keys.ts +120 -1
- package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
- package/src/jobs/job-runner.ts +82 -12
- package/src/logging/types.ts +1 -7
- package/src/migrations/pending-rebuilds.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +4 -1
- package/src/observability/__tests__/recording-tracer.test.ts +6 -4
- package/src/observability/index.ts +2 -0
- package/src/observability/noop-provider.ts +0 -9
- package/src/observability/recording-tracer.ts +0 -12
- package/src/observability/standard-metrics.ts +64 -2
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -64
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
- package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
- package/src/pipeline/dispatch-batch.ts +187 -0
- package/src/pipeline/dispatch-query.ts +165 -0
- package/src/pipeline/dispatch-shared.ts +826 -0
- package/src/pipeline/dispatch-stream.ts +90 -0
- package/src/pipeline/dispatch-write.ts +451 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +44 -1372
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +30 -2
- package/src/pipeline/event-dispatcher-admin.ts +293 -0
- package/src/pipeline/event-dispatcher-delivery.ts +305 -0
- package/src/pipeline/event-dispatcher.ts +85 -542
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/msp-rebuild.ts +42 -3
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/projection-rebuild.ts +105 -3
- package/src/pipeline/system-hooks.ts +144 -1
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +16 -32
- package/src/schema-cli.ts +76 -16
- package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
- package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
- package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
- package/src/search/index.ts +6 -0
- package/src/search/meilisearch-adapter.ts +13 -12
- package/src/search/reindex-entity.ts +177 -0
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/contains-secret.test.ts +34 -0
- package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/dek-cache.ts +26 -5
- package/src/secrets/envelope-cipher.ts +53 -0
- package/src/secrets/envelope.ts +5 -3
- package/src/secrets/index.ts +13 -1
- package/src/secrets/stored-envelope.ts +46 -0
- package/src/secrets/types.ts +2 -162
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +4 -3
- package/src/stack/redis.ts +8 -0
- package/src/stack/request-helper.ts +38 -2
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +249 -131
- package/src/testing/__tests__/late-bound.test.ts +32 -0
- package/src/testing/__tests__/wait-for.test.ts +59 -0
- package/src/testing/boot-validator-fixture.ts +121 -0
- package/src/testing/e2e-generator.ts +8 -0
- package/src/testing/file-provider-contract.ts +104 -0
- package/src/testing/handler-context.ts +3 -1
- package/src/testing/index.ts +5 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/mutable-master-key-provider.ts +29 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/__tests__/tz-dateline.test.ts +8 -10
- package/src/time/geo-tz.ts +1 -32
- package/src/time/index.ts +1 -0
- package/src/time/legacy-date.ts +18 -0
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +44 -85
- package/src/ui-types/app-schema.ts +12 -0
- package/src/ui-types/index.ts +20 -7
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/__tests__/raw-table.integration.test.ts +0 -116
- package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
- package/src/db/__tests__/encryption.test.ts +0 -39
- package/src/db/encryption.ts +0 -39
- package/src/db/row-helpers.ts +0 -4
- package/src/engine/__tests__/raw-table.test.ts +0 -150
- package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
- package/src/engine/feature-ast/extractors/round4.ts +0 -1366
|
@@ -1,574 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
// Built-in value formatters. Apps extend via module augmentation:
|
|
28
|
-
// declare module "@cosmicdrift/kumiko-framework/engine/types" {
|
|
29
|
-
// interface FieldFormatRegistry { myFormat: { myOption?: string } }
|
|
30
|
-
// }
|
|
31
|
-
// renderer-web handles all built-in keys; unknown app-specific keys fall back
|
|
32
|
-
// to String(value).
|
|
33
|
-
export interface FieldFormatRegistry {
|
|
34
|
-
timestamp: {
|
|
35
|
-
readonly locale?: string;
|
|
36
|
-
readonly dateStyle?: "full" | "long" | "medium" | "short";
|
|
37
|
-
readonly timeStyle?: "full" | "long" | "medium" | "short";
|
|
38
|
-
};
|
|
39
|
-
date: {
|
|
40
|
-
readonly locale?: string;
|
|
41
|
-
readonly dateStyle?: "full" | "long" | "medium" | "short";
|
|
42
|
-
};
|
|
43
|
-
boolean: { readonly trueLabel?: string; readonly falseLabel?: string };
|
|
44
|
-
currency: { readonly symbol?: string };
|
|
45
|
-
priority: { readonly emptyLabel?: string; readonly prefix?: string };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Discriminated union derived from the registry — one variant per key.
|
|
49
|
-
// JSON-safe: no function members, survives buildAppSchema → window.__KUMIKO_SCHEMA__.
|
|
50
|
-
export type FormatSpec = {
|
|
51
|
-
[K in keyof FieldFormatRegistry]: { readonly format: K } & FieldFormatRegistry[K];
|
|
52
|
-
}[keyof FieldFormatRegistry];
|
|
53
|
-
|
|
54
|
-
// Level-2 field renderer (ui-architecture.md §Renderer Customization):
|
|
55
|
-
// - PlatformComponent → platform-specific component from the same feature
|
|
56
|
-
// - string → cross-feature QN reference (resolved at mount-time)
|
|
57
|
-
// - FormatSpec → declarative value formatter, JSON-safe ({ format: "timestamp" } etc.)
|
|
58
|
-
export type FieldRenderer = PlatformComponent | string | FormatSpec;
|
|
59
|
-
|
|
60
|
-
// Declarative field-state condition. Evaluated by the renderer against the
|
|
61
|
-
// current row/form values. Three forms:
|
|
62
|
-
// boolean — static on/off (e.g. readOnly: true)
|
|
63
|
-
// { field, eq } — true when row[field] === eq
|
|
64
|
-
// { field, ne } — true when row[field] !== ne
|
|
65
|
-
// JSON-safe: survives buildAppSchema → window.__KUMIKO_SCHEMA__ stringify.
|
|
66
|
-
export type FieldCondition =
|
|
67
|
-
| boolean
|
|
68
|
-
| { readonly field: string; readonly eq: unknown }
|
|
69
|
-
| { readonly field: string; readonly ne: unknown };
|
|
70
|
-
|
|
71
|
-
// --- entityList ---
|
|
72
|
-
|
|
73
|
-
// `string` shorthand when the column only needs its field name; the object
|
|
74
|
-
// form carries renderer / display overrides. normalizeListColumn() below
|
|
75
|
-
// collapses both into the object form for downstream consumers.
|
|
76
|
-
export type ListColumnSpec =
|
|
77
|
-
| string
|
|
78
|
-
| {
|
|
79
|
-
readonly field: string;
|
|
80
|
-
readonly renderer?: FieldRenderer;
|
|
81
|
-
/** Optional header, overriding the default
|
|
82
|
-
* `<feature>:entity:<entity>:field:<field>` i18n convention. Resolved
|
|
83
|
-
* through `translate` like any header — an i18n key, or a plain literal
|
|
84
|
-
* shown verbatim if it isn't a key. Also the way to declare a *virtual*
|
|
85
|
-
* column whose `field` is NOT an entity field — a presentational column
|
|
86
|
-
* drawn entirely by a `columnRenderer` component that reads the `row`
|
|
87
|
-
* (e.g. a tag-chips cell), not a stored value. Such a column needs a
|
|
88
|
-
* label; without one it is rejected at boot as an unknown field. `field`
|
|
89
|
-
* is then just a stable column key (pick any unique slug). */
|
|
90
|
-
readonly label?: string;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// Pagination-Modi für entityList:
|
|
94
|
-
// - "pages": klassischer Pager (← 1 2 ... N →) — bookmarkable
|
|
95
|
-
// via ?page=N in der URL. Server liefert `total`
|
|
96
|
-
// damit der Pager "Page X of Y" rendern kann.
|
|
97
|
-
// Default für CRUD-/Admin-Listen.
|
|
98
|
-
// - "infinite": IntersectionObserver am Bottom — beim Sichtbar
|
|
99
|
-
// werden lädt cursor-basiert die nächste Page und
|
|
100
|
-
// appended an `rows`. Kein page-State in URL (Scroll-
|
|
101
|
-
// Position ist Browser-eigen).
|
|
102
|
-
// - false: Pagination aus — `executor.list()` lädt alles
|
|
103
|
-
// was zur Tenant-Sicht passt. Sinnvoll für kleine
|
|
104
|
-
// Lookup-/Master-Daten (≤ ~200 Rows).
|
|
105
|
-
export type ListPaginationMode = "pages" | "infinite" | false;
|
|
106
|
-
|
|
107
|
-
// Sort-State auf der Wire — Field-Name muss zur Entity-Definition
|
|
108
|
-
// passen UND als sortable: true markiert sein. Validator lehnt sonst
|
|
109
|
-
// beim Boot ab.
|
|
110
|
-
export type ListSortDir = "asc" | "desc";
|
|
111
|
-
export type ListSortSpec = {
|
|
112
|
-
readonly field: string;
|
|
113
|
-
readonly dir: ListSortDir;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// Screen-Level Filter — Author deklariert pro List-Screen einen festen
|
|
117
|
-
// Filter der bei jedem Query angewendet wird. Use-case: drei Buckets
|
|
118
|
-
// derselben Entity ("Upcoming Maintenance" / "Active Maintenance" /
|
|
119
|
-
// "Past Maintenance") ohne drei Custom-Pages — Filter pro Screen
|
|
120
|
-
// unterscheidet sie.
|
|
121
|
-
//
|
|
122
|
-
// Operatoren (Drizzle-konsistent):
|
|
123
|
-
// eq/ne → field = value | field != value
|
|
124
|
-
// lt/gt → field < value | field > value (numerisch / temporal)
|
|
125
|
-
// in → field IN (...values), value muss readonly array sein
|
|
126
|
-
//
|
|
127
|
-
// Field muss in der Entity existieren UND `filterable: true` haben
|
|
128
|
-
// (Boot-Validator pinned beides). `lt`/`gt` nur auf vergleichbaren
|
|
129
|
-
// Field-Types (number/money/date/timestamp/locatedTimestamp); auf
|
|
130
|
-
// text/boolean/select/multiSelect lehnt der Validator das ab.
|
|
131
|
-
//
|
|
132
|
-
// Security-Modell: Filter ist UX-Bucketing, KEINE Access-Boundary. Der
|
|
133
|
-
// Server appliziert den filter aus dem Payload — der Client kann ihn
|
|
134
|
-
// weglassen oder durch einen anderen ersetzen. Boundary bleiben
|
|
135
|
-
// access-rule + Tenant-Scope; Felder mit Sicherheits-Bias (encrypted,
|
|
136
|
-
// restricted) müssen dort geschützt werden, nicht über den Screen-Filter.
|
|
137
|
-
export type ScreenFilterOp = "eq" | "ne" | "lt" | "gt" | "in";
|
|
138
|
-
export type ScreenFilter = {
|
|
139
|
-
readonly field: string;
|
|
140
|
-
readonly op: ScreenFilterOp;
|
|
141
|
-
readonly value: unknown;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/** Deklarativer Row-Field-Extraktor — JSON-sicher (kein Function-Prop,
|
|
145
|
-
* überlebt window.__KUMIKO_SCHEMA__ / buildAppSchema).
|
|
146
|
-
*
|
|
147
|
-
* `pick`: extrahiert Felder 1:1. `{ pick: ["id", "version"] }` → `{ id: row.id, version: row.version }`
|
|
148
|
-
* `map`: benennt um. `{ map: { incidentId: "id" } }` → `{ incidentId: row.id }`
|
|
149
|
-
*
|
|
150
|
-
* Limitation: computed/template-Werte können nicht ausgedrückt werden
|
|
151
|
-
* — solche Logik gehört server-side in den Write-Handler. */
|
|
152
|
-
export type RowFieldExtractor =
|
|
153
|
-
| { readonly pick: readonly string[] }
|
|
154
|
-
| { readonly map: Readonly<Record<string, string>> };
|
|
155
|
-
|
|
156
|
-
// RowAction — per-Row Button/Dropdown-Item das einen Write-Handler
|
|
157
|
-
// triggert oder zu einem anderen Screen navigiert.
|
|
158
|
-
//
|
|
159
|
-
// Pattern: row-level Lifecycle-Operations (Maintenance start/cancel/
|
|
160
|
-
// complete, Incident resolve, Order ship etc.) — Sachen die in einem
|
|
161
|
-
// CRUD-update kein passendes Verb haben aber als WriteHandler existieren.
|
|
162
|
-
//
|
|
163
|
-
// Discriminated Union mit `kind`:
|
|
164
|
-
// - "writeHandler" (default): dispatched einen Write-Handler mit
|
|
165
|
-
// Payload pro Row.
|
|
166
|
-
// - "navigate" (Tier 2.7e): navigiert zu einem anderen Screen,
|
|
167
|
-
// optional mit URL-Search-Params aus `params`. Use-case: "Edit",
|
|
168
|
-
// "View Audit-Log", "Open in actionForm" etc.
|
|
169
|
-
export type RowAction = RowActionWriteHandler | RowActionNavigate;
|
|
170
|
-
|
|
171
|
-
export type RowActionWriteHandler = {
|
|
172
|
-
/** Default für RowActions ohne explizit gesetzten `kind` —
|
|
173
|
-
* Backwards-kompatible Form. Kann auch explizit gesetzt werden. */
|
|
174
|
-
readonly kind?: "writeHandler";
|
|
175
|
-
/** Stable id pro Screen — kebab-case, eindeutig im Action-Set. */
|
|
176
|
-
readonly id: string;
|
|
177
|
-
/** Anzeige-Text (i18n-Key). */
|
|
178
|
-
readonly label: string;
|
|
179
|
-
/** Qualified-Name des Server-Handlers, z.B.
|
|
180
|
-
* "publicstatus:write:maintenance:start". Wird via useDispatcher
|
|
181
|
-
* dispatcht. */
|
|
182
|
-
readonly handler: string;
|
|
183
|
-
/** Deklarativer Payload pro Row. Default = `{ id: row.id }`.
|
|
184
|
-
* `pick` extrahiert Felder gleichen Namens; `map` benennt um. */
|
|
185
|
-
readonly payload?: RowFieldExtractor;
|
|
186
|
-
/** i18n-Key für die Confirm-Dialog-Description. Wenn gesetzt, öffnet
|
|
187
|
-
* ein Modal vor der Ausführung — der User muss explizit bestätigen.
|
|
188
|
-
* Zusammen mit `style: "danger"` ist das die Standard-Sicherheits-
|
|
189
|
-
* Garde für destruktive Aktionen. */
|
|
190
|
-
readonly confirm?: string;
|
|
191
|
-
/** i18n-Key für den Confirm-Button-Text im Dialog. Default = `label`
|
|
192
|
-
* (also "Delete" → "Delete"-Button im Confirm). Setzen wenn die
|
|
193
|
-
* Action einen langen Namen hat ("Mark Subscription as Cancelled")
|
|
194
|
-
* und der Button kürzer sein soll ("Cancel Subscription"). */
|
|
195
|
-
readonly confirmLabel?: string;
|
|
196
|
-
/** Conditional Visibility pro Row. */
|
|
197
|
-
readonly visible?: FieldCondition;
|
|
198
|
-
/** Visual-Style. "danger" rendert rot + erzwingt einen Confirm-
|
|
199
|
-
* Dialog (auch ohne expliziten `confirm`-Key). */
|
|
200
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
export type RowActionNavigate = {
|
|
204
|
-
readonly kind: "navigate";
|
|
205
|
-
readonly id: string;
|
|
206
|
-
readonly label: string;
|
|
207
|
-
/** Screen-id (kurz, unqualified) zu dem navigiert wird. Boot-
|
|
208
|
-
* Validator prüft Existenz im selben Feature. */
|
|
209
|
-
readonly screen: string;
|
|
210
|
-
/** Feldname dessen Wert als entityId in den URL-Pfad eingebettet wird
|
|
211
|
-
* (`/<workspace>/<screen>/<entityId>`). entityEdit liest die Id
|
|
212
|
-
* AUSSCHLIESSLICH aus dem Pfad. Default: "id" wenn der Ziel-Screen
|
|
213
|
-
* ein entityEdit ist. */
|
|
214
|
-
readonly entityId?: string;
|
|
215
|
-
/** Deklarative URL-Search-Params aus row-Context. Wird in actionForm-
|
|
216
|
-
* Targets als initial values gelesen (actionForm pre-fillen).
|
|
217
|
-
* `pick` extrahiert Felder gleichen Namens; `map` benennt um. */
|
|
218
|
-
readonly params?: RowFieldExtractor;
|
|
219
|
-
/** Conditional Visibility pro Row. */
|
|
220
|
-
readonly visible?: FieldCondition;
|
|
221
|
-
readonly style?: "primary" | "secondary";
|
|
222
|
-
/** Wenn true, löst ein Klick auf die ganze Zeile (nicht nur das Aktionsmenü)
|
|
223
|
-
* diese navigate-Action aus. Max. eine pro Liste (Boot-Validator prüft). Nur
|
|
224
|
-
* auf navigate — ein Row-Klick darf keinen (evtl. destruktiven, unbestätigten)
|
|
225
|
-
* Write auslösen, daher nicht auf writeHandler-Actions. */
|
|
226
|
-
readonly rowClick?: boolean;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// ToolbarAction — Button im List-Header. Zwei Varianten: navigate auf
|
|
230
|
-
// einen anderen Screen (z.B. zu einem actionForm) oder direkt einen
|
|
231
|
-
// Handler dispatchen (z.B. "Sync All" ohne Form).
|
|
232
|
-
export type ToolbarAction =
|
|
233
|
-
| {
|
|
234
|
-
readonly kind: "navigate";
|
|
235
|
-
readonly id: string;
|
|
236
|
-
readonly label: string;
|
|
237
|
-
/** Screen-id (kurz, unqualified) zu dem navigiert wird. */
|
|
238
|
-
readonly screen: string;
|
|
239
|
-
readonly style?: "primary" | "secondary";
|
|
240
|
-
}
|
|
241
|
-
| {
|
|
242
|
-
readonly kind: "writeHandler";
|
|
243
|
-
readonly id: string;
|
|
244
|
-
readonly label: string;
|
|
245
|
-
readonly handler: string;
|
|
246
|
-
/** Statischer Payload ohne row-Context. Default = `{}`. */
|
|
247
|
-
readonly payload?: Record<string, unknown>;
|
|
248
|
-
/** i18n-Key für Confirm-Dialog-Description. Wenn gesetzt UND/ODER
|
|
249
|
-
* style="danger": Modal vor der Ausführung. */
|
|
250
|
-
readonly confirm?: string;
|
|
251
|
-
/** i18n-Key für Confirm-Button-Text im Dialog. Default = `label`. */
|
|
252
|
-
readonly confirmLabel?: string;
|
|
253
|
-
readonly style?: "primary" | "secondary" | "danger";
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
export type EntityListScreenDefinition = {
|
|
257
|
-
readonly id: string;
|
|
258
|
-
readonly type: "entityList";
|
|
259
|
-
readonly entity: string;
|
|
260
|
-
readonly columns: readonly ListColumnSpec[];
|
|
261
|
-
// Row renderer (Desktop) — when omitted, renderer draws the default table
|
|
262
|
-
// from `columns`. cardRenderer fills the same role on compact layouts.
|
|
263
|
-
readonly rowRenderer?: PlatformComponent;
|
|
264
|
-
readonly cardRenderer?: PlatformComponent;
|
|
265
|
-
/** Per-Row-Aktionen — rendert eine Actions-Spalte rechts in der Tabelle.
|
|
266
|
-
* Bis zu 2 actions als inline-Buttons; >2 als Kebab-Dropdown.
|
|
267
|
-
* Reihenfolge im Array = Reihenfolge in der UI. */
|
|
268
|
-
readonly rowActions?: readonly RowAction[];
|
|
269
|
-
/** Toolbar-Aktionen (List-Header). "Open Incident", "Schedule Maintenance"
|
|
270
|
-
* etc. — neben "+ Neu" wenn vorhanden. Reihenfolge im Array = UI-
|
|
271
|
-
* Reihenfolge, primary-style links. */
|
|
272
|
-
readonly toolbarActions?: readonly ToolbarAction[];
|
|
273
|
-
/** Server-side Filter, fest am Screen — drei Buckets derselben
|
|
274
|
-
* Entity ohne Custom-Pages (z.B. "Upcoming" / "Active" / "Past"
|
|
275
|
-
* Maintenance). User-side q-Search läuft AUF dem gefilterten Set
|
|
276
|
-
* oben drauf. Boot-Validator pinst dass field in der Entity existiert. */
|
|
277
|
-
readonly filter?: ScreenFilter;
|
|
278
|
-
// Pagination-Modus (Default "pages"). Bestimmt UI (Pager vs Scroll-
|
|
279
|
-
// Sentinel) und ob der Server `total` mitliefern muss.
|
|
280
|
-
readonly pagination?: ListPaginationMode;
|
|
281
|
-
// Page-Größe. Default 50 — guter Kompromiss zwischen "viel sichtbar"
|
|
282
|
-
// und "DB liefert schnell". Apps mit teurem Read (Joins, Computed-
|
|
283
|
-
// Fields) gehen runter; Power-User-Listen (z.B. internal Analytics)
|
|
284
|
-
// gehen hoch.
|
|
285
|
-
readonly pageSize?: number;
|
|
286
|
-
// Default-Sortierung beim Erst-Mount. Wenn URL-Param `?sort=…`
|
|
287
|
-
// gesetzt ist, gewinnt der; sonst nutzt RenderList diesen Default.
|
|
288
|
-
// `field` muss in der Entity sortable: true sein — Boot-Validator
|
|
289
|
-
// pinnt das.
|
|
290
|
-
readonly defaultSort?: ListSortSpec;
|
|
291
|
-
// Search-Toolbar im UI an/aus. Server-Search geht IMMER über den
|
|
292
|
-
// SearchAdapter (Meilisearch) — kein DB-ILIKE-Drift. Default true
|
|
293
|
-
// wenn die Entity searchable Felder hat, sonst false.
|
|
294
|
-
readonly searchable?: boolean;
|
|
295
|
-
readonly slots?: ScreenSlots;
|
|
296
|
-
readonly access?: AccessRule;
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// --- entityEdit ---
|
|
300
|
-
|
|
301
|
-
// camelCase `readOnly` instead of the spec's lowercase `readonly`: TS's
|
|
302
|
-
// `readonly` modifier on the same line would make the declaration read
|
|
303
|
-
// `readonly readonly?: FieldCondition`, which is legal but a real lese-knick.
|
|
304
|
-
// Mirrors React's `readOnly` prop so the ergonomic cost of the divergence
|
|
305
|
-
// from ui-architecture.md is minimal.
|
|
306
|
-
export type EditFieldSpec =
|
|
307
|
-
| string
|
|
308
|
-
| {
|
|
309
|
-
readonly field: string;
|
|
310
|
-
readonly span?: number;
|
|
311
|
-
readonly visible?: FieldCondition;
|
|
312
|
-
readonly readOnly?: FieldCondition;
|
|
313
|
-
readonly required?: FieldCondition;
|
|
314
|
-
readonly renderer?: FieldRenderer;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
// A section is either a normal field-grid (default — `kind` omitted keeps
|
|
318
|
-
// every existing screen-def working) or an extension slot that mounts a
|
|
319
|
-
// feature-provided component. The extension component is resolved client-
|
|
320
|
-
// side by name (same `__component` marker as custom screens / column
|
|
321
|
-
// renderers) and receives the host entity name + id, so a bundled feature
|
|
322
|
-
// (e.g. custom-fields) can load and persist its own data inside the form.
|
|
323
|
-
export type EditSectionSpec = EditFieldsSection | EditExtensionSection;
|
|
324
|
-
|
|
325
|
-
export type EditFieldsSection = {
|
|
326
|
-
readonly kind?: "fields";
|
|
327
|
-
/** Optional. Ohne Titel rendert die Section nur ihre Felder (keine h3-
|
|
328
|
-
* Überschrift) — für flache Forms (Card-Titel + Felder direkt, ein
|
|
329
|
-
* einzelner Abschnitt) wie bei den meisten shadcn-Form-Mustern. */
|
|
330
|
-
readonly title?: string;
|
|
331
|
-
readonly columns?: number;
|
|
332
|
-
readonly fields: readonly EditFieldSpec[];
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
export type EditExtensionSection = {
|
|
336
|
-
readonly kind: "extension";
|
|
337
|
-
readonly title: string;
|
|
338
|
-
readonly component: PlatformComponent;
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
export function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection {
|
|
342
|
-
return section.kind === "extension";
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export type EditLayout = {
|
|
346
|
-
readonly sections: readonly EditSectionSpec[];
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
export type EntityEditScreenDefinition = {
|
|
350
|
-
readonly id: string;
|
|
351
|
-
readonly type: "entityEdit";
|
|
352
|
-
readonly entity: string;
|
|
353
|
-
readonly layout: EditLayout;
|
|
354
|
-
/** Optionaler i18n-Key (oder Roh-String) für den Submit-Button. Default
|
|
355
|
-
* `kumiko.actions.save`. Lässt den Auto-Edit-Screen domain-spezifische
|
|
356
|
-
* CTAs zeigen ("Save Address", "Create item") statt generisch "Speichern". */
|
|
357
|
-
readonly submitLabel?: string;
|
|
358
|
-
/** Default true. `false` für Entities deren Create über einen eigenen
|
|
359
|
-
* Lifecycle-Write läuft (z.B. incident:open mit Event-Stream + Joins)
|
|
360
|
-
* statt über `<entity>:create`: unterdrückt den automatischen
|
|
361
|
-
* „+ Neu"-Button auf entityList-Screens dieser Entity und rendert den
|
|
362
|
-
* Create-Branch (Aufruf ohne entityId) als Fehler statt eines Forms,
|
|
363
|
-
* dessen Submit gegen einen nicht registrierten Handler liefe. */
|
|
364
|
-
readonly allowCreate?: boolean;
|
|
365
|
-
/** Default true. `false` wenn kein `<entity>:delete`-Handler existiert
|
|
366
|
-
* (History-/Audit-Erhalt): unterdrückt den Löschen-Button im
|
|
367
|
-
* Update-Form. */
|
|
368
|
-
readonly allowDelete?: boolean;
|
|
369
|
-
/** Optionaler per-Field-Label-i18n-Key (Field-Name → Key), überschreibt
|
|
370
|
-
* die Default-Konvention `<feature>:entity:<entity>:field:<name>`.
|
|
371
|
-
* Primär für configEdit: dessen Pseudo-Entity `__config-edit__` hat
|
|
372
|
-
* keinen natürlichen Field-Namespace — der Settings-Hub injiziert hier
|
|
373
|
-
* das `mask.title`-Label des Config-Keys. Fehlt ein Eintrag, gilt die
|
|
374
|
-
* Konvention. */
|
|
375
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
376
|
-
readonly slots?: ScreenSlots;
|
|
377
|
-
readonly access?: AccessRule;
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// --- actionForm ---
|
|
381
|
-
|
|
382
|
-
// Form-Screen für non-CRUD Write-Handler. Wird gerendert wie ein
|
|
383
|
-
// EntityEditScreen (sections + fields), aber:
|
|
384
|
-
// - kein detail-fetch beim Mount (initial-state = field-defaults)
|
|
385
|
-
// - kein CRUD-verb-mapping ("create"/"update") — Author gibt
|
|
386
|
-
// explizit die Write-Handler-QN an
|
|
387
|
-
// - Form-Object landet 1:1 als payload beim Handler; sein Zod-Schema
|
|
388
|
-
// validiert weiter
|
|
389
|
-
// - optional `redirect` zu einem anderen Screen nach Submit-Success
|
|
390
|
-
//
|
|
391
|
-
// Beispiele: "Send invitation" (mit recipient-email + role), "Approve
|
|
392
|
-
// invoice" (mit notes), "Bulk-import" (mit CSV-string + mode).
|
|
393
|
-
//
|
|
394
|
-
// Field-Shape: inline am Screen statt entity-Reference. Author hat
|
|
395
|
-
// explizite Kontrolle was die Form rendert ohne eine ganze Entity
|
|
396
|
-
// dafür anzulegen. Die FieldDefinitions sind dieselben wie auf
|
|
397
|
-
// Entities (text/select/number/...) — alle DefaultInput-Renderer
|
|
398
|
-
// greifen unverändert.
|
|
399
|
-
export type ActionFormScreenDefinition = {
|
|
400
|
-
readonly id: string;
|
|
401
|
-
readonly type: "actionForm";
|
|
402
|
-
/** Write-Handler-QN der bei Submit gerufen wird. Form-Object landet
|
|
403
|
-
* 1:1 als payload — Handler-Schema (Zod) validiert weiter. */
|
|
404
|
-
readonly handler: string;
|
|
405
|
-
/** Form-Shape: Field-Map pro Name. Nutzt dieselben FieldDefinitions
|
|
406
|
-
* wie Entity-Felder. Mindestens ein Feld erforderlich (Boot-
|
|
407
|
-
* Validator). */
|
|
408
|
-
readonly fields: Readonly<Record<string, FieldDefinition>>;
|
|
409
|
-
/** Layout analog zu EntityEditScreen: sections mit fields aus dem
|
|
410
|
-
* fields-Map oben. */
|
|
411
|
-
readonly layout: EditLayout;
|
|
412
|
-
/** i18n-key für den Submit-Button. Default: i18n-Default des
|
|
413
|
-
* Renderers (typischerweise "actions.submit"). */
|
|
414
|
-
readonly submitLabel?: string;
|
|
415
|
-
/** Nach erfolgreichem Submit zu dieser Screen-ID navigieren (kurze
|
|
416
|
-
* ID, z.B. "item-list" — gleiche Feature, der nav-Router resolved
|
|
417
|
-
* zum vollen Pfad). Cross-Feature-Redirect ist nicht supported.
|
|
418
|
-
* Wenn nicht gesetzt, bleibt der User auf dem Form-Screen. Boot-
|
|
419
|
-
* Validator prüft dass die ID einen registrierten Screen meint. */
|
|
420
|
-
readonly redirect?: string;
|
|
421
|
-
/** Ziel des Abbrechen-Buttons. Default: `redirect` (historisches
|
|
422
|
-
* Verhalten — Cancel und Submit-Redirect landen dann am selben Ort).
|
|
423
|
-
* `false` = kein Abbrechen-Button; richtig für Single-Action-Screens
|
|
424
|
-
* ohne verwerfbaren Zustand (z.B. "Test-Mail senden"), wo Abbrechen
|
|
425
|
-
* nur ein zweiter Weg zum selben Ziel wäre. Boot-Validator prüft
|
|
426
|
-
* String-Targets wie `redirect`. */
|
|
427
|
-
readonly cancelTarget?: string | false;
|
|
428
|
-
readonly slots?: ScreenSlots;
|
|
429
|
-
readonly access?: AccessRule;
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// --- custom ---
|
|
433
|
-
|
|
434
|
-
// Sub-route declared by a custom screen (Expo Router / URL-routing use).
|
|
435
|
-
// `path` is the route-segment appended to the screen's own path; the
|
|
436
|
-
// framework owns the outer routing. Components stay opaque.
|
|
437
|
-
export type CustomScreenRoute = {
|
|
438
|
-
readonly path: string;
|
|
439
|
-
readonly component: PlatformComponent;
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
export type CustomScreenDefinition = {
|
|
443
|
-
readonly id: string;
|
|
444
|
-
readonly type: "custom";
|
|
445
|
-
readonly renderer: PlatformComponent;
|
|
446
|
-
readonly routes?: readonly CustomScreenRoute[];
|
|
447
|
-
readonly access?: AccessRule;
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
// --- configEdit ---
|
|
451
|
-
|
|
452
|
-
// Form-Screen der Tenant-/User-/System-Settings aus dem bundled
|
|
453
|
-
// config-Feature liest und schreibt. Wird gerendert wie ein
|
|
454
|
-
// EntityEditScreen (sections + fields), aber:
|
|
455
|
-
// - Detail-Load via `config:query:values` (statt `<entity>:detail`)
|
|
456
|
-
// - Pre-Fill nutzt `configKeys[shortName]` → qualifizierter Key, dann
|
|
457
|
-
// `values[qualifiedKey].value`
|
|
458
|
-
// - Submit feuert pro geändertem Feld einen `config:write:set` mit
|
|
459
|
-
// {key, value, scope}; das config-feature behandelt jeden Key als
|
|
460
|
-
// eigenes Aggregate (configValue.<keyHash>) und alle N Writes laufen
|
|
461
|
-
// parallel (Promise.all). Per-Key idempotent → Retry safe.
|
|
462
|
-
// - kein Singleton-Hack nötig: pro (key+tenantId) gibt's by-design
|
|
463
|
-
// genau eine Row, der Bridge-Pattern aus dem Branding-MVP fällt weg
|
|
464
|
-
//
|
|
465
|
-
// Partial-Failure-Semantik: wenn von N parallelen Writes einer scheitert,
|
|
466
|
-
// bleiben die anderen committed (pro-Aggregate, kein Multi-Stream-Rollback).
|
|
467
|
-
// Das Form bleibt dirty bis der User retried — die schon erfolgreichen
|
|
468
|
-
// Writes feuern dann nochmal mit demselben Wert. Für `text` / `number` /
|
|
469
|
-
// `boolean` Keys ist das idempotent. Wer einen ConfigKey mit nicht-
|
|
470
|
-
// idempotentem Setter baut (Counter, append-only-list o.ä.) muss die
|
|
471
|
-
// Idempotenz im Setter sicherstellen.
|
|
472
|
-
//
|
|
473
|
-
// Field-Shape: inline am Screen wie bei `actionForm`. Author hat damit
|
|
474
|
-
// explizite Kontrolle über Input-Type (text/number/select/...) ohne
|
|
475
|
-
// Resolve-Ceremony — die FieldDefinitions sind dieselben wie auf
|
|
476
|
-
// Entities, alle DefaultInput-Renderer greifen unverändert. Field-
|
|
477
|
-
// Labels gehen über bestehende i18n-Konventionen (`<feature>:entity:
|
|
478
|
-
// <namespace>:field:<name>` o.ä. — der Author wählt den Namespace).
|
|
479
|
-
//
|
|
480
|
-
// scope MUSS mit der `createTenantConfig`/`createSystemConfig`/
|
|
481
|
-
// `createUserConfig`-Deklaration der referenzierten Keys
|
|
482
|
-
// übereinstimmen — der Boot-Validator pinnt das.
|
|
483
|
-
export type ConfigEditScreenDefinition = {
|
|
484
|
-
readonly id: string;
|
|
485
|
-
readonly type: "configEdit";
|
|
486
|
-
/** scope für config:write:set Calls. Muss zur Scope-Deklaration der
|
|
487
|
-
* in `configKeys` referenzierten Keys passen — Boot-Validator
|
|
488
|
-
* prüft das gegen die Registry. */
|
|
489
|
-
readonly scope: "tenant" | "system" | "user";
|
|
490
|
-
/** Map: form-field-name (kurz, wie im Layout referenziert) → voll-
|
|
491
|
-
* qualifizierter Config-Key (`<feature>:config:<short>`). Boot-
|
|
492
|
-
* Validator prüft dass jeder qualifizierte Key in der Registry
|
|
493
|
-
* bekannt ist. */
|
|
494
|
-
readonly configKeys: Readonly<Record<string, string>>;
|
|
495
|
-
/** Form-Shape pro Field-Name. Selbe FieldDefinitions wie auf
|
|
496
|
-
* Entities/ActionForm. Field-Names matchen die Keys in `configKeys`
|
|
497
|
-
* — Boot-Validator pinnt das. */
|
|
498
|
-
readonly fields: Readonly<Record<string, FieldDefinition>>;
|
|
499
|
-
/** Layout: Sections mit Field-Refs. Identisch zu entityEdit/
|
|
500
|
-
* actionForm. */
|
|
501
|
-
readonly layout: EditLayout;
|
|
502
|
-
/** Optionaler per-Field-Label-i18n-Key (Field-Name → Key). Der
|
|
503
|
-
* Settings-Hub setzt hier `mask.title` des jeweiligen Config-Keys,
|
|
504
|
-
* damit das am Key deklarierte Label am generierten Feld erscheint —
|
|
505
|
-
* ohne es unter der `__config-edit__`-Konvention zu duplizieren.
|
|
506
|
-
* Fehlt ein Eintrag, gilt die Konvention. */
|
|
507
|
-
readonly fieldLabels?: Readonly<Record<string, string>>;
|
|
508
|
-
/** i18n-key für den Submit-Button. Default: "kumiko.actions.save". */
|
|
509
|
-
readonly submitLabel?: string;
|
|
510
|
-
readonly slots?: ScreenSlots;
|
|
511
|
-
readonly access?: AccessRule;
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
// --- shared slots (Level 4 from ui-architecture.md) ---
|
|
515
|
-
|
|
516
|
-
export type ScreenSlots = {
|
|
517
|
-
readonly header?: PlatformComponent;
|
|
518
|
-
readonly beforeForm?: PlatformComponent;
|
|
519
|
-
readonly afterForm?: PlatformComponent;
|
|
520
|
-
readonly sidebar?: PlatformComponent;
|
|
521
|
-
readonly footer?: PlatformComponent;
|
|
522
|
-
readonly toolbar?: PlatformComponent;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
// --- discriminated union ---
|
|
526
|
-
|
|
527
|
-
export type ScreenDefinition =
|
|
528
|
-
| EntityListScreenDefinition
|
|
529
|
-
| EntityEditScreenDefinition
|
|
530
|
-
| ActionFormScreenDefinition
|
|
531
|
-
| ConfigEditScreenDefinition
|
|
532
|
-
| CustomScreenDefinition;
|
|
533
|
-
|
|
534
|
-
// Type guard — narrows FieldRenderer to FormatSpec. Useful for renderer
|
|
535
|
-
// authors who branch on the three FieldRenderer variants without manual
|
|
536
|
-
// "format" in renderer checks.
|
|
537
|
-
export function isFormatSpec(r: unknown): r is FormatSpec {
|
|
538
|
-
return typeof r === "object" && r !== null && "format" in r && typeof r.format === "string";
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Collapse the string-shorthand into the object form. Both the boot-validator
|
|
542
|
-
// and (later) ui-core's view-model builder iterate over fields/columns — the
|
|
543
|
-
// helper keeps that loop from growing two branches everywhere.
|
|
544
|
-
export function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string> {
|
|
545
|
-
const col = typeof c === "string" ? { field: c } : c;
|
|
546
|
-
if (
|
|
547
|
-
typeof process !== "undefined" &&
|
|
548
|
-
process.env.NODE_ENV !== "production" &&
|
|
549
|
-
col.renderer !== undefined &&
|
|
550
|
-
typeof col.renderer === "function"
|
|
551
|
-
) {
|
|
552
|
-
// biome-ignore lint/suspicious/noConsole: dev-only warning
|
|
553
|
-
console.warn(
|
|
554
|
-
`[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`,
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
return col;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/** Evaluates a declarative FieldCondition against the current row/form
|
|
561
|
-
* values. THE single implementation — renderer (row-action visibility),
|
|
562
|
-
* headless view-model (visible/readOnly/required) and render-edit
|
|
563
|
-
* (form-condition closures) reuse it; three hand-rolled copies had
|
|
564
|
-
* already drifted in shape. */
|
|
565
|
-
export function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean {
|
|
566
|
-
if (typeof cond === "boolean") return cond;
|
|
567
|
-
const val = values[cond.field];
|
|
568
|
-
if ("eq" in cond) return val === cond.eq;
|
|
569
|
-
return val !== cond.ne;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
export function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string> {
|
|
573
|
-
return typeof f === "string" ? { field: f } : f;
|
|
574
|
-
}
|
|
1
|
+
// Legacy path — re-exported for callers still importing this module directly.
|
|
2
|
+
export type * from "@cosmicdrift/kumiko-types/screen";
|