@cosmicdrift/kumiko-framework 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -38
- package/package.json +21 -2
- package/src/__tests__/anonymous-access.integration.test.ts +185 -0
- package/src/__tests__/consumer-cli.integration.test.ts +172 -0
- package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
- package/src/__tests__/full-stack.integration.test.ts +1 -1
- package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
- package/src/__tests__/schema-cli.integration.test.ts +50 -0
- package/src/__tests__/store-table.integration.test.ts +93 -0
- package/src/api/__tests__/api.test.ts +373 -0
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +101 -11
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +200 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
- package/src/api/__tests__/pat-scope.test.ts +36 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
- package/src/api/__tests__/request-id-middleware.test.ts +51 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/api-constants.ts +11 -0
- package/src/api/auth-middleware.ts +248 -40
- package/src/api/auth-routes.ts +576 -95
- package/src/api/index.ts +13 -4
- package/src/api/jwt.ts +170 -11
- package/src/api/pat-scope.ts +14 -0
- package/src/api/pii-leak-guard.ts +47 -0
- package/src/api/request-context.ts +3 -0
- package/src/api/request-id-middleware.ts +2 -0
- package/src/api/routes.ts +169 -2
- package/src/api/server.ts +112 -16
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/__tests__/PATTERN.md +0 -1
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/__tests__/write-brand.test.ts +21 -0
- package/src/bun-db/connection.ts +3 -3
- package/src/bun-db/index.ts +2 -0
- package/src/bun-db/query.ts +105 -32
- package/src/consumer-cli.ts +134 -0
- package/src/crypto/__tests__/blind-index.test.ts +130 -0
- package/src/crypto/__tests__/event-pii.test.ts +161 -0
- package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
- package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
- package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
- package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
- package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
- package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
- package/src/crypto/blind-index.ts +118 -0
- package/src/crypto/event-pii.ts +70 -0
- package/src/crypto/in-memory-kms-adapter.ts +50 -0
- package/src/crypto/index.ts +67 -0
- package/src/crypto/kms-adapter.ts +2 -0
- package/src/crypto/pg-kms-adapter.ts +295 -0
- package/src/crypto/pii-field-encryption.ts +248 -0
- package/src/crypto/request-kms-cache.ts +38 -0
- package/src/crypto/subject-resolver.ts +91 -0
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
- package/src/db/__tests__/blind-index.integration.test.ts +248 -0
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/collect-table-metas.test.ts +10 -10
- package/src/db/__tests__/config-seed.integration.test.ts +13 -5
- package/src/db/__tests__/dialect-instant.test.ts +1 -4
- package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
- package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
- package/src/db/__tests__/feature-table-sources.test.ts +34 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
- package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-generator.test.ts +21 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
- package/src/db/__tests__/rebuild-marker.test.ts +13 -3
- package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
- package/src/db/__tests__/schema-inspection.test.ts +7 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
- package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
- package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/apply-entity-event.ts +18 -14
- package/src/db/blind-index-cleanup.ts +55 -0
- package/src/db/bun-provider.ts +2 -2
- package/src/db/collect-table-metas.ts +6 -7
- package/src/db/config-seed.ts +9 -9
- package/src/db/connection.ts +7 -12
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +20 -26
- package/src/db/entity-field-encryption.ts +81 -24
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +63 -90
- package/src/db/event-store-executor-context.ts +404 -0
- package/src/db/event-store-executor-read.ts +275 -0
- package/src/db/event-store-executor-write.ts +644 -0
- package/src/db/event-store-executor.ts +28 -1155
- package/src/db/feature-table-sources.ts +6 -5
- package/src/db/index.ts +20 -3
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-generator.ts +39 -6
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +9 -1
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/backfill-pii.ts +277 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/queries/event-store.ts +126 -19
- package/src/db/queries/projection-rebuild.ts +22 -8
- package/src/db/queries/shadow-swap.ts +183 -0
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/query-api.ts +1 -0
- package/src/db/query.ts +1 -0
- package/src/db/rebuild-marker.ts +18 -2
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +257 -0
- package/src/db/schema-inspection.ts +1 -1
- package/src/db/table-builder.ts +102 -71
- package/src/db/tenant-db.ts +15 -53
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
- package/src/engine/__tests__/boot-validator.test.ts +219 -15
- package/src/engine/__tests__/build-app-schema.test.ts +82 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
- package/src/engine/__tests__/build-target.test.ts +3 -11
- package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
- package/src/engine/__tests__/config-helpers.test.ts +16 -0
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +163 -1
- package/src/engine/__tests__/entity-handlers.test.ts +80 -0
- package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
- package/src/engine/__tests__/factories-time.test.ts +2 -66
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
- package/src/engine/__tests__/feature-manifest.test.ts +31 -1
- package/src/engine/__tests__/field-access.test.ts +23 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/membership-roles.test.ts +4 -10
- package/src/engine/__tests__/nav.test.ts +86 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
- package/src/engine/__tests__/post-query-hook.test.ts +7 -7
- package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
- package/src/engine/__tests__/registry.test.ts +144 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/screen.test.ts +147 -1
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
- package/src/engine/__tests__/store-table.test.ts +229 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
- package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
- package/src/engine/boot-validator/action-wiring.ts +149 -0
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/config-deps.ts +40 -4
- package/src/engine/boot-validator/entity-handler.ts +20 -21
- package/src/engine/boot-validator/entity-list-screens.ts +88 -0
- package/src/engine/boot-validator/gdpr-storage.ts +0 -20
- package/src/engine/boot-validator/i18n-keys.ts +58 -4
- package/src/engine/boot-validator/index.ts +33 -12
- package/src/engine/boot-validator/nav.ts +125 -0
- package/src/engine/boot-validator/pii-retention.ts +95 -10
- package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
- package/src/engine/boot-validator/workspaces.ts +68 -0
- package/src/engine/build-app-schema.ts +16 -0
- package/src/engine/build-config-feature-schema.ts +44 -7
- package/src/engine/codemod/pipeline-codemod.ts +5 -5
- package/src/engine/config-helpers.ts +15 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/define-feature.ts +95 -947
- package/src/engine/define-handler.ts +28 -94
- package/src/engine/define-workflow.ts +1 -1
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/entity-handlers.ts +76 -11
- package/src/engine/extensions/tenant-data.ts +19 -0
- package/src/engine/extensions/user-data.ts +29 -2
- package/src/engine/factories.ts +8 -49
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
- package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
- package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
- package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
- package/src/engine/feature-ast/extractors/events.ts +322 -0
- package/src/engine/feature-ast/extractors/handlers.ts +234 -0
- package/src/engine/feature-ast/extractors/hooks.ts +243 -0
- package/src/engine/feature-ast/extractors/index.ts +34 -31
- package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
- package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
- package/src/engine/feature-ast/extractors/round1.ts +3 -3
- package/src/engine/feature-ast/extractors/round5.ts +3 -3
- package/src/engine/feature-ast/extractors/round6.ts +2 -36
- package/src/engine/feature-ast/extractors/shared.ts +64 -6
- package/src/engine/feature-ast/index.ts +2 -4
- package/src/engine/feature-ast/parse.ts +130 -23
- package/src/engine/feature-ast/patch.ts +39 -50
- package/src/engine/feature-ast/patcher.ts +37 -38
- package/src/engine/feature-ast/patterns.ts +50 -62
- package/src/engine/feature-ast/render.ts +67 -44
- package/src/engine/feature-builder-state.ts +168 -0
- package/src/engine/feature-config-events-jobs.ts +403 -0
- package/src/engine/feature-entity-handlers.ts +208 -0
- package/src/engine/feature-manifest.ts +2 -1
- package/src/engine/feature-ui-extensions.ts +500 -0
- package/src/engine/field-access.ts +13 -2
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +16 -0
- package/src/engine/index.ts +23 -7
- package/src/engine/membership-roles.ts +13 -0
- package/src/engine/object-form.ts +27 -0
- package/src/engine/ownership.ts +26 -79
- package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
- package/src/engine/pattern-library/library.ts +44 -1152
- package/src/engine/pattern-library/mixed-schemas.ts +450 -0
- package/src/engine/pattern-library/opaque-schemas.ts +124 -0
- package/src/engine/pattern-library/shared-fields.ts +75 -0
- package/src/engine/pattern-library/static-schemas.ts +456 -0
- package/src/engine/pipeline.ts +6 -11
- package/src/engine/registry-facade.ts +362 -0
- package/src/engine/registry-ingest.ts +478 -0
- package/src/engine/registry-state.ts +388 -0
- package/src/engine/registry-validate.ts +642 -0
- package/src/engine/registry.ts +78 -1658
- package/src/engine/run-pipeline.ts +1 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/soft-delete-cleanup.ts +6 -2
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -482
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -972
- package/src/engine/types/fields.ts +2 -675
- package/src/engine/types/handlers.ts +2 -774
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -54
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +86 -40
- package/src/engine/types/nav.ts +2 -63
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -138
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -574
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -132
- package/src/engine/types/workspace.ts +2 -49
- package/src/engine/validate-projection-allowlist.ts +6 -6
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
- package/src/entrypoint/index.ts +49 -6
- package/src/errors/classes.ts +22 -1
- package/src/errors/field-issue.ts +0 -3
- package/src/errors/index.ts +1 -1
- package/src/errors/write-error-info.ts +10 -22
- package/src/es-ops/README.md +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
- package/src/es-ops/context.ts +4 -2
- package/src/es-ops/types.ts +8 -1
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
- package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
- package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
- package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
- package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +64 -78
- package/src/event-store/events-schema.ts +11 -13
- package/src/event-store/index.ts +18 -3
- package/src/event-store/rebuild-dead-letter.ts +111 -0
- package/src/event-store/snapshot.ts +63 -40
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/file-ref-entity.test.ts +8 -0
- package/src/files/__tests__/files.integration.test.ts +24 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/provider-resolver.test.ts +70 -0
- package/src/files/__tests__/write-stream.test.ts +13 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/file-ref-entity.ts +2 -2
- package/src/files/file-routes.ts +13 -0
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +27 -8
- package/src/files/provider-resolver.ts +31 -20
- package/src/files/types.ts +13 -55
- package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
- package/src/i18n/required-surface-keys.ts +120 -1
- package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
- package/src/jobs/job-runner.ts +82 -12
- package/src/logging/types.ts +1 -7
- package/src/migrations/pending-rebuilds.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +4 -1
- package/src/observability/__tests__/recording-tracer.test.ts +6 -4
- package/src/observability/index.ts +2 -0
- package/src/observability/noop-provider.ts +0 -9
- package/src/observability/recording-tracer.ts +0 -12
- package/src/observability/standard-metrics.ts +64 -2
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -64
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
- package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
- package/src/pipeline/dispatch-batch.ts +187 -0
- package/src/pipeline/dispatch-query.ts +165 -0
- package/src/pipeline/dispatch-shared.ts +826 -0
- package/src/pipeline/dispatch-stream.ts +90 -0
- package/src/pipeline/dispatch-write.ts +451 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +44 -1372
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +30 -2
- package/src/pipeline/event-dispatcher-admin.ts +293 -0
- package/src/pipeline/event-dispatcher-delivery.ts +305 -0
- package/src/pipeline/event-dispatcher.ts +85 -542
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/msp-rebuild.ts +42 -3
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/projection-rebuild.ts +105 -3
- package/src/pipeline/system-hooks.ts +144 -1
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +16 -32
- package/src/schema-cli.ts +76 -16
- package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
- package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
- package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
- package/src/search/index.ts +6 -0
- package/src/search/meilisearch-adapter.ts +13 -12
- package/src/search/reindex-entity.ts +177 -0
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/contains-secret.test.ts +34 -0
- package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/dek-cache.ts +26 -5
- package/src/secrets/envelope-cipher.ts +53 -0
- package/src/secrets/envelope.ts +5 -3
- package/src/secrets/index.ts +13 -1
- package/src/secrets/stored-envelope.ts +46 -0
- package/src/secrets/types.ts +2 -162
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +4 -3
- package/src/stack/redis.ts +8 -0
- package/src/stack/request-helper.ts +38 -2
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +249 -131
- package/src/testing/__tests__/late-bound.test.ts +32 -0
- package/src/testing/__tests__/wait-for.test.ts +59 -0
- package/src/testing/boot-validator-fixture.ts +121 -0
- package/src/testing/e2e-generator.ts +8 -0
- package/src/testing/file-provider-contract.ts +104 -0
- package/src/testing/handler-context.ts +3 -1
- package/src/testing/index.ts +5 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/mutable-master-key-provider.ts +29 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/__tests__/tz-dateline.test.ts +8 -10
- package/src/time/geo-tz.ts +1 -32
- package/src/time/index.ts +1 -0
- package/src/time/legacy-date.ts +18 -0
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +44 -85
- package/src/ui-types/app-schema.ts +12 -0
- package/src/ui-types/index.ts +20 -7
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/__tests__/raw-table.integration.test.ts +0 -116
- package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
- package/src/db/__tests__/encryption.test.ts +0 -39
- package/src/db/encryption.ts +0 -39
- package/src/db/row-helpers.ts +0 -4
- package/src/engine/__tests__/raw-table.test.ts +0 -150
- package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
- package/src/engine/feature-ast/extractors/round4.ts +0 -1366
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Single enumeration of every table-bearing registration on a feature
|
|
2
|
-
// (r.projection, r.multiStreamProjection with table, r.
|
|
3
|
-
// by BOTH the setupTestStack auto-push and collectTableMetas —
|
|
4
|
-
// test-DB-push and `kumiko schema generate` cannot drift
|
|
2
|
+
// (r.projection, r.multiStreamProjection with table, r.storeTable).
|
|
3
|
+
// Consumed by BOTH the setupTestStack auto-push and collectTableMetas —
|
|
4
|
+
// one list, so test-DB-push and `kumiko schema generate` cannot drift
|
|
5
|
+
// apart again (#255).
|
|
5
6
|
// A new table-bearing registrar must be added HERE, not in the consumers.
|
|
6
7
|
|
|
7
8
|
import type { FeatureDefinition } from "../engine/types";
|
|
@@ -28,8 +29,8 @@ export function enumerateFeatureTableSources(
|
|
|
28
29
|
origin: `multiStreamProjection "${name}" (${feature.name})`,
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
|
-
for (const [name, raw] of Object.entries(feature.
|
|
32
|
-
sources.push({ table: raw.
|
|
32
|
+
for (const [name, raw] of Object.entries(feature.storeTables)) {
|
|
33
|
+
sources.push({ table: raw.meta, origin: `storeTable "${name}" (${feature.name})` });
|
|
33
34
|
}
|
|
34
35
|
return sources;
|
|
35
36
|
}
|
package/src/db/index.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
export { assertExistsIn } from "./assert-exists-in";
|
|
2
|
+
export { nullBlindIndexesForSubject } from "./blind-index-cleanup";
|
|
2
3
|
export { collectTableMetas } from "./collect-table-metas";
|
|
3
4
|
export { flattenCompoundTypes, rehydrateCompoundTypes } from "./compound-types";
|
|
4
5
|
export { seedConfigValues } from "./config-seed";
|
|
5
|
-
export type {
|
|
6
|
+
export type {
|
|
7
|
+
DbConnection,
|
|
8
|
+
DbConnectionOptions,
|
|
9
|
+
DbPoolHandle,
|
|
10
|
+
DbRow,
|
|
11
|
+
DbRunner,
|
|
12
|
+
DbTx,
|
|
13
|
+
} from "./connection";
|
|
6
14
|
export { createDbConnection, dbConnectionOptionsFromEnv } from "./connection";
|
|
7
15
|
export type { CursorQueryOptions, CursorResult } from "./cursor";
|
|
8
16
|
export { decodeCursor, encodeCursor } from "./cursor";
|
|
@@ -34,12 +42,13 @@ export {
|
|
|
34
42
|
enrichRowWithReferences,
|
|
35
43
|
enrichWithReferences,
|
|
36
44
|
} from "./eagerload";
|
|
37
|
-
export type { EncryptionProvider } from "./encryption";
|
|
38
|
-
export { createEncryptionProvider } from "./encryption";
|
|
39
45
|
export {
|
|
40
46
|
collectEncryptedFieldNames,
|
|
47
|
+
configuredEntityFieldEncryption,
|
|
48
|
+
configureEntityFieldEncryption,
|
|
41
49
|
decryptEntityFieldValues,
|
|
42
50
|
encryptEntityFieldValues,
|
|
51
|
+
resetEntityFieldEncryptionCacheForTests,
|
|
43
52
|
} from "./entity-field-encryption";
|
|
44
53
|
export type {
|
|
45
54
|
BuildEntityTableMetaOptions,
|
|
@@ -103,6 +112,7 @@ export {
|
|
|
103
112
|
fetchOne,
|
|
104
113
|
insertMany,
|
|
105
114
|
insertOne,
|
|
115
|
+
runInSavepoint,
|
|
106
116
|
selectMany,
|
|
107
117
|
transaction,
|
|
108
118
|
updateMany,
|
|
@@ -114,6 +124,13 @@ export {
|
|
|
114
124
|
} from "./rebuild-marker";
|
|
115
125
|
export { seedReferenceData } from "./reference-data";
|
|
116
126
|
export { renderTableDdl, renderTablesDdl } from "./render-ddl";
|
|
127
|
+
export {
|
|
128
|
+
diffReplayAgainstSnapshot,
|
|
129
|
+
type ReplayedSchema,
|
|
130
|
+
type ReplayedTable,
|
|
131
|
+
type ReplayMismatch,
|
|
132
|
+
replayMigrationsDir,
|
|
133
|
+
} from "./replay-migration-sql";
|
|
117
134
|
export { tableExists } from "./schema-inspection";
|
|
118
135
|
export {
|
|
119
136
|
buildBaseColumns,
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
// gespeicherter tz). Server kennt User-TZ nicht — User-spezifische
|
|
10
10
|
// Anzeige passiert client-seitig aus utc.
|
|
11
11
|
|
|
12
|
+
// Static import, not the ambient global: Bun doesn't expose Temporal on
|
|
13
|
+
// globalThis, so this crashed with "Temporal is not defined" outside boot
|
|
14
|
+
// paths that install it (#1480).
|
|
15
|
+
import { Temporal } from "temporal-polyfill";
|
|
12
16
|
import type { EntityDefinition } from "../engine/types";
|
|
13
17
|
|
|
14
18
|
// Sprint F: <name>Utc-Spalte ist jetzt instant() (siehe dialect.ts) —
|
|
@@ -202,13 +202,40 @@ export function diffSnapshots(prev: Snapshot | null, next: Snapshot): SchemaDiff
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// Managed projections are event-stream derivatives: in-place-unsafe changes (NOT NULL w/o default, UNIQUE, SET NOT NULL, type change, dropped col) → DROP+CREATE + replay; additive-safe changes stay cheap ALTERs.
|
|
205
|
+
function describeRecreateReasons(td: TableDiff): readonly string[] {
|
|
206
|
+
const reasons: string[] = [];
|
|
207
|
+
if (td.droppedColumns.length > 0) {
|
|
208
|
+
reasons.push(`dropped column(s): ${td.droppedColumns.join(", ")}`);
|
|
209
|
+
}
|
|
210
|
+
const notNullNoDefault = td.newColumns
|
|
211
|
+
.filter((c) => c.notNull && c.defaultSql === undefined)
|
|
212
|
+
.map((c) => c.name);
|
|
213
|
+
if (notNullNoDefault.length > 0) {
|
|
214
|
+
reasons.push(`new NOT NULL column(s) without default: ${notNullNoDefault.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
const newUniqueIndexes = td.newIndexes
|
|
217
|
+
.filter((idx) => idx.unique === true)
|
|
218
|
+
.map((idx) => idx.name);
|
|
219
|
+
if (newUniqueIndexes.length > 0) {
|
|
220
|
+
reasons.push(`new UNIQUE index(es): ${newUniqueIndexes.join(", ")}`);
|
|
221
|
+
}
|
|
222
|
+
const madeNotNull = td.changedColumns
|
|
223
|
+
.filter((c) => c.nullabilityChanged?.to === true)
|
|
224
|
+
.map((c) => c.name);
|
|
225
|
+
if (madeNotNull.length > 0) {
|
|
226
|
+
reasons.push(`column(s) set NOT NULL: ${madeNotNull.join(", ")}`);
|
|
227
|
+
}
|
|
228
|
+
const typeChanged = td.changedColumns
|
|
229
|
+
.filter((c) => c.typeChanged !== undefined)
|
|
230
|
+
.map((c) => c.name);
|
|
231
|
+
if (typeChanged.length > 0) {
|
|
232
|
+
reasons.push(`column type change: ${typeChanged.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
return reasons;
|
|
235
|
+
}
|
|
236
|
+
|
|
205
237
|
export function managedChangeRequiresRecreate(td: TableDiff): boolean {
|
|
206
|
-
|
|
207
|
-
if (td.newColumns.some((c) => c.notNull && c.defaultSql === undefined)) return true;
|
|
208
|
-
if (td.newIndexes.some((idx) => idx.unique === true)) return true;
|
|
209
|
-
return td.changedColumns.some(
|
|
210
|
-
(c) => c.nullabilityChanged?.to === true || c.typeChanged !== undefined,
|
|
211
|
-
);
|
|
238
|
+
return describeRecreateReasons(td).length > 0;
|
|
212
239
|
}
|
|
213
240
|
|
|
214
241
|
// --- SQL-Render ---------------------------------------------------------
|
|
@@ -296,6 +323,12 @@ export function renderMigrationSql(
|
|
|
296
323
|
lines.push(`-- ${td.tableName}`);
|
|
297
324
|
if (td.nextMeta.source === "managed" && managedChangeRequiresRecreate(td)) {
|
|
298
325
|
lines.push("-- managed projection — recreated + rebuilt from events (see .rebuild.json)");
|
|
326
|
+
lines.push(
|
|
327
|
+
`-- WARN: destructive change (${describeRecreateReasons(td).join("; ")}) forces DROP+CREATE + full event replay.`,
|
|
328
|
+
);
|
|
329
|
+
lines.push(
|
|
330
|
+
"-- Consider an Expand/Contract split across two releases to avoid the rebuild — see docs/guides/expand-contract-managed-projections.md.",
|
|
331
|
+
);
|
|
299
332
|
lines.push(`DROP TABLE IF EXISTS ${quoteIdent(td.tableName)};`);
|
|
300
333
|
lines.push(...renderTableDdl(td.nextMeta));
|
|
301
334
|
lines.push("");
|
package/src/db/migrate-runner.ts
CHANGED
|
@@ -74,14 +74,103 @@ CREATE TABLE IF NOT EXISTS "_kumiko_migrations" (
|
|
|
74
74
|
)
|
|
75
75
|
`.trim();
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
77
|
+
// Splits SQL-file text into individual statements on top-level `;`. A plain
|
|
78
|
+
// `text.split(";")` breaks the moment a `--` line comment or `/* */` block
|
|
79
|
+
// comment contains a semicolon (#1542) — it splits mid-comment before the
|
|
80
|
+
// comment is ever stripped. This scans char-by-char tracking whether we're
|
|
81
|
+
// inside a line comment, block comment, single-quoted string, or
|
|
82
|
+
// double-quoted identifier, so `;` only ends a statement in plain SQL text;
|
|
83
|
+
// comments are dropped, quoted/identifier content (incl. `''`/`""` escapes)
|
|
84
|
+
// is kept verbatim. Does not handle dollar-quoted (`$$...$$`) bodies — none
|
|
85
|
+
// of this repo's checked-in migrations use them; add that state if one ever
|
|
86
|
+
// does.
|
|
87
|
+
type SqlScanState = "normal" | "lineComment" | "blockComment" | "singleQuote" | "doubleQuote";
|
|
88
|
+
|
|
81
89
|
export function splitSqlStatements(sqlText: string): readonly string[] {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
const statements: string[] = [];
|
|
91
|
+
let current = "";
|
|
92
|
+
let state: SqlScanState = "normal";
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < sqlText.length; i++) {
|
|
95
|
+
const ch = sqlText.charAt(i);
|
|
96
|
+
const next = sqlText.charAt(i + 1);
|
|
97
|
+
|
|
98
|
+
if (state === "lineComment") {
|
|
99
|
+
if (ch === "\n") {
|
|
100
|
+
state = "normal";
|
|
101
|
+
current += ch;
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (state === "blockComment") {
|
|
106
|
+
if (ch === "*" && next === "/") {
|
|
107
|
+
state = "normal";
|
|
108
|
+
i++;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (state === "singleQuote") {
|
|
113
|
+
current += ch;
|
|
114
|
+
if (ch === "'") {
|
|
115
|
+
if (next === "'") {
|
|
116
|
+
current += next;
|
|
117
|
+
i++;
|
|
118
|
+
} else {
|
|
119
|
+
state = "normal";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (state === "doubleQuote") {
|
|
125
|
+
current += ch;
|
|
126
|
+
if (ch === '"') {
|
|
127
|
+
if (next === '"') {
|
|
128
|
+
current += next;
|
|
129
|
+
i++;
|
|
130
|
+
} else {
|
|
131
|
+
state = "normal";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// state === "normal"
|
|
138
|
+
if (ch === "-" && next === "-") {
|
|
139
|
+
state = "lineComment";
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ch === "/" && next === "*") {
|
|
144
|
+
state = "blockComment";
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch === "'") {
|
|
149
|
+
state = "singleQuote";
|
|
150
|
+
current += ch;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (ch === '"') {
|
|
154
|
+
state = "doubleQuote";
|
|
155
|
+
current += ch;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (ch === ";") {
|
|
159
|
+
statements.push(current);
|
|
160
|
+
current = "";
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
current += ch;
|
|
164
|
+
}
|
|
165
|
+
if (state === "blockComment" || state === "singleQuote" || state === "doubleQuote") {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`splitSqlStatements: unterminated ${state} — migration SQL is malformed, refusing to split`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
statements.push(current);
|
|
171
|
+
|
|
172
|
+
return statements
|
|
173
|
+
.map((s) => s.trim())
|
|
85
174
|
.filter((s) => s.length > 0)
|
|
86
175
|
.map((s) => `${s};`);
|
|
87
176
|
}
|
|
@@ -90,14 +179,21 @@ function sha256Hex(content: string): string {
|
|
|
90
179
|
return createHash("sha256").update(content).digest("hex");
|
|
91
180
|
}
|
|
92
181
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
182
|
+
// Reads <dir>/*.sql, sorted lexically (e.g. 0001_init.sql, 0002_add_locale.sql),
|
|
183
|
+
// returns Migration[] with id + checksum + statements. `preprocess` is an
|
|
184
|
+
// optional hook applied to each file's raw content before splitting/hashing
|
|
185
|
+
// — used by replayMigrationsDir to expand its DESTRUCTIVE-marker comments
|
|
186
|
+
// without a second, drifting copy of this file-discovery logic (#1522/9).
|
|
187
|
+
export function loadMigrationsFromDir(
|
|
188
|
+
dir: string,
|
|
189
|
+
preprocess?: (sql: string) => string,
|
|
190
|
+
): readonly Migration[] {
|
|
96
191
|
const files = readdirSync(dir)
|
|
97
192
|
.filter((f) => f.endsWith(".sql"))
|
|
98
193
|
.sort();
|
|
99
194
|
return files.map((file) => {
|
|
100
|
-
const
|
|
195
|
+
const raw = readFileSync(join(dir, file), "utf8");
|
|
196
|
+
const content = preprocess ? preprocess(raw) : raw;
|
|
101
197
|
return {
|
|
102
198
|
id: file.replace(/\.sql$/, ""),
|
|
103
199
|
checksum: sha256Hex(content),
|
package/src/db/pg-error.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// unwrap both layers so callers don't have to know which layer produced the
|
|
4
4
|
// error. Used by the event-store to distinguish a unique-violation on the
|
|
5
5
|
// aggregate-version index (optimistic-concurrency conflict) from the one on
|
|
6
|
-
// the
|
|
6
|
+
// the idempotency-key index (caller-side replay signal).
|
|
7
7
|
|
|
8
8
|
export type PgErrorInfo = {
|
|
9
9
|
readonly code: string | undefined;
|
|
@@ -41,6 +41,14 @@ export function isTableAlreadyExists(e: unknown): boolean {
|
|
|
41
41
|
return extractPgError(e)?.code === "42P07";
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// PG SQLSTATE 55P03 — "lock not available". Raised by a NOWAIT-style lock
|
|
45
|
+
// request that couldn't be granted; a CREATE/DROP INDEX CONCURRENTLY racing
|
|
46
|
+
// another session's DDL on the same relation can hit this instead of a
|
|
47
|
+
// duplicate-relation error, depending on exact timing.
|
|
48
|
+
export function isLockNotAvailable(e: unknown): boolean {
|
|
49
|
+
return extractPgError(e)?.code === "55P03";
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export function constraintOf(e: unknown): string | undefined {
|
|
45
53
|
return extractPgError(e)?.constraint_name;
|
|
46
54
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// `asRawClient(db)` wrappt .unsafe und .begin transparent.
|
|
4
4
|
|
|
5
5
|
import postgres from "postgres";
|
|
6
|
-
import type {
|
|
6
|
+
import type { DbConnectionOptions, DbPoolHandle } from "./api";
|
|
7
7
|
|
|
8
|
-
export function createPgConnection(url: string, options: DbConnectionOptions = {}):
|
|
8
|
+
export function createPgConnection(url: string, options: DbConnectionOptions = {}): DbPoolHandle {
|
|
9
9
|
const pgOptions: Parameters<typeof postgres>[1] = {};
|
|
10
10
|
if (options.maxConnections !== undefined) pgOptions.max = options.maxConnections;
|
|
11
11
|
if (options.idleTimeoutSeconds !== undefined) pgOptions.idle_timeout = options.idleTimeoutSeconds;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { type BunTestDb, createTestDb } from "../../../bun-db/__tests__/bun-test-db";
|
|
3
|
+
import { createEventsTable } from "../../../event-store/events-schema";
|
|
4
|
+
import { asRawClient } from "../../query";
|
|
5
|
+
import { ensureIdempotencyKeyIndex } from "../event-store";
|
|
6
|
+
|
|
7
|
+
let testDb: BunTestDb;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
testDb = await createTestDb();
|
|
11
|
+
await createEventsTable(testDb.db);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await testDb.cleanup();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function indexValidity(): Promise<boolean | undefined> {
|
|
19
|
+
const rows = (await asRawClient(testDb.db).unsafe(
|
|
20
|
+
`SELECT i.indisvalid FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid ` +
|
|
21
|
+
`WHERE c.relname = 'events_idempotency_uq'`,
|
|
22
|
+
)) as ReadonlyArray<{ indisvalid: boolean }>;
|
|
23
|
+
return rows[0]?.indisvalid;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("ensureIdempotencyKeyIndex (#1499)", () => {
|
|
27
|
+
test("repeated calls stay idempotent — index ends up valid, no throw", async () => {
|
|
28
|
+
await ensureIdempotencyKeyIndex(testDb.db);
|
|
29
|
+
await ensureIdempotencyKeyIndex(testDb.db);
|
|
30
|
+
expect(await indexValidity()).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Regression for the rolling-deploy race: two pods booting concurrently
|
|
34
|
+
// against the same DB both try to build the same CONCURRENTLY index.
|
|
35
|
+
// A plain CREATE INDEX IF NOT EXISTS no-ops the loser; a CONCURRENTLY
|
|
36
|
+
// build can instead raise a duplicate-relation error mid-build — that
|
|
37
|
+
// must be swallowed the same way createEventsTable already tolerates a
|
|
38
|
+
// racing CREATE TABLE, not crash-loop the booting pod.
|
|
39
|
+
test("concurrent calls from two racing boots don't throw", async () => {
|
|
40
|
+
// The prior test already built the index — without dropping it here,
|
|
41
|
+
// both calls below would just hit the IF NOT EXISTS no-op path (indeed
|
|
42
|
+
// what the original single-connection version of this test silently
|
|
43
|
+
// did) and never actually contend on a real CONCURRENTLY build.
|
|
44
|
+
await asRawClient(testDb.db).unsafe(
|
|
45
|
+
`DROP INDEX CONCURRENTLY IF EXISTS "events_idempotency_uq"`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Two independent connections against the SAME database, mirroring two
|
|
49
|
+
// pods — a single shared `testDb.db` pool would serialize both calls
|
|
50
|
+
// and never pin the race (kumiko-framework#1522).
|
|
51
|
+
const secondPodDb = await createTestDb({ dbName: testDb.dbName, persistent: true });
|
|
52
|
+
try {
|
|
53
|
+
await Promise.all([
|
|
54
|
+
ensureIdempotencyKeyIndex(testDb.db),
|
|
55
|
+
ensureIdempotencyKeyIndex(secondPodDb.db),
|
|
56
|
+
]);
|
|
57
|
+
expect(await indexValidity()).toBe(true);
|
|
58
|
+
} finally {
|
|
59
|
+
// persistent: true → cleanup() only closes this pool, doesn't drop the
|
|
60
|
+
// shared DB (testDb's afterAll still owns that).
|
|
61
|
+
await secondPodDb.cleanup();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("an INVALID leftover from a killed CONCURRENTLY build gets dropped and rebuilt", async () => {
|
|
66
|
+
// Simulates a build interrupted mid-flight (crash, deploy restart): the
|
|
67
|
+
// catalog entry exists but never finished — pg_index.indisvalid=false.
|
|
68
|
+
// Postgres has no direct DDL to force this state, so flip it straight
|
|
69
|
+
// in the catalog, matching what a real killed CONCURRENTLY build leaves
|
|
70
|
+
// behind.
|
|
71
|
+
await asRawClient(testDb.db).unsafe(
|
|
72
|
+
`UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'events_idempotency_uq'::regclass`,
|
|
73
|
+
);
|
|
74
|
+
expect(await indexValidity()).toBe(false);
|
|
75
|
+
|
|
76
|
+
await ensureIdempotencyKeyIndex(testDb.db);
|
|
77
|
+
|
|
78
|
+
expect(await indexValidity()).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|