@cosmicdrift/kumiko-framework 0.14.0 → 0.16.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/auth-routes.ts +2 -5
- 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 +842 -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/compliance/profiles.ts +1 -4
- 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__/cursor.test.ts +8 -32
- 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__/migrate-generator.test.ts +71 -0
- package/src/db/__tests__/migrate-runner.test.ts +19 -0
- 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__/pg-error.test.ts +43 -0
- 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 +54 -46
- 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__/duration-utils.test.ts +16 -0
- 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-access.test.ts +38 -0
- 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__/no-return-guard.test.ts +17 -0
- 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__/unmanaged-table.test.ts +98 -0
- 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 +37 -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/feature-ast/extractors/shared.ts +2 -3
- 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 +21 -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 +47 -2
- package/src/engine/types/fields.ts +4 -5
- package/src/engine/types/index.ts +2 -0
- 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__/error-helpers.test.ts +44 -0
- package/src/errors/__tests__/field-issue-compat.test.ts +16 -0
- package/src/errors/__tests__/write-failures.test.ts +1 -1
- package/src/errors/classes.ts +5 -19
- package/src/errors/field-issue.ts +11 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/zod-bridge.ts +3 -2
- 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 +11 -56
- 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-utils.test.ts +107 -0
- 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-keys.test.ts +12 -0
- 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-utils.ts +8 -7
- 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 +10 -9
- 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__/case.test.ts +16 -0
- package/src/utils/__tests__/env-parse.test.ts +1 -1
- package/src/utils/__tests__/is-plain-object.test.ts +16 -0
- package/src/utils/__tests__/parse-string-array-json.test.ts +16 -0
- package/src/utils/__tests__/safe-json.test.ts +22 -0
- package/src/utils/case.ts +6 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/is-plain-object.ts +4 -0
- package/src/utils/parse-string-array-json.ts +14 -0
- package/CHANGELOG.md +0 -474
- 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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Unit-level because the check happens before any DB call — no pgClient,
|
|
9
9
|
// no event-store schema, no setupTestStack needed.
|
|
10
10
|
|
|
11
|
-
import { describe, expect, test } from "
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
12
|
import type { AppContext, Registry } from "../../engine/types";
|
|
13
13
|
import { createEventDispatcher, type EventConsumer } from "../event-dispatcher";
|
|
14
14
|
|
|
@@ -35,18 +35,20 @@ describe("event-dispatcher — strict runOnce precondition", () => {
|
|
|
35
35
|
test("ensureRegistered() is a valid alternative to start() — runOnce no longer throws", async () => {
|
|
36
36
|
const consumers: EventConsumer[] = [{ name: "noop", handler: async () => {} }];
|
|
37
37
|
let insertCalls = 0;
|
|
38
|
+
// bun-db path: dispatcher uses db/queries/ for transition guard + transaction().
|
|
39
|
+
// INSERT/SELECT/UPDATE. Match by SQL substring so we count INSERTs and
|
|
40
|
+
// hand back empty rows for SELECT.
|
|
41
|
+
const unsafe = async (sqlText: string): Promise<unknown[]> => {
|
|
42
|
+
if (/INSERT INTO "kumiko_event_consumers"/.test(sqlText)) {
|
|
43
|
+
insertCalls += 1;
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
};
|
|
38
48
|
const stubDb = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
transaction: async (fn: (tx: unknown) => Promise<unknown>) =>
|
|
43
|
-
fn({
|
|
44
|
-
select: () => ({
|
|
45
|
-
from: () => ({ where: () => ({ for: () => [] }) }),
|
|
46
|
-
}),
|
|
47
|
-
execute: async () => [],
|
|
48
|
-
update: () => ({ set: () => ({ where: () => Promise.resolve() }) }),
|
|
49
|
-
}),
|
|
49
|
+
unsafe,
|
|
50
|
+
begin: async (fn: (tx: unknown) => Promise<unknown>) => fn(stubDb),
|
|
51
|
+
transaction: async (fn: (tx: unknown) => Promise<unknown>) => fn(stubDb),
|
|
50
52
|
};
|
|
51
53
|
const dispatcher = createEventDispatcher({
|
|
52
54
|
db: stubDb as never,
|
|
@@ -13,14 +13,10 @@
|
|
|
13
13
|
// production would take once ops wires CreateApp. No createEventDispatcher
|
|
14
14
|
// calls in the test — only the registry round-trip.
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
integer as drizzleInteger,
|
|
20
|
-
table as drizzlePgTable,
|
|
21
|
-
uuid as drizzleUuid,
|
|
22
|
-
} from "../../db/dialect";
|
|
16
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
17
|
+
import { integer, table as pgTable, uuid } from "../../db/dialect";
|
|
23
18
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
19
|
+
import { selectMany } from "../../db/query";
|
|
24
20
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
25
21
|
import { defineFeature, type FeatureDefinition } from "../../engine";
|
|
26
22
|
import type { StoredEvent } from "../../event-store";
|
|
@@ -40,10 +36,10 @@ import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
|
|
|
40
36
|
// A tiny state table a subscriber mutates so we can observe "the handler was
|
|
41
37
|
// called with this event" without relying on in-memory arrays — the state row
|
|
42
38
|
// survives even if the test framework resets process state.
|
|
43
|
-
const subscriberLogTable =
|
|
44
|
-
id:
|
|
45
|
-
eventId:
|
|
46
|
-
eventType:
|
|
39
|
+
const subscriberLogTable = pgTable("read_dispatcher_subscriber_log", {
|
|
40
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
41
|
+
eventId: integer("event_id").notNull(),
|
|
42
|
+
eventType: uuid("event_type"), // unused, kept to avoid another drizzle type import
|
|
47
43
|
});
|
|
48
44
|
|
|
49
45
|
// Per-test capture. The subscriber handlers push here; beforeEach resets.
|
|
@@ -195,10 +191,7 @@ describe("event-dispatcher — isolation between consumers", () => {
|
|
|
195
191
|
// the per-consumer transaction boundary holds.
|
|
196
192
|
// Pre-registered state rows exist from boot (strict Sprint-E mode) — at
|
|
197
193
|
// this point they're at cursor=0 / status=idle for both observers.
|
|
198
|
-
const state = await stack.db
|
|
199
|
-
.select()
|
|
200
|
-
.from(eventConsumerStateTable)
|
|
201
|
-
.where(sql`${eventConsumerStateTable.name} = ${qnA}`);
|
|
194
|
+
const state = await selectMany(stack.db, eventConsumerStateTable, { name: qnA });
|
|
202
195
|
expect(state).toHaveLength(1);
|
|
203
196
|
expect(state[0]?.lastProcessedEventId).toBe(0n);
|
|
204
197
|
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
// 4. olderThanDays / olderThan convenience: both resolve to the same
|
|
11
11
|
// cutoff semantics (createdAt < cutoff).
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
13
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
15
14
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
15
|
+
import { asRawClient, insertOne, selectMany, updateMany } from "../../db/query";
|
|
16
16
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
17
17
|
import { defineFeature } from "../../engine";
|
|
18
18
|
import { eventsTable } from "../../event-store";
|
|
@@ -77,20 +77,17 @@ async function seedOldAggregateEvent(
|
|
|
77
77
|
type: string,
|
|
78
78
|
aggregateType = "widget",
|
|
79
79
|
): Promise<bigint> {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
createdBy: admin.id,
|
|
92
|
-
})
|
|
93
|
-
.returning({ id: eventsTable.id });
|
|
80
|
+
const row = await insertOne<{ id: bigint }>(stack.db, eventsTable, {
|
|
81
|
+
aggregateId: generateId(),
|
|
82
|
+
aggregateType,
|
|
83
|
+
tenantId: admin.tenantId,
|
|
84
|
+
version: 1,
|
|
85
|
+
type,
|
|
86
|
+
payload: {},
|
|
87
|
+
metadata: { userId: admin.id },
|
|
88
|
+
createdAt,
|
|
89
|
+
createdBy: admin.id,
|
|
90
|
+
});
|
|
94
91
|
if (!row) throw new Error("seed failed");
|
|
95
92
|
return row.id;
|
|
96
93
|
}
|
|
@@ -121,7 +118,7 @@ describe("E.2 — explicit-aggregateTypes pruning", () => {
|
|
|
121
118
|
expect(result.deletedCount).toBe(1);
|
|
122
119
|
expect(result.aggregateTypes).toEqual(["obsolete"]);
|
|
123
120
|
|
|
124
|
-
const remaining = await stack.db
|
|
121
|
+
const remaining = await selectMany(stack.db, eventsTable);
|
|
125
122
|
const ids = remaining.map((r) => r.id);
|
|
126
123
|
expect(ids).toContain(widgetId);
|
|
127
124
|
expect(ids).not.toContain(obsoleteId);
|
|
@@ -146,7 +143,7 @@ describe("E.2 — explicit-aggregateTypes pruning", () => {
|
|
|
146
143
|
});
|
|
147
144
|
expect(result.deletedCount).toBe(1);
|
|
148
145
|
|
|
149
|
-
const remaining = await stack.db
|
|
146
|
+
const remaining = await selectMany(stack.db, eventsTable);
|
|
150
147
|
const ids = remaining.map((r) => r.id).sort();
|
|
151
148
|
expect(ids).toEqual([freshId]);
|
|
152
149
|
expect(ids.includes(staleId)).toBe(false);
|
|
@@ -165,7 +162,7 @@ describe("E.2 — explicit-aggregateTypes pruning", () => {
|
|
|
165
162
|
expect(result.deletedCount).toBe(1);
|
|
166
163
|
expect(result.dryRun).toBe(true);
|
|
167
164
|
|
|
168
|
-
const remaining = await stack.db
|
|
165
|
+
const remaining = await selectMany(stack.db, eventsTable);
|
|
169
166
|
expect(remaining).toHaveLength(1);
|
|
170
167
|
});
|
|
171
168
|
});
|
|
@@ -182,13 +179,17 @@ describe("E.2 — consumer-lag guard", () => {
|
|
|
182
179
|
// Only let the first one through.
|
|
183
180
|
await stack.eventDispatcher?.runOnce();
|
|
184
181
|
// Force cursor to 1 so the guard sees "consumer at 1, max candidate 3".
|
|
185
|
-
await
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
await updateMany(
|
|
183
|
+
stack.db,
|
|
184
|
+
eventConsumerStateTable,
|
|
185
|
+
{ lastProcessedEventId: 1n, status: "idle" },
|
|
186
|
+
{ name: observerQn },
|
|
187
|
+
);
|
|
189
188
|
|
|
190
189
|
// Age all three events past the cutoff.
|
|
191
|
-
await stack.db.
|
|
190
|
+
await asRawClient(stack.db).unsafe(
|
|
191
|
+
`UPDATE kumiko_events SET created_at = now() - interval '30 days'`,
|
|
192
|
+
);
|
|
192
193
|
|
|
193
194
|
await expect(
|
|
194
195
|
pruneEvents(stack.db, {
|
|
@@ -204,7 +205,9 @@ describe("E.2 — consumer-lag guard", () => {
|
|
|
204
205
|
await disableConsumer(stack.db, observerQn);
|
|
205
206
|
|
|
206
207
|
// Cursor is at 1 but consumer is disabled — should be skipped.
|
|
207
|
-
await stack.db.
|
|
208
|
+
await asRawClient(stack.db).unsafe(
|
|
209
|
+
`UPDATE kumiko_events SET created_at = now() - interval '30 days'`,
|
|
210
|
+
);
|
|
208
211
|
|
|
209
212
|
const result = await pruneEvents(stack.db, {
|
|
210
213
|
olderThanDays: 7,
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
// the stream version internally — a sequence of appendOne calls writes
|
|
8
8
|
// consecutive versions without re-reading the DB.
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
10
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
12
11
|
import { z } from "zod";
|
|
13
12
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
14
|
-
import {
|
|
13
|
+
import { asRawClient } from "../../db/query-api";
|
|
14
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
15
15
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
16
16
|
import { UnprocessableError, writeFailure } from "../../errors";
|
|
17
17
|
import { loadAggregate } from "../../event-store";
|
|
@@ -26,7 +26,7 @@ const cartEntity = createEntity({
|
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
const cartTable =
|
|
29
|
+
const cartTable = buildEntityTable("f4wCart", cartEntity);
|
|
30
30
|
|
|
31
31
|
const cartFeature = defineFeature("f4w", (r) => {
|
|
32
32
|
r.entity("f4wCart", cartEntity);
|
|
@@ -144,8 +144,8 @@ afterAll(async () => {
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
afterEach(async () => {
|
|
147
|
-
await stack.db.
|
|
148
|
-
|
|
147
|
+
await asRawClient(stack.db).unsafe(
|
|
148
|
+
`TRUNCATE kumiko_events, read_f4w_carts, kumiko_event_consumers RESTART IDENTITY CASCADE`,
|
|
149
149
|
);
|
|
150
150
|
await stack.eventDispatcher?.ensureRegistered();
|
|
151
151
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import {
|
|
4
4
|
createEntity,
|
|
@@ -200,7 +200,7 @@ describe("runPostSave", () => {
|
|
|
200
200
|
|
|
201
201
|
test("postSave errors don't throw — logged and continued", async () => {
|
|
202
202
|
const calls: string[] = [];
|
|
203
|
-
const consoleSpy =
|
|
203
|
+
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
204
204
|
|
|
205
205
|
const registry = makeRegistry({
|
|
206
206
|
postSave: [
|
|
@@ -234,7 +234,7 @@ describe("runPostSave", () => {
|
|
|
234
234
|
|
|
235
235
|
test("system hook failure doesn't block other system hooks", async () => {
|
|
236
236
|
const calls: string[] = [];
|
|
237
|
-
const consoleSpy =
|
|
237
|
+
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
238
238
|
|
|
239
239
|
const registry = makeRegistry();
|
|
240
240
|
|
|
@@ -334,7 +334,7 @@ describe("runPostSave phase routing", () => {
|
|
|
334
334
|
});
|
|
335
335
|
|
|
336
336
|
test("afterCommit phase: hook errors are logged, never thrown", async () => {
|
|
337
|
-
const consoleSpy =
|
|
337
|
+
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
338
338
|
const afterRan: string[] = [];
|
|
339
339
|
const feature = defineFeature("phases", (r) => {
|
|
340
340
|
r.entity("user", createEntity({ table: "Users", fields: {} }));
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
// returned events into whatever domain-state shape the feature wants.
|
|
6
6
|
// Events are upcasted by the dispatcher, so the reducer sees the current
|
|
7
7
|
// payload shape even for old v1 events.
|
|
8
|
+
//
|
|
9
|
+
// Bun.SQL-only setup. KEIN postgres-js, KEIN setupTestStack.
|
|
8
10
|
|
|
9
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
11
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
10
12
|
import { z } from "zod";
|
|
13
|
+
import type { DbConnection, DbTx } from "../../db/connection";
|
|
11
14
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
12
|
-
import {
|
|
15
|
+
import { insertOne } from "../../db/query";
|
|
16
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
13
17
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
14
18
|
import { append, loadAggregate as loadAggregateRaw } from "../../event-store";
|
|
15
19
|
import {
|
|
@@ -29,7 +33,7 @@ const invoiceEntity = createEntity({
|
|
|
29
33
|
status: createTextField({ required: true }),
|
|
30
34
|
},
|
|
31
35
|
});
|
|
32
|
-
const invoiceTable =
|
|
36
|
+
const invoiceTable = buildEntityTable("asof-invoice", invoiceEntity);
|
|
33
37
|
|
|
34
38
|
// --- Feature ---
|
|
35
39
|
|
|
@@ -214,8 +218,8 @@ describe("ctx.loadAggregate via queryHandler — Marten AggregateStreamAsync equ
|
|
|
214
218
|
// against v2 (integer cents) — without upcasting it would blow up or
|
|
215
219
|
// produce garbage.
|
|
216
220
|
const invoiceId = "00000000-0000-4000-8000-000000000042";
|
|
217
|
-
await stack.db.
|
|
218
|
-
await tx
|
|
221
|
+
await (stack.db as DbConnection).begin(async (tx: DbTx) => {
|
|
222
|
+
await insertOne(tx, invoiceTable, {
|
|
219
223
|
id: invoiceId,
|
|
220
224
|
tenantId: admin.tenantId,
|
|
221
225
|
customer: "LegacyCo",
|
package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts}
RENAMED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// error on the skip counter, advances the cursor, and keeps delivering. The
|
|
10
10
|
// consumer stays "idle".
|
|
11
11
|
|
|
12
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
12
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
13
13
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
14
14
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
15
15
|
import { defineFeature } from "../../engine";
|
package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts}
RENAMED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
// a three-hop causal chain (order.placed → order.confirmed → order.shipped).
|
|
11
11
|
// 5. ctx.loadAggregate reads the triggering stream's full history.
|
|
12
12
|
|
|
13
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
13
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
16
|
-
import {
|
|
16
|
+
import { selectMany } from "../../db/query";
|
|
17
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
17
18
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
18
19
|
import { eventsTable } from "../../event-store";
|
|
19
20
|
import {
|
|
@@ -33,7 +34,7 @@ const orderEntity = createEntity({
|
|
|
33
34
|
},
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
const orderTable =
|
|
37
|
+
const orderTable = buildEntityTable("mmh-order", orderEntity);
|
|
37
38
|
|
|
38
39
|
// Snapshot what each MSP-apply observed via ctx.loadAggregate.
|
|
39
40
|
const confirmLoadCounts: number[] = [];
|
|
@@ -132,7 +133,7 @@ async function postWrite(correlationId: string, item: string) {
|
|
|
132
133
|
async function drainUntilShipped(aggregateId: string, maxPasses = 10): Promise<void> {
|
|
133
134
|
for (let i = 0; i < maxPasses; i++) {
|
|
134
135
|
await stack.eventDispatcher?.runOnce();
|
|
135
|
-
const rows = await stack.db
|
|
136
|
+
const rows = await selectMany(stack.db, eventsTable);
|
|
136
137
|
if (rows.some((r) => r.aggregateId === aggregateId && r.type === "mmh:event:shipped")) return;
|
|
137
138
|
}
|
|
138
139
|
throw new Error(`drainUntilShipped: never saw shipped event for ${aggregateId}`);
|
|
@@ -147,7 +148,7 @@ describe("Runde 3 / C.2b — MSP-apply ctx cascades", () => {
|
|
|
147
148
|
|
|
148
149
|
await stack.eventDispatcher?.runOnce();
|
|
149
150
|
|
|
150
|
-
const rows = await stack.db
|
|
151
|
+
const rows = await selectMany(stack.db, eventsTable);
|
|
151
152
|
const types = rows.map((r) => r.type).sort();
|
|
152
153
|
// Order: CRUD create, placed, confirmed (hop 1 fired).
|
|
153
154
|
expect(types).toContain("mmh-order.created");
|
|
@@ -160,7 +161,7 @@ describe("Runde 3 / C.2b — MSP-apply ctx cascades", () => {
|
|
|
160
161
|
|
|
161
162
|
// Find the aggregateId from the first placed event.
|
|
162
163
|
await stack.eventDispatcher?.runOnce();
|
|
163
|
-
const placedRow = (await stack.db
|
|
164
|
+
const placedRow = (await selectMany(stack.db, eventsTable)).find(
|
|
164
165
|
(r) => r.type === "mmh:event:placed",
|
|
165
166
|
);
|
|
166
167
|
expect(placedRow).toBeDefined();
|
|
@@ -168,7 +169,7 @@ describe("Runde 3 / C.2b — MSP-apply ctx cascades", () => {
|
|
|
168
169
|
|
|
169
170
|
await drainUntilShipped(aggregateId);
|
|
170
171
|
|
|
171
|
-
const rows = (await stack.db
|
|
172
|
+
const rows = (await selectMany(stack.db, eventsTable))
|
|
172
173
|
.filter((r) => r.aggregateId === aggregateId)
|
|
173
174
|
.sort((a, b) => Number(a.id - b.id));
|
|
174
175
|
|
|
@@ -220,7 +221,7 @@ describe("Runde 3 / C.2b — MSP-apply ctx cascades", () => {
|
|
|
220
221
|
await stack.eventDispatcher?.runOnce();
|
|
221
222
|
|
|
222
223
|
// Every event written on this chain belongs to the admin's tenant.
|
|
223
|
-
const rows = await stack.db
|
|
224
|
+
const rows = await selectMany(stack.db, eventsTable);
|
|
224
225
|
for (const row of rows) {
|
|
225
226
|
expect(row.tenantId).toBe(admin.tenantId);
|
|
226
227
|
}
|
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, {
|