@cosmicdrift/kumiko-framework 0.13.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 +7 -7
- 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 -472
- 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/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts}
RENAMED
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
// carry a different state row (kumiko_event_consumers, not kumiko_projections)
|
|
16
16
|
// and a different apply signature (3rd ctx arg).
|
|
17
17
|
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
19
|
+
import { sql } from "@cosmicdrift/kumiko-framework/db";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
|
|
22
22
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
23
|
-
import {
|
|
23
|
+
import { asRawClient, selectMany, updateMany } from "../../db/query";
|
|
24
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
24
25
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
25
26
|
import {
|
|
26
27
|
eventConsumerStateTable,
|
|
@@ -41,13 +42,13 @@ const invoiceEntity = createEntity({
|
|
|
41
42
|
table: "read_mspreb_invoices",
|
|
42
43
|
fields: { customer: createTextField({ required: true }) },
|
|
43
44
|
});
|
|
44
|
-
const invoiceTable =
|
|
45
|
+
const invoiceTable = buildEntityTable("msp-reb-invoice", invoiceEntity);
|
|
45
46
|
|
|
46
47
|
const paymentEntity = createEntity({
|
|
47
48
|
table: "read_mspreb_payments",
|
|
48
49
|
fields: { customer: createTextField({ required: true }) },
|
|
49
50
|
});
|
|
50
|
-
const paymentTable =
|
|
51
|
+
const paymentTable = buildEntityTable("msp-reb-payment", paymentEntity);
|
|
51
52
|
|
|
52
53
|
// Main read-model: running balance per customer.
|
|
53
54
|
const balanceTable = pgTable("read_mspreb_balance", {
|
|
@@ -88,33 +89,17 @@ const feature = defineFeature("mspreb", (r) => {
|
|
|
88
89
|
apply: {
|
|
89
90
|
[invoiceBilled.name]: async (event, tx) => {
|
|
90
91
|
const p = event.payload as { customer: string; cents: number };
|
|
91
|
-
await tx
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
tenantId: event.tenantId,
|
|
96
|
-
invoicesCents: p.cents,
|
|
97
|
-
paymentsCents: 0,
|
|
98
|
-
})
|
|
99
|
-
.onConflictDoUpdate({
|
|
100
|
-
target: balanceTable.customer,
|
|
101
|
-
set: { invoicesCents: sql`${balanceTable.invoicesCents} + ${p.cents}` },
|
|
102
|
-
});
|
|
92
|
+
await asRawClient(tx).unsafe(
|
|
93
|
+
`INSERT INTO "read_mspreb_balance" (customer, tenant_id, invoices_cents, payments_cents) VALUES ($1::uuid, $2::uuid, $3, 0) ON CONFLICT (customer) DO UPDATE SET invoices_cents = read_mspreb_balance.invoices_cents + $3`,
|
|
94
|
+
[p.customer, event.tenantId, p.cents],
|
|
95
|
+
);
|
|
103
96
|
},
|
|
104
97
|
[paymentReceived.name]: async (event, tx) => {
|
|
105
98
|
const p = event.payload as { customer: string; cents: number };
|
|
106
|
-
await tx
|
|
107
|
-
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
tenantId: event.tenantId,
|
|
111
|
-
invoicesCents: 0,
|
|
112
|
-
paymentsCents: p.cents,
|
|
113
|
-
})
|
|
114
|
-
.onConflictDoUpdate({
|
|
115
|
-
target: balanceTable.customer,
|
|
116
|
-
set: { paymentsCents: sql`${balanceTable.paymentsCents} + ${p.cents}` },
|
|
117
|
-
});
|
|
99
|
+
await asRawClient(tx).unsafe(
|
|
100
|
+
`INSERT INTO "read_mspreb_balance" (customer, tenant_id, invoices_cents, payments_cents) VALUES ($1::uuid, $2::uuid, 0, $3) ON CONFLICT (customer) DO UPDATE SET payments_cents = read_mspreb_balance.payments_cents + $3`,
|
|
101
|
+
[p.customer, event.tenantId, p.cents],
|
|
102
|
+
);
|
|
118
103
|
},
|
|
119
104
|
},
|
|
120
105
|
});
|
|
@@ -248,10 +233,12 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
|
|
|
248
233
|
// Disable the saga MSP for this test — it runs on the same event types
|
|
249
234
|
// and would trip its own ctx.appendEvent path during live delivery
|
|
250
235
|
// (which is fine in production, but noise here).
|
|
251
|
-
await
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
236
|
+
await updateMany(
|
|
237
|
+
stack.db,
|
|
238
|
+
eventConsumerStateTable,
|
|
239
|
+
{ status: "disabled", updatedAt: sql`now()` },
|
|
240
|
+
{ name: SAGA_MSP },
|
|
241
|
+
);
|
|
255
242
|
|
|
256
243
|
await stack.http.writeOk("mspreb:write:invoice:bill", { customer: alice, cents: 10_00 }, admin);
|
|
257
244
|
await stack.http.writeOk("mspreb:write:invoice:bill", { customer: alice, cents: 5_00 }, admin);
|
|
@@ -263,7 +250,7 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
|
|
|
263
250
|
await stack.http.writeOk("mspreb:write:invoice:bill", { customer: bob, cents: 7_50 }, admin);
|
|
264
251
|
await runFullDispatcher();
|
|
265
252
|
|
|
266
|
-
const liveRows = await stack.db
|
|
253
|
+
const liveRows = await selectMany(stack.db, balanceTable);
|
|
267
254
|
const aliceLive = liveRows.find((r) => r.customer === alice);
|
|
268
255
|
const bobLive = liveRows.find((r) => r.customer === bob);
|
|
269
256
|
expect(aliceLive).toMatchObject({ invoicesCents: 15_00, paymentsCents: 3_00 });
|
|
@@ -279,7 +266,7 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
|
|
|
279
266
|
expect(result.eventsProcessed).toBe(4); // 2 invoices + 1 payment + 1 invoice
|
|
280
267
|
expect(result.lastProcessedEventId).toBeGreaterThan(0n);
|
|
281
268
|
|
|
282
|
-
const rebuiltRows = await stack.db
|
|
269
|
+
const rebuiltRows = await selectMany(stack.db, balanceTable);
|
|
283
270
|
expect(rebuiltRows).toEqual(liveRows);
|
|
284
271
|
|
|
285
272
|
// Consumer cursor is at head after rebuild — the live dispatcher should
|
|
@@ -291,28 +278,29 @@ describe("rebuildMultiStreamProjection — rebuildable read-model", () => {
|
|
|
291
278
|
|
|
292
279
|
test("rebuild after table corruption restores the correct state", async () => {
|
|
293
280
|
const carol = "00000000-0000-4000-8000-000000000c03";
|
|
294
|
-
await
|
|
295
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
281
|
+
await updateMany(
|
|
282
|
+
stack.db,
|
|
283
|
+
eventConsumerStateTable,
|
|
284
|
+
{ status: "disabled", updatedAt: sql`now()` },
|
|
285
|
+
{ name: SAGA_MSP },
|
|
286
|
+
);
|
|
298
287
|
await stack.http.writeOk("mspreb:write:invoice:bill", { customer: carol, cents: 42_00 }, admin);
|
|
299
288
|
await runFullDispatcher();
|
|
300
289
|
|
|
301
290
|
// Corrupt the read-model — simulate a buggy apply() landing bad numbers.
|
|
302
|
-
await
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
291
|
+
await updateMany(
|
|
292
|
+
stack.db,
|
|
293
|
+
balanceTable,
|
|
294
|
+
{ invoicesCents: -999, paymentsCents: 999 },
|
|
295
|
+
{ customer: carol },
|
|
296
|
+
);
|
|
306
297
|
|
|
307
298
|
await rebuildMultiStreamProjection(BALANCE_MSP, {
|
|
308
299
|
db: stack.db,
|
|
309
300
|
registry: stack.registry,
|
|
310
301
|
});
|
|
311
302
|
|
|
312
|
-
const [row] = await stack.db
|
|
313
|
-
.select()
|
|
314
|
-
.from(balanceTable)
|
|
315
|
-
.where(eq(balanceTable.customer, carol));
|
|
303
|
+
const [row] = await selectMany(stack.db, balanceTable, { customer: carol });
|
|
316
304
|
expect(row).toMatchObject({ invoicesCents: 42_00, paymentsCents: 0 });
|
|
317
305
|
});
|
|
318
306
|
});
|
|
@@ -339,18 +327,22 @@ describe("rebuildMultiStreamProjection — guard rails", () => {
|
|
|
339
327
|
test("saga MSP using ctx.appendEvent fails rebuild at the first appendEvent call", async () => {
|
|
340
328
|
const dave = "00000000-0000-4000-8000-000000000d04";
|
|
341
329
|
// Disable the saga in live passes so we control when the apply runs.
|
|
342
|
-
await
|
|
343
|
-
.
|
|
344
|
-
|
|
345
|
-
|
|
330
|
+
await updateMany(
|
|
331
|
+
stack.db,
|
|
332
|
+
eventConsumerStateTable,
|
|
333
|
+
{ status: "disabled", updatedAt: sql`now()` },
|
|
334
|
+
{ name: SAGA_MSP },
|
|
335
|
+
);
|
|
346
336
|
await stack.http.writeOk("mspreb:write:invoice:bill", { customer: dave, cents: 1_00 }, admin);
|
|
347
337
|
// Put the consumer back to idle so rebuild doesn't treat it as "just
|
|
348
338
|
// disabled on purpose" — rebuild is opinionated about WHEN it refuses,
|
|
349
339
|
// not about the consumer's live-status.
|
|
350
|
-
await
|
|
351
|
-
.
|
|
352
|
-
|
|
353
|
-
|
|
340
|
+
await updateMany(
|
|
341
|
+
stack.db,
|
|
342
|
+
eventConsumerStateTable,
|
|
343
|
+
{ status: "idle", updatedAt: sql`now()` },
|
|
344
|
+
{ name: SAGA_MSP },
|
|
345
|
+
);
|
|
354
346
|
|
|
355
347
|
await expect(
|
|
356
348
|
rebuildMultiStreamProjection(SAGA_MSP, {
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
// event-dispatcher — at-least-once delivery, strictly ordered by events.id
|
|
7
7
|
// per MSP consumer, dead-letters on repeated handler failures.
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
9
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
|
|
13
12
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
14
|
-
import {
|
|
13
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
14
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
15
15
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
16
16
|
import {
|
|
17
17
|
createTestUser,
|
|
@@ -28,13 +28,13 @@ const shipmentEntity = createEntity({
|
|
|
28
28
|
table: "read_msp_shipments",
|
|
29
29
|
fields: { customer: createTextField({ required: true }) },
|
|
30
30
|
});
|
|
31
|
-
const shipmentTable =
|
|
31
|
+
const shipmentTable = buildEntityTable("msp-shipment", shipmentEntity);
|
|
32
32
|
|
|
33
33
|
const refundEntity = createEntity({
|
|
34
34
|
table: "read_msp_refunds",
|
|
35
35
|
fields: { customer: createTextField({ required: true }) },
|
|
36
36
|
});
|
|
37
|
-
const refundTable =
|
|
37
|
+
const refundTable = buildEntityTable("msp-refund", refundEntity);
|
|
38
38
|
|
|
39
39
|
// Cross-cutting MSP: one row per customer, sums shipments − refunds. Key
|
|
40
40
|
// differences from a single-stream projection:
|
|
@@ -68,41 +68,19 @@ const mspFeature = defineFeature("msptest", (r) => {
|
|
|
68
68
|
apply: {
|
|
69
69
|
[shipmentBilled.name]: async (event, tx) => {
|
|
70
70
|
const p = event.payload as { customer: string; cents: number };
|
|
71
|
-
await tx
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
tenantId: event.tenantId,
|
|
76
|
-
shipments: 1,
|
|
77
|
-
refunds: 0,
|
|
78
|
-
netCents: p.cents,
|
|
79
|
-
})
|
|
80
|
-
.onConflictDoUpdate({
|
|
81
|
-
target: customerBalanceTable.customer,
|
|
82
|
-
set: {
|
|
83
|
-
shipments: sql`${customerBalanceTable.shipments} + 1`,
|
|
84
|
-
netCents: sql`${customerBalanceTable.netCents} + ${p.cents}`,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
71
|
+
await asRawClient(tx).unsafe(
|
|
72
|
+
`INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 1, 0, $3) ON CONFLICT (customer) DO UPDATE SET shipments = read_msp_customer_balance.shipments + 1, net_cents = read_msp_customer_balance.net_cents + $3`,
|
|
73
|
+
[p.customer, event.tenantId, p.cents],
|
|
74
|
+
);
|
|
87
75
|
},
|
|
88
76
|
[refundIssued.name]: async (event, tx) => {
|
|
89
77
|
const p = event.payload as { customer: string; cents: number };
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
refunds: 1,
|
|
97
|
-
netCents: -p.cents,
|
|
98
|
-
})
|
|
99
|
-
.onConflictDoUpdate({
|
|
100
|
-
target: customerBalanceTable.customer,
|
|
101
|
-
set: {
|
|
102
|
-
refunds: sql`${customerBalanceTable.refunds} + 1`,
|
|
103
|
-
netCents: sql`${customerBalanceTable.netCents} - ${p.cents}`,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
78
|
+
// -$3 ohne cast wird vom planner als "unary minus on unknown type"
|
|
79
|
+
// mehrdeutig — wir casten $3 explizit auf integer.
|
|
80
|
+
await asRawClient(tx).unsafe(
|
|
81
|
+
`INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 0, 1, -$3::integer) ON CONFLICT (customer) DO UPDATE SET refunds = read_msp_customer_balance.refunds + 1, net_cents = read_msp_customer_balance.net_cents - $3::integer`,
|
|
82
|
+
[p.customer, event.tenantId, p.cents],
|
|
83
|
+
);
|
|
106
84
|
},
|
|
107
85
|
},
|
|
108
86
|
});
|
|
@@ -207,10 +185,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
207
185
|
// Drain the dispatcher — MSPs run async.
|
|
208
186
|
await stack.eventDispatcher?.runOnce();
|
|
209
187
|
|
|
210
|
-
const rows = await stack.db
|
|
211
|
-
.select()
|
|
212
|
-
.from(customerBalanceTable)
|
|
213
|
-
.orderBy(customerBalanceTable.customer);
|
|
188
|
+
const rows = await selectMany(stack.db, customerBalanceTable);
|
|
214
189
|
const byCustomer = new Map(rows.map((r) => [r.customer, r]));
|
|
215
190
|
|
|
216
191
|
expect(byCustomer.get(customerA)).toMatchObject({
|
|
@@ -239,10 +214,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
239
214
|
expect(pass2?.byConsumer[mspName]?.processed ?? 0).toBe(0);
|
|
240
215
|
|
|
241
216
|
// Row state is stable across the no-op pass.
|
|
242
|
-
const [row] = await stack.db
|
|
243
|
-
.select()
|
|
244
|
-
.from(customerBalanceTable)
|
|
245
|
-
.where(eq(customerBalanceTable.customer, cust));
|
|
217
|
+
const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
|
|
246
218
|
expect(row?.shipments).toBe(1);
|
|
247
219
|
expect(row?.netCents).toBe(42);
|
|
248
220
|
});
|
|
@@ -273,10 +245,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
273
245
|
);
|
|
274
246
|
await stack.eventDispatcher?.runOnce();
|
|
275
247
|
|
|
276
|
-
const rows = await stack.db
|
|
277
|
-
.select()
|
|
278
|
-
.from(customerBalanceTable)
|
|
279
|
-
.orderBy(customerBalanceTable.customer);
|
|
248
|
+
const rows = await selectMany(stack.db, customerBalanceTable);
|
|
280
249
|
const alpha = rows.find((r) => r.customer === customerAlpha);
|
|
281
250
|
const beta = rows.find((r) => r.customer === customerBeta);
|
|
282
251
|
|
|
@@ -298,10 +267,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
298
267
|
|
|
299
268
|
// Only the shipment-billed event was folded in; the auto "created"
|
|
300
269
|
// event was silently skipped.
|
|
301
|
-
const [row] = await stack.db
|
|
302
|
-
.select()
|
|
303
|
-
.from(customerBalanceTable)
|
|
304
|
-
.where(eq(customerBalanceTable.customer, cust));
|
|
270
|
+
const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
|
|
305
271
|
expect(row?.shipments).toBe(1);
|
|
306
272
|
});
|
|
307
273
|
});
|
package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts}
RENAMED
|
@@ -13,27 +13,25 @@
|
|
|
13
13
|
// jitter does not. If this ever flakes in CI, drop to 3000 — the goal is
|
|
14
14
|
// "catastrophic regression detector", not "perf SLO".
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
table as drizzlePgTable,
|
|
21
|
-
uuid as drizzleUuid,
|
|
22
|
-
} from "../../db/dialect";
|
|
16
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
18
|
+
import { integer, table as pgTable, uuid as pgUuid } from "../../db/dialect";
|
|
19
|
+
import { asRawClient } from "../../db/query";
|
|
23
20
|
import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
|
|
24
21
|
import type { ProjectionDefinition } from "../../engine/types";
|
|
25
22
|
import { createEventsTable } from "../../event-store";
|
|
26
23
|
import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
|
|
27
|
-
import {
|
|
24
|
+
import { TestUsers, unsafePushTables } from "../../stack";
|
|
25
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
28
26
|
import { generateId as uuid } from "../../utils";
|
|
29
27
|
|
|
30
28
|
// Counter projection: every task.created bumps a counter, every
|
|
31
29
|
// task.updated is a no-op. Enough to exercise the apply path —
|
|
32
30
|
// rebuild cost is dominated by event iteration + apply dispatch,
|
|
33
31
|
// not the projection state shape.
|
|
34
|
-
const taskCountTable =
|
|
35
|
-
tenantId:
|
|
36
|
-
count:
|
|
32
|
+
const taskCountTable = pgTable("read_perf_rebuild_task_count", {
|
|
33
|
+
tenantId: pgUuid("tenant_id").primaryKey(),
|
|
34
|
+
count: integer("count").notNull().default(0),
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
const taskCountProjection: ProjectionDefinition = {
|
|
@@ -42,13 +40,10 @@ const taskCountProjection: ProjectionDefinition = {
|
|
|
42
40
|
table: taskCountTable,
|
|
43
41
|
apply: {
|
|
44
42
|
"task.created": async (event, tx) => {
|
|
45
|
-
await tx
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
target: taskCountTable.tenantId,
|
|
50
|
-
set: { count: sql`${taskCountTable.count} + 1` },
|
|
51
|
-
});
|
|
43
|
+
await asRawClient(tx).unsafe(
|
|
44
|
+
`INSERT INTO "read_perf_rebuild_task_count" (tenant_id, count) VALUES ($1, 1) ON CONFLICT (tenant_id) DO UPDATE SET count = read_perf_rebuild_task_count.count + 1`,
|
|
45
|
+
[event.tenantId],
|
|
46
|
+
);
|
|
52
47
|
},
|
|
53
48
|
"task.updated": async (_event, _tx) => {
|
|
54
49
|
// No-op apply — measuring event-iteration overhead, not per-event
|
|
@@ -69,11 +64,12 @@ const feature = defineFeature("perfrebuild", (r) => {
|
|
|
69
64
|
});
|
|
70
65
|
|
|
71
66
|
const admin = TestUsers.admin;
|
|
72
|
-
let testDb:
|
|
67
|
+
let testDb: BunTestDb;
|
|
73
68
|
const registry = createRegistry([feature]);
|
|
74
69
|
const qualifiedProjectionName = "perfrebuild:projection:task-count";
|
|
75
70
|
|
|
76
71
|
beforeAll(async () => {
|
|
72
|
+
await ensureTemporalPolyfill();
|
|
77
73
|
testDb = await createTestDb();
|
|
78
74
|
await createEventsTable(testDb.db);
|
|
79
75
|
await createProjectionStateTable(testDb.db);
|
|
@@ -85,8 +81,8 @@ afterAll(async () => {
|
|
|
85
81
|
});
|
|
86
82
|
|
|
87
83
|
beforeEach(async () => {
|
|
88
|
-
await testDb.db.
|
|
89
|
-
|
|
84
|
+
await asRawClient(testDb.db).unsafe(
|
|
85
|
+
`TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
90
86
|
);
|
|
91
87
|
});
|
|
92
88
|
|
|
@@ -96,25 +92,31 @@ beforeEach(async () => {
|
|
|
96
92
|
async function seedEvents(count: number, depth: number): Promise<void> {
|
|
97
93
|
const userId = uuid();
|
|
98
94
|
// v1 creates
|
|
99
|
-
await testDb.db.
|
|
95
|
+
await asRawClient(testDb.db).unsafe(
|
|
96
|
+
`
|
|
100
97
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
101
|
-
SELECT gen_random_uuid(), 'task', $
|
|
98
|
+
SELECT gen_random_uuid(), 'task', $1::uuid, 1, 'task.created',
|
|
102
99
|
jsonb_build_object('title', 'Task ' || gs.n),
|
|
103
|
-
jsonb_build_object('userId', $
|
|
104
|
-
$
|
|
105
|
-
FROM generate_series(1, $
|
|
106
|
-
|
|
100
|
+
jsonb_build_object('userId', $2::text),
|
|
101
|
+
$3::text
|
|
102
|
+
FROM generate_series(1, $4) AS gs(n);
|
|
103
|
+
`,
|
|
104
|
+
[admin.tenantId, userId, userId, count],
|
|
105
|
+
);
|
|
107
106
|
// v2..depth updates
|
|
108
107
|
for (let v = 2; v <= depth; v++) {
|
|
109
|
-
await testDb.db.
|
|
108
|
+
await asRawClient(testDb.db).unsafe(
|
|
109
|
+
`
|
|
110
110
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
111
|
-
SELECT e.aggregate_id, 'task', $
|
|
112
|
-
jsonb_build_object('title', 'Task v' || $
|
|
113
|
-
jsonb_build_object('userId', $
|
|
114
|
-
$
|
|
111
|
+
SELECT e.aggregate_id, 'task', $1::uuid, $2, 'task.updated',
|
|
112
|
+
jsonb_build_object('title', 'Task v' || $3),
|
|
113
|
+
jsonb_build_object('userId', $4::text),
|
|
114
|
+
$5::text
|
|
115
115
|
FROM kumiko_events e
|
|
116
|
-
WHERE e.aggregate_type = 'task' AND e.version = $
|
|
117
|
-
|
|
116
|
+
WHERE e.aggregate_type = 'task' AND e.version = $6;
|
|
117
|
+
`,
|
|
118
|
+
[admin.tenantId, v, v, userId, userId, v - 1],
|
|
119
|
+
);
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Memory `feedback_no_fake_dispatcher`: real HTTP-Calls via setupTestStack,
|
|
10
10
|
// nicht createTestDispatcher.
|
|
11
11
|
|
|
12
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
12
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
15
15
|
import type { PostQueryHookFn } from "../../engine/types";
|
|
@@ -10,15 +10,11 @@
|
|
|
10
10
|
// - status lifecycle (idle → rebuilding → idle on success, → failed on throw)
|
|
11
11
|
// - never-rebuilt projection has sensible default state
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
integer as drizzleInteger,
|
|
17
|
-
table as drizzlePgTable,
|
|
18
|
-
uuid as drizzleUuid,
|
|
19
|
-
} from "../../db/dialect";
|
|
13
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
import { integer, table as pgTable, uuid } from "../../db/dialect";
|
|
20
15
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
21
|
-
import {
|
|
16
|
+
import { asRawClient, insertOne, selectMany } from "../../db/query";
|
|
17
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
22
18
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
23
19
|
import {
|
|
24
20
|
createEntity,
|
|
@@ -54,23 +50,19 @@ const itemEntity = createEntity({
|
|
|
54
50
|
},
|
|
55
51
|
softDelete: true,
|
|
56
52
|
});
|
|
57
|
-
const itemTable =
|
|
53
|
+
const itemTable = buildEntityTable("rebuild-item", itemEntity);
|
|
58
54
|
|
|
59
|
-
const itemsPerGroupTable =
|
|
60
|
-
groupId:
|
|
61
|
-
tenantId:
|
|
62
|
-
itemCount:
|
|
55
|
+
const itemsPerGroupTable = pgTable("read_rebuild_items_per_group", {
|
|
56
|
+
groupId: uuid("group_id").primaryKey(),
|
|
57
|
+
tenantId: uuid("tenant_id").notNull(),
|
|
58
|
+
itemCount: integer("item_count").notNull().default(0),
|
|
63
59
|
});
|
|
64
60
|
|
|
65
61
|
async function bump(tx: unknown, groupId: string, tenantId: string, delta: number): Promise<void> {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.onConflictDoUpdate({
|
|
71
|
-
target: itemsPerGroupTable.groupId,
|
|
72
|
-
set: { itemCount: sql`${itemsPerGroupTable.itemCount} + ${delta}` },
|
|
73
|
-
});
|
|
62
|
+
await asRawClient(tx).unsafe(
|
|
63
|
+
`INSERT INTO "read_rebuild_items_per_group" (group_id, tenant_id, item_count) VALUES ($1::uuid, $2::uuid, $3) ON CONFLICT (group_id) DO UPDATE SET item_count = read_rebuild_items_per_group.item_count + $3`,
|
|
64
|
+
[groupId, tenantId, delta],
|
|
65
|
+
);
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
type ItemCreated = { groupId: string };
|
|
@@ -121,8 +113,8 @@ afterAll(async () => {
|
|
|
121
113
|
});
|
|
122
114
|
|
|
123
115
|
beforeEach(async () => {
|
|
124
|
-
await testDb.db.
|
|
125
|
-
|
|
116
|
+
await asRawClient(testDb.db).unsafe(
|
|
117
|
+
`TRUNCATE kumiko_events, read_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
126
118
|
);
|
|
127
119
|
});
|
|
128
120
|
|
|
@@ -138,10 +130,7 @@ async function appendCreatedEvent(groupId: string, name: string): Promise<void>
|
|
|
138
130
|
}
|
|
139
131
|
|
|
140
132
|
async function getCount(groupId: string): Promise<number | undefined> {
|
|
141
|
-
const [row] = await testDb.db
|
|
142
|
-
.select()
|
|
143
|
-
.from(itemsPerGroupTable)
|
|
144
|
-
.where(eq(itemsPerGroupTable.groupId, groupId));
|
|
133
|
+
const [row] = await selectMany(testDb.db, itemsPerGroupTable, { groupId: groupId });
|
|
145
134
|
return row?.itemCount;
|
|
146
135
|
}
|
|
147
136
|
|
|
@@ -174,9 +163,11 @@ describe("rebuildProjection — happy path", () => {
|
|
|
174
163
|
await appendCreatedEvent(group, "b");
|
|
175
164
|
|
|
176
165
|
// Seed the projection table with a stale/wrong value.
|
|
177
|
-
await testDb.db
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
await insertOne(testDb.db, itemsPerGroupTable, {
|
|
167
|
+
groupId: group,
|
|
168
|
+
tenantId: admin.tenantId,
|
|
169
|
+
itemCount: 999,
|
|
170
|
+
});
|
|
180
171
|
|
|
181
172
|
const result = await rebuildProjection(qualifiedProjectionName, {
|
|
182
173
|
db: testDb.db,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// drizzle-tables directly. Auto-filters by tenant_id when the projection
|
|
4
4
|
// table carries that column.
|
|
5
5
|
|
|
6
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
6
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import {
|
|
9
9
|
integer as pgInteger,
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
uuid as pgUuid,
|
|
13
13
|
} from "../../db/dialect";
|
|
14
14
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
15
|
-
import {
|
|
15
|
+
import { insertOne } from "../../db/query";
|
|
16
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
16
17
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
17
18
|
import {
|
|
18
19
|
resetEventStore,
|
|
@@ -26,7 +27,7 @@ const widgetEntity = createEntity({
|
|
|
26
27
|
table: "read_qp_widgets",
|
|
27
28
|
fields: { name: createTextField({ required: true }) },
|
|
28
29
|
});
|
|
29
|
-
const widgetTable =
|
|
30
|
+
const widgetTable = buildEntityTable("qp-widget", widgetEntity);
|
|
30
31
|
|
|
31
32
|
// Tenant-scoped projection — auto-filter by tenant_id.
|
|
32
33
|
const tenantScopedTable = pgTable("read_qp_widget_count_tenant", {
|
|
@@ -52,7 +53,7 @@ const qpFeature = defineFeature("qp", (r) => {
|
|
|
52
53
|
apply: {
|
|
53
54
|
"qp-widget.created": async (event, tx) => {
|
|
54
55
|
const p = event.payload as { name?: string };
|
|
55
|
-
await tx
|
|
56
|
+
await insertOne(tx, tenantScopedTable, {
|
|
56
57
|
widgetId: event.aggregateId,
|
|
57
58
|
tenantId: event.tenantId,
|
|
58
59
|
label: p.name ?? "?",
|
|
@@ -68,7 +69,7 @@ const qpFeature = defineFeature("qp", (r) => {
|
|
|
68
69
|
apply: {
|
|
69
70
|
"qp-widget.created": async (event, tx) => {
|
|
70
71
|
const p = event.payload as { name?: string };
|
|
71
|
-
await tx
|
|
72
|
+
await insertOne(tx, systemScopedTable, {
|
|
72
73
|
widgetId: event.aggregateId,
|
|
73
74
|
label: p.name ?? "?",
|
|
74
75
|
});
|
package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { createTestRedis, type TestRedis } from "../../stack";
|
|
3
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
3
4
|
import { createEntityCache } from "../entity-cache";
|
|
4
5
|
import { createEventDedup } from "../event-dedup";
|
|
5
6
|
import { createIdempotencyGuard } from "../idempotency";
|
|
@@ -7,6 +8,7 @@ import { createIdempotencyGuard } from "../idempotency";
|
|
|
7
8
|
let testRedis: TestRedis;
|
|
8
9
|
|
|
9
10
|
beforeAll(async () => {
|
|
11
|
+
await ensureTemporalPolyfill();
|
|
10
12
|
testRedis = await createTestRedis();
|
|
11
13
|
});
|
|
12
14
|
|