@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
package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts}
RENAMED
|
@@ -9,15 +9,35 @@
|
|
|
9
9
|
// - Batch: single INSERT with multi-VALUES; atomic rollback on any
|
|
10
10
|
// failure; predecessor pre-flight per aggregate in the batch.
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
import type { DbConnection, PgClient } from "../../db/connection";
|
|
14
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
15
15
|
import { createTestDb, type TestDb } from "../../stack";
|
|
16
16
|
import { generateId as uuid } from "../../utils";
|
|
17
17
|
import { appendRaw, appendRawBatch, type RawEventToAppend } from "../admin-api";
|
|
18
18
|
import { VersionConflictError } from "../errors";
|
|
19
19
|
import { append, loadAggregate } from "../event-store";
|
|
20
|
-
import { createEventsTable } from "../events-schema";
|
|
20
|
+
import { createEventsTable, eventsTable } from "../events-schema";
|
|
21
|
+
|
|
22
|
+
// Test-only spy: wrap a DbConnection's `.unsafe()` to capture the SQL
|
|
23
|
+
// string of every query the framework runs. Used to assert batching
|
|
24
|
+
// behaviour (single multi-VALUES INSERT vs N statements).
|
|
25
|
+
function spyQueries(db: DbConnection): { db: DbConnection; queries: string[] } {
|
|
26
|
+
const queries: string[] = [];
|
|
27
|
+
const wrapped = new Proxy(db, {
|
|
28
|
+
get(target, prop, receiver) {
|
|
29
|
+
if (prop === "unsafe") {
|
|
30
|
+
return (sql: string, params?: readonly unknown[]) => {
|
|
31
|
+
queries.push(sql);
|
|
32
|
+
// biome-ignore lint/suspicious/noExplicitAny: postgres-js .unsafe signature variance
|
|
33
|
+
return (target as any).unsafe(sql, params);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return Reflect.get(target, prop, receiver);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return { db: wrapped, queries };
|
|
40
|
+
}
|
|
21
41
|
|
|
22
42
|
let testDb: TestDb;
|
|
23
43
|
|
|
@@ -35,7 +55,7 @@ afterAll(async () => {
|
|
|
35
55
|
});
|
|
36
56
|
|
|
37
57
|
beforeEach(async () => {
|
|
38
|
-
await testDb.db.
|
|
58
|
+
await asRawClient(testDb.db).unsafe(`TRUNCATE kumiko_events RESTART IDENTITY`);
|
|
39
59
|
});
|
|
40
60
|
|
|
41
61
|
function makeEvent(partial: Partial<RawEventToAppend> = {}): RawEventToAppend {
|
|
@@ -86,9 +106,12 @@ describe("appendRaw — single event", () => {
|
|
|
86
106
|
}),
|
|
87
107
|
);
|
|
88
108
|
|
|
89
|
-
const rows = await testDb.db.
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
const rows = await asRawClient(testDb.db).unsafe<{ created_by: string }>(
|
|
110
|
+
`
|
|
111
|
+
SELECT created_by FROM kumiko_events WHERE aggregate_id = $1::uuid
|
|
112
|
+
`,
|
|
113
|
+
[aggregateId],
|
|
114
|
+
);
|
|
92
115
|
expect(rows[0]?.created_by).toBe(legacyUser);
|
|
93
116
|
expect(rows[0]?.created_by).not.toBe(userMigration);
|
|
94
117
|
});
|
|
@@ -113,12 +136,7 @@ describe("appendRaw — single event", () => {
|
|
|
113
136
|
}),
|
|
114
137
|
);
|
|
115
138
|
|
|
116
|
-
const rows = await testDb.db
|
|
117
|
-
payload: Record<string, unknown>;
|
|
118
|
-
metadata: Record<string, unknown>;
|
|
119
|
-
}>(sql`
|
|
120
|
-
SELECT payload, metadata FROM kumiko_events WHERE aggregate_id = ${aggregateId}::uuid
|
|
121
|
-
`);
|
|
139
|
+
const rows = await selectMany(testDb.db, eventsTable, { aggregateId });
|
|
122
140
|
expect(rows[0]?.payload).toEqual(payload);
|
|
123
141
|
expect(rows[0]?.metadata).toEqual(metadata);
|
|
124
142
|
});
|
|
@@ -128,9 +146,12 @@ describe("appendRaw — single event", () => {
|
|
|
128
146
|
// client; the callback is invoked per NOTIFY payload. The resolved value
|
|
129
147
|
// is a meta-object with an `unlisten` method, not a plain function.
|
|
130
148
|
const notifications: string[] = [];
|
|
131
|
-
const subscription = await testDb.client.listen(
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
const subscription = await (testDb.client as PgClient).listen(
|
|
150
|
+
"kumiko_events_new",
|
|
151
|
+
(payload: string) => {
|
|
152
|
+
notifications.push(payload);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
134
155
|
|
|
135
156
|
try {
|
|
136
157
|
// appendRaw path — MUST NOT fire.
|
|
@@ -176,9 +197,12 @@ describe("appendRaw — single event", () => {
|
|
|
176
197
|
).rejects.toBeInstanceOf(VersionConflictError);
|
|
177
198
|
|
|
178
199
|
// Sanity: no row landed.
|
|
179
|
-
const rows = await testDb.db.
|
|
180
|
-
|
|
181
|
-
|
|
200
|
+
const rows = await asRawClient(testDb.db).unsafe<{ c: number }>(
|
|
201
|
+
`
|
|
202
|
+
SELECT count(*)::int as c FROM kumiko_events WHERE aggregate_id = $1::uuid
|
|
203
|
+
`,
|
|
204
|
+
[aggregateId],
|
|
205
|
+
);
|
|
182
206
|
expect(rows[0]?.c).toBe(0);
|
|
183
207
|
});
|
|
184
208
|
|
|
@@ -188,21 +212,19 @@ describe("appendRaw — single event", () => {
|
|
|
188
212
|
await appendRaw(testDb.db, makeEvent({ aggregateId, expectedVersion: 1 }));
|
|
189
213
|
await appendRaw(testDb.db, makeEvent({ aggregateId, expectedVersion: 2 }));
|
|
190
214
|
|
|
191
|
-
const rows = await testDb.db.
|
|
192
|
-
|
|
193
|
-
|
|
215
|
+
const rows = await asRawClient(testDb.db).unsafe<{ version: number }>(
|
|
216
|
+
`
|
|
217
|
+
SELECT version FROM kumiko_events WHERE aggregate_id = $1::uuid ORDER BY version
|
|
218
|
+
`,
|
|
219
|
+
[aggregateId],
|
|
220
|
+
);
|
|
194
221
|
expect(rows.map((r) => r.version)).toEqual([1, 2, 3]);
|
|
195
222
|
});
|
|
196
223
|
});
|
|
197
224
|
|
|
198
225
|
describe("appendRawBatch — multi-event", () => {
|
|
199
226
|
test("writes all events in a single INSERT statement (query-log spy)", async () => {
|
|
200
|
-
const
|
|
201
|
-
const loggedDb = drizzle(testDb.client, {
|
|
202
|
-
logger: {
|
|
203
|
-
logQuery: (q) => queries.push(q),
|
|
204
|
-
},
|
|
205
|
-
});
|
|
227
|
+
const { db: loggedDb, queries } = spyQueries(testDb.db);
|
|
206
228
|
|
|
207
229
|
const aggregateId = uuid();
|
|
208
230
|
const events: readonly RawEventToAppend[] = [
|
|
@@ -217,9 +239,12 @@ describe("appendRawBatch — multi-event", () => {
|
|
|
217
239
|
expect(inserts).toHaveLength(1);
|
|
218
240
|
|
|
219
241
|
// All three events persisted with ascending versions.
|
|
220
|
-
const rows = await testDb.db.
|
|
221
|
-
|
|
222
|
-
|
|
242
|
+
const rows = await asRawClient(testDb.db).unsafe<{ version: number; type: string }>(
|
|
243
|
+
`
|
|
244
|
+
SELECT version, type FROM kumiko_events WHERE aggregate_id = $1::uuid ORDER BY version
|
|
245
|
+
`,
|
|
246
|
+
[aggregateId],
|
|
247
|
+
);
|
|
223
248
|
expect(rows.map((r) => ({ v: r.version, t: r.type }))).toEqual([
|
|
224
249
|
{ v: 1, t: "legacy.order.created" },
|
|
225
250
|
{ v: 2, t: "legacy.order.accepted" },
|
|
@@ -263,7 +288,7 @@ describe("appendRawBatch — multi-event", () => {
|
|
|
263
288
|
await expect(appendRawBatch(testDb.db, batch)).rejects.toBeInstanceOf(VersionConflictError);
|
|
264
289
|
|
|
265
290
|
// Only the seed event survived — multi-VALUES INSERT is atomic.
|
|
266
|
-
const rows = await testDb.db.
|
|
291
|
+
const rows = await asRawClient(testDb.db).unsafe<{ c: number }>(`
|
|
267
292
|
SELECT count(*)::int as c FROM kumiko_events
|
|
268
293
|
`);
|
|
269
294
|
expect(rows[0]?.c).toBe(1);
|
|
@@ -280,9 +305,12 @@ describe("appendRawBatch — multi-event", () => {
|
|
|
280
305
|
]),
|
|
281
306
|
).rejects.toBeInstanceOf(VersionConflictError);
|
|
282
307
|
|
|
283
|
-
const rows = await testDb.db.
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
const rows = await asRawClient(testDb.db).unsafe<{ c: number }>(
|
|
309
|
+
`
|
|
310
|
+
SELECT count(*)::int as c FROM kumiko_events WHERE aggregate_id = $1::uuid
|
|
311
|
+
`,
|
|
312
|
+
[aggregateId],
|
|
313
|
+
);
|
|
286
314
|
expect(rows[0]?.c).toBe(0);
|
|
287
315
|
});
|
|
288
316
|
|
|
@@ -301,9 +329,12 @@ describe("appendRawBatch — multi-event", () => {
|
|
|
301
329
|
).rejects.toBeInstanceOf(VersionConflictError);
|
|
302
330
|
|
|
303
331
|
// Zero events persisted — the whole batch is rejected before the INSERT.
|
|
304
|
-
const rows = await testDb.db.
|
|
305
|
-
|
|
306
|
-
|
|
332
|
+
const rows = await asRawClient(testDb.db).unsafe<{ c: number }>(
|
|
333
|
+
`
|
|
334
|
+
SELECT count(*)::int as c FROM kumiko_events WHERE aggregate_id = $1::uuid
|
|
335
|
+
`,
|
|
336
|
+
[aggregateId],
|
|
337
|
+
);
|
|
307
338
|
expect(rows[0]?.c).toBe(0);
|
|
308
339
|
});
|
|
309
340
|
|
|
@@ -326,12 +357,7 @@ describe("appendRawBatch — multi-event", () => {
|
|
|
326
357
|
});
|
|
327
358
|
|
|
328
359
|
test("empty array is a no-op — no query, no throw", async () => {
|
|
329
|
-
const
|
|
330
|
-
const loggedDb = drizzle(testDb.client, {
|
|
331
|
-
logger: {
|
|
332
|
-
logQuery: (q) => queries.push(q),
|
|
333
|
-
},
|
|
334
|
-
});
|
|
360
|
+
const { db: loggedDb, queries } = spyQueries(testDb.db);
|
|
335
361
|
|
|
336
362
|
await appendRawBatch(loggedDb, []);
|
|
337
363
|
expect(queries).toHaveLength(0);
|
package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts}
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
3
|
+
import { asRawClient } from "../../db/query";
|
|
4
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
4
5
|
import { generateId as uuid } from "../../utils";
|
|
5
6
|
import {
|
|
6
7
|
append,
|
|
@@ -14,13 +15,14 @@ import {
|
|
|
14
15
|
VersionConflictError,
|
|
15
16
|
} from "../index";
|
|
16
17
|
|
|
17
|
-
let testDb:
|
|
18
|
+
let testDb: BunTestDb;
|
|
18
19
|
|
|
19
20
|
const tenantA = uuid();
|
|
20
21
|
const tenantB = uuid();
|
|
21
22
|
const userA = uuid();
|
|
22
23
|
|
|
23
24
|
beforeAll(async () => {
|
|
25
|
+
await ensureTemporalPolyfill();
|
|
24
26
|
testDb = await createTestDb();
|
|
25
27
|
await createEventsTable(testDb.db);
|
|
26
28
|
});
|
|
@@ -30,7 +32,7 @@ afterAll(async () => {
|
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
beforeEach(async () => {
|
|
33
|
-
await testDb.db.
|
|
35
|
+
await asRawClient(testDb.db).unsafe(`TRUNCATE kumiko_events RESTART IDENTITY`);
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
describe("event-store: append + load", () => {
|
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
// Not a strict SLA test — the threshold is generous enough to survive CI
|
|
10
10
|
// noise but tight enough that an index-miss regression would fail loudly.
|
|
11
11
|
|
|
12
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
12
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
13
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
13
14
|
import type { TenantId } from "../../engine/types";
|
|
14
|
-
import {
|
|
15
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
15
16
|
import { generateId as uuid } from "../../utils";
|
|
16
17
|
import { append, createEventsTable, getStreamVersion } from "../index";
|
|
17
18
|
|
|
18
|
-
let testDb:
|
|
19
|
+
let testDb: BunTestDb;
|
|
19
20
|
const tenantId: TenantId = uuid();
|
|
20
21
|
const userId = uuid();
|
|
21
22
|
|
|
22
23
|
beforeAll(async () => {
|
|
24
|
+
await ensureTemporalPolyfill();
|
|
23
25
|
testDb = await createTestDb();
|
|
24
26
|
await createEventsTable(testDb.db);
|
|
25
27
|
});
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
// latency, single-node PG. Production deploys are slower; these numbers
|
|
13
13
|
// are the ceiling. Red test = framework regression, no slack tolerated.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
16
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
17
|
+
import { asRawClient } from "../../db/query";
|
|
17
18
|
import type { TenantId } from "../../engine/types";
|
|
18
|
-
import {
|
|
19
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
19
20
|
import { generateId as uuid } from "../../utils";
|
|
20
21
|
import {
|
|
21
22
|
append,
|
|
@@ -25,11 +26,12 @@ import {
|
|
|
25
26
|
saveSnapshot,
|
|
26
27
|
} from "../index";
|
|
27
28
|
|
|
28
|
-
let testDb:
|
|
29
|
+
let testDb: BunTestDb;
|
|
29
30
|
const tenantId = uuid() as TenantId;
|
|
30
31
|
const userId = uuid();
|
|
31
32
|
|
|
32
33
|
beforeAll(async () => {
|
|
34
|
+
await ensureTemporalPolyfill();
|
|
33
35
|
testDb = await createTestDb();
|
|
34
36
|
await createEventsTable(testDb.db);
|
|
35
37
|
});
|
|
@@ -39,8 +41,8 @@ afterAll(async () => {
|
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
beforeEach(async () => {
|
|
42
|
-
await testDb.db.
|
|
43
|
-
|
|
44
|
+
await asRawClient(testDb.db).unsafe(
|
|
45
|
+
`TRUNCATE kumiko_events, kumiko_snapshots, kumiko_archived_streams RESTART IDENTITY CASCADE`,
|
|
44
46
|
);
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -196,21 +198,27 @@ describe("event-store performance — Gate A", () => {
|
|
|
196
198
|
// loadAggregateWithSnapshot performance on a finished stream, not
|
|
197
199
|
// the seed phase.
|
|
198
200
|
const aggregateId = uuid();
|
|
199
|
-
await testDb.db.
|
|
201
|
+
await asRawClient(testDb.db).unsafe(
|
|
202
|
+
`
|
|
200
203
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
201
|
-
SELECT $
|
|
204
|
+
SELECT $1::uuid, 'task', $2::uuid, 1, 'task.created',
|
|
202
205
|
jsonb_build_object('title', 'v1'),
|
|
203
|
-
jsonb_build_object('userId', $
|
|
204
|
-
$
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
jsonb_build_object('userId', $3::text),
|
|
207
|
+
$4::text;
|
|
208
|
+
`,
|
|
209
|
+
[aggregateId, tenantId, userId, userId],
|
|
210
|
+
);
|
|
211
|
+
await asRawClient(testDb.db).unsafe(
|
|
212
|
+
`
|
|
207
213
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
208
|
-
SELECT $
|
|
214
|
+
SELECT $1::uuid, 'task', $2::uuid, gs.v, 'task.updated',
|
|
209
215
|
jsonb_build_object('title', 'v' || gs.v),
|
|
210
|
-
jsonb_build_object('userId', $
|
|
211
|
-
$
|
|
216
|
+
jsonb_build_object('userId', $3::text),
|
|
217
|
+
$4::text
|
|
212
218
|
FROM generate_series(2, 1000) gs(v);
|
|
213
|
-
|
|
219
|
+
`,
|
|
220
|
+
[aggregateId, tenantId, userId, userId],
|
|
221
|
+
);
|
|
214
222
|
|
|
215
223
|
// Snapshot @ version 900 — typische Policy: snapshot every N events
|
|
216
224
|
await saveSnapshot(testDb.db, {
|
|
@@ -9,10 +9,15 @@
|
|
|
9
9
|
// in < 50ms (typical asOf/reducer rehydrate budget). Same gate the
|
|
10
10
|
// spike proved on raw SQL, now enforced on the framework path.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Bun.SQL-only setup. KEIN postgres-js, KEIN createTestDb.
|
|
13
|
+
// Event-store functions expect DbRunner (= postgres-js) but Bun.SQL
|
|
14
|
+
// ist strukturell kompatibel (beide haben .unsafe()/.begin()) — kein Cast nötig.
|
|
15
|
+
|
|
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 { asRawClient } from "../../db/query";
|
|
14
19
|
import type { TenantId } from "../../engine/types";
|
|
15
|
-
import {
|
|
20
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
16
21
|
import { generateId as uuid } from "../../utils";
|
|
17
22
|
import {
|
|
18
23
|
append,
|
|
@@ -25,7 +30,7 @@ import {
|
|
|
25
30
|
saveSnapshot,
|
|
26
31
|
} from "../index";
|
|
27
32
|
|
|
28
|
-
let
|
|
33
|
+
let bun: BunTestDb;
|
|
29
34
|
const tenant = uuid() as TenantId;
|
|
30
35
|
const userId = uuid();
|
|
31
36
|
|
|
@@ -49,17 +54,18 @@ const reducer: SnapshotReducer<CounterState> = (state, event) => {
|
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
beforeAll(async () => {
|
|
52
|
-
|
|
53
|
-
await
|
|
57
|
+
await ensureTemporalPolyfill();
|
|
58
|
+
bun = await createTestDb();
|
|
59
|
+
await createEventsTable(bun.db);
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
afterAll(async () => {
|
|
57
|
-
await
|
|
63
|
+
await bun.cleanup();
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
beforeEach(async () => {
|
|
61
|
-
await
|
|
62
|
-
|
|
67
|
+
await asRawClient(bun.db).unsafe(
|
|
68
|
+
`TRUNCATE kumiko_events, kumiko_snapshots, kumiko_archived_streams RESTART IDENTITY`,
|
|
63
69
|
);
|
|
64
70
|
});
|
|
65
71
|
|
|
@@ -67,7 +73,7 @@ beforeEach(async () => {
|
|
|
67
73
|
async function seedAggregate(eventCount: number): Promise<string> {
|
|
68
74
|
const aggId = uuid();
|
|
69
75
|
for (let i = 0; i < eventCount; i++) {
|
|
70
|
-
await append(
|
|
76
|
+
await append(bun.db, {
|
|
71
77
|
aggregateId: aggId,
|
|
72
78
|
aggregateType: "counter",
|
|
73
79
|
tenantId: tenant,
|
|
@@ -85,7 +91,7 @@ async function seedAggregate(eventCount: number): Promise<string> {
|
|
|
85
91
|
// raw SQL) keeps the invariant honest: if loadAggregate semantics drift,
|
|
86
92
|
// this helper shifts with them.
|
|
87
93
|
async function loadFullState(aggregateId: string): Promise<CounterState> {
|
|
88
|
-
const events = await loadAggregate(
|
|
94
|
+
const events = await loadAggregate(bun.db, aggregateId, tenant);
|
|
89
95
|
let state: CounterState = initial;
|
|
90
96
|
for (const event of events) {
|
|
91
97
|
state = reducer(state, event);
|
|
@@ -96,7 +102,7 @@ async function loadFullState(aggregateId: string): Promise<CounterState> {
|
|
|
96
102
|
describe("snapshot-store — round-trip", () => {
|
|
97
103
|
test("saveSnapshot + loadLatestSnapshot roundtrip the state", async () => {
|
|
98
104
|
const aggId = uuid();
|
|
99
|
-
await saveSnapshot(
|
|
105
|
+
await saveSnapshot(bun.db, {
|
|
100
106
|
aggregateId: aggId,
|
|
101
107
|
tenantId: tenant,
|
|
102
108
|
aggregateType: "counter",
|
|
@@ -104,7 +110,7 @@ describe("snapshot-store — round-trip", () => {
|
|
|
104
110
|
state: { count: 100, label: "checkpoint" },
|
|
105
111
|
});
|
|
106
112
|
|
|
107
|
-
const loaded = await loadLatestSnapshot<CounterState>(
|
|
113
|
+
const loaded = await loadLatestSnapshot<CounterState>(bun.db, aggId, tenant);
|
|
108
114
|
expect(loaded).not.toBeNull();
|
|
109
115
|
expect(loaded?.version).toBe(42);
|
|
110
116
|
expect(loaded?.state).toEqual({ count: 100, label: "checkpoint" });
|
|
@@ -113,28 +119,28 @@ describe("snapshot-store — round-trip", () => {
|
|
|
113
119
|
|
|
114
120
|
test("saveSnapshot is idempotent — re-snapshotting the same version upserts", async () => {
|
|
115
121
|
const aggId = uuid();
|
|
116
|
-
await saveSnapshot(
|
|
122
|
+
await saveSnapshot(bun.db, {
|
|
117
123
|
aggregateId: aggId,
|
|
118
124
|
tenantId: tenant,
|
|
119
125
|
aggregateType: "counter",
|
|
120
126
|
version: 10,
|
|
121
127
|
state: { count: 10, label: "v1" },
|
|
122
128
|
});
|
|
123
|
-
await saveSnapshot(
|
|
129
|
+
await saveSnapshot(bun.db, {
|
|
124
130
|
aggregateId: aggId,
|
|
125
131
|
tenantId: tenant,
|
|
126
132
|
aggregateType: "counter",
|
|
127
133
|
version: 10,
|
|
128
134
|
state: { count: 10, label: "v2-updated" },
|
|
129
135
|
});
|
|
130
|
-
const loaded = await loadLatestSnapshot<CounterState>(
|
|
136
|
+
const loaded = await loadLatestSnapshot<CounterState>(bun.db, aggId, tenant);
|
|
131
137
|
expect(loaded?.state.label).toBe("v2-updated");
|
|
132
138
|
});
|
|
133
139
|
|
|
134
140
|
test("loadLatestSnapshot picks the highest version when multiple exist", async () => {
|
|
135
141
|
const aggId = uuid();
|
|
136
142
|
for (const v of [5, 50, 20, 100, 75]) {
|
|
137
|
-
await saveSnapshot(
|
|
143
|
+
await saveSnapshot(bun.db, {
|
|
138
144
|
aggregateId: aggId,
|
|
139
145
|
tenantId: tenant,
|
|
140
146
|
aggregateType: "counter",
|
|
@@ -142,7 +148,7 @@ describe("snapshot-store — round-trip", () => {
|
|
|
142
148
|
state: { count: v * 2, label: `at-${v}` },
|
|
143
149
|
});
|
|
144
150
|
}
|
|
145
|
-
const loaded = await loadLatestSnapshot<CounterState>(
|
|
151
|
+
const loaded = await loadLatestSnapshot<CounterState>(bun.db, aggId, tenant);
|
|
146
152
|
expect(loaded?.version).toBe(100);
|
|
147
153
|
expect(loaded?.state.label).toBe("at-100");
|
|
148
154
|
});
|
|
@@ -155,7 +161,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
155
161
|
// Snapshot MUST reflect that truth; otherwise the "snapshot + deltas =
|
|
156
162
|
// full replay" invariant is meaningless.
|
|
157
163
|
const partial = { count: 40, label: "snap-at-40" };
|
|
158
|
-
await saveSnapshot(
|
|
164
|
+
await saveSnapshot(bun.db, {
|
|
159
165
|
aggregateId: aggId,
|
|
160
166
|
tenantId: tenant,
|
|
161
167
|
aggregateType: "counter",
|
|
@@ -165,7 +171,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
165
171
|
|
|
166
172
|
const full = await loadFullState(aggId);
|
|
167
173
|
const snapBased = await loadAggregateWithSnapshot<CounterState>(
|
|
168
|
-
|
|
174
|
+
bun.db,
|
|
169
175
|
aggId,
|
|
170
176
|
tenant,
|
|
171
177
|
reducer,
|
|
@@ -181,7 +187,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
181
187
|
const aggId = await seedAggregate(20);
|
|
182
188
|
const full = await loadFullState(aggId);
|
|
183
189
|
const snapBased = await loadAggregateWithSnapshot<CounterState>(
|
|
184
|
-
|
|
190
|
+
bun.db,
|
|
185
191
|
aggId,
|
|
186
192
|
tenant,
|
|
187
193
|
reducer,
|
|
@@ -198,14 +204,14 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
198
204
|
// presence. Otherwise a snapshot would silently survive a GDPR-style
|
|
199
205
|
// archival and leak state that the event log hid.
|
|
200
206
|
const aggId = await seedAggregate(10);
|
|
201
|
-
await saveSnapshot(
|
|
207
|
+
await saveSnapshot(bun.db, {
|
|
202
208
|
aggregateId: aggId,
|
|
203
209
|
tenantId: tenant,
|
|
204
210
|
aggregateType: "counter",
|
|
205
211
|
version: 10,
|
|
206
212
|
state: { count: 10, label: "pre-archive" },
|
|
207
213
|
});
|
|
208
|
-
await archiveStream(
|
|
214
|
+
await archiveStream(bun.db, {
|
|
209
215
|
tenantId: tenant,
|
|
210
216
|
aggregateId: aggId,
|
|
211
217
|
aggregateType: "counter",
|
|
@@ -213,7 +219,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
213
219
|
});
|
|
214
220
|
|
|
215
221
|
const snapBased = await loadAggregateWithSnapshot<CounterState>(
|
|
216
|
-
|
|
222
|
+
bun.db,
|
|
217
223
|
aggId,
|
|
218
224
|
tenant,
|
|
219
225
|
reducer,
|
|
@@ -225,7 +231,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
225
231
|
|
|
226
232
|
// includeArchived opt-in surfaces the snapshot + deltas for ops tooling.
|
|
227
233
|
const archived = await loadAggregateWithSnapshot<CounterState>(
|
|
228
|
-
|
|
234
|
+
bun.db,
|
|
229
235
|
aggId,
|
|
230
236
|
tenant,
|
|
231
237
|
reducer,
|
|
@@ -238,7 +244,7 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
238
244
|
|
|
239
245
|
test("1000-event aggregate with snapshot at v900 loads in under 50ms", async () => {
|
|
240
246
|
const aggId = await seedAggregate(1000);
|
|
241
|
-
await saveSnapshot(
|
|
247
|
+
await saveSnapshot(bun.db, {
|
|
242
248
|
aggregateId: aggId,
|
|
243
249
|
tenantId: tenant,
|
|
244
250
|
aggregateType: "counter",
|
|
@@ -247,11 +253,11 @@ describe("snapshot-store — loadAggregateWithSnapshot", () => {
|
|
|
247
253
|
});
|
|
248
254
|
|
|
249
255
|
// Warm cache
|
|
250
|
-
await loadAggregateWithSnapshot<CounterState>(
|
|
256
|
+
await loadAggregateWithSnapshot<CounterState>(bun.db, aggId, tenant, reducer, initial);
|
|
251
257
|
|
|
252
258
|
const start = performance.now();
|
|
253
259
|
const snapBased = await loadAggregateWithSnapshot<CounterState>(
|
|
254
|
-
|
|
260
|
+
bun.db,
|
|
255
261
|
aggId,
|
|
256
262
|
tenant,
|
|
257
263
|
reducer,
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
// - die throw-Policy bleibt unverändert (Regression-Guard)
|
|
7
7
|
// - listDeadLetters filtert per eventType
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
10
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
11
|
+
import { asRawClient, insertOne, selectMany } from "../../db/query";
|
|
12
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
12
13
|
import type { StoredEvent } from "../event-store";
|
|
13
14
|
import { createEventsTable, eventsTable } from "../events-schema";
|
|
14
15
|
import { type EventUpcasters, makeUpcastCtx, upcastStoredEvents } from "../upcaster";
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
upcasterDeadLetterTable,
|
|
19
20
|
} from "../upcaster-dead-letter";
|
|
20
21
|
|
|
21
|
-
let testDb:
|
|
22
|
+
let testDb: BunTestDb;
|
|
22
23
|
|
|
23
24
|
const TENANT_ID = "00000000-0000-4000-8000-0000000000aa";
|
|
24
25
|
|
|
@@ -78,6 +79,7 @@ const passthroughUpcasters: EventUpcasters = new Map([
|
|
|
78
79
|
]);
|
|
79
80
|
|
|
80
81
|
beforeAll(async () => {
|
|
82
|
+
await ensureTemporalPolyfill();
|
|
81
83
|
testDb = await createTestDb();
|
|
82
84
|
await createEventsTable(testDb.db);
|
|
83
85
|
await createUpcasterDeadLetterTable(testDb.db);
|
|
@@ -88,8 +90,8 @@ afterAll(async () => {
|
|
|
88
90
|
});
|
|
89
91
|
|
|
90
92
|
afterEach(async () => {
|
|
91
|
-
await testDb.db.
|
|
92
|
-
await testDb.db.
|
|
93
|
+
await asRawClient(testDb.db).unsafe(`DELETE FROM "${upcasterDeadLetterTable.tableName}"`);
|
|
94
|
+
await asRawClient(testDb.db).unsafe(`DELETE FROM "${eventsTable.tableName}"`);
|
|
93
95
|
});
|
|
94
96
|
|
|
95
97
|
describe("upcaster error-policy: throw (default)", () => {
|
|
@@ -165,7 +167,7 @@ describe("upcaster error-policy: quarantine", () => {
|
|
|
165
167
|
);
|
|
166
168
|
|
|
167
169
|
// Drop one directly to add noise of a different type.
|
|
168
|
-
await testDb.db
|
|
170
|
+
await insertOne(testDb.db, upcasterDeadLetterTable, {
|
|
169
171
|
eventId: "99",
|
|
170
172
|
tenantId: TENANT_ID,
|
|
171
173
|
aggregateId: "other",
|
|
@@ -195,10 +197,7 @@ describe("upcaster error-policy: quarantine", () => {
|
|
|
195
197
|
errorPolicy: "quarantine",
|
|
196
198
|
});
|
|
197
199
|
|
|
198
|
-
const rows = await testDb.db
|
|
199
|
-
|
|
200
|
-
.from(upcasterDeadLetterTable)
|
|
201
|
-
.where(eq(upcasterDeadLetterTable.eventId, "30"));
|
|
202
|
-
expect(rows[0]?.c).toBe(2);
|
|
200
|
+
const rows = await selectMany(testDb.db, upcasterDeadLetterTable, { eventId: "30" });
|
|
201
|
+
expect(rows).toHaveLength(2);
|
|
203
202
|
});
|
|
204
203
|
});
|