@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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
2
3
|
import type { TableColumns } from "../../db/dialect";
|
|
3
4
|
import { createEventStoreExecutor, type EventStoreExecutor } from "../../db/event-store-executor";
|
|
4
|
-
import {
|
|
5
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
5
6
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
6
7
|
import {
|
|
7
8
|
createEntity,
|
|
@@ -11,13 +12,14 @@ import {
|
|
|
11
12
|
type Registry,
|
|
12
13
|
} from "../../engine";
|
|
13
14
|
import { createEventsTable } from "../../event-store";
|
|
14
|
-
import {
|
|
15
|
+
import { TestUsers, unsafeCreateEntityTable } from "../../stack";
|
|
16
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
15
17
|
import { createCascadeDeleteHook } from "../cascade-handler";
|
|
16
18
|
|
|
17
19
|
// biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables
|
|
18
20
|
type Table = TableColumns<any>;
|
|
19
21
|
|
|
20
|
-
let testDb:
|
|
22
|
+
let testDb: BunTestDb;
|
|
21
23
|
let tdb: TenantDb;
|
|
22
24
|
let registry: Registry;
|
|
23
25
|
let departmentTable: Table;
|
|
@@ -77,6 +79,7 @@ const memberEntity = createEntity({
|
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
beforeAll(async () => {
|
|
82
|
+
await ensureTemporalPolyfill();
|
|
80
83
|
testDb = await createTestDb();
|
|
81
84
|
await createEventsTable(testDb.db);
|
|
82
85
|
tdb = createTenantDb(testDb.db, admin.tenantId);
|
|
@@ -90,14 +93,14 @@ beforeAll(async () => {
|
|
|
90
93
|
await unsafeCreateEntityTable(testDb.db, teamEntity);
|
|
91
94
|
await unsafeCreateEntityTable(testDb.db, memberEntity);
|
|
92
95
|
|
|
93
|
-
departmentTable =
|
|
94
|
-
userTable =
|
|
95
|
-
sessionTable =
|
|
96
|
-
groupTable =
|
|
97
|
-
userGroupRestrictTable =
|
|
98
|
-
userGroupCascadeTable =
|
|
99
|
-
teamTable =
|
|
100
|
-
memberTable =
|
|
96
|
+
departmentTable = buildEntityTable("department", departmentEntity);
|
|
97
|
+
userTable = buildEntityTable("user", userEntity);
|
|
98
|
+
sessionTable = buildEntityTable("session", sessionEntity);
|
|
99
|
+
groupTable = buildEntityTable("group", groupEntity);
|
|
100
|
+
userGroupRestrictTable = buildEntityTable("user-group-restrict", userGroupRestrictEntity);
|
|
101
|
+
userGroupCascadeTable = buildEntityTable("user-group-cascade", userGroupCascadeEntity);
|
|
102
|
+
teamTable = buildEntityTable("team", teamEntity);
|
|
103
|
+
memberTable = buildEntityTable("member", memberEntity);
|
|
101
104
|
|
|
102
105
|
const feature = defineFeature("cascade", (r) => {
|
|
103
106
|
r.entity("department", departmentEntity);
|
|
@@ -219,10 +222,10 @@ describe("cascade delete: cascade", () => {
|
|
|
219
222
|
const user = await userExecutor.create({ name: "Cascade User" }, admin, tdb);
|
|
220
223
|
if (!user.isSuccess) throw new Error("Setup failed");
|
|
221
224
|
|
|
222
|
-
await sessionExecutor.create({ userId: user.data.id, token: "abc" }, admin, tdb);
|
|
223
|
-
await sessionExecutor.create({ userId: user.data.id, token: "def" }, admin, tdb);
|
|
225
|
+
const s1 = await sessionExecutor.create({ userId: user.data.id, token: "abc" }, admin, tdb);
|
|
226
|
+
const s2 = await sessionExecutor.create({ userId: user.data.id, token: "def" }, admin, tdb);
|
|
227
|
+
if (!s1.isSuccess || !s2.isSuccess) throw new Error("Setup failed");
|
|
224
228
|
|
|
225
|
-
// Verify sessions exist
|
|
226
229
|
const before = await sessionExecutor.list({}, admin, tdb);
|
|
227
230
|
const sessionsBefore = before.rows.filter((r) => r["userId"] === user.data.id);
|
|
228
231
|
expect(sessionsBefore.length).toBe(2);
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
// The active propagation into cascaded writes is covered by
|
|
16
16
|
// msp-multi-hop.integration.ts.
|
|
17
17
|
|
|
18
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
18
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { requestContext } from "../../api/request-context";
|
|
21
21
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
22
|
-
import {
|
|
22
|
+
import { selectMany } from "../../db/query";
|
|
23
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
23
24
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
24
25
|
import { eventsTable } from "../../event-store";
|
|
25
26
|
import {
|
|
@@ -39,7 +40,7 @@ const orderEntity = createEntity({
|
|
|
39
40
|
},
|
|
40
41
|
});
|
|
41
42
|
|
|
42
|
-
const orderTable =
|
|
43
|
+
const orderTable = buildEntityTable("causation-order", orderEntity);
|
|
43
44
|
|
|
44
45
|
// MSP-apply observation sink — every apply run pushes its reqCtx snapshot
|
|
45
46
|
// here so the tests can assert what the event-dispatcher wrapped it with.
|
|
@@ -85,7 +86,7 @@ const causationFeature = defineFeature("causation", (r) => {
|
|
|
85
86
|
[placed.name]: async (event) => {
|
|
86
87
|
const ctx = requestContext.get();
|
|
87
88
|
applyObservations.push({
|
|
88
|
-
forEventId: String(event
|
|
89
|
+
forEventId: String(event["id"]),
|
|
89
90
|
correlationId: ctx?.correlationId,
|
|
90
91
|
causationId: ctx?.causationId,
|
|
91
92
|
});
|
|
@@ -118,11 +119,9 @@ afterEach(async () => {
|
|
|
118
119
|
|
|
119
120
|
// --- Helpers ---
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const rows = await stack.db.select().from(eventsTable);
|
|
125
|
-
return rows.filter((r) => r.type === type);
|
|
122
|
+
async function eventsByType(type: string) {
|
|
123
|
+
const rows = await selectMany(stack.db, eventsTable);
|
|
124
|
+
return rows.filter((r: Record<string, unknown>) => r["type"] === type);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
// --- Tests ---
|
|
@@ -133,7 +132,7 @@ describe("Runde 2 — correlationId on root HTTP request", () => {
|
|
|
133
132
|
|
|
134
133
|
const [placedEvent] = await eventsByType("causation:event:placed");
|
|
135
134
|
expect(placedEvent).toBeDefined();
|
|
136
|
-
const meta = placedEvent?.metadata as {
|
|
135
|
+
const meta = placedEvent?.["metadata"] as {
|
|
137
136
|
requestId?: string;
|
|
138
137
|
correlationId?: string;
|
|
139
138
|
causationId?: string;
|
|
@@ -159,10 +158,10 @@ describe("Runde 2 — correlationId on root HTTP request", () => {
|
|
|
159
158
|
const crudEvent = (await eventsByType("causation-order.created"))[0];
|
|
160
159
|
const placedEvent = (await eventsByType("causation:event:placed"))[0];
|
|
161
160
|
|
|
162
|
-
expect((crudEvent?.metadata as { correlationId?: string })?.correlationId).toBe(
|
|
161
|
+
expect((crudEvent?.["metadata"] as { correlationId?: string })?.correlationId).toBe(
|
|
163
162
|
"test-chain-abc123",
|
|
164
163
|
);
|
|
165
|
-
expect((placedEvent?.metadata as { correlationId?: string })?.correlationId).toBe(
|
|
164
|
+
expect((placedEvent?.["metadata"] as { correlationId?: string })?.correlationId).toBe(
|
|
166
165
|
"test-chain-abc123",
|
|
167
166
|
);
|
|
168
167
|
});
|
|
@@ -192,7 +191,7 @@ describe("Runde 2 — event-dispatcher propagates correlation + causation to MSP
|
|
|
192
191
|
// should have seen as causationId.
|
|
193
192
|
const [placedEvent] = await eventsByType("causation:event:placed");
|
|
194
193
|
expect(placedEvent).toBeDefined();
|
|
195
|
-
const placedId = String(placedEvent?.id);
|
|
194
|
+
const placedId = String(placedEvent?.["id"]);
|
|
196
195
|
|
|
197
196
|
// Observation recorded inside the MSP apply.
|
|
198
197
|
expect(applyObservations).toHaveLength(1);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
4
|
-
import {
|
|
4
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
5
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
5
6
|
import {
|
|
6
7
|
access,
|
|
7
8
|
createEntity,
|
|
@@ -27,7 +28,7 @@ const bagEntity = createEntity({
|
|
|
27
28
|
counter: createNumberField({ default: 0 }),
|
|
28
29
|
},
|
|
29
30
|
});
|
|
30
|
-
const bagTable =
|
|
31
|
+
const bagTable = buildEntityTable("bag", bagEntity);
|
|
31
32
|
|
|
32
33
|
// secret has a system-only read field — proves queryAs(system) reads it,
|
|
33
34
|
// plain query doesn't.
|
|
@@ -41,7 +42,7 @@ const secretEntity = createEntity({
|
|
|
41
42
|
}),
|
|
42
43
|
},
|
|
43
44
|
});
|
|
44
|
-
const secretTable =
|
|
45
|
+
const secretTable = buildEntityTable("secret", secretEntity);
|
|
45
46
|
|
|
46
47
|
let stack: TestStack;
|
|
47
48
|
const admin = TestUsers.admin;
|
|
@@ -76,7 +77,7 @@ const bridgeFeature = defineFeature("ctxbridge", (r) => {
|
|
|
76
77
|
"secret:by-owner",
|
|
77
78
|
z.object({ owner: z.string() }),
|
|
78
79
|
async (query, ctx) => {
|
|
79
|
-
const rows = await ctx.db
|
|
80
|
+
const rows = await selectMany(ctx.db, secretTable);
|
|
80
81
|
return (
|
|
81
82
|
(rows as Array<Record<string, unknown>>).find((r) => r["owner"] === query.payload.owner) ??
|
|
82
83
|
null
|
|
@@ -154,8 +155,8 @@ afterAll(async () => {
|
|
|
154
155
|
|
|
155
156
|
beforeEach(async () => {
|
|
156
157
|
afterCommitLog.length = 0;
|
|
157
|
-
await stack.db.
|
|
158
|
-
await stack.db.
|
|
158
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${bagTable.tableName}"`);
|
|
159
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${secretTable.tableName}"`);
|
|
159
160
|
// Clear the event-dedup cache — tests re-use entity ids (Postgres sequences
|
|
160
161
|
// reset, each test sees id=1). Without flushing Redis the second test hits
|
|
161
162
|
// a dedup hit on the same handler:id:version:phase key and the hook is
|
|
@@ -195,8 +196,8 @@ describe("ctx.writeAs shares the outer transaction", () => {
|
|
|
195
196
|
expect(body.isSuccess).toBe(false);
|
|
196
197
|
|
|
197
198
|
// Both tables empty — outer bag + inner secret rolled back together
|
|
198
|
-
const bags = await stack.db
|
|
199
|
-
const secrets = await stack.db
|
|
199
|
+
const bags = await selectMany(stack.db, bagTable);
|
|
200
|
+
const secrets = await selectMany(stack.db, secretTable);
|
|
200
201
|
expect(bags).toHaveLength(0);
|
|
201
202
|
expect(secrets).toHaveLength(0);
|
|
202
203
|
});
|
|
@@ -209,8 +210,8 @@ describe("ctx.writeAs shares the outer transaction", () => {
|
|
|
209
210
|
);
|
|
210
211
|
expect((await res.json()).isSuccess).toBe(true);
|
|
211
212
|
|
|
212
|
-
const bags = await stack.db
|
|
213
|
-
const secrets = await stack.db
|
|
213
|
+
const bags = await selectMany(stack.db, bagTable);
|
|
214
|
+
const secrets = await selectMany(stack.db, secretTable);
|
|
214
215
|
expect(bags).toHaveLength(1);
|
|
215
216
|
expect(secrets).toHaveLength(1);
|
|
216
217
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createSystemUser } from "../../engine/system-user";
|
|
3
|
+
import { InternalError } from "../../errors";
|
|
4
|
+
import {
|
|
5
|
+
describeShape,
|
|
6
|
+
dispatcherSpanAttributes,
|
|
7
|
+
extractNestedSpecs,
|
|
8
|
+
isFailedWriteResult,
|
|
9
|
+
isLifecycleResult,
|
|
10
|
+
isWriteResultShape,
|
|
11
|
+
prefixValidationPath,
|
|
12
|
+
resolveType,
|
|
13
|
+
wrapToKumiko,
|
|
14
|
+
} from "../dispatcher-utils";
|
|
15
|
+
|
|
16
|
+
describe("isFailedWriteResult", () => {
|
|
17
|
+
test("narrows failed write results", () => {
|
|
18
|
+
const result = { isSuccess: false as const, error: { code: "validation_error" } };
|
|
19
|
+
expect(isFailedWriteResult(result)).toBe(true);
|
|
20
|
+
expect(isFailedWriteResult({ isSuccess: true, data: {} })).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("isWriteResultShape / isLifecycleResult", () => {
|
|
25
|
+
test("detects write-result envelope", () => {
|
|
26
|
+
expect(isWriteResultShape({ isSuccess: true, data: 1 })).toBe(true);
|
|
27
|
+
expect(isWriteResultShape({ kind: "created" })).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("detects lifecycle results", () => {
|
|
31
|
+
expect(isLifecycleResult({ kind: "deleted" })).toBe(true);
|
|
32
|
+
expect(isLifecycleResult(null)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("describeShape", () => {
|
|
37
|
+
test("summarizes unknown values", () => {
|
|
38
|
+
expect(describeShape(null)).toBe("null");
|
|
39
|
+
expect(describeShape("x")).toBe("string");
|
|
40
|
+
expect(describeShape({ a: 1, b: 2 })).toContain("object with keys");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("dispatcherSpanAttributes", () => {
|
|
45
|
+
test("includes handler, operation, user, tenant, optional feature", () => {
|
|
46
|
+
const user = createSystemUser("tenant-1");
|
|
47
|
+
const attrs = dispatcherSpanAttributes("feat:query:task:list", "query", user, "feat");
|
|
48
|
+
expect(attrs).toMatchObject({
|
|
49
|
+
"kumiko.handler": "feat:query:task:list",
|
|
50
|
+
"kumiko.operation": "query",
|
|
51
|
+
"kumiko.feature": "feat",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("prefixValidationPath", () => {
|
|
57
|
+
test("prefixes validation field paths", () => {
|
|
58
|
+
const info = {
|
|
59
|
+
code: "validation_error",
|
|
60
|
+
httpStatus: 400,
|
|
61
|
+
i18nKey: "errors.validation.failed",
|
|
62
|
+
message: "Validation failed",
|
|
63
|
+
details: {
|
|
64
|
+
fields: [{ path: "title", code: "too_small", i18nKey: "errors.validation.too_small" }],
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const prefixed = prefixValidationPath(info, "tasks.0");
|
|
68
|
+
const fields = (prefixed.details as { fields: { path: string }[] }).fields;
|
|
69
|
+
expect(fields[0]?.path).toBe("tasks.0.title");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("leaves non-validation errors unchanged", () => {
|
|
73
|
+
const info = {
|
|
74
|
+
code: "not_found",
|
|
75
|
+
httpStatus: 404,
|
|
76
|
+
i18nKey: "errors.notFound",
|
|
77
|
+
message: "missing",
|
|
78
|
+
};
|
|
79
|
+
expect(prefixValidationPath(info, "x")).toBe(info);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("resolveType", () => {
|
|
84
|
+
test("unwraps HandlerRef objects", () => {
|
|
85
|
+
expect(resolveType({ name: "feat:write:task:create" })).toBe("feat:write:task:create");
|
|
86
|
+
expect(resolveType("feat:query:task:list")).toBe("feat:query:task:list");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("wrapToKumiko", () => {
|
|
91
|
+
test("passes through KumikoError instances", () => {
|
|
92
|
+
const err = new InternalError();
|
|
93
|
+
expect(wrapToKumiko(err)).toBe(err);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("wraps generic Error as InternalError", () => {
|
|
97
|
+
const wrapped = wrapToKumiko(new TypeError("boom"));
|
|
98
|
+
expect(wrapped.code).toBe("internal_error");
|
|
99
|
+
expect(wrapped.cause).toBeInstanceOf(TypeError);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("extractNestedSpecs", () => {
|
|
104
|
+
test("returns null for non-create handlers", () => {
|
|
105
|
+
expect(extractNestedSpecs("feat:write:task:update", { tasks: [] }, {} as never)).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from "
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
|
|
4
4
|
import type { TenantId } from "../../engine/types/identifiers";
|
|
@@ -97,7 +97,7 @@ describe("dispatcher.write", () => {
|
|
|
97
97
|
const res = await dispatcher.write("alias:write:item:create", { name: "x" }, user);
|
|
98
98
|
expect(res.isSuccess).toBe(true);
|
|
99
99
|
expect(captured.fromCtx).toBe(captured.fromEvent);
|
|
100
|
-
expect((captured.fromCtx as { id:
|
|
100
|
+
expect((captured.fromCtx as { id: unknown }).id).toBe(user.id);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test("runs validation hooks", async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { createTestRedis, type TestRedis } from "../../stack";
|
|
3
3
|
import { createDistributedLock } from "../distributed-lock";
|
|
4
4
|
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
// Without all three pieces wired together (registry opens defineEvent names,
|
|
10
10
|
// projections-runner fires on appendEvent, dispatcher routes appendEvent to
|
|
11
11
|
// the aggregate stream), any of the assertions below go red.
|
|
12
|
-
|
|
13
|
-
import { eq } from "drizzle-orm";
|
|
14
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
12
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
15
13
|
import { z } from "zod";
|
|
16
14
|
import {
|
|
17
15
|
integer as pgInteger,
|
|
@@ -20,7 +18,8 @@ import {
|
|
|
20
18
|
uuid as pgUuid,
|
|
21
19
|
} from "../../db/dialect";
|
|
22
20
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
23
|
-
import {
|
|
21
|
+
import { insertOne, selectMany, updateMany } from "../../db/query";
|
|
22
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
24
23
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
25
24
|
import { loadAggregate } from "../../event-store";
|
|
26
25
|
import {
|
|
@@ -41,7 +40,7 @@ const shipmentEntity = createEntity({
|
|
|
41
40
|
},
|
|
42
41
|
});
|
|
43
42
|
|
|
44
|
-
const shipmentTable =
|
|
43
|
+
const shipmentTable = buildEntityTable("domain-shipment", shipmentEntity);
|
|
45
44
|
|
|
46
45
|
// --- Read-model table (fed by the projection below) ---
|
|
47
46
|
|
|
@@ -69,7 +68,7 @@ const shippingFeature = defineFeature("shipping", (r) => {
|
|
|
69
68
|
// Auto CRUD event — fires on shipment create.
|
|
70
69
|
"domain-shipment.created": async (event, tx) => {
|
|
71
70
|
const payload = event.payload as { cargo?: string };
|
|
72
|
-
await tx
|
|
71
|
+
await insertOne(tx, billingTable, {
|
|
73
72
|
shipmentId: event.aggregateId,
|
|
74
73
|
tenantId: event.tenantId,
|
|
75
74
|
cargo: payload.cargo ?? "",
|
|
@@ -82,10 +81,12 @@ const shippingFeature = defineFeature("shipping", (r) => {
|
|
|
82
81
|
// create-apply just inserted.
|
|
83
82
|
[shipmentBilled.name]: async (event, tx) => {
|
|
84
83
|
const payload = event.payload as { cost: number };
|
|
85
|
-
await
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.
|
|
84
|
+
await updateMany(
|
|
85
|
+
tx,
|
|
86
|
+
billingTable,
|
|
87
|
+
{ totalCost: payload.cost, billedMarker: "billed" },
|
|
88
|
+
{ shipmentId: event.aggregateId },
|
|
89
|
+
);
|
|
89
90
|
},
|
|
90
91
|
},
|
|
91
92
|
});
|
|
@@ -196,7 +197,7 @@ describe("Marten gold-standard: domain events → inline projections", () => {
|
|
|
196
197
|
admin,
|
|
197
198
|
);
|
|
198
199
|
|
|
199
|
-
const rows = await stack.db
|
|
200
|
+
const rows = await selectMany(stack.db, billingTable);
|
|
200
201
|
expect(rows).toHaveLength(1);
|
|
201
202
|
expect(rows[0]?.shipmentId).toBe(data.id);
|
|
202
203
|
expect(rows[0]?.billedMarker).toBe("pending");
|
|
@@ -212,10 +213,7 @@ describe("Marten gold-standard: domain events → inline projections", () => {
|
|
|
212
213
|
|
|
213
214
|
await stack.http.writeOk("shipping:write:shipment:bill", { id: created.id, cost: 1500 }, admin);
|
|
214
215
|
|
|
215
|
-
const [row] = await stack.db
|
|
216
|
-
.select()
|
|
217
|
-
.from(billingTable)
|
|
218
|
-
.where(eq(billingTable.shipmentId, created.id));
|
|
216
|
+
const [row] = await selectMany(stack.db, billingTable, { shipmentId: created.id });
|
|
219
217
|
expect(row).toBeDefined();
|
|
220
218
|
expect(row?.billedMarker).toBe("billed");
|
|
221
219
|
expect(row?.totalCost).toBe(1500);
|
package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
4
4
|
import { defineFeature, type SaveContext } from "../../engine";
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
// it hits the events-table. Mismatches throw ValidationError.
|
|
11
11
|
// 3. r.defineEvent returns `{ name: qualifiedName, schema }` — callers
|
|
12
12
|
// pass `def.name` to ctx.appendEvent without building the qn manually.
|
|
13
|
-
|
|
14
|
-
import { eq } from "drizzle-orm";
|
|
15
|
-
import { afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
13
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
16
14
|
import { z } from "zod";
|
|
15
|
+
import { selectMany } from "../../db/query";
|
|
17
16
|
import { defineFeature } from "../../engine";
|
|
18
17
|
import { eventsTable } from "../../event-store";
|
|
19
18
|
import {
|
|
@@ -146,10 +145,7 @@ describe("E.3 — ctx.appendEvent strict validation", () => {
|
|
|
146
145
|
);
|
|
147
146
|
expect(res.status).toBe(200);
|
|
148
147
|
|
|
149
|
-
const stored = await stack.db
|
|
150
|
-
.select()
|
|
151
|
-
.from(eventsTable)
|
|
152
|
-
.where(eq(eventsTable.type, welcomeEventName));
|
|
148
|
+
const stored = await selectMany(stack.db, eventsTable, { type: welcomeEventName });
|
|
153
149
|
expect(stored).toHaveLength(1);
|
|
154
150
|
expect(stored[0]?.payload).toEqual({ userId, email: "welcome@test.de" });
|
|
155
151
|
expect(stored[0]?.aggregateType).toBe("user");
|
|
@@ -164,7 +160,7 @@ describe("E.3 — ctx.appendEvent strict validation", () => {
|
|
|
164
160
|
// Non-Kumiko throw inside a handler → wrapped as InternalError → 500.
|
|
165
161
|
expect(res.status).toBe(500);
|
|
166
162
|
|
|
167
|
-
const stored = await stack.db
|
|
163
|
+
const stored = await selectMany(stack.db, eventsTable);
|
|
168
164
|
// TX rolled back: no event landed.
|
|
169
165
|
expect(stored).toHaveLength(0);
|
|
170
166
|
});
|
|
@@ -175,10 +171,7 @@ describe("E.3 — ctx.appendEvent strict validation", () => {
|
|
|
175
171
|
// ValidationError is a first-class Kumiko error → 400.
|
|
176
172
|
expect(res.status).toBe(400);
|
|
177
173
|
|
|
178
|
-
const stored = await stack.db
|
|
179
|
-
.select()
|
|
180
|
-
.from(eventsTable)
|
|
181
|
-
.where(eq(eventsTable.type, welcomeEventName));
|
|
174
|
+
const stored = await selectMany(stack.db, eventsTable, { type: welcomeEventName });
|
|
182
175
|
expect(stored).toHaveLength(0);
|
|
183
176
|
});
|
|
184
177
|
});
|
|
@@ -193,10 +186,7 @@ describe("E.3 — cross-feature ownership guard", () => {
|
|
|
193
186
|
const res = await stack.http.write("emitter:write:emit:foreign-event", { userId }, admin);
|
|
194
187
|
expect(res.status).toBe(500);
|
|
195
188
|
|
|
196
|
-
const stored = await stack.db
|
|
197
|
-
.select()
|
|
198
|
-
.from(eventsTable)
|
|
199
|
-
.where(eq(eventsTable.type, foreignEventName));
|
|
189
|
+
const stored = await selectMany(stack.db, eventsTable, { type: foreignEventName });
|
|
200
190
|
expect(stored).toHaveLength(0);
|
|
201
191
|
});
|
|
202
192
|
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// via payload, not via a wrapped DB handle. Tenant-isolation-via-MSP is
|
|
13
13
|
// tested in multi-stream-projection.integration.ts.
|
|
14
14
|
|
|
15
|
-
import { afterEach, beforeAll, describe, expect, test } from "
|
|
15
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
16
16
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
17
17
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
18
18
|
import { defineFeature } from "../../engine";
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
// single-instance runs. Any regression in the locking strategy shows up
|
|
19
19
|
// here first.
|
|
20
20
|
|
|
21
|
-
import { afterEach, beforeAll, describe, expect, test } from "
|
|
21
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
22
22
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
23
|
+
import { insertMany } from "../../db/query";
|
|
23
24
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
24
25
|
import { defineFeature } from "../../engine";
|
|
25
26
|
import { eventsTable, type StoredEvent } from "../../event-store";
|
|
@@ -91,7 +92,7 @@ async function bulkSeedWidgetCreated(count: number, namePrefix: string): Promise
|
|
|
91
92
|
metadata: { userId: admin.id },
|
|
92
93
|
createdBy: admin.id,
|
|
93
94
|
}));
|
|
94
|
-
await stack.db
|
|
95
|
+
await insertMany(stack.db, eventsTable, rows);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
function buildDispatcherWith(consumers: readonly EventConsumer[]): EventDispatcher {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 2. The dispatcher starts cleanly when pgClient is wired and stops
|
|
12
12
|
// without leaking the LISTEN connection.
|
|
13
13
|
|
|
14
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
15
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
16
16
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
17
17
|
import { defineFeature } from "../../engine";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// will never succeed (broken payload, removed code).
|
|
15
15
|
// (list/status are read-only; covered by event-dispatcher wiring tests.)
|
|
16
16
|
|
|
17
|
-
import { afterEach, beforeAll, describe, expect, test } from "
|
|
17
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
18
18
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
19
19
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
20
20
|
import { defineFeature } from "../../engine";
|
|
@@ -110,7 +110,7 @@ describe("E.9 — restartConsumer", () => {
|
|
|
110
110
|
expect(after.attempts).toBe(0);
|
|
111
111
|
expect(after.lastError).toBeNull();
|
|
112
112
|
// Cursor unchanged — next pass retries the SAME failing event.
|
|
113
|
-
expect(after.lastProcessedEventId).toBe(cursorBefore);
|
|
113
|
+
expect(after.lastProcessedEventId).toBe(cursorBefore!);
|
|
114
114
|
|
|
115
115
|
// Retry still poisoned (handler still throws) — attempts climbs again.
|
|
116
116
|
await stack.eventDispatcher?.runOnce();
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// moment delivery latency regresses from TCP-round-trip to
|
|
15
15
|
// pollIntervalMs.
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
18
|
+
import { asRawClient, insertOne, selectMany, updateMany } from "../../db/query";
|
|
19
19
|
import { defineFeature } from "../../engine";
|
|
20
20
|
import { eventsTable } from "../../event-store";
|
|
21
21
|
import {
|
|
@@ -70,7 +70,7 @@ afterEach(async () => {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
async function seedOldWidgetEvent(createdAt: Temporal.Instant): Promise<void> {
|
|
73
|
-
await stack.db
|
|
73
|
+
await insertOne(stack.db, eventsTable, {
|
|
74
74
|
aggregateId: generateId(),
|
|
75
75
|
aggregateType: "widget",
|
|
76
76
|
tenantId: admin.tenantId,
|
|
@@ -94,7 +94,7 @@ describe("Second audit — consumer pre-registration on start()", () => {
|
|
|
94
94
|
// every consumer at cursor=0, refuses to prune past them.
|
|
95
95
|
await stack.eventDispatcher?.start();
|
|
96
96
|
try {
|
|
97
|
-
const rows = await stack.db
|
|
97
|
+
const rows = await selectMany(stack.db, eventConsumerStateTable);
|
|
98
98
|
const names = new Set(rows.map((r) => r.name));
|
|
99
99
|
|
|
100
100
|
// The test-stack wires only feature MSP consumers (systemHooks: []),
|
|
@@ -138,17 +138,18 @@ describe("Second audit — consumer pre-registration on start()", () => {
|
|
|
138
138
|
|
|
139
139
|
// Advance the cursor explicitly so we can prove the second start
|
|
140
140
|
// doesn't clobber it back to 0.
|
|
141
|
-
await
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
await updateMany(
|
|
142
|
+
stack.db,
|
|
143
|
+
eventConsumerStateTable,
|
|
144
|
+
{ lastProcessedEventId: 42n },
|
|
145
|
+
{ name: "audit:projection:default-scope" },
|
|
146
|
+
);
|
|
145
147
|
|
|
146
148
|
await stack.eventDispatcher?.start();
|
|
147
149
|
try {
|
|
148
|
-
const [row] = await stack.db
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.where(eq(eventConsumerStateTable.name, "audit:projection:default-scope"));
|
|
150
|
+
const [row] = await selectMany(stack.db, eventConsumerStateTable, {
|
|
151
|
+
name: "audit:projection:default-scope",
|
|
152
|
+
});
|
|
152
153
|
expect(row?.lastProcessedEventId).toBe(42n);
|
|
153
154
|
} finally {
|
|
154
155
|
await stack.eventDispatcher?.stop();
|
|
@@ -255,13 +256,13 @@ describe("Second audit — LISTEN gauge", () => {
|
|
|
255
256
|
// `LISTEN "kumiko_events_new"` and is now idle. pg_terminate_backend
|
|
256
257
|
// on it closes the TCP; postgres.js's onclose handler re-subscribes
|
|
257
258
|
// and fires onlisten again.
|
|
258
|
-
await
|
|
259
|
-
|
|
259
|
+
await asRawClient(
|
|
260
|
+
recStack.db,
|
|
261
|
+
).unsafe(`SELECT pg_terminate_backend(pid) FROM pg_stat_activity
|
|
260
262
|
WHERE datname = current_database()
|
|
261
263
|
AND query ILIKE 'listen%'
|
|
262
264
|
AND state = 'idle'
|
|
263
|
-
AND pid <> pg_backend_pid()
|
|
264
|
-
);
|
|
265
|
+
AND pid <> pg_backend_pid()`);
|
|
265
266
|
|
|
266
267
|
// Wait for the SECOND gauge.set(1) — the reconnect. Generous timeout
|
|
267
268
|
// because postgres.js's reconnect loop includes backoff.
|