@cosmicdrift/kumiko-framework 0.387.0 → 0.389.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/AGENTS.md +9 -1
- package/agents/examples/anonymous-access.md +1 -1
- package/agents/examples/apex-landing.md +204 -0
- package/agents/examples/api-worker-split.md +64 -0
- package/agents/examples/app-config.md +14 -0
- package/agents/examples/auth-mfa.md +115 -0
- package/agents/examples/env-composition.md +28 -1
- package/agents/examples/golden-path.md +13 -1
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/lifecycle-hooks.md +55 -2
- package/agents/examples/list-table-options.md +466 -0
- package/agents/examples/pdf-audit-export.md +4 -2
- package/agents/examples/prod-boot-options.md +16 -0
- package/agents/examples/rate-limiting.md +48 -0
- package/agents/examples/record-detail-layout.md +692 -0
- package/agents/examples/seed-migration.md +212 -0
- package/agents/examples/state-machine.md +3 -2
- package/agents/examples/tenant-isolation.md +73 -1
- package/agents/examples/wizard-form.md +941 -0
- package/agents/examples/writeform-section.md +548 -0
- package/agents/examples.md +1 -0
- package/agents/features.md +19 -2
- package/agents/widgets.md +2 -0
- package/dist/api/api-constants.d.ts +1 -0
- package/dist/api/api-constants.js +2 -0
- package/dist/api/auth-routes-shared.d.ts +2 -0
- package/dist/api/auth-routes-shared.js +5 -0
- package/dist/api/client-error-route.js +10 -5
- package/dist/api/entity-read-access.d.ts +2 -1
- package/dist/api/entity-read-access.js +14 -0
- package/dist/api/export/export-cells-reference.d.ts +9 -0
- package/dist/api/export/export-cells-reference.js +21 -0
- package/dist/api/export/export-cells.d.ts +11 -0
- package/dist/api/export/export-cells.js +136 -0
- package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
- package/dist/api/export/export-ciphertext-guard.js +27 -0
- package/dist/api/export/export-columns.d.ts +29 -0
- package/dist/api/export/export-columns.js +85 -0
- package/dist/api/export/export-errors.d.ts +4 -0
- package/dist/api/export/export-errors.js +14 -0
- package/dist/api/export/export-route.d.ts +4 -0
- package/dist/api/export/export-route.js +74 -0
- package/dist/api/export/export-rows.d.ts +13 -0
- package/dist/api/export/export-rows.js +62 -0
- package/dist/api/export/export-screen.d.ts +12 -0
- package/dist/api/export/export-screen.js +47 -0
- package/dist/api/export/export-xlsx-writer.d.ts +4 -0
- package/dist/api/export/export-xlsx-writer.js +21 -0
- package/dist/api/extra-route.d.ts +4 -0
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +3 -1
- package/dist/api/pii-leak-guard.d.ts +3 -0
- package/dist/api/pii-leak-guard.js +3 -2
- package/dist/api/request-context.d.ts +1 -1
- package/dist/api/request-context.js +1 -1
- package/dist/api/routes.d.ts +16 -0
- package/dist/api/routes.js +2 -2
- package/dist/api/server-boot-checks.d.ts +10 -0
- package/dist/api/server-boot-checks.js +157 -0
- package/dist/api/server-event-dispatcher.d.ts +26 -0
- package/dist/api/server-event-dispatcher.js +208 -0
- package/dist/api/server-extra-routes.d.ts +11 -0
- package/dist/api/server-extra-routes.js +206 -0
- package/dist/api/server-middleware.d.ts +20 -0
- package/dist/api/server-middleware.js +211 -0
- package/dist/api/server-routes.d.ts +19 -0
- package/dist/api/server-routes.js +299 -0
- package/dist/api/server-websocket-routes.d.ts +4 -0
- package/dist/api/server-websocket-routes.js +108 -0
- package/dist/api/server-wiring.d.ts +32 -0
- package/dist/api/server-wiring.js +238 -0
- package/dist/api/server.d.ts +10 -15
- package/dist/api/server.js +48 -1251
- package/dist/api/session-cookies.js +4 -4
- package/dist/api/sse-route.d.ts +2 -1
- package/dist/api/sse-route.js +27 -13
- package/dist/config/index.d.ts +3 -1
- package/dist/crypto/owner-transition-rekey.d.ts +5 -1
- package/dist/crypto/owner-transition-rekey.js +1 -1
- package/dist/crypto/pg-kms-adapter.d.ts +1 -0
- package/dist/crypto/pg-kms-adapter.js +12 -18
- package/dist/crypto/pii-field-encryption.d.ts +5 -1
- package/dist/crypto/pii-field-encryption.js +3 -1
- package/dist/db/aggregate-sql.d.ts +23 -0
- package/dist/db/aggregate-sql.js +165 -0
- package/dist/db/closed-connection.d.ts +9 -0
- package/dist/db/closed-connection.js +88 -0
- package/dist/db/connection.d.ts +4 -5
- package/dist/db/connection.js +29 -20
- package/dist/db/dialect.js +1 -1
- package/dist/db/distinct-on.d.ts +1 -1
- package/dist/db/driver-row-coercion.d.ts +2 -0
- package/dist/db/driver-row-coercion.js +186 -0
- package/dist/db/driver-value-preparation.d.ts +16 -0
- package/dist/db/driver-value-preparation.js +101 -0
- package/dist/db/entity-field-encryption.d.ts +1 -0
- package/dist/db/entity-field-encryption.js +1 -1
- package/dist/db/entity-table-meta.js +103 -110
- package/dist/db/event-store-executor-context.d.ts +2 -1
- package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
- package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
- package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
- package/dist/db/event-store-executor-read-aggregate.js +143 -0
- package/dist/db/event-store-executor-read-filter.d.ts +31 -0
- package/dist/db/event-store-executor-read-filter.js +206 -0
- package/dist/db/event-store-executor-read-paging.d.ts +14 -0
- package/dist/db/event-store-executor-read-paging.js +99 -0
- package/dist/db/event-store-executor-read-rows.d.ts +6 -0
- package/dist/db/event-store-executor-read-rows.js +32 -0
- package/dist/db/event-store-executor-read-search.d.ts +28 -0
- package/dist/db/event-store-executor-read-search.js +221 -0
- package/dist/db/event-store-executor-read-sort.d.ts +41 -0
- package/dist/db/event-store-executor-read-sort.js +130 -0
- package/dist/db/event-store-executor-read-where.d.ts +24 -0
- package/dist/db/event-store-executor-read-where.js +81 -0
- package/dist/db/event-store-executor-read.d.ts +2 -9
- package/dist/db/event-store-executor-read.js +48 -701
- package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
- package/dist/db/event-store-executor-write-atomic.js +41 -0
- package/dist/db/event-store-executor-write-steps.d.ts +17 -0
- package/dist/db/event-store-executor-write-steps.js +95 -0
- package/dist/db/event-store-executor-write.js +405 -509
- package/dist/db/event-store-executor.js +2 -1
- package/dist/db/executor-managed-tables.js +1 -1
- package/dist/db/index.d.ts +7 -3
- package/dist/db/index.js +6 -2
- package/dist/db/list-aggregate-labels.d.ts +14 -0
- package/dist/db/list-aggregate-labels.js +26 -0
- package/dist/db/migrate-generator.js +71 -86
- package/dist/db/migrate-runner.js +7 -4
- package/dist/db/pg-error.js +26 -21
- package/dist/db/queries/backfill-pii.js +3 -6
- package/dist/db/queries/database-now.js +2 -1
- package/dist/db/queries/shadow-swap.js +2 -3
- package/dist/db/query.d.ts +16 -15
- package/dist/db/query.js +76 -804
- package/dist/db/reference-data.js +19 -2
- package/dist/db/statement-timeout.js +2 -1
- package/dist/db/table-builder.js +113 -166
- package/dist/db/table-info.d.ts +10 -0
- package/dist/db/table-info.js +45 -0
- package/dist/db/temporal-value-guards.d.ts +6 -0
- package/dist/db/temporal-value-guards.js +15 -0
- package/dist/db/tenant-db-runner.d.ts +1 -0
- package/dist/db/tenant-db-runner.js +3 -0
- package/dist/db/tenant-db-upsert.d.ts +5 -0
- package/dist/db/tenant-db-upsert.js +91 -0
- package/dist/db/tenant-db.js +50 -10
- package/dist/db/where-clause.d.ts +6 -0
- package/dist/db/where-clause.js +236 -0
- package/dist/derivatives/delete-stored-file.js +2 -1
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
- package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
- package/dist/engine/boot-validator/detail-screens.js +2 -2
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +17 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
- package/dist/engine/boot-validator/form-derivations.js +35 -1
- package/dist/engine/boot-validator/i18n-keys.js +5 -3
- package/dist/engine/boot-validator/index.js +2 -0
- package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
- package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
- package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
- package/dist/engine/boot-validator/list-column-layout.js +41 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
- package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-export-options.js +27 -0
- package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
- package/dist/engine/boot-validator/list-table-options.js +77 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
- package/dist/engine/boot-validator/pii-retention.js +233 -155
- package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
- package/dist/engine/boot-validator/projection-list-screens.js +3 -13
- package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
- package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
- package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
- package/dist/engine/boot-validator/screens-actions.js +8 -1
- package/dist/engine/boot-validator/screens-dashboard.js +2 -5
- package/dist/engine/boot-validator/screens-forms.js +2 -6
- package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.js +235 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +56 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
- package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
- package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
- package/dist/engine/boot-validator/screens-lists.js +24 -11
- package/dist/engine/boot-validator/screens.d.ts +0 -2
- package/dist/engine/boot-validator/screens.js +99 -860
- package/dist/engine/build-app-schema.d.ts +2 -1
- package/dist/engine/build-app-schema.js +65 -97
- package/dist/engine/build-config-feature-schema.js +6 -1
- package/dist/engine/define-feature.js +13 -2
- package/dist/engine/define-handler.js +1 -0
- package/dist/engine/entity-handlers.d.ts +13 -0
- package/dist/engine/entity-handlers.js +10 -21
- package/dist/engine/entity-list-runtime-options.d.ts +8 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- package/dist/engine/feature-ast/extractors/handlers.js +1 -0
- package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
- package/dist/engine/feature-ast/extractors/index.js +1 -1
- package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
- package/dist/engine/feature-ast/extractors/round1.js +6 -0
- package/dist/engine/feature-ast/extractors/round2.js +25 -6
- package/dist/engine/feature-ast/index.d.ts +1 -1
- package/dist/engine/feature-ast/parse.js +3 -1
- package/dist/engine/feature-ast/patch.d.ts +2 -0
- package/dist/engine/feature-ast/patch.js +5 -182
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -245
- package/dist/engine/feature-ast/pattern-change-schema.js +10 -163
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
- package/dist/engine/feature-ast/pattern-id-matchers.js +211 -0
- package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
- package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
- package/dist/engine/feature-ast/patterns.d.ts +14 -1
- package/dist/engine/feature-ast/patterns.js +44 -51
- package/dist/engine/feature-ast/render.js +52 -87
- package/dist/engine/feature-builder-state.d.ts +1 -0
- package/dist/engine/feature-builder-state.js +1 -0
- package/dist/engine/feature-entity-handlers.js +3 -0
- package/dist/engine/feature-manifest.d.ts +1 -0
- package/dist/engine/feature-manifest.js +1 -0
- package/dist/engine/field-access.d.ts +1 -0
- package/dist/engine/field-access.js +12 -0
- package/dist/engine/form-derivations.js +18 -7
- package/dist/engine/handler-field-references.js +1 -1
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/engine/pattern-library/library.js +2 -1
- package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
- package/dist/engine/pattern-library/static-schemas.js +9 -0
- package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
- package/dist/engine/project-app-schema-for-roles.js +6 -4
- package/dist/engine/registry-ingest.js +1 -1
- package/dist/engine/schema-builder.js +228 -212
- package/dist/engine/screen-filter-ops.js +2 -0
- package/dist/engine/screen-helpers.js +1 -1
- package/dist/engine/screen-kind.d.ts +7 -0
- package/dist/engine/screen-kind.js +1 -0
- package/dist/engine/strip-disabled-features-from-app-schema.js +58 -17
- package/dist/engine/types/feature.d.ts +1 -0
- package/dist/engine/types/index.d.ts +4 -3
- package/dist/engine/types/screen.d.ts +1 -1
- package/dist/engine/types/screen.js +1 -1
- package/dist/engine/validate-projection-allowlist.js +2 -1
- package/dist/entrypoint/index.d.ts +6 -1
- package/dist/entrypoint/index.js +10 -1
- package/dist/errors/docs-url.d.ts +1 -1
- package/dist/errors/docs-url.js +22 -2
- package/dist/es-ops/context.js +1 -1
- package/dist/event-store/admin-api.js +1 -1
- package/dist/event-store/event-store.d.ts +1 -0
- package/dist/event-store/event-store.js +17 -8
- package/dist/event-store/provenance-append.js +1 -1
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/create-file-ref-from-bytes.js +7 -1
- package/dist/files/create-file-ref.d.ts +3 -0
- package/dist/files/create-file-ref.js +11 -2
- package/dist/files/file-handle.js +2 -3
- package/dist/files/file-routes.d.ts +2 -0
- package/dist/files/file-routes.js +40 -10
- package/dist/files/provider-resolver.d.ts +1 -0
- package/dist/files/provider-resolver.js +1 -1
- package/dist/files/record-write-check.d.ts +6 -0
- package/dist/files/record-write-check.js +13 -0
- package/dist/files/resolve-content-type.js +43 -0
- package/dist/files/upload-ledger.d.ts +2 -2
- package/dist/files/upload-ledger.js +11 -4
- package/dist/i18n/required-surface-keys.js +189 -208
- package/dist/internal/stack-support.d.ts +2 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/internal/testing.js +1 -1
- package/dist/jobs/job-runner-context.d.ts +32 -0
- package/dist/jobs/job-runner-context.js +305 -0
- package/dist/jobs/job-runner-execution.d.ts +7 -0
- package/dist/jobs/job-runner-execution.js +160 -0
- package/dist/jobs/job-runner-gates.d.ts +19 -0
- package/dist/jobs/job-runner-gates.js +198 -0
- package/dist/jobs/job-runner-helpers.d.ts +17 -3
- package/dist/jobs/job-runner-helpers.js +61 -2
- package/dist/jobs/job-runner-scope.d.ts +44 -0
- package/dist/jobs/job-runner-scope.js +102 -0
- package/dist/jobs/job-runner-types.d.ts +12 -0
- package/dist/jobs/job-runner.d.ts +0 -1
- package/dist/jobs/job-runner.js +50 -657
- package/dist/observability/escape-hatch-report.d.ts +3 -0
- package/dist/observability/escape-hatch-report.js +21 -3
- package/dist/pipeline/after-rollback-sink.d.ts +6 -0
- package/dist/pipeline/after-rollback-sink.js +32 -0
- package/dist/pipeline/cascade-handler.js +165 -138
- package/dist/pipeline/dispatch-batch.js +8 -1
- package/dist/pipeline/dispatch-query.js +10 -3
- package/dist/pipeline/dispatch-shared.d.ts +13 -3
- package/dist/pipeline/dispatch-shared.js +62 -710
- package/dist/pipeline/dispatch-write.js +296 -189
- package/dist/pipeline/event-dispatcher-admin.js +2 -1
- package/dist/pipeline/event-dispatcher-delivery.js +6 -6
- package/dist/pipeline/event-dispatcher.d.ts +1 -0
- package/dist/pipeline/event-dispatcher.js +6 -9
- package/dist/pipeline/handler-context-auth.d.ts +9 -0
- package/dist/pipeline/handler-context-auth.js +54 -0
- package/dist/pipeline/handler-context-config.d.ts +14 -0
- package/dist/pipeline/handler-context-config.js +113 -0
- package/dist/pipeline/handler-context-db.d.ts +24 -0
- package/dist/pipeline/handler-context-db.js +149 -0
- package/dist/pipeline/handler-context-dispatch.d.ts +23 -0
- package/dist/pipeline/handler-context-dispatch.js +101 -0
- package/dist/pipeline/handler-context-events.d.ts +41 -0
- package/dist/pipeline/handler-context-events.js +239 -0
- package/dist/pipeline/handler-context-notify.d.ts +12 -0
- package/dist/pipeline/handler-context-notify.js +55 -0
- package/dist/pipeline/handler-context-observability.d.ts +7 -0
- package/dist/pipeline/handler-context-observability.js +21 -0
- package/dist/pipeline/handler-context-scope.d.ts +48 -0
- package/dist/pipeline/handler-context-scope.js +88 -0
- package/dist/pipeline/notify-scope.js +12 -1
- package/dist/pipeline/pre-handler-gates.js +20 -4
- package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
- package/dist/pipeline/pre-transaction-reservations.js +9 -3
- package/dist/pipeline/projection-rebuild.js +2 -1
- package/dist/pipeline/system-hooks.d.ts +3 -1
- package/dist/pipeline/system-hooks.js +55 -6
- package/dist/pipeline/write-result-masking.js +13 -10
- package/dist/schema-cli/apply-command.d.ts +2 -0
- package/dist/schema-cli/apply-command.js +97 -0
- package/dist/schema-cli/baseline-command.d.ts +2 -0
- package/dist/schema-cli/baseline-command.js +33 -0
- package/dist/schema-cli/generate-command.d.ts +2 -0
- package/dist/schema-cli/generate-command.js +90 -0
- package/dist/schema-cli/shared.d.ts +39 -0
- package/dist/schema-cli/shared.js +95 -0
- package/dist/schema-cli/status-command.d.ts +2 -0
- package/dist/schema-cli/status-command.js +73 -0
- package/dist/schema-cli/validate-boot-input.d.ts +5 -0
- package/dist/schema-cli/validate-boot-input.js +130 -0
- package/dist/schema-cli/validate-command.d.ts +2 -0
- package/dist/schema-cli/validate-command.js +130 -0
- package/dist/schema-cli.d.ts +2 -18
- package/dist/schema-cli.js +47 -604
- package/dist/search/meilisearch-adapter.js +2 -1
- package/dist/search/reindex-entity.js +15 -5
- package/dist/ui-types/index.d.ts +2 -2
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-changelog.d.ts +2 -0
- package/dist/upgrade-changelog.js +14 -0
- package/dist/upgrade-cli.d.ts +3 -12
- package/dist/upgrade-cli.js +171 -197
- package/dist/upgrade-marker-parse.d.ts +32 -0
- package/dist/upgrade-marker-parse.js +73 -0
- package/package.json +9 -6
- package/src/changes.json +555 -0
- package/src/scripts/codemod/codemod-support.ts +1 -1
- package/src/scripts/codemod/db-factory.ts +1 -1
- package/src/scripts/codemod/feature-layout.ts +10 -2
- package/src/scripts/codemod/object-form.ts +41 -4
- package/src/scripts/codemod/package-merge.ts +58 -8
- package/src/scripts/codemod/pii-personal-migration.ts +175 -145
- package/src/scripts/codemod/testing-injection.ts +122 -42
- package/src/scripts/codemod/testing-move.ts +29 -7
- package/dist/engine/provider-plugin-context.d.ts +0 -3
- package/dist/engine/provider-plugin-context.js +0 -28
|
@@ -97,3 +97,215 @@ Wenn eine kaputte Migration den Boot blockiert: setze `skippable: true` im seed-
|
|
|
97
97
|
## See also
|
|
98
98
|
|
|
99
99
|
- **Driver-Use-Case:** `seeds/2026-02-01-fix-admin-roles.ts` in this recipe
|
|
100
|
+
|
|
101
|
+
## Source files
|
|
102
|
+
|
|
103
|
+
### scripts/smoke.ts
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// biome-ignore-all lint/suspicious/noConsole: CLI tool output
|
|
107
|
+
//
|
|
108
|
+
// Lokaler Smoke-Test für seed-migrations.
|
|
109
|
+
//
|
|
110
|
+
// Was er prüft (offline, ohne Pod-Deploy):
|
|
111
|
+
// 1. Alle seed-files in ./seeds/ können dynamic-import geladen werden
|
|
112
|
+
// → catched syntax-errors + missing default-exports + falsche paths
|
|
113
|
+
// 2. Jeder handler-QN den die seeds referenzieren ist in der echten
|
|
114
|
+
// App-Registry registriert → catched camelCase-typos + drift
|
|
115
|
+
// (Bug-Klasse 3 aus Phase 1 retro)
|
|
116
|
+
// 3. Jeder system-user (mit oder ohne tenantIdOverride) hat Access auf
|
|
117
|
+
// die referenzierten Handler → catched access-rule-Drift
|
|
118
|
+
// (Bug-Klasse 4 aus Phase 1 retro)
|
|
119
|
+
//
|
|
120
|
+
// Schreibt NICHTS in die DB. Reine Read-only Validation.
|
|
121
|
+
//
|
|
122
|
+
// Usage:
|
|
123
|
+
// bun scripts/smoke.ts (gegen ./seeds/)
|
|
124
|
+
// bun scripts/smoke.ts --seeds-dir foo (custom path)
|
|
125
|
+
//
|
|
126
|
+
// In CI: vor `build`-Step laufen lassen — fängt 80% der Phase-1-Bugs
|
|
127
|
+
// lokal in <5 Sekunden statt nach 8-Minuten-Docker-Build im Pod-Boot.
|
|
128
|
+
|
|
129
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
130
|
+
import { resolve } from "node:path";
|
|
131
|
+
// App-spezifisch: nimm die EFFEKTIVEN Features die deine App zur Runtime
|
|
132
|
+
// nutzt. Im typischen Setup mit `runProdApp({ features, auth })`:
|
|
133
|
+
//
|
|
134
|
+
// import { composeFeatures } from "@cosmicdrift/kumiko-dev-server";
|
|
135
|
+
// import { APP_FEATURES } from "../src/run-config";
|
|
136
|
+
// const composed = composeFeatures(APP_FEATURES, { includeBundled: true, ... });
|
|
137
|
+
// const features = composed.features;
|
|
138
|
+
//
|
|
139
|
+
// Bei reiner App-Feature-Setup (kein auth-mode) reicht ein direkter
|
|
140
|
+
// Import der App-Features. Hier im Recipe der minimale set für die
|
|
141
|
+
// referenzierten Recipe-Seeds (tenant + config) — App-Author tauscht
|
|
142
|
+
// gegen sein eigenes Feature-Set.
|
|
143
|
+
import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";
|
|
144
|
+
import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
145
|
+
import {
|
|
146
|
+
createRegistry,
|
|
147
|
+
createSystemUser,
|
|
148
|
+
SYSTEM_TENANT_ID,
|
|
149
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
150
|
+
|
|
151
|
+
const features = [createConfigFeature(), createTenantFeature()];
|
|
152
|
+
|
|
153
|
+
const seedsDir = (() => {
|
|
154
|
+
const idx = process.argv.indexOf("--seeds-dir");
|
|
155
|
+
return idx >= 0 ? resolve(process.argv[idx + 1] ?? "./seeds") : resolve("./seeds");
|
|
156
|
+
})();
|
|
157
|
+
|
|
158
|
+
if (!existsSync(seedsDir)) {
|
|
159
|
+
console.log(`✓ no seeds-dir at ${seedsDir} — nothing to smoke-test`);
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const seedFiles = readdirSync(seedsDir)
|
|
164
|
+
.filter((n) => n.endsWith(".ts") && !n.startsWith("_") && !n.startsWith("."))
|
|
165
|
+
.sort();
|
|
166
|
+
|
|
167
|
+
if (seedFiles.length === 0) {
|
|
168
|
+
console.log(`✓ ${seedsDir} is empty — nothing to smoke-test`);
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const registry = createRegistry(features);
|
|
173
|
+
|
|
174
|
+
let failed = 0;
|
|
175
|
+
|
|
176
|
+
for (const file of seedFiles) {
|
|
177
|
+
const filePath = resolve(seedsDir, file);
|
|
178
|
+
console.log(`\nseed: ${file}`);
|
|
179
|
+
|
|
180
|
+
// 1. Module-load
|
|
181
|
+
try {
|
|
182
|
+
const mod = await import(filePath);
|
|
183
|
+
if (!mod.default || typeof mod.default.run !== "function") {
|
|
184
|
+
console.error(" ✗ missing valid SeedMigration default-export");
|
|
185
|
+
failed++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
console.log(` ✓ loads: "${mod.default.description}"`);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error(` ✗ load fail: ${err instanceof Error ? err.message : String(err)}`);
|
|
191
|
+
failed++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 2 + 3. QN-Resolution + Access (regex-based, gleiche Logik wie runner's
|
|
196
|
+
// dry-run-validator). Pattern matched inline-string-QNs:
|
|
197
|
+
const source = readFileSync(filePath, "utf-8");
|
|
198
|
+
const qnPattern = /systemWriteAs\s*\(\s*["']([^"']+)["']/g;
|
|
199
|
+
const qns = new Set<string>();
|
|
200
|
+
for (const m of source.matchAll(qnPattern)) {
|
|
201
|
+
const qn = m[1];
|
|
202
|
+
if (qn) qns.add(qn);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const qn of qns) {
|
|
206
|
+
const handler = registry.getWriteHandler(qn);
|
|
207
|
+
if (!handler) {
|
|
208
|
+
console.error(` ✗ handler "${qn}" not registered`);
|
|
209
|
+
failed++;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const accessRoles = (handler.access as { roles?: readonly string[] }).roles ?? [];
|
|
213
|
+
const systemUserRoles = createSystemUser(SYSTEM_TENANT_ID).roles;
|
|
214
|
+
const hasAccess = systemUserRoles.some((r) => accessRoles.includes(r));
|
|
215
|
+
if (!hasAccess) {
|
|
216
|
+
console.error(
|
|
217
|
+
` ✗ system-user roles=${JSON.stringify([...systemUserRoles])} NOT in access.roles=${JSON.stringify([...accessRoles])} for "${qn}"`,
|
|
218
|
+
);
|
|
219
|
+
failed++;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
console.log(` ✓ "${qn}" registered + accessible`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (failed > 0) {
|
|
227
|
+
console.error(`\n${failed} issue(s) — fix before push.`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
console.log(`\n✓ all ${seedFiles.length} seed-file(s) pass smoke.`);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### seeds/2026-01-01-initial-tenants.ts
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
// Initial-Seed: Demo-Tenants anlegen.
|
|
237
|
+
//
|
|
238
|
+
// Note: in real apps wäre "initial tenants" wahrscheinlich nicht als
|
|
239
|
+
// seed-migration sondern als `r.config({seeds})` oder `auth.admin`-
|
|
240
|
+
// Config umgesetzt — die laufen jedes-Boot idempotent. Hier ist es als
|
|
241
|
+
// seed-migration gezeigt um das Pattern zu demonstrieren.
|
|
242
|
+
//
|
|
243
|
+
// Pattern: jede seed-migration arbeitet write-only via `systemWriteAs`
|
|
244
|
+
// damit alles als Event-Stream persistiert wird. Read-only Lookups
|
|
245
|
+
// (find*) am Context oder direkt via ctx.db.
|
|
246
|
+
|
|
247
|
+
import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
|
|
248
|
+
|
|
249
|
+
export default {
|
|
250
|
+
description: "initial demo tenants (alice-corp, beta-inc)",
|
|
251
|
+
run: async (ctx) => {
|
|
252
|
+
// Idempotent-Check: wenn Tenants schon existieren (z.B. via auth.admin-
|
|
253
|
+
// seed), skip diese Migration. Verhindert Konflikt beim re-boot wenn
|
|
254
|
+
// jemand den Marker manuell gelöscht hat.
|
|
255
|
+
const existing = await ctx.findTenants();
|
|
256
|
+
if (existing.some((t) => t.tenantKey === "alice-corp")) return;
|
|
257
|
+
|
|
258
|
+
await ctx.systemWriteAs("tenant:write:create", {
|
|
259
|
+
name: "Alice Corp",
|
|
260
|
+
tenantKey: "alice-corp",
|
|
261
|
+
});
|
|
262
|
+
await ctx.systemWriteAs("tenant:write:create", {
|
|
263
|
+
name: "Beta Inc",
|
|
264
|
+
tenantKey: "beta-inc",
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
} satisfies SeedMigration;
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### seeds/2026-02-01-fix-admin-roles.ts
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// Driver-Pattern: ergänze fehlende Rolle auf existierenden Memberships.
|
|
274
|
+
// Spiegelt den realen publicstatus-Bug 2026-05-20 (admin@publicstatus.eu
|
|
275
|
+
// hatte "Admin" aber nicht "TenantAdmin" → text-content + legal-pages
|
|
276
|
+
// access_denied weil bundled-features TenantAdmin erwarten).
|
|
277
|
+
//
|
|
278
|
+
// Funktioniert für jede App mit dem gleichen Pattern: User-Lookup per
|
|
279
|
+
// E-Mail → alle Memberships durchgehen → bei Bedarf via existierendem
|
|
280
|
+
// updateMemberRoles-Handler korrigieren.
|
|
281
|
+
|
|
282
|
+
import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
|
|
283
|
+
|
|
284
|
+
export default {
|
|
285
|
+
description: "ergänze TenantAdmin-Rolle für admin@example.com auf allen Memberships",
|
|
286
|
+
run: async (ctx) => {
|
|
287
|
+
const admin = await ctx.findUserByEmail("admin@example.com");
|
|
288
|
+
if (!admin) return; // idempotent: admin nicht (mehr) vorhanden
|
|
289
|
+
|
|
290
|
+
for (const m of await ctx.findMembershipsOfUser(admin.id)) {
|
|
291
|
+
if (m.roles.includes("TenantAdmin")) continue; // bereits korrigiert
|
|
292
|
+
|
|
293
|
+
await ctx.systemWriteAs(
|
|
294
|
+
"tenant:write:update-member-roles", // kebab-case QN — wird vom Dry-Run-Validator gecheckt
|
|
295
|
+
{
|
|
296
|
+
userId: admin.id,
|
|
297
|
+
tenantId: m.tenantId,
|
|
298
|
+
roles: Array.from(new Set([...m.roles, "TenantAdmin"])),
|
|
299
|
+
},
|
|
300
|
+
// tenantIdOverride: stream-tenant aus kumiko_events.v1 — NICHT
|
|
301
|
+
// m.tenantId (payload-tenant). Beide stimmen oft überein, weichen
|
|
302
|
+
// aber ab wenn das Aggregate ursprünglich von einem fremden
|
|
303
|
+
// Executor angelegt wurde (seedTenantMembership by=systemAdmin —
|
|
304
|
+
// publicstatus-Pattern). findMembershipsOfUser liefert beide,
|
|
305
|
+
// kein eigener JOIN nötig.
|
|
306
|
+
m.streamTenantId,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
} satisfies SeedMigration;
|
|
311
|
+
```
|
|
@@ -492,6 +492,7 @@ export const invoiceTable = buildEntityTable("invoice", invoiceEntity);
|
|
|
492
492
|
|
|
493
493
|
import type { EventDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
494
494
|
import { z } from "zod";
|
|
495
|
+
import { INVOICE_STATES } from "./entities/invoice.ts";
|
|
495
496
|
|
|
496
497
|
export const FEATURE_NAME = "billing";
|
|
497
498
|
export const ENTITY_NAME = "invoice";
|
|
@@ -523,8 +524,8 @@ export const INVOICE_CREATED_EVENT = `${ENTITY_NAME}.created` as const;
|
|
|
523
524
|
|
|
524
525
|
export const emptyEventPayload = z.object({});
|
|
525
526
|
export const statusForcedPayload = z.object({
|
|
526
|
-
newStatus: z.enum(
|
|
527
|
-
fromStatus: z.enum(
|
|
527
|
+
newStatus: z.enum(INVOICE_STATES),
|
|
528
|
+
fromStatus: z.enum(INVOICE_STATES),
|
|
528
529
|
});
|
|
529
530
|
|
|
530
531
|
// The EventDefs r.defineEvent returns inside the feature setup. Handlers receive them so that
|
|
@@ -13,6 +13,7 @@ Multi-tenant data isolation by default.
|
|
|
13
13
|
## What it shows
|
|
14
14
|
|
|
15
15
|
- Tenant filter on ordinary handlers
|
|
16
|
+
- `ctx.db.upsert`: insert-or-update on a tenant-scoped, non-event-sourced table. `conflictTarget` must contain `tenantId` and match a declared unique index (here `tenant_id, key`); event-fed tables are rejected
|
|
16
17
|
- What breaks if you forget tenancy (and how the framework prevents it)
|
|
17
18
|
|
|
18
19
|
## Source
|
|
@@ -37,6 +38,7 @@ bun test
|
|
|
37
38
|
// Proves: tenant_id scopes all data, cross-tenant access returns nothing
|
|
38
39
|
|
|
39
40
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
41
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
40
42
|
import type { SaveContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
41
43
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
42
44
|
import { createTestUser, TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
type TestStack,
|
|
46
48
|
unsafeCreateEntityTable,
|
|
47
49
|
} from "@cosmicdrift/kumiko-testing/stack";
|
|
48
|
-
import { noteEntity, noteFeature } from "../feature";
|
|
50
|
+
import { noteEntity, noteFeature, preferenceTable } from "../feature";
|
|
49
51
|
|
|
50
52
|
let stack: TestStack;
|
|
51
53
|
|
|
@@ -136,12 +138,48 @@ describe("tenant isolation", () => {
|
|
|
136
138
|
expect(listB.rows.some((r) => r["title"] === "A-Note")).toBe(false);
|
|
137
139
|
});
|
|
138
140
|
});
|
|
141
|
+
|
|
142
|
+
describe("ctx.db.upsert", () => {
|
|
143
|
+
const setPreference = (user: typeof tenantBAdmin, key: string, value: string) =>
|
|
144
|
+
stack.http.writeOk<{ value: string }>("notes:write:preference:set", { key, value }, user);
|
|
145
|
+
|
|
146
|
+
test("a second upsert with the same key updates the tenant's row instead of inserting", async () => {
|
|
147
|
+
await setPreference(tenantAAdmin, "theme", "light");
|
|
148
|
+
const second = await setPreference(tenantAAdmin, "theme", "dark");
|
|
149
|
+
|
|
150
|
+
expect(second.value).toBe("dark");
|
|
151
|
+
const rows = await selectMany(stack.db, preferenceTable, {
|
|
152
|
+
tenantId: tenantAAdmin.tenantId,
|
|
153
|
+
key: "theme",
|
|
154
|
+
});
|
|
155
|
+
expect(rows).toHaveLength(1);
|
|
156
|
+
expect(rows[0]?.["value"]).toBe("dark");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("the same key in another tenant creates a separate row and leaves A's value alone", async () => {
|
|
160
|
+
await setPreference(tenantAAdmin, "language", "de");
|
|
161
|
+
await setPreference(tenantBAdmin, "language", "en");
|
|
162
|
+
|
|
163
|
+
const rowsA = await selectMany(stack.db, preferenceTable, {
|
|
164
|
+
tenantId: tenantAAdmin.tenantId,
|
|
165
|
+
key: "language",
|
|
166
|
+
});
|
|
167
|
+
const rowsB = await selectMany(stack.db, preferenceTable, {
|
|
168
|
+
tenantId: tenantBAdmin.tenantId,
|
|
169
|
+
key: "language",
|
|
170
|
+
});
|
|
171
|
+
expect(rowsA.map((row) => row["value"])).toEqual(["de"]);
|
|
172
|
+
expect(rowsB.map((row) => row["value"])).toEqual(["en"]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
139
175
|
```
|
|
140
176
|
|
|
141
177
|
### src/feature.ts
|
|
142
178
|
|
|
143
179
|
```ts
|
|
180
|
+
import { defineUnmanagedTable } from "@cosmicdrift/kumiko-framework/db";
|
|
144
181
|
import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
182
|
+
import * as z from "zod";
|
|
145
183
|
|
|
146
184
|
export const noteEntity = createEntity({
|
|
147
185
|
table: "read_sample_notes",
|
|
@@ -151,6 +189,26 @@ export const noteEntity = createEntity({
|
|
|
151
189
|
},
|
|
152
190
|
});
|
|
153
191
|
|
|
192
|
+
// Not event-sourced: ctx.db.upsert rejects executor-managed tables.
|
|
193
|
+
export const preferenceTable = defineUnmanagedTable({
|
|
194
|
+
tableName: "sample_tenant_preferences",
|
|
195
|
+
columns: [
|
|
196
|
+
{
|
|
197
|
+
name: "id",
|
|
198
|
+
pgType: "uuid",
|
|
199
|
+
notNull: true,
|
|
200
|
+
primaryKey: true,
|
|
201
|
+
defaultSql: "gen_random_uuid()",
|
|
202
|
+
},
|
|
203
|
+
{ name: "tenant_id", pgType: "uuid", notNull: true },
|
|
204
|
+
{ name: "key", pgType: "text", notNull: true },
|
|
205
|
+
{ name: "value", pgType: "text", notNull: true },
|
|
206
|
+
],
|
|
207
|
+
indexes: [
|
|
208
|
+
{ name: "sample_tenant_preferences_tenant_key", columns: ["tenant_id", "key"], unique: true },
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
|
|
154
212
|
const adminWrite = { access: { roles: ["Admin"] } } as const;
|
|
155
213
|
const openRead = {
|
|
156
214
|
access: {
|
|
@@ -168,5 +226,19 @@ export const noteFeature = defineFeature("notes", (r) => {
|
|
|
168
226
|
read: openRead,
|
|
169
227
|
verbs: { update: false, delete: false, restore: false },
|
|
170
228
|
});
|
|
229
|
+
|
|
230
|
+
r.storeTable(preferenceTable, { reason: "settings.tenant_preferences" });
|
|
231
|
+
|
|
232
|
+
r.writeHandler({
|
|
233
|
+
name: "preference:set",
|
|
234
|
+
schema: z.object({ key: z.string().min(1), value: z.string() }),
|
|
235
|
+
handler: async (event, ctx) => {
|
|
236
|
+
const row = await ctx.db.upsert<{ value: string }>(preferenceTable, event.payload, {
|
|
237
|
+
conflictTarget: ["tenantId", "key"],
|
|
238
|
+
});
|
|
239
|
+
return { isSuccess: true, data: { value: row?.value } };
|
|
240
|
+
},
|
|
241
|
+
...adminWrite,
|
|
242
|
+
});
|
|
171
243
|
});
|
|
172
244
|
```
|