@cosmicdrift/kumiko-framework 0.14.0 → 0.15.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/package.json +6 -6
- package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
- package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
- package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
- package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
- package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
- package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
- package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
- package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
- package/src/api/__tests__/api.test.ts +1 -1
- package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
- package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
- package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/csrf-middleware.test.ts +1 -1
- package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
- package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
- package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
- package/src/api/__tests__/readiness.test.ts +1 -1
- package/src/api/__tests__/request-id-middleware.test.ts +1 -1
- package/src/api/__tests__/sse-broker.test.ts +12 -12
- package/src/api/__tests__/sse-route.test.ts +1 -1
- package/src/api/readiness.ts +2 -2
- package/src/auth/__tests__/roles.test.ts +2 -2
- package/src/bun-db/__tests__/PATTERN.md +73 -0
- package/src/bun-db/__tests__/_helpers.ts +103 -0
- package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
- package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
- package/src/bun-db/__tests__/bun-test-db.ts +19 -0
- package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
- package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
- package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
- package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
- package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
- package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
- package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
- package/src/bun-db/connection.ts +84 -0
- package/src/bun-db/index.ts +31 -0
- package/src/bun-db/query.ts +845 -0
- package/src/compliance/__tests__/duration-spec.test.ts +1 -1
- package/src/compliance/__tests__/profiles.test.ts +1 -1
- package/src/compliance/__tests__/sub-processors.test.ts +1 -1
- package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
- package/src/db/__tests__/big-int-field.test.ts +15 -14
- package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
- package/src/db/__tests__/compound-types.test.ts +1 -1
- package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
- package/src/db/__tests__/connection-options.test.ts +1 -1
- package/src/db/__tests__/dialect-instant.test.ts +1 -1
- package/src/db/__tests__/encryption.test.ts +1 -1
- package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
- package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
- package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
- package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
- package/src/db/__tests__/located-timestamp.test.ts +1 -1
- package/src/db/__tests__/money.test.ts +1 -1
- package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
- package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
- package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
- package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
- package/src/db/__tests__/sql-inventory.test.ts +56 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
- package/src/db/__tests__/table-builder-required.test.ts +20 -22
- package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
- package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
- package/src/db/api.ts +46 -0
- package/src/db/apply-entity-event.ts +45 -36
- package/src/db/assert-exists-in.ts +5 -16
- package/src/db/bun-provider.ts +37 -0
- package/src/db/config-seed.ts +4 -4
- package/src/db/connection.ts +14 -57
- package/src/db/cursor.ts +5 -56
- package/src/db/dialect.ts +472 -99
- package/src/db/eagerload.ts +5 -12
- package/src/db/entity-table-meta.ts +390 -0
- package/src/db/event-store-executor.ts +158 -100
- package/src/db/index.ts +33 -5
- package/src/db/migrate-generator.ts +350 -0
- package/src/db/migrate-runner.ts +206 -0
- package/src/db/postgres-provider.ts +25 -0
- package/src/db/queries/entity-read.ts +15 -0
- package/src/db/queries/es-ops.ts +17 -0
- package/src/db/queries/event-consumer.ts +170 -0
- package/src/db/queries/event-store-admin.ts +127 -0
- package/src/db/queries/event-store.ts +155 -0
- package/src/db/queries/projection-rebuild.ts +59 -0
- package/src/db/queries/raw-sql.ts +15 -0
- package/src/db/queries/schema-drift.ts +35 -0
- package/src/db/queries/seed-context.ts +58 -0
- package/src/db/queries/table-ops.ts +11 -0
- package/src/db/queries/test-stack.ts +56 -0
- package/src/db/query-api.ts +22 -0
- package/src/db/query.ts +30 -0
- package/src/db/reference-data.ts +19 -22
- package/src/db/render-ddl.ts +57 -0
- package/src/db/row-helpers.ts +3 -52
- package/src/db/schema-inspection.ts +17 -4
- package/src/db/sql-inventory.ts +208 -0
- package/src/db/table-builder.ts +48 -40
- package/src/db/tenant-db.ts +105 -326
- package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
- package/src/engine/__tests__/boot-validator.test.ts +4 -3
- package/src/engine/__tests__/build-app-schema.test.ts +1 -1
- package/src/engine/__tests__/build-target.test.ts +1 -1
- package/src/engine/__tests__/claim-keys.test.ts +1 -1
- package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
- package/src/engine/__tests__/config-helpers.test.ts +1 -1
- package/src/engine/__tests__/effective-features.test.ts +1 -1
- package/src/engine/__tests__/engine.test.ts +1 -1
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +3 -3
- package/src/engine/__tests__/extends-registrar.test.ts +4 -4
- package/src/engine/__tests__/factories-long-text.test.ts +1 -1
- package/src/engine/__tests__/factories-time.test.ts +1 -1
- package/src/engine/__tests__/field-predicates.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +1 -1
- package/src/engine/__tests__/identifiers.test.ts +1 -1
- package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
- package/src/engine/__tests__/nav.test.ts +1 -1
- package/src/engine/__tests__/ownership.test.ts +10 -11
- package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
- package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
- package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
- package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +1 -1
- package/src/engine/__tests__/projection-helpers.test.ts +25 -17
- package/src/engine/__tests__/projection.test.ts +4 -4
- package/src/engine/__tests__/qualified-name.test.ts +1 -1
- package/src/engine/__tests__/raw-table.test.ts +9 -8
- package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
- package/src/engine/__tests__/run-in.test.ts +1 -1
- package/src/engine/__tests__/schema-builder.test.ts +1 -1
- package/src/engine/__tests__/screen.test.ts +1 -1
- package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
- package/src/engine/__tests__/state-machine.test.ts +1 -1
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
- package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
- package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
- package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
- package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
- package/src/engine/__tests__/steps-read.test.ts +34 -40
- package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
- package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
- package/src/engine/__tests__/steps-workflow.test.ts +7 -7
- package/src/engine/__tests__/system-user.test.ts +1 -1
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
- package/src/engine/__tests__/validation-hooks.test.ts +1 -1
- package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
- package/src/engine/boot-validator/entity-handler.ts +3 -3
- package/src/engine/boot-validator/ownership.ts +1 -1
- package/src/engine/define-feature.ts +1 -2
- package/src/engine/entity-handlers.ts +5 -5
- package/src/engine/factories.ts +1 -1
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
- package/src/engine/ownership.ts +113 -41
- package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
- package/src/engine/projection-helpers.ts +2 -11
- package/src/engine/registry.ts +2 -2
- package/src/engine/steps/read-find-many.ts +13 -13
- package/src/engine/steps/read-find-one.ts +7 -9
- package/src/engine/steps/unsafe-projection-delete.ts +4 -5
- package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
- package/src/engine/types/feature.ts +7 -2
- package/src/engine/types/fields.ts +4 -5
- package/src/engine/types/step.ts +10 -10
- package/src/engine/validate-projection-allowlist.ts +23 -3
- package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
- package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
- package/src/env/__tests__/compose-env-schema.test.ts +1 -1
- package/src/env/__tests__/dry-run.test.ts +1 -1
- package/src/errors/__tests__/classes.test.ts +1 -1
- package/src/errors/__tests__/write-failures.test.ts +1 -1
- package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
- package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
- package/src/es-ops/__tests__/runner.test.ts +29 -19
- package/src/es-ops/context.ts +9 -43
- package/src/es-ops/operations-schema.ts +2 -2
- package/src/es-ops/runner.ts +12 -26
- package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
- package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
- package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
- package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
- package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
- package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
- package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
- package/src/event-store/admin-api.ts +55 -83
- package/src/event-store/archive.ts +15 -39
- package/src/event-store/event-store.ts +92 -86
- package/src/event-store/events-schema.ts +2 -1
- package/src/event-store/index.ts +1 -0
- package/src/event-store/snapshot.ts +26 -24
- package/src/event-store/upcaster-dead-letter.ts +19 -18
- package/src/files/__tests__/content-disposition.test.ts +1 -1
- package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
- package/src/files/__tests__/file-handle.test.ts +1 -1
- package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
- package/src/files/__tests__/read-stream.test.ts +1 -1
- package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
- package/src/files/__tests__/write-stream.test.ts +1 -1
- package/src/files/__tests__/zip-stream.test.ts +1 -1
- package/src/files/file-ref-table.ts +2 -2
- package/src/files/file-routes.ts +7 -9
- package/src/files/storage-tracking.ts +9 -17
- package/src/i18n/__tests__/i18n.test.ts +1 -1
- package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
- package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
- package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
- package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
- package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
- package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
- package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
- package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
- package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
- package/src/migrations/projection-detection.ts +12 -1
- package/src/migrations/schema-drift.ts +7 -23
- package/src/observability/__tests__/console-provider.test.ts +1 -1
- package/src/observability/__tests__/metric-validator.test.ts +1 -1
- package/src/observability/__tests__/noop-provider.test.ts +1 -1
- package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
- package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-tracer.test.ts +1 -1
- package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
- package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
- package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
- package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
- package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
- package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
- package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
- package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
- package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
- package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
- package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
- package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
- package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
- package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
- package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
- package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
- package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
- package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
- package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
- package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
- package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
- package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
- package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
- package/src/pipeline/cascade-handler.ts +13 -21
- package/src/pipeline/dispatcher.ts +43 -48
- package/src/pipeline/event-consumer-state.ts +11 -2
- package/src/pipeline/event-dispatcher.ts +86 -146
- package/src/pipeline/event-retention.ts +14 -24
- package/src/pipeline/msp-rebuild.ts +54 -78
- package/src/pipeline/projection-rebuild.ts +65 -67
- package/src/pipeline/projection-state.ts +2 -2
- package/src/random/__tests__/generate.test.ts +13 -13
- package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
- package/src/redis/__tests__/redis-options.test.ts +1 -1
- package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
- package/src/search/__tests__/search-adapter.test.ts +1 -1
- package/src/secrets/__tests__/dek-cache.test.ts +1 -3
- package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/__tests__/leak-guard.test.ts +1 -1
- package/src/secrets/__tests__/rotation.test.ts +1 -1
- package/src/stack/db.ts +25 -48
- package/src/stack/push-entity-projection-tables.ts +2 -4
- package/src/stack/table-helpers.ts +98 -61
- package/src/stack/test-stack.ts +8 -7
- package/src/testing/__tests__/db-cleanup.test.ts +40 -0
- package/src/testing/__tests__/e2e-generator.test.ts +1 -1
- package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
- package/src/testing/db-cleanup.ts +44 -0
- package/src/testing/expect-error.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/multipart-helper.ts +94 -0
- package/src/testing/shared-entities.ts +5 -5
- package/src/time/__tests__/polyfill.test.ts +1 -1
- package/src/time/__tests__/tz-context.test.ts +1 -1
- package/src/utils/__tests__/assert.test.ts +1 -1
- package/src/utils/__tests__/env-parse.test.ts +1 -1
- package/CHANGELOG.md +0 -474
- package/src/db/__tests__/cursor.test.ts +0 -41
- package/src/db/__tests__/db-helpers.test.ts +0 -369
- package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
- package/src/db/__tests__/row-helpers.test.ts +0 -59
- package/src/engine/steps/_drizzle-boundary.ts +0 -19
- package/src/files/__tests__/file-field-column.integration.ts +0 -103
package/src/engine/types/step.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// (see TS-typing notes in the design doc). M.1 uses unsafeAppendEvent
|
|
10
10
|
// semantics under the hood for r.step.aggregate.appendEvent.
|
|
11
11
|
|
|
12
|
-
import type { SQL, Table } from "drizzle-orm";
|
|
13
12
|
import type { EventStoreExecutor } from "../../db/event-store-executor";
|
|
13
|
+
import type { WhereObject } from "../../db/query";
|
|
14
14
|
import type { KumikoEventTypeMap } from "./event-type-map";
|
|
15
15
|
import type { HandlerContext, WriteEvent, WriteResult } from "./handlers";
|
|
16
16
|
import type { SaveContext } from "./hooks";
|
|
@@ -200,31 +200,31 @@ export type StepNamespace = {
|
|
|
200
200
|
// and NOT registered as an aggregate-table via r.entity. See
|
|
201
201
|
// step-vocabulary.md "Was unsafeProjection.* überspringt".
|
|
202
202
|
readonly unsafeProjectionUpsert: (args: {
|
|
203
|
-
readonly table:
|
|
203
|
+
readonly table: unknown;
|
|
204
204
|
readonly on: readonly string[];
|
|
205
205
|
readonly row: StepResolver<Record<string, unknown>>;
|
|
206
206
|
}) => StepInstance;
|
|
207
207
|
// Sibling: delete row(s) from a read-side projection table. Same
|
|
208
208
|
// boot-validation contract as unsafeProjectionUpsert.
|
|
209
209
|
readonly unsafeProjectionDelete: (args: {
|
|
210
|
-
readonly table:
|
|
211
|
-
readonly where: StepResolver<
|
|
210
|
+
readonly table: unknown;
|
|
211
|
+
readonly where: StepResolver<WhereObject>;
|
|
212
212
|
}) => StepInstance;
|
|
213
|
-
// Read sub-namespace — thin wrapper on
|
|
214
|
-
// tenant-filter (does NOT auto-inject like ctx.queryProjection does).
|
|
213
|
+
// Read sub-namespace — thin wrapper on selectMany/fetchOne (bun-db).
|
|
214
|
+
// Caller-owned tenant-filter (does NOT auto-inject like ctx.queryProjection does).
|
|
215
215
|
readonly read: {
|
|
216
216
|
readonly findOne: (
|
|
217
217
|
name: string,
|
|
218
218
|
opts: {
|
|
219
|
-
readonly table:
|
|
220
|
-
readonly where: StepResolver<
|
|
219
|
+
readonly table: unknown;
|
|
220
|
+
readonly where: StepResolver<WhereObject | undefined>;
|
|
221
221
|
},
|
|
222
222
|
) => StepInstance;
|
|
223
223
|
readonly findMany: (
|
|
224
224
|
name: string,
|
|
225
225
|
opts: {
|
|
226
|
-
readonly table:
|
|
227
|
-
readonly where?: StepResolver<
|
|
226
|
+
readonly table: unknown;
|
|
227
|
+
readonly where?: StepResolver<WhereObject | undefined>;
|
|
228
228
|
readonly limit?: number;
|
|
229
229
|
},
|
|
230
230
|
) => StepInstance;
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
// caught by this validator. A future lint-rule will enforce the contract
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
|
|
26
|
-
import { getTableName, type Table } from "drizzle-orm";
|
|
27
26
|
import { getStep } from "./define-step";
|
|
28
27
|
import { buildPipelineSteps } from "./pipeline";
|
|
29
28
|
import type { FeatureDefinition, SessionUser, TenantId, WriteEvent } from "./types";
|
|
@@ -56,7 +55,28 @@ function* walkAllSteps(steps: readonly StepInstance[]): Generator<StepInstance,
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
// @cast-boundary drizzle-bridge — reads table name from drizzle Symbol
|
|
59
|
+
// without importing drizzle-orm (bun-db pattern, see bun-db/query.ts).
|
|
60
|
+
const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
|
|
61
|
+
|
|
62
|
+
function resolveTableNameFromStep(table: unknown): string {
|
|
63
|
+
if (typeof table === "object" && table !== null) {
|
|
64
|
+
// EntityTableMeta discriminator
|
|
65
|
+
if (
|
|
66
|
+
"source" in table &&
|
|
67
|
+
"tableName" in table &&
|
|
68
|
+
typeof (table as Record<string, unknown>)["tableName"] === "string"
|
|
69
|
+
) {
|
|
70
|
+
return (table as Record<string, unknown>)["tableName"] as string;
|
|
71
|
+
}
|
|
72
|
+
// drizzle pgTable
|
|
73
|
+
const name = (table as Record<symbol, unknown>)[KUMIKO_NAME_SYMBOL];
|
|
74
|
+
if (typeof name === "string") return name;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`validate-projection-allowlist: cannot resolve table name from ${String(table)}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type UnsafeProjectionStepArgs = { readonly table: unknown };
|
|
60
80
|
|
|
61
81
|
const DUMMY_USER: SessionUser = {
|
|
62
82
|
id: "00000000-0000-0000-0000-000000000000",
|
|
@@ -137,7 +157,7 @@ export function validateProjectionAllowlist(features: readonly FeatureDefinition
|
|
|
137
157
|
`without a \`table\` argument.`,
|
|
138
158
|
);
|
|
139
159
|
}
|
|
140
|
-
const tableName =
|
|
160
|
+
const tableName = resolveTableNameFromStep(stepArgs.table);
|
|
141
161
|
|
|
142
162
|
const aggregateOwner = aggregateTables.get(tableName);
|
|
143
163
|
if (aggregateOwner) {
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
// so a future refactor that drops the merge fails here instead of silently
|
|
13
13
|
// regressing to the Welle-2.5 state.
|
|
14
14
|
|
|
15
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
17
18
|
import { createRegistry, defineFeature } from "../../engine";
|
|
18
19
|
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
19
20
|
import { createEventConsumerStateTable } from "../../pipeline";
|
|
20
|
-
import {
|
|
21
|
+
import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
|
|
21
22
|
import { waitFor } from "../../testing";
|
|
22
23
|
import { createAllInOneEntrypoint } from "../index";
|
|
23
24
|
|
|
@@ -72,7 +73,7 @@ const mixedLaneFeature = defineFeature("mixed", (r) => {
|
|
|
72
73
|
const JWT = "entrypoint-wiring-test-secret-must-be-32-chars!";
|
|
73
74
|
const adminUser = TestUsers.admin;
|
|
74
75
|
|
|
75
|
-
let testDb:
|
|
76
|
+
let testDb: BunTestDb;
|
|
76
77
|
let testRedis: TestRedis;
|
|
77
78
|
|
|
78
79
|
beforeAll(async () => {
|
package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts}
RENAMED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
// guard — buildServer always wires an SSE consumer so this only
|
|
12
12
|
// fires with systemConsumers explicitly disabled).
|
|
13
13
|
|
|
14
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
16
17
|
import { createRegistry, defineFeature } from "../../engine";
|
|
17
18
|
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
18
19
|
import { createEventConsumerStateTable } from "../../pipeline";
|
|
19
|
-
import {
|
|
20
|
+
import { createTestRedis, type TestRedis } from "../../stack";
|
|
20
21
|
import { createAllInOneEntrypoint, createApiEntrypoint, createWorkerEntrypoint } from "../index";
|
|
21
22
|
|
|
22
23
|
const splitFeature = defineFeature("split", (r) => {
|
|
@@ -38,7 +39,7 @@ function uniquePrefix(label: string): string {
|
|
|
38
39
|
return `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
let testDb:
|
|
42
|
+
let testDb: BunTestDb;
|
|
42
43
|
let testRedis: TestRedis;
|
|
43
44
|
|
|
44
45
|
beforeAll(async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from "
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { defineFeature } from "../../engine/define-feature";
|
|
4
4
|
import { camelCase, composeEnvSchema, KumikoBootError, parseEnv, pulumiConfigKey } from "../index";
|
|
@@ -10,25 +10,27 @@
|
|
|
10
10
|
// Feature in den Tests zu schwer wäre — wir testen nur den Read-Helper-
|
|
11
11
|
// Layer, nicht die volle Event-Store-Pipeline.
|
|
12
12
|
|
|
13
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
14
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
14
15
|
import { tmpdir } from "node:os";
|
|
15
16
|
import { join } from "node:path";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
18
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
19
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
19
20
|
import { createSeedMigrationContext } from "../context";
|
|
20
21
|
import { createEsOperationsTable, esOperationsTable } from "../operations-schema";
|
|
21
22
|
import { runPendingSeedMigrations } from "../runner";
|
|
22
23
|
|
|
23
|
-
let testDb:
|
|
24
|
+
let testDb: BunTestDb;
|
|
24
25
|
|
|
25
26
|
beforeAll(async () => {
|
|
27
|
+
await ensureTemporalPolyfill();
|
|
26
28
|
testDb = await createTestDb();
|
|
27
29
|
await createEsOperationsTable(testDb.db);
|
|
28
30
|
|
|
29
31
|
// Minimal-Schema-Stubs für die 3 Read-Tabellen die context.ts liest.
|
|
30
32
|
// Spalten matchen production (siehe Sysadmin-Stream-Tenant-Bug Memory).
|
|
31
|
-
await testDb.db.
|
|
33
|
+
await asRawClient(testDb.db).unsafe(`
|
|
32
34
|
CREATE TABLE IF NOT EXISTS read_users (
|
|
33
35
|
id uuid PRIMARY KEY,
|
|
34
36
|
email text NOT NULL,
|
|
@@ -54,7 +56,7 @@ afterAll(async () => {
|
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
beforeEach(async () => {
|
|
57
|
-
await testDb.db.
|
|
59
|
+
await asRawClient(testDb.db).unsafe(`
|
|
58
60
|
TRUNCATE kumiko_es_operations, kumiko_events, read_users, read_tenant_memberships, read_tenants
|
|
59
61
|
RESTART IDENTITY CASCADE
|
|
60
62
|
`);
|
|
@@ -70,26 +72,32 @@ async function insertMembershipWithEvent(args: {
|
|
|
70
72
|
readonly streamTenantId: string;
|
|
71
73
|
readonly roles: string;
|
|
72
74
|
}): Promise<void> {
|
|
73
|
-
await testDb.db.
|
|
75
|
+
await asRawClient(testDb.db).unsafe(
|
|
76
|
+
`
|
|
74
77
|
INSERT INTO read_tenant_memberships (id, user_id, tenant_id, roles)
|
|
75
|
-
VALUES ($
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
VALUES ($1::uuid, $2, $3::uuid, $4)
|
|
79
|
+
`,
|
|
80
|
+
[args.id, args.userId, args.payloadTenantId, args.roles],
|
|
81
|
+
);
|
|
82
|
+
await asRawClient(testDb.db).unsafe(
|
|
83
|
+
`
|
|
78
84
|
INSERT INTO kumiko_events
|
|
79
85
|
(aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
80
86
|
VALUES
|
|
81
|
-
($
|
|
87
|
+
($1::uuid, 'tenant-membership', $2::uuid, 1,
|
|
82
88
|
'tenant-membership.created', '{}'::jsonb, '{"userId":"system"}'::jsonb, 'system')
|
|
83
|
-
|
|
89
|
+
`,
|
|
90
|
+
[args.id, args.streamTenantId],
|
|
91
|
+
);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
function makeMockDispatcher() {
|
|
87
95
|
return {
|
|
88
|
-
write:
|
|
89
|
-
query:
|
|
90
|
-
command:
|
|
91
|
-
batch:
|
|
92
|
-
resolveAuthClaims:
|
|
96
|
+
write: mock(async () => ({ isSuccess: true as const, data: {} })),
|
|
97
|
+
query: mock(),
|
|
98
|
+
command: mock(),
|
|
99
|
+
batch: mock(),
|
|
100
|
+
resolveAuthClaims: mock(),
|
|
93
101
|
};
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -105,10 +113,13 @@ describe("SeedMigrationContext.findUserByEmail (integration)", () => {
|
|
|
105
113
|
test("liest existing user-row korrekt + maps tenant_id → tenantId", async () => {
|
|
106
114
|
const userId = "01900000-0000-7000-8000-000000000001";
|
|
107
115
|
const tenantId = "00000000-0000-4000-8000-000000000099";
|
|
108
|
-
await testDb.db.
|
|
116
|
+
await asRawClient(testDb.db).unsafe(
|
|
117
|
+
`
|
|
109
118
|
INSERT INTO read_users (id, email, tenant_id)
|
|
110
|
-
VALUES ($
|
|
111
|
-
|
|
119
|
+
VALUES ($1::uuid, 'admin@example.com', $2::uuid)
|
|
120
|
+
`,
|
|
121
|
+
[userId, tenantId],
|
|
122
|
+
);
|
|
112
123
|
|
|
113
124
|
const ctx = createSeedMigrationContext({
|
|
114
125
|
dispatcher: makeMockDispatcher() as never,
|
|
@@ -231,11 +242,14 @@ describe("SeedMigrationContext.findMembershipsOfUser (integration)", () => {
|
|
|
231
242
|
// statt einer mit fehlendem stream-tenant zu arbeiten und schwer
|
|
232
243
|
// diagnostizierbare version_conflict-Errors zu produzieren.
|
|
233
244
|
const userId = "01900000-0000-7000-8000-000000000003";
|
|
234
|
-
await testDb.db.
|
|
245
|
+
await asRawClient(testDb.db).unsafe(
|
|
246
|
+
`
|
|
235
247
|
INSERT INTO read_tenant_memberships (id, user_id, tenant_id, roles) VALUES
|
|
236
|
-
('00000000-0000-4000-8000-0000000000d1'::uuid, $
|
|
248
|
+
('00000000-0000-4000-8000-0000000000d1'::uuid, $1,
|
|
237
249
|
'00000000-0000-4000-8000-000000000005'::uuid, '["Admin"]')
|
|
238
|
-
|
|
250
|
+
`,
|
|
251
|
+
[userId],
|
|
252
|
+
);
|
|
239
253
|
const ctx = createSeedMigrationContext({
|
|
240
254
|
dispatcher: makeMockDispatcher() as never,
|
|
241
255
|
dbRunner: testDb.db,
|
|
@@ -247,7 +261,7 @@ describe("SeedMigrationContext.findMembershipsOfUser (integration)", () => {
|
|
|
247
261
|
|
|
248
262
|
describe("SeedMigrationContext.findTenants (integration)", () => {
|
|
249
263
|
test("returnt alle Tenants sortiert nach inserted_at", async () => {
|
|
250
|
-
await testDb.db.
|
|
264
|
+
await asRawClient(testDb.db).unsafe(`
|
|
251
265
|
INSERT INTO read_tenants (id, name, tenant_key, inserted_at) VALUES
|
|
252
266
|
('00000000-0000-4000-8000-000000000002'::uuid, 'Beta', 'beta', '2026-01-02'),
|
|
253
267
|
('00000000-0000-4000-8000-000000000001'::uuid, 'Alpha', 'alpha', '2026-01-01')
|
|
@@ -296,7 +310,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
|
|
|
296
310
|
|
|
297
311
|
// Kritisch: KEIN Marker — beim nächsten Boot ohne env-flag würde
|
|
298
312
|
// der Seed dann tatsächlich laufen.
|
|
299
|
-
const markers = await testDb.db
|
|
313
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
300
314
|
expect(markers).toHaveLength(0);
|
|
301
315
|
} finally {
|
|
302
316
|
delete process.env[envKey];
|
|
@@ -329,7 +343,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
|
|
|
329
343
|
expect(r.appliedIds).toEqual(["2026-05-20-skippable-but-no-flag"]);
|
|
330
344
|
expect(r.skippedIds).toEqual([]);
|
|
331
345
|
|
|
332
|
-
const markers = await testDb.db
|
|
346
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
333
347
|
expect(markers).toHaveLength(1);
|
|
334
348
|
} finally {
|
|
335
349
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -341,7 +355,7 @@ describe("runPendingSeedMigrations: skippable + env-flag (integration)", () => {
|
|
|
341
355
|
|
|
342
356
|
describe("SeedMigrationContext.db (escape-hatch, integration)", () => {
|
|
343
357
|
test("ctx.db kann für eigene Lookups genutzt werden (read-only)", async () => {
|
|
344
|
-
await testDb.db.
|
|
358
|
+
await asRawClient(testDb.db).unsafe(`
|
|
345
359
|
INSERT INTO read_tenants (id, name, tenant_key) VALUES
|
|
346
360
|
('00000000-0000-4000-8000-000000000007'::uuid, 'Lucky', 'lucky')
|
|
347
361
|
`);
|
|
@@ -349,8 +363,8 @@ describe("SeedMigrationContext.db (escape-hatch, integration)", () => {
|
|
|
349
363
|
dispatcher: makeMockDispatcher() as never,
|
|
350
364
|
dbRunner: testDb.db,
|
|
351
365
|
});
|
|
352
|
-
const rows = (await ctx.db.
|
|
353
|
-
|
|
366
|
+
const rows = (await asRawClient(ctx.db).unsafe(
|
|
367
|
+
`SELECT name FROM read_tenants WHERE tenant_key = 'lucky'`,
|
|
354
368
|
)) as unknown as readonly { name: string }[];
|
|
355
369
|
expect(rows[0]?.name).toBe("Lucky");
|
|
356
370
|
});
|
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
// Heavy lifting (mock-dispatcher, in-memory-applied-set) liegt in
|
|
9
9
|
// runner.test.ts. Hier nur DB-Round-Trip-Wahrheit.
|
|
10
10
|
|
|
11
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
12
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
13
|
import { tmpdir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
16
|
+
import { asRawClient, insertOne, selectMany } from "../../db/query";
|
|
17
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
17
18
|
import { createSeedMigrationContext } from "../context";
|
|
18
19
|
import { createEsOperationsTable, esOperationsTable } from "../operations-schema";
|
|
19
20
|
import { runPendingSeedMigrations } from "../runner";
|
|
20
21
|
|
|
21
|
-
let testDb:
|
|
22
|
+
let testDb: BunTestDb;
|
|
22
23
|
|
|
23
24
|
beforeAll(async () => {
|
|
25
|
+
await ensureTemporalPolyfill();
|
|
24
26
|
testDb = await createTestDb();
|
|
25
27
|
await createEsOperationsTable(testDb.db);
|
|
26
28
|
});
|
|
@@ -30,7 +32,7 @@ afterAll(async () => {
|
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
beforeEach(async () => {
|
|
33
|
-
await testDb.db.
|
|
35
|
+
await asRawClient(testDb.db).unsafe(`TRUNCATE kumiko_es_operations RESTART IDENTITY`);
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
function makeTempSeedsDir(files: readonly { name: string; content: string }[]): string {
|
|
@@ -42,14 +44,14 @@ function makeTempSeedsDir(files: readonly { name: string; content: string }[]):
|
|
|
42
44
|
function makeMockDispatcher() {
|
|
43
45
|
const calls: Array<{ qn: string; payload: unknown }> = [];
|
|
44
46
|
return {
|
|
45
|
-
write:
|
|
47
|
+
write: mock(async (qn: string, payload: unknown) => {
|
|
46
48
|
calls.push({ qn, payload });
|
|
47
49
|
return { isSuccess: true as const, data: {} };
|
|
48
50
|
}),
|
|
49
|
-
query:
|
|
50
|
-
command:
|
|
51
|
-
batch:
|
|
52
|
-
resolveAuthClaims:
|
|
51
|
+
query: mock(),
|
|
52
|
+
command: mock(),
|
|
53
|
+
batch: mock(),
|
|
54
|
+
resolveAuthClaims: mock(),
|
|
53
55
|
calls,
|
|
54
56
|
};
|
|
55
57
|
}
|
|
@@ -82,7 +84,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
82
84
|
expect(r1.appliedIds).toEqual(["2026-05-20-noop"]);
|
|
83
85
|
|
|
84
86
|
// Marker landed
|
|
85
|
-
const markers1 = await testDb.db
|
|
87
|
+
const markers1 = await selectMany(testDb.db, esOperationsTable);
|
|
86
88
|
expect(markers1).toHaveLength(1);
|
|
87
89
|
expect(markers1[0]?.id).toBe("2026-05-20-noop");
|
|
88
90
|
expect(markers1[0]?.operationType).toBe("seed-migration");
|
|
@@ -98,7 +100,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
98
100
|
logger: () => {},
|
|
99
101
|
});
|
|
100
102
|
expect(r2.appliedIds).toEqual([]);
|
|
101
|
-
const markers2 = await testDb.db
|
|
103
|
+
const markers2 = await selectMany(testDb.db, esOperationsTable);
|
|
102
104
|
expect(markers2).toHaveLength(1);
|
|
103
105
|
} finally {
|
|
104
106
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -130,7 +132,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
130
132
|
}),
|
|
131
133
|
).rejects.toThrow(/boom/);
|
|
132
134
|
|
|
133
|
-
const markers = await testDb.db
|
|
135
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
134
136
|
expect(markers).toHaveLength(0);
|
|
135
137
|
} finally {
|
|
136
138
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -192,14 +194,14 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
192
194
|
]);
|
|
193
195
|
try {
|
|
194
196
|
const dispatcher = {
|
|
195
|
-
write:
|
|
197
|
+
write: mock(async () => ({
|
|
196
198
|
isSuccess: false as const,
|
|
197
199
|
error: { code: "version_conflict", message: "stream changed" },
|
|
198
200
|
})),
|
|
199
|
-
query:
|
|
200
|
-
command:
|
|
201
|
-
batch:
|
|
202
|
-
resolveAuthClaims:
|
|
201
|
+
query: mock(),
|
|
202
|
+
command: mock(),
|
|
203
|
+
batch: mock(),
|
|
204
|
+
resolveAuthClaims: mock(),
|
|
203
205
|
};
|
|
204
206
|
|
|
205
207
|
await expect(
|
|
@@ -214,7 +216,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
214
216
|
).rejects.toThrow(/version_conflict/);
|
|
215
217
|
|
|
216
218
|
// Kein Marker — bei nächstem Boot würde der Seed retried
|
|
217
|
-
const markers = await testDb.db
|
|
219
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
218
220
|
expect(markers).toHaveLength(0);
|
|
219
221
|
} finally {
|
|
220
222
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -264,7 +266,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
264
266
|
expect(dispatcher.write).toHaveBeenCalledTimes(1);
|
|
265
267
|
// Marker NICHT gesetzt — retry beim nächsten Boot wird die Migration
|
|
266
268
|
// nochmal ausführen. Wenn der Write nicht idempotent ist → Duplikat.
|
|
267
|
-
const markers = await testDb.db
|
|
269
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
268
270
|
expect(markers).toHaveLength(0);
|
|
269
271
|
} finally {
|
|
270
272
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -293,7 +295,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
293
295
|
]);
|
|
294
296
|
try {
|
|
295
297
|
// Pre-seed marker als wäre ein parallel-Pod schon durch
|
|
296
|
-
await testDb.db
|
|
298
|
+
await insertOne(testDb.db, esOperationsTable, {
|
|
297
299
|
id: "2026-05-20-race",
|
|
298
300
|
operationType: "seed-migration",
|
|
299
301
|
durationMs: 42,
|
|
@@ -318,7 +320,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
318
320
|
});
|
|
319
321
|
|
|
320
322
|
expect(dispatcher.write).not.toHaveBeenCalled();
|
|
321
|
-
const markers = await testDb.db
|
|
323
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
322
324
|
expect(markers).toHaveLength(1); // nur der pre-seeded
|
|
323
325
|
} finally {
|
|
324
326
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -354,7 +356,7 @@ describe("runPendingSeedMigrations (integration)", () => {
|
|
|
354
356
|
).rejects.toThrow(/stop here/);
|
|
355
357
|
|
|
356
358
|
// Nur first hat marker — fails warf, never wurde nie attempted
|
|
357
|
-
const markers = await testDb.db
|
|
359
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
358
360
|
expect(markers.map((m) => m.id)).toEqual(["2026-05-19-first"]);
|
|
359
361
|
} finally {
|
|
360
362
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// dispatcher-call) testen wir gegen Postgres in der integration-test.
|
|
3
3
|
// Hier nur die pure-logic-Pfade die kein echtes DB brauchen.
|
|
4
4
|
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
5
6
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { tmpdir } from "node:os";
|
|
7
8
|
import { join } from "node:path";
|
|
8
|
-
import { describe, expect, test } from "vitest";
|
|
9
9
|
import { runPendingSeedMigrations } from "../runner";
|
|
10
10
|
|
|
11
11
|
function makeTempSeedsDir(files: readonly { name: string; content: string }[]): string {
|
|
@@ -14,31 +14,41 @@ function makeTempSeedsDir(files: readonly { name: string; content: string }[]):
|
|
|
14
14
|
return dir;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Minimal DB-Stub — Runner
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// Minimal DB-Stub — Runner uses bun-db helpers (selectMany, insertOne) +
|
|
18
|
+
// asRawClient(tx).unsafe(...) for the advisory lock + re-check + insert.
|
|
19
|
+
// Returns parsed rows based on the SQL text shape:
|
|
20
|
+
// SELECT * FROM "kumiko_es_operations" ... → applied-set rows
|
|
21
|
+
// SELECT pg_advisory_xact_lock(...) → ignored
|
|
22
|
+
// SELECT 1 FROM "kumiko_es_operations" ... → empty (= not applied)
|
|
23
|
+
// INSERT INTO "kumiko_es_operations" ... → record + add to applied
|
|
20
24
|
function makeStubDb(initialApplied: readonly string[] = []) {
|
|
21
25
|
const inserts: Array<Record<string, unknown>> = [];
|
|
22
26
|
const applied = new Set(initialApplied);
|
|
27
|
+
const unsafe = async (
|
|
28
|
+
sqlText: string,
|
|
29
|
+
params?: readonly unknown[],
|
|
30
|
+
): Promise<readonly unknown[]> => {
|
|
31
|
+
if (/SELECT \* FROM "kumiko_es_operations"/.test(sqlText)) {
|
|
32
|
+
return Array.from(applied).map((id) => ({
|
|
33
|
+
id,
|
|
34
|
+
operationType: "seed-migration",
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
if (/INSERT INTO "kumiko_es_operations"/.test(sqlText)) {
|
|
38
|
+
const id = String(params?.[0]);
|
|
39
|
+
inserts.push({ id });
|
|
40
|
+
applied.add(id);
|
|
41
|
+
return [{ id }];
|
|
42
|
+
}
|
|
43
|
+
// pg_advisory_xact_lock + re-check both yield empty → "not applied, run".
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
23
46
|
const db = {
|
|
47
|
+
unsafe,
|
|
48
|
+
begin: async (cb: (tx: unknown) => Promise<unknown>) => cb(db),
|
|
24
49
|
transaction: async (cb: (tx: unknown) => Promise<void>) => {
|
|
25
50
|
await cb(db);
|
|
26
51
|
},
|
|
27
|
-
select: () => ({
|
|
28
|
-
from: () => ({
|
|
29
|
-
where: async () => Array.from(applied).map((id) => ({ id })),
|
|
30
|
-
}),
|
|
31
|
-
}),
|
|
32
|
-
insert: () => ({
|
|
33
|
-
values: async (row: Record<string, unknown>) => {
|
|
34
|
-
inserts.push(row);
|
|
35
|
-
if (typeof row["id"] === "string") applied.add(row["id"]);
|
|
36
|
-
},
|
|
37
|
-
}),
|
|
38
|
-
// execute: für pg_advisory_xact_lock + re-check. Leere Liste = "nicht
|
|
39
|
-
// applied im Inner-Lock-Scope, weiter mit Run". applied-set check via
|
|
40
|
-
// select() oben wird sowieso schon angewendet.
|
|
41
|
-
execute: async (_q: unknown) => [],
|
|
42
52
|
};
|
|
43
53
|
return { db, inserts, applied };
|
|
44
54
|
}
|
package/src/es-ops/context.ts
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
// config-seed.ts:40). Events haben createdBy = SYSTEM_TENANT_ID-User
|
|
8
8
|
// → audit-fähig.
|
|
9
9
|
|
|
10
|
-
import { sql } from "drizzle-orm";
|
|
11
10
|
import type { DbRunner } from "../db";
|
|
11
|
+
import {
|
|
12
|
+
selectAllTenants,
|
|
13
|
+
selectMembershipsOfUser,
|
|
14
|
+
selectUserByEmail,
|
|
15
|
+
} from "../db/queries/seed-context";
|
|
12
16
|
import { createSystemUser, SYSTEM_TENANT_ID } from "../engine";
|
|
13
17
|
import type { Dispatcher } from "../pipeline/dispatcher";
|
|
14
18
|
import type { SeedMembershipRow, SeedMigrationContext, SeedTenantRow } from "./types";
|
|
@@ -59,46 +63,13 @@ export function createSeedMigrationContext(
|
|
|
59
63
|
},
|
|
60
64
|
|
|
61
65
|
findUserByEmail: async (email) => {
|
|
62
|
-
|
|
63
|
-
// UserQueries.findForAuth aber ohne Dispatcher-Roundtrip; Seeds
|
|
64
|
-
// greifen oft 1-N Lookups → direkt schneller).
|
|
65
|
-
// @cast-boundary db-row — drizzle execute(sql) returns row-array
|
|
66
|
-
// direkt (kein { rows }-Wrapper); column-types vom SQL-Cast oben
|
|
67
|
-
const rows = (await args.dbRunner.execute(
|
|
68
|
-
sql`SELECT id::text AS id, email, tenant_id::text AS tenant_id
|
|
69
|
-
FROM read_users
|
|
70
|
-
WHERE email = ${email}
|
|
71
|
-
LIMIT 1`,
|
|
72
|
-
)) as unknown as readonly { id: string; email: string; tenant_id: string }[];
|
|
73
|
-
const row = rows[0];
|
|
66
|
+
const row = await selectUserByEmail(args.dbRunner, email);
|
|
74
67
|
if (!row) return null;
|
|
75
|
-
return { id: row.id, email: row.email, tenantId: row.
|
|
68
|
+
return { id: row.id, email: row.email, tenantId: row.tenantId };
|
|
76
69
|
},
|
|
77
70
|
|
|
78
71
|
findMembershipsOfUser: async (userId) => {
|
|
79
|
-
|
|
80
|
-
// der v1-Row) neben dem payload-tenant (memberships.tenant_id) zu
|
|
81
|
-
// liefern. Die beiden divergieren wenn das Aggregate von einem
|
|
82
|
-
// Executor mit fremder tenantId angelegt wurde (seedTenantMembership
|
|
83
|
-
// by=systemAdmin) — typischer publicstatus-Driver-Use-Case.
|
|
84
|
-
// INNER (nicht LEFT): kein v1-Event bei vorhandener Read-Row wäre
|
|
85
|
-
// Data-Drift, kein legitimer Zustand für Seed-Migrations.
|
|
86
|
-
// @cast-boundary db-row — roles ist JSON-string in der text-Spalte
|
|
87
|
-
// (Memory: tenant-membership.created payload "[\"User\"]"), wird unten geparst
|
|
88
|
-
const rows = (await args.dbRunner.execute(
|
|
89
|
-
sql`SELECT m.user_id::text AS user_id,
|
|
90
|
-
m.tenant_id::text AS tenant_id,
|
|
91
|
-
e.tenant_id::text AS stream_tenant_id,
|
|
92
|
-
m.roles
|
|
93
|
-
FROM read_tenant_memberships m
|
|
94
|
-
JOIN kumiko_events e ON e.aggregate_id = m.id AND e.version = 1
|
|
95
|
-
WHERE m.user_id = ${userId}`,
|
|
96
|
-
)) as unknown as readonly {
|
|
97
|
-
user_id: string;
|
|
98
|
-
tenant_id: string;
|
|
99
|
-
stream_tenant_id: string;
|
|
100
|
-
roles: string;
|
|
101
|
-
}[];
|
|
72
|
+
const rows = await selectMembershipsOfUser(args.dbRunner, userId);
|
|
102
73
|
return rows.map(
|
|
103
74
|
(r): SeedMembershipRow => ({
|
|
104
75
|
userId: r.user_id,
|
|
@@ -110,12 +81,7 @@ export function createSeedMigrationContext(
|
|
|
110
81
|
},
|
|
111
82
|
|
|
112
83
|
findTenants: async () => {
|
|
113
|
-
|
|
114
|
-
const rows = (await args.dbRunner.execute(
|
|
115
|
-
sql`SELECT id::text AS id, name, tenant_key
|
|
116
|
-
FROM read_tenants
|
|
117
|
-
ORDER BY inserted_at`,
|
|
118
|
-
)) as unknown as readonly { id: string; name: string; tenant_key: string }[];
|
|
84
|
+
const rows = await selectAllTenants(args.dbRunner);
|
|
119
85
|
return rows.map((r): SeedTenantRow => ({ id: r.id, name: r.name, tenantKey: r.tenant_key }));
|
|
120
86
|
},
|
|
121
87
|
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
// bunx kumiko ops seed:status → operation_type = "seed-migration"
|
|
11
11
|
// bunx kumiko ops projection:status → operation_type = "projection-rebuild"
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// sql now comes from native dialect
|
|
14
14
|
import { type DbConnection, tableExists } from "../db";
|
|
15
|
-
import { index, integer, table as pgTable, text, timestamp } from "../db/dialect";
|
|
15
|
+
import { index, integer, table as pgTable, sql, text, timestamp } from "../db/dialect";
|
|
16
16
|
import { unsafePushTables } from "../stack";
|
|
17
17
|
|
|
18
18
|
export type EsOperationType = "seed-migration";
|