@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
|
@@ -11,42 +11,80 @@
|
|
|
11
11
|
// gone it also drops every action, redirect, nav and workspace member that
|
|
12
12
|
// pointed at them. The one reference it does not resolve is a nav `parent`
|
|
13
13
|
// pointing at a missing nav; that is pruned here.
|
|
14
|
+
import { createFallbackLogger, scrubLogText } from "../logging/index.js";
|
|
14
15
|
import { resolveScreenTargetQn } from "./boot-validator/screens.js";
|
|
16
|
+
import { workspaceOwnerOf } from "./build-app-schema.js";
|
|
15
17
|
import { isToggleableFeature } from "./effective-features.js";
|
|
18
|
+
import { isPureGroupingNav } from "./project-app-schema-for-roles.js";
|
|
16
19
|
import { isValidQn, qualifyEntityName } from "./qualified-name.js";
|
|
20
|
+
const schemaLogger = createFallbackLogger("app-schema");
|
|
17
21
|
// Sorted, so the result doubles as a stable cache key. The trial gate is
|
|
18
22
|
// consulted like the dispatcher's feature gate does, but only for toggleable
|
|
19
23
|
// features: a trial can only unlock tier (toggleable) features, so a
|
|
20
|
-
// cascade-disabled always-on feature needs no gate call.
|
|
24
|
+
// cascade-disabled always-on feature needs no gate call. A failing gate counts
|
|
25
|
+
// as "not unlocked" (the handlers answer 403 then anyway), so a gate outage
|
|
26
|
+
// hides the add-on instead of taking the whole schema endpoint down.
|
|
21
27
|
export async function resolveDisabledFeatureNames(registry, effectiveFeatures, tenantId) {
|
|
22
28
|
const enabled = effectiveFeatures(tenantId);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const trialGate = effectiveFeatures.trialGate;
|
|
30
|
+
const disabled = [...registry.features].filter(([featureName]) => !enabled.has(featureName));
|
|
31
|
+
const unlockedByTrial = await Promise.all(disabled.map(async ([featureName, feature]) => {
|
|
32
|
+
if (trialGate === undefined || !isToggleableFeature(feature))
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
return await trialGate(tenantId, featureName);
|
|
30
36
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
catch (error) {
|
|
38
|
+
schemaLogger.warn("trial gate failed, treating feature as not unlocked", {
|
|
39
|
+
featureName,
|
|
40
|
+
error: scrubLogText(error instanceof Error ? error.message : String(error)),
|
|
41
|
+
});
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
return disabled
|
|
46
|
+
.filter((_, index) => !unlockedByTrial[index])
|
|
47
|
+
.map(([featureName]) => featureName)
|
|
48
|
+
.sort();
|
|
34
49
|
}
|
|
35
50
|
function navParentQn(featureName, parent) {
|
|
36
51
|
return isValidQn(parent) ? parent : qualifyEntityName(featureName, "nav", parent);
|
|
37
52
|
}
|
|
38
|
-
|
|
53
|
+
function navQnsByParent(features) {
|
|
54
|
+
const childrenByParent = new Map();
|
|
55
|
+
for (const feature of features) {
|
|
56
|
+
for (const nav of feature.navs ?? []) {
|
|
57
|
+
if (nav.parent === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
const parentQn = navParentQn(feature.featureName, nav.parent);
|
|
60
|
+
const children = childrenByParent.get(parentQn) ?? [];
|
|
61
|
+
children.push(qualifyEntityName(feature.featureName, "nav", nav.id));
|
|
62
|
+
childrenByParent.set(parentQn, children);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return childrenByParent;
|
|
66
|
+
}
|
|
67
|
+
// Navs of enabled features whose parent chain ends in a removed nav, plus pure grouping
|
|
68
|
+
// navs whose children are all removed (an empty section the role projection cannot drop,
|
|
69
|
+
// because it only drops groups that still have children).
|
|
39
70
|
function collectOrphanedNavQns(features, removedNavQns) {
|
|
40
71
|
const orphaned = new Set();
|
|
72
|
+
const childrenByParent = navQnsByParent(features);
|
|
41
73
|
let changed = true;
|
|
42
74
|
while (changed) {
|
|
43
75
|
changed = false;
|
|
44
76
|
for (const feature of features) {
|
|
45
77
|
for (const nav of feature.navs ?? []) {
|
|
46
78
|
const qn = qualifyEntityName(feature.featureName, "nav", nav.id);
|
|
47
|
-
if (removedNavQns.has(qn)
|
|
79
|
+
if (removedNavQns.has(qn))
|
|
48
80
|
continue;
|
|
49
|
-
|
|
81
|
+
const parentRemoved = nav.parent !== undefined &&
|
|
82
|
+
removedNavQns.has(navParentQn(feature.featureName, nav.parent));
|
|
83
|
+
const children = childrenByParent.get(qn) ?? [];
|
|
84
|
+
const emptiedGroup = isPureGroupingNav(nav) &&
|
|
85
|
+
children.length > 0 &&
|
|
86
|
+
children.every((childQn) => removedNavQns.has(childQn));
|
|
87
|
+
if (parentRemoved || emptiedGroup) {
|
|
50
88
|
removedNavQns.add(qn);
|
|
51
89
|
orphaned.add(qn);
|
|
52
90
|
changed = true;
|
|
@@ -104,10 +142,13 @@ export function stripDisabledFeaturesFromAppSchema(schema, registry, disabledFea
|
|
|
104
142
|
const { navs: _navs, ...rest } = feature;
|
|
105
143
|
return navs.length > 0 ? { ...rest, navs } : rest;
|
|
106
144
|
});
|
|
107
|
-
// Workspace
|
|
108
|
-
//
|
|
145
|
+
// Workspace ids are unique per feature only, so the owner decides. A definition the build did
|
|
146
|
+
// not register (schema assembled elsewhere) falls back to the short id.
|
|
109
147
|
const disabledWorkspaceIds = new Set(disabledFeatureNames.flatMap((featureName) => Object.keys(registry.features.get(featureName)?.workspaces ?? {})));
|
|
110
|
-
const workspaces = schema.workspaces?.filter((ws) =>
|
|
148
|
+
const workspaces = schema.workspaces?.filter((ws) => {
|
|
149
|
+
const owner = workspaceOwnerOf(ws.definition);
|
|
150
|
+
return owner === undefined ? !disabledWorkspaceIds.has(ws.definition.id) : !disabled.has(owner);
|
|
151
|
+
});
|
|
111
152
|
const { workspaces: _original, ...restSchema } = schema;
|
|
112
153
|
return {
|
|
113
154
|
...restSchema,
|
|
@@ -4,10 +4,11 @@ export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefin
|
|
|
4
4
|
export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
|
|
5
5
|
export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
|
|
6
6
|
export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
|
|
7
|
-
export type { BootCheckContext, BootCheckFn, BootOptionName,
|
|
7
|
+
export type { BootCheckContext, BootCheckFn, BootOptionName, FeatureDefinition, FeatureMetricDef, FeatureMetricType, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
|
|
8
|
+
export type { EntityObjectDefinition, FeatureRegistrar, HookDefinition, } from "@cosmicdrift/kumiko-types/feature-registrar";
|
|
8
9
|
export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
|
|
9
10
|
export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityFieldDefinition, EntityIndexDef, EntityLongTextFieldDef, EntityTextFieldDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, ResolvedPiiStance, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
|
|
10
|
-
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent,
|
|
11
|
+
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
|
|
11
12
|
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
12
13
|
export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
|
|
13
14
|
export type { HttpRouteDefinition, HttpRouteHandler, HttpRouteHandlerDeps, HttpRouteMethod, } from "@cosmicdrift/kumiko-types/http-route";
|
|
@@ -17,7 +18,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
|
|
|
17
18
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
19
|
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
19
20
|
export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
|
|
20
|
-
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
|
+
export type { ActionFormScreenDefinition, BundledListsOptions, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, ExpandableRowGrouping, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, ListTableOptions, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
22
|
export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
22
23
|
export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
|
|
23
24
|
export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type * from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
-
export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
1
|
+
export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
26
26
|
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
27
|
+
import { resolveTableName } from "../db/entity-table-meta.js";
|
|
27
28
|
import { schemaTableName } from "../db/table-introspection.js";
|
|
28
29
|
import { getStep } from "./define-step.js";
|
|
29
30
|
import { buildPipelineSteps } from "./pipeline.js";
|
|
@@ -79,7 +80,7 @@ export function validateProjectionAllowlist(features) {
|
|
|
79
80
|
const aggregateTables = new Map();
|
|
80
81
|
for (const f of features) {
|
|
81
82
|
for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
|
|
82
|
-
const tableName = entity
|
|
83
|
+
const tableName = resolveTableName(entityName, entity, undefined);
|
|
83
84
|
const existing = aggregateTables.get(tableName);
|
|
84
85
|
if (existing && existing !== f.name) {
|
|
85
86
|
throw new Error(`Aggregate-table "${tableName}" is registered by both feature "${existing}" and feature "${f.name}" via r.entity. ` +
|
|
@@ -10,7 +10,7 @@ import type { Lifecycle } from "../lifecycle/index.ts";
|
|
|
10
10
|
import type { ObservabilityProvider } from "../observability/index.ts";
|
|
11
11
|
import type { Dispatcher, DispatcherOptions } from "../pipeline/dispatcher.ts";
|
|
12
12
|
import type { EventDedup, EventDispatcher } from "../pipeline/index.ts";
|
|
13
|
-
import type { SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
|
|
13
|
+
import type { LifecycleHooks, SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
|
|
14
14
|
export type BaseEntrypointOptions = {
|
|
15
15
|
readonly registry: Registry;
|
|
16
16
|
readonly context: AppContext;
|
|
@@ -21,6 +21,7 @@ export type BaseEntrypointOptions = {
|
|
|
21
21
|
readonly systemHooks?: SystemHooks;
|
|
22
22
|
readonly eventDedup?: EventDedup;
|
|
23
23
|
readonly lifecycle?: Lifecycle;
|
|
24
|
+
readonly instanceId?: ServerOptions["instanceId"];
|
|
24
25
|
};
|
|
25
26
|
type JobsBlock = {
|
|
26
27
|
readonly redisUrl: string;
|
|
@@ -49,6 +50,7 @@ export type ApiEntrypointOptions = BaseEntrypointOptions & {
|
|
|
49
50
|
readonly runLocal?: boolean;
|
|
50
51
|
};
|
|
51
52
|
readonly trustedProxyHops?: ServerOptions["trustedProxyHops"];
|
|
53
|
+
readonly errorDocs?: ServerOptions["errorDocs"];
|
|
52
54
|
};
|
|
53
55
|
export type WorkerEntrypointOptions = BaseEntrypointOptions & JobsBlock & {
|
|
54
56
|
readonly eventDispatcher?: ServerOptions["eventDispatcher"];
|
|
@@ -61,6 +63,8 @@ export type ApiEntrypoint = {
|
|
|
61
63
|
readonly lifecycle: Lifecycle;
|
|
62
64
|
readonly observability: ObservabilityProvider;
|
|
63
65
|
readonly dispatcher: Dispatcher;
|
|
66
|
+
readonly context: AppContext;
|
|
67
|
+
readonly lifecycleHooks: LifecycleHooks;
|
|
64
68
|
readonly eventDispatcher?: EventDispatcher;
|
|
65
69
|
readonly mode: "api";
|
|
66
70
|
start(): Promise<void>;
|
|
@@ -71,6 +75,7 @@ export type WorkerEntrypoint = {
|
|
|
71
75
|
readonly eventDispatcher: EventDispatcher;
|
|
72
76
|
readonly jobRunner: JobRunner;
|
|
73
77
|
readonly observability: ObservabilityProvider;
|
|
78
|
+
readonly context: AppContext;
|
|
74
79
|
readonly dispatcher: Dispatcher;
|
|
75
80
|
readonly mode: "worker";
|
|
76
81
|
start(): Promise<void>;
|
package/dist/entrypoint/index.js
CHANGED
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
// connected to any API instance via Redis Pub/Sub, no per-instance
|
|
41
41
|
// cursor needed. Without REDIS_URL, the in-memory broker still only
|
|
42
42
|
// reaches clients on the same process.
|
|
43
|
-
import { buildServer
|
|
43
|
+
import { buildServer } from "../api/server.js";
|
|
44
|
+
import { withFileProviderResolver } from "../api/server-wiring.js";
|
|
44
45
|
import { createJobRunner } from "../jobs/job-runner.js";
|
|
45
46
|
import { createLifecycle } from "../lifecycle/index.js";
|
|
46
47
|
import { createNoopProvider } from "../observability/index.js";
|
|
@@ -119,6 +120,8 @@ function buildApiServer(opts, lifecycle, dispatcherOverride, jobRunner, processL
|
|
|
119
120
|
jwtIssuer: opts.jwtIssuer,
|
|
120
121
|
auth: opts.auth,
|
|
121
122
|
trustedProxyHops: opts.trustedProxyHops,
|
|
123
|
+
errorDocs: opts.errorDocs,
|
|
124
|
+
instanceId: opts.instanceId,
|
|
122
125
|
anonymousAccess: opts.anonymousAccess,
|
|
123
126
|
sseBroker: opts.sseBroker,
|
|
124
127
|
rateLimit: opts.rateLimit,
|
|
@@ -145,6 +148,7 @@ function buildWorkerServer(opts, lifecycle, jobRunner) {
|
|
|
145
148
|
jwtSecret: opts.jwtSecret,
|
|
146
149
|
lifecycle,
|
|
147
150
|
processLane: "worker",
|
|
151
|
+
instanceId: opts.instanceId,
|
|
148
152
|
observability: opts.observability,
|
|
149
153
|
dispatcherOptions,
|
|
150
154
|
systemHooks: opts.systemHooks,
|
|
@@ -240,6 +244,8 @@ export function createApiEntrypoint(options) {
|
|
|
240
244
|
lifecycle,
|
|
241
245
|
observability: server.observability,
|
|
242
246
|
dispatcher: server.dispatcher,
|
|
247
|
+
context: server.context,
|
|
248
|
+
lifecycleHooks: server.lifecycleHooks,
|
|
243
249
|
...(server.eventDispatcher && { eventDispatcher: server.eventDispatcher }),
|
|
244
250
|
mode: "api",
|
|
245
251
|
async start() {
|
|
@@ -274,6 +280,7 @@ export function createWorkerEntrypoint(options) {
|
|
|
274
280
|
jobRunner,
|
|
275
281
|
observability: server.observability,
|
|
276
282
|
dispatcher: server.dispatcher,
|
|
283
|
+
context: server.context,
|
|
277
284
|
mode: "worker",
|
|
278
285
|
async start() {
|
|
279
286
|
await eventDispatcher.start();
|
|
@@ -319,6 +326,8 @@ export function createAllInOneEntrypoint(options) {
|
|
|
319
326
|
jobRunner: workerJobRunner,
|
|
320
327
|
observability: server.observability,
|
|
321
328
|
dispatcher: server.dispatcher,
|
|
329
|
+
context: server.context,
|
|
330
|
+
lifecycleHooks: server.lifecycleHooks,
|
|
322
331
|
mode: "all-in-one",
|
|
323
332
|
async start() {
|
|
324
333
|
await eventDispatcher.start();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { KumikoError } from "./kumiko-error.ts";
|
|
2
|
-
/** Links an app's own error reasons to its own docs;
|
|
2
|
+
/** Links an app's own error reasons to its own docs; uncovered reasons fall back to the framework page of the error code. */
|
|
3
3
|
export type ErrorDocsConfig = {
|
|
4
4
|
readonly baseUrl: string;
|
|
5
5
|
readonly reasons?: readonly string[] | "all";
|
package/dist/errors/docs-url.js
CHANGED
|
@@ -5,6 +5,26 @@ const frameworkReasons = new Set([
|
|
|
5
5
|
...Object.values(FrameworkReasons),
|
|
6
6
|
...Object.values(AgentReasons),
|
|
7
7
|
]);
|
|
8
|
+
// Codes of the built-in error classes: only these have a framework docs page.
|
|
9
|
+
const frameworkErrorCodes = new Set([
|
|
10
|
+
"validation_error",
|
|
11
|
+
"feature_disabled",
|
|
12
|
+
"access_denied",
|
|
13
|
+
"unauthenticated",
|
|
14
|
+
"not_found",
|
|
15
|
+
"conflict",
|
|
16
|
+
"version_conflict",
|
|
17
|
+
"precondition_failed",
|
|
18
|
+
"record_locked",
|
|
19
|
+
"idempotent_replay",
|
|
20
|
+
"unique_violation",
|
|
21
|
+
"unprocessable",
|
|
22
|
+
"unconfigured",
|
|
23
|
+
"internal_error",
|
|
24
|
+
"member_read_timeout",
|
|
25
|
+
"rate_limited",
|
|
26
|
+
"rate_limit_unavailable",
|
|
27
|
+
]);
|
|
8
28
|
function reasonOf(err) {
|
|
9
29
|
if (!err.details || typeof err.details !== "object")
|
|
10
30
|
return undefined;
|
|
@@ -22,7 +42,7 @@ function errorsUrl(baseUrl, slug) {
|
|
|
22
42
|
}
|
|
23
43
|
// A reason the framework documents (or no reason, or the code itself) links to the framework docs, overridable via
|
|
24
44
|
// KUMIKO_DOCS_URL. An app's own reason links to the app docs where `errorDocs` covers it; every
|
|
25
|
-
// other reason falls back to the framework page of the error code,
|
|
45
|
+
// other reason falls back to the framework page of the error code, unless an app subclass overrode the code.
|
|
26
46
|
export function resolveErrorDocsUrl(err, errorDocs) {
|
|
27
47
|
const reason = reasonOf(err);
|
|
28
48
|
const frameworkBaseUrl = process.env["KUMIKO_DOCS_URL"] ?? DEFAULT_DOCS_BASE_URL;
|
|
@@ -32,5 +52,5 @@ export function resolveErrorDocsUrl(err, errorDocs) {
|
|
|
32
52
|
if (errorDocs && isCoveredByConfig(reason, errorDocs)) {
|
|
33
53
|
return errorsUrl(errorDocs.baseUrl, reason);
|
|
34
54
|
}
|
|
35
|
-
return errorsUrl(frameworkBaseUrl, err.code);
|
|
55
|
+
return frameworkErrorCodes.has(err.code) ? errorsUrl(frameworkBaseUrl, err.code) : undefined;
|
|
36
56
|
}
|
package/dist/es-ops/context.js
CHANGED
|
@@ -43,7 +43,7 @@ export function createSeedMigrationContext(args) {
|
|
|
43
43
|
if (!result.isSuccess) {
|
|
44
44
|
const code = result.error?.code ?? "unknown";
|
|
45
45
|
const message = result.error?.message ?? "(no message)";
|
|
46
|
-
throw new Error(`
|
|
46
|
+
throw new Error(`systemWriteAs("${handlerQualifiedName}") failed: ${code} — ${message}`);
|
|
47
47
|
}
|
|
48
48
|
return result;
|
|
49
49
|
},
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// path `@cosmicdrift/kumiko-framework/event-store/admin-api`. The guard script
|
|
7
7
|
// `scripts/guard-admin-api.ts` blocks calls from application code. Allowlist: samples/*/migration/,
|
|
8
8
|
// scripts/migrations/, this definition, and the guard script itself.
|
|
9
|
+
import { instantToTimestamptzParam } from "../db/driver-value-preparation.js";
|
|
9
10
|
import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
|
|
10
11
|
import { claimXactId, runAtomicEventWrite } from "../db/queries/event-store.js";
|
|
11
12
|
import { eventPredecessorExists, findExistingEventVersion, insertRawEventBatch, insertRawFirstEvent, insertRawSubsequentEvent, } from "../db/queries/event-store-admin.js";
|
|
12
|
-
import { instantToTimestamptzParam } from "../db/query.js";
|
|
13
13
|
import { IdempotentAppendConflictError, VersionConflictError } from "./errors.js";
|
|
14
14
|
function mapEventUniqueViolation(e, event) {
|
|
15
15
|
if (constraintOf(e) === "events_idempotency_uq" && event.metadata.idempotencyKey) {
|
|
@@ -19,6 +19,7 @@ export declare function append(db: DbRunner, event: EventToAppend): Promise<Stor
|
|
|
19
19
|
export declare function loadAggregate(db: DbRunner, aggregateId: string, tenantId: TenantId, options?: {
|
|
20
20
|
readonly includeArchived?: boolean;
|
|
21
21
|
readonly aggregateType?: string;
|
|
22
|
+
readonly atVersion?: number;
|
|
22
23
|
}): Promise<readonly StoredEvent[]>;
|
|
23
24
|
export declare function loadAggregateForWriting(db: DbRunner, aggregateId: string, tenantId: TenantId, options: {
|
|
24
25
|
readonly aggregateType: string;
|
|
@@ -3,9 +3,10 @@ import { isPersonalDataGated, UNATTRIBUTED_ORIGIN, } from "@cosmicdrift/kumiko-t
|
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
4
|
import { requestContext } from "../api/request-context.js";
|
|
5
5
|
import { encryptEventPayloadPii } from "../crypto/event-pii.js";
|
|
6
|
+
import { instantToTimestamptzParam } from "../db/driver-value-preparation.js";
|
|
6
7
|
import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
|
|
7
8
|
import { claimXactId, insertFirstEventRow, insertSubsequentEventRow, notifyPgChannel, runAtomicEventWrite, selectAggregateMaxVersion, selectEventsHighWaterMark, selectStreamMaxVersion, selectStreamMaxVersions, } from "../db/queries/event-store.js";
|
|
8
|
-
import {
|
|
9
|
+
import { selectMany } from "../db/query.js";
|
|
9
10
|
import { isStreamArchived } from "./archive.js";
|
|
10
11
|
import { IdempotentAppendConflictError, SeedTimeOrderError, VersionConflictError, } from "./errors.js";
|
|
11
12
|
import { eventsTable } from "./events-schema.js";
|
|
@@ -14,8 +15,11 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
|
|
|
14
15
|
// Append one event atomically. Two guarantees combined:
|
|
15
16
|
//
|
|
16
17
|
// 1. UNIQUE (tenant_id, aggregate_id, version) serializes concurrent writers
|
|
17
|
-
// within a tenant
|
|
18
|
-
//
|
|
18
|
+
// within a tenant. A second writer racing the same expectedVersion hits
|
|
19
|
+
// ON CONFLICT DO NOTHING (no row), then the stream version is read inside
|
|
20
|
+
// the still-intact transaction → VersionConflictError. That relies on
|
|
21
|
+
// READ COMMITTED; REPEATABLE READ/SERIALIZABLE raise 40001 instead. Only
|
|
22
|
+
// the idempotency index still raises 23505.
|
|
19
23
|
// Cross-tenant aggregate_id collisions are not conflicts by definition:
|
|
20
24
|
// two tenants owning a row with the same UUID is just isolation, not a
|
|
21
25
|
// race.
|
|
@@ -28,7 +32,7 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
|
|
|
28
32
|
// stays semantically obvious when read in isolation.
|
|
29
33
|
//
|
|
30
34
|
// Creates (expectedVersion === 0) skip the predecessor check — no predecessor
|
|
31
|
-
// exists yet. Colliding creates fall out via
|
|
35
|
+
// exists yet. Colliding creates fall out via the same index (tenant_id, aggregate_id, version=1).
|
|
32
36
|
// Channel name used by append() → NOTIFY and the event-dispatcher → LISTEN
|
|
33
37
|
// (Sprint E.4). The event-dispatcher subscribes to this channel on start and
|
|
34
38
|
// fires a runOnce immediately on each commit, so delivery latency is bounded
|
|
@@ -66,6 +70,8 @@ export async function append(db, event) {
|
|
|
66
70
|
}
|
|
67
71
|
catch (e) {
|
|
68
72
|
if (isUniqueViolation(e)) {
|
|
73
|
+
// The version index normally never throws (ON CONFLICT DO NOTHING); it
|
|
74
|
+
// only lands here for an unknown or renamed constraint.
|
|
69
75
|
// Two unique constraints on this table: events_aggregate_version_uq
|
|
70
76
|
// (tenant_id, aggregate_id, version) — a concurrent writer won the
|
|
71
77
|
// race to the next version — and events_idempotency_uq (tenant_id,
|
|
@@ -207,9 +213,12 @@ export async function loadAggregate(db, aggregateId, tenantId, options) {
|
|
|
207
213
|
if (archived)
|
|
208
214
|
return [];
|
|
209
215
|
}
|
|
210
|
-
const rows = await selectMany(db, eventsTable,
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
const rows = await selectMany(db, eventsTable, {
|
|
217
|
+
aggregateId,
|
|
218
|
+
tenantId,
|
|
219
|
+
...(options?.aggregateType !== undefined && { aggregateType: options.aggregateType }),
|
|
220
|
+
...(options?.atVersion !== undefined && { version: options.atVersion }),
|
|
221
|
+
}, { orderBy: { col: "version", direction: "asc" } });
|
|
213
222
|
return rows.map(toStoredEvent);
|
|
214
223
|
}
|
|
215
224
|
// Events and the stream's latest version from ONE read, so both come from the same
|
|
@@ -305,7 +314,7 @@ export async function loadEventsAfterVersion(db, aggregateId, tenantId, afterVer
|
|
|
305
314
|
// abort iteration. Checked both at batch boundaries (before the next
|
|
306
315
|
// fetch) AND between yields (so abort lands within one event regardless
|
|
307
316
|
// of batch size). Throws AbortError on the first check after abort;
|
|
308
|
-
// in-flight queries are not actively cancelled (
|
|
317
|
+
// in-flight queries are not actively cancelled (connection
|
|
309
318
|
// cancel is a separate, riskier concern handled per-query at the TenantDb
|
|
310
319
|
// boundary).
|
|
311
320
|
export async function* streamAllEventsByType(db, aggregateType, batchSize = 1000, signal) {
|
|
@@ -32,7 +32,7 @@ export async function appendProvenanceEvent(db, event) {
|
|
|
32
32
|
message: `appendProvenanceEvent: event tenant "${event.tenantId}" does not match the TenantDb's tenant "${db.tenantId}".`,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
//
|
|
35
|
+
// Postgres aborts the whole surrounding transaction on a statement
|
|
36
36
|
// error (25P02) even when the caller's catch swallows it — the savepoint
|
|
37
37
|
// confines that to this scope. Pool connections have no ambient tx to poison.
|
|
38
38
|
await runInSavepointIfSupported(runner, async (sp) => {
|
|
@@ -13,6 +13,9 @@ export function fileRefIdsOf(value) {
|
|
|
13
13
|
return value.filter((item) => typeof item === "string" && isUuid(item));
|
|
14
14
|
}
|
|
15
15
|
function isPendingUploadFor(row, entityName, fieldName, user) {
|
|
16
|
+
// Every anonymous visitor shares one user id, so insertedById cannot tell their uploads apart.
|
|
17
|
+
if (isAnonymousSessionUser(user))
|
|
18
|
+
return false;
|
|
16
19
|
return (row["entityId"] === null &&
|
|
17
20
|
row["entityType"] === entityName &&
|
|
18
21
|
row["fieldName"] === fieldName &&
|
|
@@ -79,9 +82,6 @@ export async function bindPendingFileRefs(args) {
|
|
|
79
82
|
}
|
|
80
83
|
async function loadPendingRow(lookup) {
|
|
81
84
|
const { fileRefId, entityName, fieldName, user, db } = lookup;
|
|
82
|
-
// Every anonymous visitor shares one user id, so insertedById cannot tell their uploads apart.
|
|
83
|
-
if (isAnonymousSessionUser(user))
|
|
84
|
-
return undefined;
|
|
85
85
|
if (!isUuid(fileRefId))
|
|
86
86
|
return undefined;
|
|
87
87
|
const row = (await loadFileRefRows(db, [fileRefId], user.tenantId)).get(fileRefId);
|
|
@@ -27,7 +27,10 @@ export async function createFileRefFromBytes(ctx, input) {
|
|
|
27
27
|
registry: ctx.registry,
|
|
28
28
|
defaultMaxUploadSize: readFilesRouteOptions(ctx.registry).maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE,
|
|
29
29
|
writeBytes: (key, data, mimeType) => files.writeUploadBytes(key, data, mimeType),
|
|
30
|
-
deleteBytes: (key) =>
|
|
30
|
+
deleteBytes: async (key) => {
|
|
31
|
+
await files.ref(key).delete();
|
|
32
|
+
await files.forgetUploadBytes(key);
|
|
33
|
+
},
|
|
31
34
|
}, {
|
|
32
35
|
fileName: input.fileName,
|
|
33
36
|
declaredMimeType: input.mimeType,
|
|
@@ -43,6 +46,9 @@ export async function createFileRefFromBytes(ctx, input) {
|
|
|
43
46
|
return fileRejectedFailure(outcome.error);
|
|
44
47
|
case "failed":
|
|
45
48
|
return outcome.failure;
|
|
49
|
+
case "target-locked":
|
|
50
|
+
// Only the HTTP route supplies the lock re-check, so this path cannot produce it.
|
|
51
|
+
return writeFailure(new InternalError({ message: "createFileRefFromBytes: unexpected target-locked outcome" }));
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
function fileRejectedFailure(error) {
|
|
@@ -50,6 +50,7 @@ export type StoreFileDeps = {
|
|
|
50
50
|
readonly defaultMaxUploadSize: string;
|
|
51
51
|
readonly writeBytes: (storageKey: string, data: Uint8Array, mimeType: string) => Promise<void>;
|
|
52
52
|
readonly deleteBytes: (storageKey: string) => Promise<void>;
|
|
53
|
+
readonly isAttachTargetLocked?: (rowDb: TenantDb) => Promise<boolean>;
|
|
53
54
|
};
|
|
54
55
|
export type CreatedFileRef = {
|
|
55
56
|
readonly id: string;
|
|
@@ -80,6 +81,8 @@ export type StoreFileOutcome = {
|
|
|
80
81
|
} | {
|
|
81
82
|
readonly kind: "rejected";
|
|
82
83
|
readonly error: string;
|
|
84
|
+
} | {
|
|
85
|
+
readonly kind: "target-locked";
|
|
83
86
|
} | {
|
|
84
87
|
readonly kind: "failed";
|
|
85
88
|
readonly failure: WriteFailure;
|
|
@@ -41,6 +41,8 @@ export function resolveAttachedField(registry, entityType, fieldName) {
|
|
|
41
41
|
return { kind: "unresolvable" };
|
|
42
42
|
return { kind: "resolved", fieldDef };
|
|
43
43
|
}
|
|
44
|
+
class AttachTargetLockedError extends Error {
|
|
45
|
+
}
|
|
44
46
|
function resolveUploadField(registry, input) {
|
|
45
47
|
const { entityType, entityId, fieldName, actionForm } = input;
|
|
46
48
|
if (actionForm === undefined)
|
|
@@ -196,14 +198,21 @@ export async function storeFileAsFileRef(deps, input) {
|
|
|
196
198
|
// skip: the create failure is the error worth reporting; the leftover bytes stay in storage.
|
|
197
199
|
}
|
|
198
200
|
};
|
|
201
|
+
const createRow = async (rowDb) => {
|
|
202
|
+
if (await deps.isAttachTargetLocked?.(rowDb))
|
|
203
|
+
throw new AttachTargetLockedError();
|
|
204
|
+
return createFileRefRow({ user: deps.user, tenantDb: rowDb }, upload.uploaded);
|
|
205
|
+
};
|
|
199
206
|
let row;
|
|
200
207
|
try {
|
|
201
208
|
row = deps.rowInOwnTransaction
|
|
202
|
-
? await runInOwnTransaction(deps.tenantDb,
|
|
203
|
-
: await
|
|
209
|
+
? await runInOwnTransaction(deps.tenantDb, createRow)
|
|
210
|
+
: await createRow(deps.tenantDb);
|
|
204
211
|
}
|
|
205
212
|
catch (e) {
|
|
206
213
|
await deleteUploadedBytes();
|
|
214
|
+
if (e instanceof AttachTargetLockedError)
|
|
215
|
+
return { kind: "target-locked" };
|
|
207
216
|
throw e;
|
|
208
217
|
}
|
|
209
218
|
if ("failure" in row) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// suffix before the file extension — `foo/bar.jpg` + `"medium-<16 hex>"` →
|
|
11
11
|
// `foo/bar.medium-<16 hex>.jpg`. The suffix must be a `variantSuffix(...)` so
|
|
12
12
|
// the erasure sweep recognizes it. Stable, reversible, no extra lookup tables.
|
|
13
|
-
import { isDerivativeSuffix } from "../derivatives/variant-key.js";
|
|
13
|
+
import { derivativeListPrefix, isDerivativeSuffix } from "../derivatives/variant-key.js";
|
|
14
14
|
import { InternalError } from "../errors/index.js";
|
|
15
15
|
import { assertSafeStorageKey } from "./types.js";
|
|
16
16
|
// `getProvider` is a lazily-resolved, memoized accessor — the provider is
|
|
@@ -127,6 +127,5 @@ export function deriveKey(key, suffix) {
|
|
|
127
127
|
// fileRef's original plus every already-rendered derivative, none of which have their own
|
|
128
128
|
// `file_refs` row.
|
|
129
129
|
export function storageKeyStemPrefix(key) {
|
|
130
|
-
|
|
131
|
-
return ext === "" ? `${key}.` : `${stem}.`;
|
|
130
|
+
return derivativeListPrefix(key);
|
|
132
131
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RateLimitResolver } from "@cosmicdrift/kumiko-types/rate-limit-types";
|
|
1
2
|
import type { WriteErrorInfo } from "@cosmicdrift/kumiko-types/write-error-info-types";
|
|
2
3
|
import { Hono } from "hono";
|
|
3
4
|
import type { DbConnection } from "../db/connection.ts";
|
|
@@ -30,6 +31,7 @@ export type FileAccessGuard = (args: {
|
|
|
30
31
|
}) => FileAccessDecision | Promise<FileAccessDecision>;
|
|
31
32
|
export type FileRoutesOptions = {
|
|
32
33
|
readonly db: DbConnection;
|
|
34
|
+
readonly metaBatchRateLimitResolver?: RateLimitResolver;
|
|
33
35
|
readonly resolveProvider: FileProviderResolver;
|
|
34
36
|
readonly registry?: Registry;
|
|
35
37
|
readonly maxUploadSize?: string;
|