@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/AGENTS.md
CHANGED
|
@@ -208,7 +208,12 @@ produces code that compiles and then fails in production.
|
|
|
208
208
|
one page (default 50, `limit` at most 200). Pass
|
|
209
209
|
`filter: { field, op, value }` (`eq`, `ne`, `lt`, `gt`, `lte`, `gte`, `in`)
|
|
210
210
|
so the database filters, and `totalCount: true` to get `total` over all
|
|
211
|
-
matching rows; `limit: 1, totalCount: true` is a count query.
|
|
211
|
+
matching rows; `limit: 1, totalCount: true` is a count query. Groups and
|
|
212
|
+
sums are the same: pass `aggregate: { groupBy?, measures: [{ field, kind }] }`
|
|
213
|
+
(`kind` is `sum`, `avg`, `count`, `min` or `max`) and the list answers with
|
|
214
|
+
`{ groups: [{ key, label?, count, measures }] }` over the same filters,
|
|
215
|
+
search and ownership, at most 200 groups (`truncated: true` beyond that).
|
|
216
|
+
Custom handlers call `executor.aggregate(...)` or `answerListAggregate`.
|
|
212
217
|
- Never change an event schema in place once it has been emitted. Version it
|
|
213
218
|
(see `agents/examples/event-sourcing.md`).
|
|
214
219
|
- Never read or write another tenant's data. `ctx.db` is tenant scoped on
|
package/LICENSE
CHANGED
|
@@ -1,57 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
controlling, or under common control with the Licensor ("Affiliates"), or
|
|
23
|
-
contractors acting on their behalf. The Licensor remains free to use the
|
|
24
|
-
Licensed Work for any purpose, including for the operation of kumiko.rocks.
|
|
25
|
-
|
|
26
|
-
Change Date: 2030-05-05
|
|
27
|
-
Change License: Apache License, Version 2.0
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Terms
|
|
31
|
-
|
|
32
|
-
The Licensor hereby grants you the right to copy, modify, create derivative works,
|
|
33
|
-
redistribute, and make non-production use of the Licensed Work. The Licensor may
|
|
34
|
-
make an Additional Use Grant, above, permitting limited production use.
|
|
35
|
-
|
|
36
|
-
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
37
|
-
available distribution of the Licensed Work under this License, whichever comes
|
|
38
|
-
first, this License will convert to the Change License.
|
|
39
|
-
|
|
40
|
-
This Business Source License governs use of the Licensed Work in all cases, except
|
|
41
|
-
as to any use that is explicitly granted in the Additional Use Grant above or
|
|
42
|
-
under the Change License after the Change Date.
|
|
43
|
-
|
|
44
|
-
If your use of the Licensed Work does not comply with the requirements of this
|
|
45
|
-
License, you must cease use of the Licensed Work immediately.
|
|
46
|
-
|
|
47
|
-
All copies of the Licensed Work, and all derivative works thereof, must include
|
|
48
|
-
this License.
|
|
49
|
-
|
|
50
|
-
This License does not grant you any right, title, or interest in any trademark,
|
|
51
|
-
logo, or branding of the Licensor, except as required to comply with this License.
|
|
52
|
-
|
|
53
|
-
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
|
|
54
|
-
“AS IS” BASIS. LICENSOR DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
|
55
|
-
WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
56
|
-
TITLE, AND NON-INFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DAMAGES
|
|
57
|
-
ARISING OUT OF OR RELATED TO THIS LICENSE OR THE USE OF THE LICENSED WORK.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marc Frost
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @cosmicdrift/kumiko-framework
|
|
2
2
|
|
|
3
|
-
[](../../LICENSE)
|
|
4
4
|
[](https://www.typescriptlang.org/)
|
|
5
5
|
|
|
6
6
|
Framework core for Kumiko — engine, pipeline, API, DB, event-store, and
|
|
@@ -131,4 +131,4 @@ sample under `samples/`; the roadmap lives in [docs/plans/uebersicht.md](../../d
|
|
|
131
131
|
|
|
132
132
|
## License
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
MIT, see [LICENSE](../../LICENSE).
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
|
|
61
61
|
const TENANT_ID = "00000000-0000-4000-8000-000000000001" as TenantId;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Each value is written by exactly one test; the tenant is fixed, so rows are told apart by value, not by reset.
|
|
64
64
|
const PRODUCT_NAME = "Espresso Beans (anonymous read)";
|
|
65
65
|
const GUEST_EMAIL = "guest-checkout@example.com";
|
|
66
66
|
|
|
@@ -41,6 +41,210 @@ bun run screenshot # → screenshots/landing.png + lightbox.png
|
|
|
41
41
|
|
|
42
42
|
## Source files
|
|
43
43
|
|
|
44
|
+
### e2e/header-layout.spec.ts
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// Regression for issue #2038: below the 1120px .container breakpoint, the
|
|
48
|
+
// header's `<div class="container nav">` lost its horizontal padding because
|
|
49
|
+
// `.nav`'s `padding` shorthand (same specificity, declared later) zeroed out
|
|
50
|
+
// the inline padding `.container` set. Verified against a real narrow
|
|
51
|
+
// viewport, not derived from source — matches how the bug was first found.
|
|
52
|
+
|
|
53
|
+
import { expect, boundaryGuardedTest as test } from "@cosmicdrift/kumiko-testing/e2e";
|
|
54
|
+
import { renderLanding, SAMPLE_PLANS } from "../src/feature";
|
|
55
|
+
|
|
56
|
+
test("header keeps the page's horizontal padding below 1120px (#2038)", async ({ page }) => {
|
|
57
|
+
await page.setViewportSize({ width: 390, height: 844 });
|
|
58
|
+
await page.setContent(renderLanding({ plans: SAMPLE_PLANS }), {
|
|
59
|
+
waitUntil: "domcontentloaded",
|
|
60
|
+
url: "http://localhost/",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const headerContainer = page.locator("header .container.nav");
|
|
64
|
+
const footerContainer = page.locator("footer .container");
|
|
65
|
+
|
|
66
|
+
const [headerPadding, footerPadding] = await Promise.all([
|
|
67
|
+
headerContainer.evaluate((el) => {
|
|
68
|
+
const s = getComputedStyle(el);
|
|
69
|
+
return { left: s.paddingLeft, right: s.paddingRight };
|
|
70
|
+
}),
|
|
71
|
+
footerContainer.evaluate((el) => {
|
|
72
|
+
const s = getComputedStyle(el);
|
|
73
|
+
return { left: s.paddingLeft, right: s.paddingRight };
|
|
74
|
+
}),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// The header is indented exactly like the rest of the page — not 0.
|
|
78
|
+
expect(headerPadding.left).not.toBe("0px");
|
|
79
|
+
expect(headerPadding).toEqual(footerPadding);
|
|
80
|
+
|
|
81
|
+
// The nav's rightmost action stays inside the viewport, not clipped at the edge.
|
|
82
|
+
const actionBox = await page.locator("header .nav-actions a").last().boundingBox();
|
|
83
|
+
expect(actionBox).not.toBeNull();
|
|
84
|
+
expect(actionBox!.x + actionBox!.width).toBeLessThanOrEqual(390);
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### e2e/screenshot.spec.ts
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// E2E screenshot renderer for the Apex landing example. Renders the sample page
|
|
92
|
+
// to HTML, paints it in a real browser and shoots a full-page PNG. The output is
|
|
93
|
+
// what the docs guide embeds — regenerate with `bun run screenshot`, never hand-
|
|
94
|
+
// captured, so it can't drift from the renderer.
|
|
95
|
+
//
|
|
96
|
+
// SCREENSHOT_DIR overrides the output dir (CI writes straight into the docs repo).
|
|
97
|
+
|
|
98
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
99
|
+
import { resolve } from "node:path";
|
|
100
|
+
import { expect, boundaryGuardedTest as test } from "@cosmicdrift/kumiko-testing/e2e";
|
|
101
|
+
import type { Page } from "@playwright/test";
|
|
102
|
+
import { HERO_SCREENSHOT } from "../src/constants";
|
|
103
|
+
import { renderLanding, SAMPLE_PLANS } from "../src/feature";
|
|
104
|
+
|
|
105
|
+
const OUT_DIR = process.env["SCREENSHOT_DIR"] ?? resolve(import.meta.dirname, "../screenshots");
|
|
106
|
+
const HERO_APP_PNG = resolve(OUT_DIR, "hero-app.png");
|
|
107
|
+
const LANDING_PNG = resolve(OUT_DIR, "landing.png");
|
|
108
|
+
const LIGHTBOX_PNG = resolve(OUT_DIR, "lightbox.png");
|
|
109
|
+
|
|
110
|
+
/** Minimal in-app board mock — hero frame shows product UI, not the full landing PNG. */
|
|
111
|
+
const HERO_APP_HTML = `<!doctype html>
|
|
112
|
+
<html lang="en">
|
|
113
|
+
<head>
|
|
114
|
+
<meta charset="utf-8" />
|
|
115
|
+
<style>
|
|
116
|
+
body { margin: 0; background: #f4f4f5; font-family: system-ui, sans-serif; }
|
|
117
|
+
#board {
|
|
118
|
+
width: 720px; padding: 16px; background: #fff; border-radius: 12px;
|
|
119
|
+
border: 1px solid #e4e4e7; box-sizing: border-box;
|
|
120
|
+
}
|
|
121
|
+
.top { display: flex; justify-content: space-between; margin-bottom: 16px; font-weight: 600; }
|
|
122
|
+
.cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
|
123
|
+
.col { background: #fafafa; border-radius: 8px; padding: 10px; min-height: 140px; }
|
|
124
|
+
.label { font-size: 11px; color: #71717a; margin-bottom: 8px; }
|
|
125
|
+
.card {
|
|
126
|
+
background: #fff; border: 1px solid #e4e4e7; border-radius: 6px;
|
|
127
|
+
padding: 8px; margin-bottom: 8px; font-size: 13px;
|
|
128
|
+
}
|
|
129
|
+
.tag {
|
|
130
|
+
display: inline-block; font-size: 10px; padding: 2px 6px; border-radius: 4px;
|
|
131
|
+
background: #ede9fe; color: #5b21b6; margin-left: 4px;
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
134
|
+
</head>
|
|
135
|
+
<body>
|
|
136
|
+
<div id="board">
|
|
137
|
+
<div class="top"><span>Tasklane — Q3 roadmap</span><span style="color:#71717a;font-weight:400">3 projects</span></div>
|
|
138
|
+
<div class="cols">
|
|
139
|
+
<div class="col">
|
|
140
|
+
<div class="label">Backlog</div>
|
|
141
|
+
<div class="card">Auth hardening<span class="tag">P1</span></div>
|
|
142
|
+
<div class="card">Export CSV</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="col">
|
|
145
|
+
<div class="label">In progress</div>
|
|
146
|
+
<div class="card">Board filters</div>
|
|
147
|
+
<div class="card">Invite flow</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="col">
|
|
150
|
+
<div class="label">Done</div>
|
|
151
|
+
<div class="card">Dark mode</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</body>
|
|
156
|
+
</html>`;
|
|
157
|
+
|
|
158
|
+
function heroForMount(): typeof HERO_SCREENSHOT {
|
|
159
|
+
const b64 = readFileSync(HERO_APP_PNG).toString("base64");
|
|
160
|
+
return { ...HERO_SCREENSHOT, src: `data:image/png;base64,${b64}` };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function assertHeroImageLoaded(page: Page): Promise<void> {
|
|
164
|
+
const img = page.locator(".shot-frame img");
|
|
165
|
+
await expect(img).toBeVisible();
|
|
166
|
+
await expect
|
|
167
|
+
.poll(async () => img.evaluate((el) => (el as HTMLImageElement).naturalWidth))
|
|
168
|
+
.toBeGreaterThan(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** setContent does not reliably fetch routed /screenshots/* — inline data: URL instead. */
|
|
172
|
+
async function mountLanding(page: Page): Promise<void> {
|
|
173
|
+
if (!existsSync(HERO_APP_PNG)) {
|
|
174
|
+
throw new Error("hero-app.png missing — run hero-app test first (serial suite)");
|
|
175
|
+
}
|
|
176
|
+
await page.setContent(renderLanding({ plans: SAMPLE_PLANS, heroScreenshot: heroForMount() }), {
|
|
177
|
+
waitUntil: "networkidle",
|
|
178
|
+
url: "http://localhost/",
|
|
179
|
+
});
|
|
180
|
+
await assertHeroImageLoaded(page);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
test.describe.configure({ mode: "serial" });
|
|
184
|
+
|
|
185
|
+
test("hero-app — product screenshot asset", async ({ page }) => {
|
|
186
|
+
mkdirSync(OUT_DIR, { recursive: true });
|
|
187
|
+
await page.setContent(HERO_APP_HTML, { waitUntil: "networkidle" });
|
|
188
|
+
await page.locator("#board").screenshot({ path: HERO_APP_PNG });
|
|
189
|
+
expect(statSync(HERO_APP_PNG).size).toBeGreaterThan(5 * 1024);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("apex landing — full page", async ({ page }) => {
|
|
193
|
+
await mountLanding(page);
|
|
194
|
+
await page.screenshot({ path: LANDING_PNG, fullPage: true });
|
|
195
|
+
expect(statSync(LANDING_PNG).size).toBeGreaterThan(5 * 1024);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("apex lightbox — open (screenshot)", async ({ page }) => {
|
|
199
|
+
await mountLanding(page);
|
|
200
|
+
await page.locator(".shot-frame img").click();
|
|
201
|
+
await expect(page.locator("#apex-lightbox")).toBeVisible();
|
|
202
|
+
await expect
|
|
203
|
+
.poll(async () =>
|
|
204
|
+
page.locator("#apex-lightbox img").evaluate((el) => (el as HTMLImageElement).naturalWidth),
|
|
205
|
+
)
|
|
206
|
+
.toBeGreaterThan(0);
|
|
207
|
+
await page.screenshot({ path: LIGHTBOX_PNG });
|
|
208
|
+
expect(statSync(LIGHTBOX_PNG).size).toBeGreaterThan(5 * 1024);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("apex lightbox — closes on button", async ({ page }) => {
|
|
212
|
+
await mountLanding(page);
|
|
213
|
+
await page.locator(".shot-frame img").click();
|
|
214
|
+
await expect(page.locator("#apex-lightbox")).toBeVisible();
|
|
215
|
+
await page.locator(".apex-lightbox__close").click();
|
|
216
|
+
await expect(page.locator("#apex-lightbox")).not.toBeVisible();
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### playwright.config.ts
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
224
|
+
|
|
225
|
+
// No webServer: the Apex renderer is a pure function, so the runner feeds its
|
|
226
|
+
// HTML straight into the page via setContent — nothing to boot.
|
|
227
|
+
export default defineConfig({
|
|
228
|
+
testDir: "./e2e",
|
|
229
|
+
fullyParallel: false,
|
|
230
|
+
forbidOnly: !!process.env["CI"],
|
|
231
|
+
retries: 0,
|
|
232
|
+
workers: 1,
|
|
233
|
+
reporter: [["list"]],
|
|
234
|
+
timeout: 60_000,
|
|
235
|
+
use: {
|
|
236
|
+
viewport: { width: 1920, height: 1080 },
|
|
237
|
+
deviceScaleFactor: 2,
|
|
238
|
+
},
|
|
239
|
+
projects: [
|
|
240
|
+
{
|
|
241
|
+
name: "chromium",
|
|
242
|
+
use: { ...devices["Desktop Chrome"], viewport: { width: 1920, height: 1080 } },
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
44
248
|
### src/__tests__/landing.test.ts
|
|
45
249
|
|
|
46
250
|
```ts
|
|
@@ -85,6 +85,70 @@ before the worker picks up the job and writes the fulfillment back.
|
|
|
85
85
|
|
|
86
86
|
## Source files
|
|
87
87
|
|
|
88
|
+
### bin/api.ts
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// API process — `runSingleInstance: false` makes this process API-only:
|
|
92
|
+
// HTTP + event writes + BullMQ ENQUEUE, no worker-lane consumption, no
|
|
93
|
+
// multiStreamProjection application. A dedicated worker MUST run next to
|
|
94
|
+
// it (bin/worker.ts) or the read-side stays empty.
|
|
95
|
+
import { frameworkCoreEnvSchema } from "@cosmicdrift/kumiko-dev-server/env-schema";
|
|
96
|
+
import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
|
|
97
|
+
import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
98
|
+
import { createApiWorkerSplitFeature } from "../src/feature";
|
|
99
|
+
|
|
100
|
+
const features = [createApiWorkerSplitFeature()];
|
|
101
|
+
|
|
102
|
+
await runProdApp({
|
|
103
|
+
features,
|
|
104
|
+
envSchema: composeEnvSchema({ core: frameworkCoreEnvSchema, features }),
|
|
105
|
+
runSingleInstance: false,
|
|
106
|
+
// worker-lane queue prefix; must match bin/worker.ts.
|
|
107
|
+
jobs: { queueNamePrefix: "api-worker-split" },
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### bin/kumiko.ts
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
#!/usr/bin/env bun
|
|
115
|
+
// Schema CLI for the sample: creates the framework infra tables
|
|
116
|
+
// (idempotent) and applies kumiko/migrations. Requires DATABASE_URL.
|
|
117
|
+
import { runStandaloneSchemaCli } from "@cosmicdrift/kumiko-dev-server/schema-apply";
|
|
118
|
+
import { createApiWorkerSplitFeature } from "../src/feature";
|
|
119
|
+
|
|
120
|
+
await runStandaloneSchemaCli({ features: [createApiWorkerSplitFeature()] });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### bin/worker.ts
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// Worker process — consumes the worker-lane BullMQ queue and applies the
|
|
127
|
+
// multiStreamProjections the API skipped. No HTTP surface.
|
|
128
|
+
//
|
|
129
|
+
// wireComponents runs after the entrypoint is up and hands over
|
|
130
|
+
// `dispatchSystemWrite` — a dispatcher-scoped system write. The job's
|
|
131
|
+
// write-bridge is wired here (fw#1717): JobContext has no write/query, so
|
|
132
|
+
// background components persist through this dispatcher instead.
|
|
133
|
+
//
|
|
134
|
+
// ponytail: BullMQ may pop jobs between entrypoint.start() and this hook —
|
|
135
|
+
// those hits throw "no fulfill-write bridge wired" and rely on BullMQ retry
|
|
136
|
+
// until the bridge is set. Upgrade: defer job registration until after wire,
|
|
137
|
+
// or hold fulfillWrite behind a deferred Promise.
|
|
138
|
+
import { runWorkerApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
139
|
+
import { createApiWorkerSplitFeature, setOrderFulfillWrite } from "../src/feature";
|
|
140
|
+
|
|
141
|
+
await runWorkerApp({
|
|
142
|
+
features: [createApiWorkerSplitFeature()],
|
|
143
|
+
jobs: { queueNamePrefix: "api-worker-split" },
|
|
144
|
+
wireComponents: async ({ dispatchSystemWrite }) => {
|
|
145
|
+
setOrderFulfillWrite(({ handlerQn, payload, tenantId }) =>
|
|
146
|
+
dispatchSystemWrite({ handlerQn, payload, tenantId }),
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
88
152
|
### src/__tests__/feature.integration.test.ts
|
|
89
153
|
|
|
90
154
|
```ts
|
|
@@ -37,6 +37,20 @@ for the files around it.
|
|
|
37
37
|
|
|
38
38
|
## Source files
|
|
39
39
|
|
|
40
|
+
### kumiko.config.ts
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { defineAppConfig } from "@cosmicdrift/kumiko-framework/config";
|
|
44
|
+
import { auditExportFeature, notesFeature } from "./src/feature";
|
|
45
|
+
|
|
46
|
+
export default defineAppConfig({
|
|
47
|
+
features: [notesFeature],
|
|
48
|
+
prodFeatures: [auditExportFeature],
|
|
49
|
+
client: { entries: ["src/client.tsx"] },
|
|
50
|
+
ports: { dev: 4510, e2e: 4511, infra: { postgres: 15510 } },
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
40
54
|
### src/__tests__/app-config.test.ts
|
|
41
55
|
|
|
42
56
|
```ts
|
|
@@ -83,11 +83,16 @@ features: [..., createSessionsFeature(), mfaFeature, createAccountSecurityFeatur
|
|
|
83
83
|
5. **MFA state changes revoke every OTHER live session** (stolen-session defense) — never the session that made the change.
|
|
84
84
|
6. **Enforcement is opt-in.** Without a tenant enabling `auth-mfa:config:required`, users choose for themselves whether to enroll.
|
|
85
85
|
|
|
86
|
+
## Confirmation code input
|
|
87
|
+
|
|
88
|
+
`src/web/confirmation-code-step.tsx` shows the `Input kind="code"` renderer primitive outside the MFA screen, for example an e-mail or invite confirmation code. Pass `length` (here 6), `autoFocus` and `onComplete`. The input accepts digits only, so pasting "123 456" yields `123456`, and `onComplete` fires once when the value reaches `length`. `autoComplete` defaults to `one-time-code`, so mobile keyboards offer the code from the SMS or mail. After a wrong code the step clears the value and bumps a `key`, which remounts the input and returns focus to it.
|
|
89
|
+
|
|
86
90
|
## Tests
|
|
87
91
|
|
|
88
92
|
- **Baseline:** a user without MFA enrolled still gets a JWT straight from `/auth/login`.
|
|
89
93
|
- **End-to-end:** enroll via `enable-start`/`enable-confirm`, log in (gets a challenge, no JWT), complete via `/auth/mfa/verify` (gets a JWT) — copies the `buildServer({ auth: ... })` block from the sample comment verbatim.
|
|
90
94
|
- **Account security:** the composed screen boot-validates next to `auth-mfa` and `sessions` and shows a sidebar entry once `nav` is set.
|
|
95
|
+
- **Confirmation code step:** typing or pasting six digits calls `onConfirm` once; a rejected code shows the error, clears the field and refocuses it.
|
|
91
96
|
|
|
92
97
|
## Source files
|
|
93
98
|
|
|
@@ -388,3 +393,113 @@ export {
|
|
|
388
393
|
} from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
|
|
389
394
|
export { currentTotpCode } from "@cosmicdrift/kumiko-bundled-features/auth-mfa/testing";
|
|
390
395
|
```
|
|
396
|
+
|
|
397
|
+
### src/web/__tests__/confirmation-code-step.test.tsx
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
401
|
+
import { PrimitivesProvider } from "@cosmicdrift/kumiko-renderer";
|
|
402
|
+
import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer/web";
|
|
403
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
404
|
+
import { ConfirmationCodeStep } from "../confirmation-code-step.tsx";
|
|
405
|
+
|
|
406
|
+
function renderStep(onConfirm: (code: string) => Promise<boolean>): void {
|
|
407
|
+
render(
|
|
408
|
+
<PrimitivesProvider value={defaultPrimitives}>
|
|
409
|
+
<ConfirmationCodeStep onConfirm={onConfirm} />
|
|
410
|
+
</PrimitivesProvider>,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function type(value: string): void {
|
|
415
|
+
fireEvent.change(screen.getByTestId("confirmation-code"), { target: { value } });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
describe("ConfirmationCodeStep", () => {
|
|
419
|
+
test("focuses the input on mount", () => {
|
|
420
|
+
renderStep(async () => true);
|
|
421
|
+
expect(document.activeElement).toBe(screen.getByTestId("confirmation-code"));
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("does not confirm before six digits", () => {
|
|
425
|
+
const onConfirm = mock(async (_code: string) => true);
|
|
426
|
+
renderStep(onConfirm);
|
|
427
|
+
type("12345");
|
|
428
|
+
expect(onConfirm).not.toHaveBeenCalled();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("pasted '123 456' confirms once with 123456", async () => {
|
|
432
|
+
const onConfirm = mock(async (_code: string) => true);
|
|
433
|
+
renderStep(onConfirm);
|
|
434
|
+
type("123 456");
|
|
435
|
+
await waitFor(() => expect(onConfirm).toHaveBeenCalledTimes(1));
|
|
436
|
+
expect(onConfirm).toHaveBeenCalledWith("123456");
|
|
437
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("a rejected code shows the error, clears the field and refocuses", async () => {
|
|
441
|
+
const onConfirm = mock(async (_code: string) => false);
|
|
442
|
+
renderStep(onConfirm);
|
|
443
|
+
type("123456");
|
|
444
|
+
await waitFor(() => expect(screen.getByRole("alert")).toBeTruthy());
|
|
445
|
+
const input = screen.getByTestId("confirmation-code");
|
|
446
|
+
expect(input).toHaveProperty("value", "");
|
|
447
|
+
await waitFor(() => expect(document.activeElement).toBe(input));
|
|
448
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### src/web/confirmation-code-step.tsx
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
457
|
+
import { type ReactNode, useState } from "react";
|
|
458
|
+
|
|
459
|
+
const CONFIRMATION_CODE_LENGTH = 6;
|
|
460
|
+
|
|
461
|
+
export type ConfirmationCodeStepProps = {
|
|
462
|
+
/** Resolves true when the server accepted the code. */
|
|
463
|
+
readonly onConfirm: (code: string) => Promise<boolean>;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export function ConfirmationCodeStep({ onConfirm }: ConfirmationCodeStepProps): ReactNode {
|
|
467
|
+
const { Input, Banner } = usePrimitives();
|
|
468
|
+
const [code, setCode] = useState("");
|
|
469
|
+
const [attempt, setAttempt] = useState(0);
|
|
470
|
+
const [pending, setPending] = useState(false);
|
|
471
|
+
const [rejected, setRejected] = useState(false);
|
|
472
|
+
|
|
473
|
+
const confirm = async (submittedCode: string): Promise<void> => {
|
|
474
|
+
if (pending) return;
|
|
475
|
+
setPending(true);
|
|
476
|
+
setRejected(false);
|
|
477
|
+
const accepted = await onConfirm(submittedCode).finally(() => setPending(false));
|
|
478
|
+
if (accepted) return;
|
|
479
|
+
setRejected(true);
|
|
480
|
+
setCode("");
|
|
481
|
+
// The input was disabled while pending and lost focus; remounting restores autoFocus.
|
|
482
|
+
setAttempt((n) => n + 1);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<>
|
|
487
|
+
<Input
|
|
488
|
+
key={attempt}
|
|
489
|
+
kind="code"
|
|
490
|
+
length={CONFIRMATION_CODE_LENGTH}
|
|
491
|
+
id="confirmation-code"
|
|
492
|
+
name="confirmation-code"
|
|
493
|
+
testId="confirmation-code"
|
|
494
|
+
value={code}
|
|
495
|
+
onChange={setCode}
|
|
496
|
+
onComplete={(next) => void confirm(next)}
|
|
497
|
+
disabled={pending}
|
|
498
|
+
hasError={rejected}
|
|
499
|
+
autoFocus
|
|
500
|
+
/>
|
|
501
|
+
{rejected ? <Banner variant="error">That code is not valid. Try again.</Banner> : null}
|
|
502
|
+
</>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
```
|
|
@@ -18,7 +18,8 @@ instead of keeping a hand-written copy of every feature's variables.
|
|
|
18
18
|
`kmsEnvSchema` and `s3EnvSchema`. `composeEnvSchema` collects them and
|
|
19
19
|
records which feature declared each variable.
|
|
20
20
|
- **App-level blocks in `extend`**: `buildInfoEnvSchema`, `demoAdminEnvSchema`,
|
|
21
|
-
`stripeEnvSchema({ prices })
|
|
21
|
+
`stripeEnvSchema({ prices })`, `dodoEnvSchema({ prices })` and
|
|
22
|
+
`createS3EnvSchema({ prefix })` for a
|
|
22
23
|
second bucket. These belong to no mounted feature, so the app adds them.
|
|
23
24
|
- **Typed result**: `parseEnv(composed.schema, …)` returns the merged type,
|
|
24
25
|
so `parsed.S3_BUCKET` is `string | undefined` and `parsed.APP_PUBLIC_URL` is
|
|
@@ -34,6 +35,7 @@ instead of keeping a hand-written copy of every feature's variables.
|
|
|
34
35
|
| `smtpEnvSchema` | `channel-email` | `createChannelEmailFeature()` |
|
|
35
36
|
| `s3EnvSchema`, `createS3EnvSchema({ prefix })` | `file-provider-s3` | `createFileProviderS3Feature({ fromEnv: true })` |
|
|
36
37
|
| `stripeEnvSchema({ prices })` | `subscription-stripe` | app (`extend`); the feature registers the bridge keys |
|
|
38
|
+
| `dodoEnvSchema({ prices })` | `subscription-dodo` | app (`extend`); the feature registers the bridge keys |
|
|
37
39
|
| `mfaTokenSecretsEnvSchema` | `auth-mfa` | `createAuthMfaFeature()` |
|
|
38
40
|
| `buildInfoEnvSchema`, `demoAdminEnvSchema` | `foundation-shared` | app (`extend`) |
|
|
39
41
|
| `prometheusMetricsEnvSchema` | `framework/observability` | app (`extend`) |
|
|
@@ -45,6 +47,26 @@ Every app that boots with `runProdApp`. Pass the composed schema as
|
|
|
45
47
|
|
|
46
48
|
## Source files
|
|
47
49
|
|
|
50
|
+
### bin/server.ts
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// Production entrypoint. The env schema covers the same feature list that
|
|
54
|
+
// runProdApp mounts, so `KUMIKO_DRY_RUN_ENV=boot bun bin/server.ts` lists
|
|
55
|
+
// every variable of this app without connecting anywhere.
|
|
56
|
+
import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
57
|
+
import { composeAppEnv, createEnvCompositionFeatures } from "../src/env";
|
|
58
|
+
|
|
59
|
+
const features = createEnvCompositionFeatures();
|
|
60
|
+
|
|
61
|
+
await runProdApp({
|
|
62
|
+
features: [...features],
|
|
63
|
+
envSchema: composeAppEnv(features),
|
|
64
|
+
// The recipe declares no PII fields; crypto-shredding is mounted only for
|
|
65
|
+
// its env block.
|
|
66
|
+
allowPlaintextPii: "env-composition recipe has no PII fields",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
48
70
|
### src/__tests__/dry-run-boot.test.ts
|
|
49
71
|
|
|
50
72
|
```ts
|
|
@@ -175,6 +197,7 @@ describe("env composition", () => {
|
|
|
175
197
|
expect(sources["S3_BUCKET"]).toBe("file-provider-s3-env");
|
|
176
198
|
expect(sources["RELEASES_S3_BUCKET"]).toBe("app");
|
|
177
199
|
expect(sources["STRIPE_PRICE_PRO"]).toBe("app");
|
|
200
|
+
expect(sources["DODO_PRODUCT_PRO"]).toBe("app");
|
|
178
201
|
});
|
|
179
202
|
|
|
180
203
|
test("parses into a typed env", () => {
|
|
@@ -183,6 +206,7 @@ describe("env composition", () => {
|
|
|
183
206
|
...minimalValidValues,
|
|
184
207
|
S3_BUCKET: "uploads",
|
|
185
208
|
STRIPE_PRICE_PRO: "price_pro",
|
|
209
|
+
DODO_PRODUCT_PRO: "pdt_pro",
|
|
186
210
|
});
|
|
187
211
|
|
|
188
212
|
const bucket: string | undefined = parsed.S3_BUCKET;
|
|
@@ -190,6 +214,7 @@ describe("env composition", () => {
|
|
|
190
214
|
expect(bucket).toBe("uploads");
|
|
191
215
|
expect(publicUrl).toBe("https://app.example.com");
|
|
192
216
|
expect(parsed.KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION).toBe("1");
|
|
217
|
+
expect(parsed.DODO_PRODUCT_PRO).toBe("pdt_pro");
|
|
193
218
|
});
|
|
194
219
|
|
|
195
220
|
test("reports every missing required variable with its source", () => {
|
|
@@ -245,6 +270,7 @@ import {
|
|
|
245
270
|
demoAdminEnvSchema,
|
|
246
271
|
} from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
|
|
247
272
|
import { createSecretsFeature } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
273
|
+
import { dodoEnvSchema } from "@cosmicdrift/kumiko-bundled-features/subscription-dodo";
|
|
248
274
|
import { stripeEnvSchema } from "@cosmicdrift/kumiko-bundled-features/subscription-stripe";
|
|
249
275
|
import { frameworkCoreEnvSchema } from "@cosmicdrift/kumiko-dev-server/env-schema";
|
|
250
276
|
import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
|
|
@@ -266,6 +292,7 @@ const appEnvSchema = z.object({
|
|
|
266
292
|
...buildInfoEnvSchema.shape,
|
|
267
293
|
...demoAdminEnvSchema.shape,
|
|
268
294
|
...stripeEnvSchema({ prices: ["PRO", "TEAM"] }).shape,
|
|
295
|
+
...dodoEnvSchema({ prices: ["PRO", "TEAM"] }).shape,
|
|
269
296
|
...createS3EnvSchema({ prefix: "RELEASES_S3_" }).shape,
|
|
270
297
|
APP_PUBLIC_URL: z.url().describe("Public origin of the app, used in mail links."),
|
|
271
298
|
});
|
|
@@ -80,7 +80,7 @@ await runProdApp({
|
|
|
80
80
|
// Required (1): routes run anonymous, need tenant resolution
|
|
81
81
|
anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
|
|
82
82
|
// Required (2): boot check + internal lookup use ctx.templateResolver
|
|
83
|
-
extraContext: ({ db }) => ({
|
|
83
|
+
extraContext: ({ db, registry }) => ({
|
|
84
84
|
templateResolver: createTemplateResolverApi(db, registry),
|
|
85
85
|
}),
|
|
86
86
|
});
|
|
@@ -57,6 +57,22 @@ bun test samples/recipes/prod-boot-options
|
|
|
57
57
|
|
|
58
58
|
## Source files
|
|
59
59
|
|
|
60
|
+
### bin/server.ts
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
|
|
64
|
+
import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
65
|
+
import { appFeatures, bootOptions } from "../src/app";
|
|
66
|
+
|
|
67
|
+
const features = appFeatures();
|
|
68
|
+
|
|
69
|
+
await runProdApp({
|
|
70
|
+
features,
|
|
71
|
+
envSchema: composeEnvSchema({ features }),
|
|
72
|
+
...bootOptions(process.env),
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
60
76
|
### src/__tests__/boot-options.test.ts
|
|
61
77
|
|
|
62
78
|
```ts
|