@cosmicdrift/kumiko-framework 0.386.0 → 0.388.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 +6 -1
- package/LICENSE +21 -57
- package/README.md +2 -2
- 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/legal-pages.md +1 -1
- 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/wizard-form.md +941 -0
- package/agents/examples/writeform-section.md +548 -0
- package/agents/features.md +19 -2
- package/agents/widgets.md +40 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-error-route.js +8 -2
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +3 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/request-context.d.ts +1 -1
- package/dist/api/request-context.js +1 -1
- 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 +203 -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 +293 -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 +236 -0
- package/dist/api/server.d.ts +10 -15
- package/dist/api/server.js +48 -1250
- package/dist/api/session-cookies.js +4 -4
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +31 -27
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- 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.js +2 -6
- package/dist/crypto/pii-field-encryption.d.ts +5 -1
- package/dist/crypto/pii-field-encryption.js +7 -5
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/aggregate-sql.d.ts +21 -0
- package/dist/db/aggregate-sql.js +155 -0
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/closed-connection.d.ts +9 -0
- package/dist/db/closed-connection.js +95 -0
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +3 -3
- 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 +99 -0
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- 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.d.ts +2 -2
- package/dist/db/entity-table-meta.js +105 -112
- package/dist/db/event-store-executor-context.d.ts +3 -2
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
- package/dist/db/event-store-executor-read-aggregate.js +112 -0
- package/dist/db/event-store-executor-read-filter.d.ts +31 -0
- package/dist/db/event-store-executor-read-filter.js +203 -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 +50 -704
- package/dist/db/event-store-executor-write-steps.d.ts +20 -0
- package/dist/db/event-store-executor-write-steps.js +100 -0
- package/dist/db/event-store-executor-write.js +401 -510
- package/dist/db/index.d.ts +6 -2
- package/dist/db/index.js +5 -1
- package/dist/db/list-aggregate-labels.d.ts +14 -0
- package/dist/db/list-aggregate-labels.js +26 -0
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +97 -105
- package/dist/db/migrate-runner.js +29 -26
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/query.d.ts +3 -16
- package/dist/db/query.js +8 -761
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/reference-data.js +6 -1
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +137 -190
- 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.d.ts +2 -2
- package/dist/db/tenant-db.js +48 -11
- package/dist/db/where-clause.d.ts +6 -0
- package/dist/db/where-clause.js +236 -0
- package/dist/derivatives/delete-stored-file.js +6 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +4 -4
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +77 -61
- package/dist/engine/boot-validator/form-derivations.js +35 -1
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +6 -4
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +22 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +258 -187
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
- package/dist/engine/boot-validator/screens-actions.js +23 -17
- package/dist/engine/boot-validator/screens-dashboard.js +2 -5
- package/dist/engine/boot-validator/screens-forms.js +12 -17
- 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 +228 -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 +52 -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 +26 -24
- package/dist/engine/boot-validator/screens.d.ts +0 -2
- package/dist/engine/boot-validator/screens.js +102 -869
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +130 -170
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +21 -13
- package/dist/engine/define-handler.js +13 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.d.ts +13 -0
- package/dist/engine/entity-handlers.js +22 -34
- package/dist/engine/entity-list-runtime-options.d.ts +8 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +2 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/round2.js +25 -6
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +4 -182
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
- package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
- package/dist/engine/feature-ast/patterns.d.ts +11 -2
- package/dist/engine/feature-ast/patterns.js +43 -51
- package/dist/engine/feature-ast/render.js +50 -89
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +12 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/form-derivations.js +18 -7
- package/dist/engine/handler-field-references.js +1 -1
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
- package/dist/engine/project-app-schema-for-roles.js +7 -5
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +13 -13
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +251 -233
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/screen-kind.d.ts +7 -0
- package/dist/engine/screen-kind.js +1 -0
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +9 -28
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.d.ts +4 -1
- package/dist/entrypoint/index.js +10 -4
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/docs-url.d.ts +1 -1
- package/dist/errors/docs-url.js +22 -2
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +29 -29
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +7 -8
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +12 -6
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/create-file-ref-from-bytes.js +4 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +7 -9
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +22 -13
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/resolve-content-type.js +43 -0
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/upload-ledger.js +7 -0
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +188 -212
- package/dist/internal/stack-support.d.ts +2 -1
- package/dist/internal/stack-support.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 +69 -4
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- 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 +57 -669
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/escape-hatch-report.d.ts +3 -0
- package/dist/observability/escape-hatch-report.js +12 -1
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/cascade-handler.js +165 -138
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +20 -13
- package/dist/pipeline/dispatch-shared.d.ts +14 -3
- package/dist/pipeline/dispatch-shared.js +67 -721
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +297 -190
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-admin.js +2 -1
- package/dist/pipeline/event-dispatcher-delivery.js +4 -6
- package/dist/pipeline/event-dispatcher.js +17 -28
- 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 +21 -0
- package/dist/pipeline/handler-context-dispatch.js +89 -0
- package/dist/pipeline/handler-context-events.d.ts +43 -0
- package/dist/pipeline/handler-context-events.js +237 -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/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- 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 +15 -13
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- 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 -510
- package/dist/search/index.js +4 -4
- package/dist/search/meilisearch-adapter.js +2 -1
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +159 -130
- package/dist/utils/compare.js +1 -1
- package/package.json +5 -5
- package/src/changes.json +358 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/feature-layout.ts +10 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- 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 +192 -153
- package/src/scripts/codemod/testing-injection.ts +122 -42
- package/src/scripts/codemod/testing-move.ts +29 -7
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
package/dist/random/generate.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
// Random
|
|
2
|
-
// - Tenant
|
|
3
|
-
// - Webhook
|
|
4
|
-
// - API
|
|
5
|
-
// - Tenant
|
|
6
|
-
// - Test
|
|
1
|
+
// Random generator for human-readable resource identifiers:
|
|
2
|
+
// - Tenant keys ("happy-cloud", "swift-river-k8x")
|
|
3
|
+
// - Webhook subscriber slugs ("bold-falcon-receiver" with custom nouns)
|
|
4
|
+
// - API key display names (offer 3 suggestions on creation)
|
|
5
|
+
// - Tenant subdomain suggestions in the subdomain setup screen
|
|
6
|
+
// - Test fixtures with readable identifiers
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// Authority
|
|
11
|
-
//
|
|
8
|
+
// NOT for security tokens (CSRF, session, API keys, OAuth state,
|
|
9
|
+
// reset tokens). Math.random() is not cryptographically unpredictable.
|
|
10
|
+
// Authority binding for tenant keys uses JWT + DB lookup, not slug secrecy;
|
|
11
|
+
// the slug may be guessable.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Safe across environments: Math.random() runs in Bun, Node, Metro/RN, and
|
|
14
|
+
// Expo Web. No node:crypto imports.
|
|
15
15
|
import { ADJECTIVES, NOUNS } from "./words.js";
|
|
16
|
-
// Alphabet
|
|
17
|
-
// -
|
|
18
|
-
// -
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// Alphabet without characters that are easily confused when entered manually:
|
|
17
|
+
// - no 0/O (zero versus uppercase O)
|
|
18
|
+
// - no 1/l/I (one versus lowercase L versus uppercase I)
|
|
19
|
+
// Result: 32 characters, safe for spelling over the phone AND for copying
|
|
20
|
+
// from email text in unfamiliar fonts.
|
|
21
21
|
const NO_CONFUSABLE_ALPHABET = "23456789abcdefghjkmnpqrstuvwxyz";
|
|
22
22
|
const NO_CONFUSABLE_CHARS = Object.freeze(NO_CONFUSABLE_ALPHABET.split(""));
|
|
23
23
|
function pickRandom(arr) {
|
|
@@ -25,15 +25,15 @@ function pickRandom(arr) {
|
|
|
25
25
|
throw new Error("pickRandom: cannot pick from empty array");
|
|
26
26
|
}
|
|
27
27
|
const value = arr[Math.floor(Math.random() * arr.length)];
|
|
28
|
-
//
|
|
28
|
+
// The length check above guarantees that the index is in range.
|
|
29
29
|
if (value === undefined) {
|
|
30
30
|
throw new Error("pickRandom: indexed value undefined (sparse array?)");
|
|
31
31
|
}
|
|
32
32
|
return value;
|
|
33
33
|
}
|
|
34
|
-
/** Sync
|
|
35
|
-
* (
|
|
36
|
-
*
|
|
34
|
+
/** Sync generator: produces "happy-cloud" or "happy-cloud-k8x"
|
|
35
|
+
* (with suffix). No conflict check — the caller is responsible for
|
|
36
|
+
* uniqueness, or can use generateUniqueName() for the async variant. */
|
|
37
37
|
export function generateAdjNounName(options = {}) {
|
|
38
38
|
const sep = options.separator ?? "-";
|
|
39
39
|
const adjs = options.adjectives ?? ADJECTIVES;
|
|
@@ -44,9 +44,9 @@ export function generateAdjNounName(options = {}) {
|
|
|
44
44
|
}
|
|
45
45
|
return name;
|
|
46
46
|
}
|
|
47
|
-
/** Random
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
/** Random string from the no-confusable alphabet. For suffixes after
|
|
48
|
+
* collisions or as standalone short IDs (e.g. webhook verification codes
|
|
49
|
+
* to be read aloud over the phone). length ≥ 1. */
|
|
50
50
|
export function generateNoConfusableId(length) {
|
|
51
51
|
if (length < 1) {
|
|
52
52
|
throw new Error(`generateNoConfusableId: length must be ≥ 1 (got ${length})`);
|
|
@@ -57,17 +57,17 @@ export function generateNoConfusableId(length) {
|
|
|
57
57
|
}
|
|
58
58
|
return id;
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
* Attempts.
|
|
63
|
-
*
|
|
60
|
+
/** Generates a unique Adj-Noun slug by trying up to maxCleanAttempts
|
|
61
|
+
* clean word pairs, then using a random suffix up to maxTotal-
|
|
62
|
+
* Attempts. Throws if that also fails (= the caller has a bug
|
|
63
|
+
* in isAvailable, or the word list is defective).
|
|
64
64
|
*
|
|
65
|
-
*
|
|
65
|
+
* Example:
|
|
66
66
|
* const slug = await generateUniqueName({
|
|
67
67
|
* isAvailable: async (s) =>
|
|
68
68
|
* !(await db.select().from(tenants).where(eq(tenants.tenantKey, s)).then(r => r.length > 0)),
|
|
69
69
|
* });
|
|
70
|
-
* // → "happy-cloud"
|
|
70
|
+
* // → "happy-cloud" or "happy-cloud-k8x" on collision
|
|
71
71
|
*/
|
|
72
72
|
export async function generateUniqueName(options) {
|
|
73
73
|
const maxClean = options.maxCleanAttempts ?? 3;
|
package/dist/random/words.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// PII-
|
|
1
|
+
// Word lists for human-readable resource slugs (tenant keys, webhook-
|
|
2
|
+
// subscribers, API-key display names, test fixtures). Heroku/Docker-
|
|
3
|
+
// style — adjective + noun produce pronounceable, memorable,
|
|
4
|
+
// PII-free identifiers.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// -
|
|
8
|
-
// -
|
|
9
|
-
// -
|
|
10
|
-
// -
|
|
11
|
-
// - Lowercase, ASCII-only,
|
|
12
|
-
// - 3
|
|
13
|
-
// -
|
|
14
|
-
// -
|
|
6
|
+
// Selection criteria:
|
|
7
|
+
// - Emotionally neutral and professional (no "fluffy", "sneaky")
|
|
8
|
+
// - No animal names (Heroku cliché)
|
|
9
|
+
// - No personal names (cultural appropriation, celebrity collisions)
|
|
10
|
+
// - No thematic clusters (neither weather-only nor geography-only)
|
|
11
|
+
// - Lowercase, ASCII-only, with no hyphens within a word
|
|
12
|
+
// - 3–10 letters per word (compact slug)
|
|
13
|
+
// - Pronounceable in German AND English (user phone support)
|
|
14
|
+
// - No words with ambiguous meanings in English and German
|
|
15
15
|
//
|
|
16
|
-
// 191 × 173 = 33
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
16
|
+
// 191 × 173 = 33,043 clean combinations. Birthday bound: from ~180
|
|
17
|
+
// tenants onward, the first collision becomes likely cumulatively (~50%);
|
|
18
|
+
// for an individual draw, p(collision) remains < 1% (see generate.ts). On
|
|
19
|
+
// collision, the suffix path in generateUniqueName is used.
|
|
20
20
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// Extension: appending more words below is sufficient (sorting helps with
|
|
22
|
+
// reviews but is not required). Callers can also provide custom lists via
|
|
23
|
+
// generateUniqueName({ adjectives, nouns }).
|
|
24
24
|
export const ADJECTIVES = Object.freeze([
|
|
25
25
|
"agile",
|
|
26
26
|
"amber",
|
|
@@ -108,21 +108,25 @@ export function createFailOpenReporter(label, meter, now = Date.now) {
|
|
|
108
108
|
lastWarnAt = nowMs;
|
|
109
109
|
logger.warn("rate limiter unavailable, failing open", {
|
|
110
110
|
label,
|
|
111
|
-
// ioredis errors can carry the command args, i.e. the bucket key
|
|
111
|
+
// ioredis errors can carry the command args, i.e. the bucket key. scrubLogText masks an e-mail
|
|
112
|
+
// address in it; the client IP stays in the line as an operational datum.
|
|
112
113
|
error: scrubLogText(err instanceof Error ? err.message : String(err)),
|
|
113
114
|
});
|
|
114
115
|
};
|
|
115
116
|
}
|
|
117
|
+
const failClosedLogger = createFallbackLogger("rate-limit");
|
|
116
118
|
function defaultOnFailClosed(label) {
|
|
117
119
|
return (err) => {
|
|
118
120
|
// Loud by default — fail-closed for an unknown reason is an ops
|
|
119
121
|
// signal, not something to swallow silently. Production deploys
|
|
120
122
|
// override this with a structured logger; the default keeps the
|
|
121
123
|
// noise visible if no logger is wired.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
failClosedLogger.error("fail-closed (refusing request)", {
|
|
125
|
+
label,
|
|
126
|
+
// ioredis errors can carry the command args, i.e. the bucket key. scrubLogText masks an e-mail
|
|
127
|
+
// address in it; the client IP stays in the line as an operational datum.
|
|
128
|
+
error: scrubLogText(err instanceof Error ? err.message : String(err)),
|
|
129
|
+
});
|
|
126
130
|
};
|
|
127
131
|
}
|
|
128
132
|
function setRateLimitHeaders(c, decision) {
|
package/dist/redis/client.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// production defaults stay consistent and env-var wiring lives in one
|
|
9
9
|
// place. Tests / one-off scripts can still `new Redis()` directly.
|
|
10
10
|
//
|
|
11
|
-
// Split out of index.ts
|
|
12
|
-
//
|
|
11
|
+
// Split out of index.ts so pubsub-signal.ts can import this without a
|
|
12
|
+
// same-directory cycle back through the barrel.
|
|
13
13
|
import { Redis } from "ioredis";
|
|
14
14
|
import { scrubLogText } from "../logging/scrub.js";
|
|
15
15
|
import { readPositiveIntEnv } from "../utils/env-parse.js";
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { createFallbackLogger } from "../logging/utils.js";
|
|
1
2
|
import { createRedisClient } from "./client.js";
|
|
2
3
|
// Redis emits 'error' on connection blips (reconnects transparently); an
|
|
3
|
-
// unhandled listener crashes the process
|
|
4
|
-
// class with BullMQ's internal client
|
|
5
|
-
function logConnectionError(
|
|
4
|
+
// unhandled listener crashes the process — job-runner hit the same
|
|
5
|
+
// class with BullMQ's internal client.
|
|
6
|
+
function logConnectionError(log, role) {
|
|
6
7
|
return (err) => {
|
|
7
|
-
|
|
8
|
-
console.error(`[kumiko:${label}] ${role} connection error:`, err.message);
|
|
8
|
+
log.error(`${role} connection error`, { error: err.message });
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export function createRedisPubSubSignal(opts) {
|
|
12
|
+
const log = createFallbackLogger(`kumiko:${opts.label}`);
|
|
12
13
|
const publisher = createRedisClient(opts.redisUrl, opts.clientOptions);
|
|
13
14
|
const subscriber = createRedisClient(opts.redisUrl, opts.clientOptions);
|
|
14
|
-
publisher.on("error", logConnectionError(
|
|
15
|
-
subscriber.on("error", logConnectionError(
|
|
15
|
+
publisher.on("error", logConnectionError(log, "publisher"));
|
|
16
|
+
subscriber.on("error", logConnectionError(log, "subscriber"));
|
|
16
17
|
const listeners = new Set();
|
|
17
18
|
const reconnectListeners = new Set();
|
|
18
19
|
// Sole purpose: stop close() from racing the initial psubscribe (see
|
|
@@ -26,8 +27,7 @@ export function createRedisPubSubSignal(opts) {
|
|
|
26
27
|
payload = JSON.parse(message);
|
|
27
28
|
}
|
|
28
29
|
catch (err) {
|
|
29
|
-
|
|
30
|
-
console.error(`[kumiko:${opts.label}] dropping unparseable message on "${channel}":`, err);
|
|
30
|
+
log.error(`dropping unparseable message on "${channel}"`, { err });
|
|
31
31
|
// skip: unparseable payload already logged above, nothing to deliver
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
@@ -37,14 +37,14 @@ export function createRedisPubSubSignal(opts) {
|
|
|
37
37
|
// ponytail: psubscribe on one pattern rather than per-channel
|
|
38
38
|
// subscribe/unsubscribe — every instance receives every message that
|
|
39
39
|
// matches, filtered by the caller. That's strictly no worse than the
|
|
40
|
-
//
|
|
40
|
+
// per-instance-cursor model (which already read every event
|
|
41
41
|
// from Postgres in every process) and sidesteps the subscribe-ack/
|
|
42
42
|
// first-publish race a per-channel scheme would have. Revisit with
|
|
43
43
|
// per-channel subscribe only if fanout volume becomes the bottleneck.
|
|
44
44
|
//
|
|
45
45
|
// Awaited by close() below — quitting while this is still in flight (a
|
|
46
46
|
// shutdown landing immediately after construction, e.g. a test closing
|
|
47
|
-
// right after building the signal) makes ioredis
|
|
47
|
+
// right after building the signal) makes ioredis reject the queued
|
|
48
48
|
// psubscribe with "Connection is closed" instead of it completing
|
|
49
49
|
// cleanly first. Not caught here or logged when a close is already
|
|
50
50
|
// underway (`closed`), so an intentional shutdown never surfaces this
|
|
@@ -54,8 +54,7 @@ export function createRedisPubSubSignal(opts) {
|
|
|
54
54
|
// this rejection is the expected side effect, not an ops-visible error.
|
|
55
55
|
if (closed)
|
|
56
56
|
return;
|
|
57
|
-
|
|
58
|
-
console.error(`[kumiko:${opts.label}] psubscribe failed:`, err);
|
|
57
|
+
log.error("psubscribe failed", { err });
|
|
59
58
|
});
|
|
60
59
|
// ioredis re-sends the psubscribe itself (autoResubscribe) but emits no ack
|
|
61
60
|
// event; repeating it here queues behind that one on the same connection,
|
|
@@ -77,8 +76,7 @@ export function createRedisPubSubSignal(opts) {
|
|
|
77
76
|
// skip: a close() already underway intentionally quit the connection
|
|
78
77
|
if (closed)
|
|
79
78
|
return;
|
|
80
|
-
|
|
81
|
-
console.error(`[kumiko:${opts.label}] resubscribe failed:`, err);
|
|
79
|
+
log.error("resubscribe failed", { err });
|
|
82
80
|
});
|
|
83
81
|
});
|
|
84
82
|
return {
|
|
@@ -88,8 +86,7 @@ export function createRedisPubSubSignal(opts) {
|
|
|
88
86
|
// connections — this rejection is the expected side effect.
|
|
89
87
|
if (closed)
|
|
90
88
|
return;
|
|
91
|
-
|
|
92
|
-
console.error(`[kumiko:${opts.label}] publish failed:`, err);
|
|
89
|
+
log.error("publish failed", { err });
|
|
93
90
|
});
|
|
94
91
|
},
|
|
95
92
|
async publishConfirmed(channel, payload) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { configureBlindIndexKey, configurePiiSubjectKms, resolveKmsWiringAsync, } from "../crypto/index.js";
|
|
2
|
+
import { createDbConnection, runMigrationsFromDir } from "../db/index.js";
|
|
3
|
+
import { createRegistry } from "../engine/registry.js";
|
|
4
|
+
import { createEventsTable } from "../event-store/index.js";
|
|
5
|
+
import { queueRebuildsFromMarkers, reportPendingRebuildRun, runPendingRebuilds, } from "../migrations/index.js";
|
|
6
|
+
import { createEventConsumerStateTable, createProjectionStateTable } from "../pipeline/index.js";
|
|
7
|
+
import { migrationsDirExistsOrReport, readDatabaseUrlOrReport, } from "./shared.js";
|
|
8
|
+
async function ensureFrameworkInfrastructureTables(db) {
|
|
9
|
+
// Framework infrastructure tables (event store + pipeline state) are not captured by
|
|
10
|
+
// `generate`, which handles only entity read tables. Existing databases have them
|
|
11
|
+
// from the legacy-drizzle foundation; without them, a greenfield database (the
|
|
12
|
+
// first app without a cutover) would lack kumiko_events and runProdApp would fail
|
|
13
|
+
// during boot. All three are idempotent (tableExists-gated), so they are no-ops for
|
|
14
|
+
// existing databases.
|
|
15
|
+
await createEventsTable(db);
|
|
16
|
+
await createEventConsumerStateTable(db);
|
|
17
|
+
await createProjectionStateTable(db);
|
|
18
|
+
}
|
|
19
|
+
function reportAppliedMigrations(out, result) {
|
|
20
|
+
out.log("");
|
|
21
|
+
if (result.applied.length === 0) {
|
|
22
|
+
out.log(` ✓ All ${result.skipped.length} migrations already applied.`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
out.log(` ✓ Applied ${result.applied.length}:`);
|
|
26
|
+
for (const id of result.applied)
|
|
27
|
+
out.log(` + ${id}`);
|
|
28
|
+
if (result.skipped.length > 0)
|
|
29
|
+
out.log(` (${result.skipped.length} already applied)`);
|
|
30
|
+
}
|
|
31
|
+
out.log("");
|
|
32
|
+
}
|
|
33
|
+
function configureRebuildKms(wiring) {
|
|
34
|
+
if ("kms" in wiring) {
|
|
35
|
+
configurePiiSubjectKms(wiring.kms);
|
|
36
|
+
configureBlindIndexKey(wiring.blindIndexKey);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function rebuildQueuedProjections(db, ctx, features, appliedIds) {
|
|
40
|
+
const thisRunTables = await queueRebuildsFromMarkers(db, {
|
|
41
|
+
migrationsDir: ctx.migrationsDir,
|
|
42
|
+
appliedIds,
|
|
43
|
+
});
|
|
44
|
+
const registry = createRegistry(features);
|
|
45
|
+
const rebuildRun = await runPendingRebuilds(db, registry, { thisRunTables });
|
|
46
|
+
reportPendingRebuildRun(rebuildRun, ctx.out.log);
|
|
47
|
+
}
|
|
48
|
+
export async function runApplyCommand(ctx) {
|
|
49
|
+
const { out, options, migrationsDir } = ctx;
|
|
50
|
+
const dbUrl = readDatabaseUrlOrReport(out);
|
|
51
|
+
if (dbUrl === undefined)
|
|
52
|
+
return 1;
|
|
53
|
+
if (!migrationsDirExistsOrReport(ctx))
|
|
54
|
+
return 1;
|
|
55
|
+
const { db, close } = createDbConnection({ url: dbUrl });
|
|
56
|
+
let wiring;
|
|
57
|
+
try {
|
|
58
|
+
const result = await runMigrationsFromDir(db, migrationsDir);
|
|
59
|
+
await ensureFrameworkInfrastructureTables(db);
|
|
60
|
+
reportAppliedMigrations(out, result);
|
|
61
|
+
// Projection-rebuild for tables a freshly applied migration changed
|
|
62
|
+
// (marker NNNN_<name>.rebuild.json from `generate`). Needs the composed
|
|
63
|
+
// feature set → only when the caller passed `features` (the app bin);
|
|
64
|
+
// the dev CLI omits it and applies migrations only. Runs unconditionally
|
|
65
|
+
// (not gated on result.applied.length > 0): a persistent queue
|
|
66
|
+
// (kumiko_pending_rebuilds) survives a failed/crashed rebuild from an
|
|
67
|
+
// earlier apply, so a later apply with zero new migrations still must
|
|
68
|
+
// retry it — otherwise a failed rebuild is silently never retried,
|
|
69
|
+
// since the migration itself is already tracked applied.
|
|
70
|
+
if (options.features) {
|
|
71
|
+
// A rebuild replays applyEntityEvent, which computes bidx columns from
|
|
72
|
+
// configureBlindIndexKey/configurePiiSubjectKms — both boot-injected by
|
|
73
|
+
// run{Prod,Dev}App but never by this standalone CLI (the migrate-db
|
|
74
|
+
// initContainer). Without wiring them here, a rebuild would silently
|
|
75
|
+
// null out bidx columns and, for ciphertext, throw only once it hits
|
|
76
|
+
// the row.
|
|
77
|
+
wiring = await resolveKmsWiringAsync(process.env, {
|
|
78
|
+
logPrefix: "[kumiko schema apply]",
|
|
79
|
+
log: out.log,
|
|
80
|
+
slots: options.kmsSlots,
|
|
81
|
+
});
|
|
82
|
+
configureRebuildKms(wiring);
|
|
83
|
+
await rebuildQueuedProjections(db, ctx, options.features, result.applied);
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
out.err("");
|
|
89
|
+
out.err(` ✗ ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`);
|
|
90
|
+
out.err("");
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await wiring?.close();
|
|
95
|
+
await close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { baselineMigrations, createDbConnection, loadMigrationsFromDir } from "../db/index.js";
|
|
2
|
+
import { describeThrown, migrationsDirExistsOrReport, readDatabaseUrlOrReport, } from "./shared.js";
|
|
3
|
+
export async function runBaselineCommand(ctx) {
|
|
4
|
+
const { out, migrationsDir } = ctx;
|
|
5
|
+
// Adopt an existing DB: mark all checked-in migrations as applied WITHOUT
|
|
6
|
+
// running their SQL (prod tables already exist — cutover from the legacy
|
|
7
|
+
// drizzle system). Afterwards the boot-gate is drift-free.
|
|
8
|
+
const dbUrl = readDatabaseUrlOrReport(out);
|
|
9
|
+
if (dbUrl === undefined)
|
|
10
|
+
return 1;
|
|
11
|
+
if (!migrationsDirExistsOrReport(ctx))
|
|
12
|
+
return 1;
|
|
13
|
+
const { db, close } = createDbConnection({ url: dbUrl });
|
|
14
|
+
try {
|
|
15
|
+
const result = await baselineMigrations(db, loadMigrationsFromDir(migrationsDir));
|
|
16
|
+
out.log("");
|
|
17
|
+
out.log(` ✓ Marked ${result.marked.length} migration(s) as applied (no SQL run):`);
|
|
18
|
+
for (const id of result.marked)
|
|
19
|
+
out.log(` + ${id}`);
|
|
20
|
+
if (result.alreadyTracked.length > 0) {
|
|
21
|
+
out.log(` (${result.alreadyTracked.length} already tracked)`);
|
|
22
|
+
}
|
|
23
|
+
out.log("");
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
out.err(` ✗ ${describeThrown(e)}`);
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { assertValidMigrationName, generateMigration, readMigrationSqlTexts, rebuildTablesFromDiff, writeRebuildMarker, writeSnapshotJson, } from "../db/index.js";
|
|
4
|
+
import { describeThrown, loadPreviousSnapshot, loadSchemaModOrReport, nextSequenceNumber, SNAPSHOT_FILENAME, } from "./shared.js";
|
|
5
|
+
const GENERATE_USAGE = " Usage: schema generate <name>";
|
|
6
|
+
function parseGenerateName(ctx) {
|
|
7
|
+
const { argv, out } = ctx;
|
|
8
|
+
const name = argv[1];
|
|
9
|
+
if (name === "--help" || name === "-h") {
|
|
10
|
+
out.log(GENERATE_USAGE);
|
|
11
|
+
return { kind: "exit", code: 0 };
|
|
12
|
+
}
|
|
13
|
+
if (!name) {
|
|
14
|
+
out.err(GENERATE_USAGE);
|
|
15
|
+
return { kind: "exit", code: 1 };
|
|
16
|
+
}
|
|
17
|
+
// name lands unescaped in `${seq}_${name}.sql` (generateMigration) —
|
|
18
|
+
// shared allowlist blocks flag-like names, path traversal, and
|
|
19
|
+
// ENAMETOOLONG-length names.
|
|
20
|
+
try {
|
|
21
|
+
assertValidMigrationName(name);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
out.err(` ${describeThrown(e)}`);
|
|
25
|
+
out.err(GENERATE_USAGE);
|
|
26
|
+
return { kind: "exit", code: 1 };
|
|
27
|
+
}
|
|
28
|
+
// Trailing args (e.g. a typo'd `--dry-run` — there is no such flag) were
|
|
29
|
+
// silently ignored and the migration got written anyway. Reject them
|
|
30
|
+
// before any IO instead of pretending they did something.
|
|
31
|
+
if (argv.length > 2) {
|
|
32
|
+
out.err(` Unrecognized argument(s): ${argv.slice(2).join(" ")}`);
|
|
33
|
+
out.err(GENERATE_USAGE);
|
|
34
|
+
return { kind: "exit", code: 1 };
|
|
35
|
+
}
|
|
36
|
+
return { kind: "name", name };
|
|
37
|
+
}
|
|
38
|
+
function isEmptyMigration(result) {
|
|
39
|
+
return (result.diff.newTables.length === 0 &&
|
|
40
|
+
result.diff.changedTables.length === 0 &&
|
|
41
|
+
result.diff.droppedTables.length === 0 &&
|
|
42
|
+
result.retiredDrops.length === 0);
|
|
43
|
+
}
|
|
44
|
+
function writeMigrationFiles(migrationsDir, snapshotPath, result) {
|
|
45
|
+
if (!existsSync(migrationsDir))
|
|
46
|
+
mkdirSync(migrationsDir, { recursive: true });
|
|
47
|
+
writeFileSync(join(migrationsDir, result.filename), result.sqlContent);
|
|
48
|
+
writeSnapshotJson(snapshotPath, result.snapshot);
|
|
49
|
+
}
|
|
50
|
+
function reportGeneratedMigration(out, result, rebuildTables) {
|
|
51
|
+
out.log("");
|
|
52
|
+
out.log(` ✓ ${result.filename}`);
|
|
53
|
+
out.log(` new tables: ${result.diff.newTables.length}, changed: ${result.diff.changedTables.length}, dropped: ${result.diff.droppedTables.length}, retired framework tables dropped: ${result.retiredDrops.length}`);
|
|
54
|
+
if (rebuildTables.length > 0) {
|
|
55
|
+
out.log(` rebuild-marker: ${result.filename.replace(/\.sql$/, ".rebuild.json")} (${rebuildTables.length} table(s))`);
|
|
56
|
+
}
|
|
57
|
+
out.log("");
|
|
58
|
+
out.log(" Review, hand-edit if needed, then git add + commit. Apply via: schema apply");
|
|
59
|
+
out.log("");
|
|
60
|
+
}
|
|
61
|
+
export async function runGenerateCommand(ctx) {
|
|
62
|
+
const { out, migrationsDir } = ctx;
|
|
63
|
+
const parsed = parseGenerateName(ctx);
|
|
64
|
+
if (parsed.kind === "exit")
|
|
65
|
+
return parsed.code;
|
|
66
|
+
const schemaMod = await loadSchemaModOrReport(ctx);
|
|
67
|
+
if (schemaMod === undefined)
|
|
68
|
+
return 1;
|
|
69
|
+
const snapshotPath = join(migrationsDir, SNAPSHOT_FILENAME);
|
|
70
|
+
const prevSnapshot = loadPreviousSnapshot(snapshotPath);
|
|
71
|
+
const result = generateMigration({
|
|
72
|
+
metas: schemaMod.entityMetas,
|
|
73
|
+
prevSnapshot,
|
|
74
|
+
name: parsed.name,
|
|
75
|
+
sequenceNumber: nextSequenceNumber(migrationsDir),
|
|
76
|
+
priorMigrationsSql: readMigrationSqlTexts(migrationsDir),
|
|
77
|
+
});
|
|
78
|
+
if (isEmptyMigration(result)) {
|
|
79
|
+
out.log(" No schema changes detected — no new migration file written.");
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
writeMigrationFiles(migrationsDir, snapshotPath, result);
|
|
83
|
+
// Rebuild markers are written only for incremental migrations — the initial
|
|
84
|
+
// migration (prevSnapshot===null) only creates tables, so there are no historical
|
|
85
|
+
// events to replay.
|
|
86
|
+
const rebuildTables = prevSnapshot === null ? [] : rebuildTablesFromDiff(result.diff);
|
|
87
|
+
writeRebuildMarker(migrationsDir, result.filename, rebuildTables);
|
|
88
|
+
reportGeneratedMigration(out, result, rebuildTables);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type renderTablesDdl } from "../db/index.ts";
|
|
2
|
+
import type { FeatureDefinition } from "../engine/types/feature.ts";
|
|
3
|
+
export type SchemaCliOut = {
|
|
4
|
+
readonly log: (line: string) => void;
|
|
5
|
+
readonly err: (line: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export type RunSchemaCliOptions = {
|
|
8
|
+
readonly features?: undefined;
|
|
9
|
+
readonly kmsSlots?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
/** Composed app features. When given, `apply` rebuilds the projections whose
|
|
12
|
+
* tables a freshly applied migration changed (via its `.rebuild.json`
|
|
13
|
+
* marker). Omitted (dev `kumiko schema`) → no rebuild, migrations only. */
|
|
14
|
+
readonly features: readonly FeatureDefinition[];
|
|
15
|
+
/** Schema-declared Key Manager slots, `kmsSlotsOf(composedEnv.schema)`, or
|
|
16
|
+
* `[]` when the app wires none. This CLI parses no env schema, so the
|
|
17
|
+
* rebuild's KMS wiring cannot derive them itself. */
|
|
18
|
+
readonly kmsSlots: readonly string[];
|
|
19
|
+
};
|
|
20
|
+
export type SchemaCliContext = {
|
|
21
|
+
readonly argv: readonly string[];
|
|
22
|
+
readonly out: SchemaCliOut;
|
|
23
|
+
readonly options: RunSchemaCliOptions;
|
|
24
|
+
readonly schemaFile: string;
|
|
25
|
+
readonly migrationsDir: string;
|
|
26
|
+
};
|
|
27
|
+
export type SchemaSubcommandHandler = (ctx: SchemaCliContext) => Promise<number>;
|
|
28
|
+
export declare const SNAPSHOT_FILENAME = ".snapshot.json";
|
|
29
|
+
export type SchemaMod = {
|
|
30
|
+
readonly entityMetas: Parameters<typeof renderTablesDdl>[0];
|
|
31
|
+
readonly features?: unknown;
|
|
32
|
+
readonly validateBootOptions?: unknown;
|
|
33
|
+
};
|
|
34
|
+
export declare function describeThrown(e: unknown): string;
|
|
35
|
+
export declare function nextSequenceNumber(migrationsDir: string): number;
|
|
36
|
+
export declare function loadPreviousSnapshot(snapshotPath: string): import("../db/migrate-generator.ts").Snapshot | null;
|
|
37
|
+
export declare function loadSchemaModOrReport(ctx: SchemaCliContext): Promise<SchemaMod | undefined>;
|
|
38
|
+
export declare function readDatabaseUrlOrReport(out: SchemaCliOut): string | undefined;
|
|
39
|
+
export declare function migrationsDirExistsOrReport(ctx: SchemaCliContext): boolean;
|