@cosmicdrift/kumiko-framework 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
- package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
- package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
- package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
- package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
- package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
- package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
- package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
- package/src/api/__tests__/api.test.ts +1 -1
- package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
- package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
- package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/csrf-middleware.test.ts +1 -1
- package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
- package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
- package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
- package/src/api/__tests__/readiness.test.ts +1 -1
- package/src/api/__tests__/request-id-middleware.test.ts +1 -1
- package/src/api/__tests__/sse-broker.test.ts +12 -12
- package/src/api/__tests__/sse-route.test.ts +1 -1
- package/src/api/readiness.ts +2 -2
- package/src/auth/__tests__/roles.test.ts +2 -2
- package/src/bun-db/__tests__/PATTERN.md +73 -0
- package/src/bun-db/__tests__/_helpers.ts +103 -0
- package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
- package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
- package/src/bun-db/__tests__/bun-test-db.ts +19 -0
- package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
- package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
- package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
- package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
- package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
- package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
- package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
- package/src/bun-db/connection.ts +84 -0
- package/src/bun-db/index.ts +31 -0
- package/src/bun-db/query.ts +845 -0
- package/src/compliance/__tests__/duration-spec.test.ts +1 -1
- package/src/compliance/__tests__/profiles.test.ts +1 -1
- package/src/compliance/__tests__/sub-processors.test.ts +1 -1
- package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
- package/src/db/__tests__/big-int-field.test.ts +15 -14
- package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
- package/src/db/__tests__/compound-types.test.ts +1 -1
- package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
- package/src/db/__tests__/connection-options.test.ts +1 -1
- package/src/db/__tests__/dialect-instant.test.ts +1 -1
- package/src/db/__tests__/encryption.test.ts +1 -1
- package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
- package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
- package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
- package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
- package/src/db/__tests__/located-timestamp.test.ts +1 -1
- package/src/db/__tests__/money.test.ts +1 -1
- package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
- package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
- package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
- package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
- package/src/db/__tests__/sql-inventory.test.ts +56 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
- package/src/db/__tests__/table-builder-required.test.ts +20 -22
- package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
- package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
- package/src/db/api.ts +46 -0
- package/src/db/apply-entity-event.ts +45 -36
- package/src/db/assert-exists-in.ts +5 -16
- package/src/db/bun-provider.ts +37 -0
- package/src/db/config-seed.ts +4 -4
- package/src/db/connection.ts +14 -57
- package/src/db/cursor.ts +5 -56
- package/src/db/dialect.ts +472 -99
- package/src/db/eagerload.ts +5 -12
- package/src/db/entity-table-meta.ts +390 -0
- package/src/db/event-store-executor.ts +158 -100
- package/src/db/index.ts +33 -5
- package/src/db/migrate-generator.ts +350 -0
- package/src/db/migrate-runner.ts +206 -0
- package/src/db/postgres-provider.ts +25 -0
- package/src/db/queries/entity-read.ts +15 -0
- package/src/db/queries/es-ops.ts +17 -0
- package/src/db/queries/event-consumer.ts +170 -0
- package/src/db/queries/event-store-admin.ts +127 -0
- package/src/db/queries/event-store.ts +155 -0
- package/src/db/queries/projection-rebuild.ts +59 -0
- package/src/db/queries/raw-sql.ts +15 -0
- package/src/db/queries/schema-drift.ts +35 -0
- package/src/db/queries/seed-context.ts +58 -0
- package/src/db/queries/table-ops.ts +11 -0
- package/src/db/queries/test-stack.ts +56 -0
- package/src/db/query-api.ts +22 -0
- package/src/db/query.ts +30 -0
- package/src/db/reference-data.ts +19 -22
- package/src/db/render-ddl.ts +57 -0
- package/src/db/row-helpers.ts +3 -52
- package/src/db/schema-inspection.ts +17 -4
- package/src/db/sql-inventory.ts +208 -0
- package/src/db/table-builder.ts +48 -40
- package/src/db/tenant-db.ts +105 -326
- package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
- package/src/engine/__tests__/boot-validator.test.ts +4 -3
- package/src/engine/__tests__/build-app-schema.test.ts +1 -1
- package/src/engine/__tests__/build-target.test.ts +1 -1
- package/src/engine/__tests__/claim-keys.test.ts +1 -1
- package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
- package/src/engine/__tests__/config-helpers.test.ts +1 -1
- package/src/engine/__tests__/effective-features.test.ts +1 -1
- package/src/engine/__tests__/engine.test.ts +1 -1
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +3 -3
- package/src/engine/__tests__/extends-registrar.test.ts +4 -4
- package/src/engine/__tests__/factories-long-text.test.ts +1 -1
- package/src/engine/__tests__/factories-time.test.ts +1 -1
- package/src/engine/__tests__/field-predicates.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +1 -1
- package/src/engine/__tests__/identifiers.test.ts +1 -1
- package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
- package/src/engine/__tests__/nav.test.ts +1 -1
- package/src/engine/__tests__/ownership.test.ts +10 -11
- package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
- package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
- package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
- package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +1 -1
- package/src/engine/__tests__/projection-helpers.test.ts +25 -17
- package/src/engine/__tests__/projection.test.ts +4 -4
- package/src/engine/__tests__/qualified-name.test.ts +1 -1
- package/src/engine/__tests__/raw-table.test.ts +9 -8
- package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
- package/src/engine/__tests__/run-in.test.ts +1 -1
- package/src/engine/__tests__/schema-builder.test.ts +1 -1
- package/src/engine/__tests__/screen.test.ts +1 -1
- package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
- package/src/engine/__tests__/state-machine.test.ts +1 -1
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
- package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
- package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
- package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
- package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
- package/src/engine/__tests__/steps-read.test.ts +34 -40
- package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
- package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
- package/src/engine/__tests__/steps-workflow.test.ts +7 -7
- package/src/engine/__tests__/system-user.test.ts +1 -1
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
- package/src/engine/__tests__/validation-hooks.test.ts +1 -1
- package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
- package/src/engine/boot-validator/entity-handler.ts +3 -3
- package/src/engine/boot-validator/ownership.ts +1 -1
- package/src/engine/define-feature.ts +1 -2
- package/src/engine/entity-handlers.ts +5 -5
- package/src/engine/factories.ts +1 -1
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
- package/src/engine/ownership.ts +113 -41
- package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
- package/src/engine/projection-helpers.ts +2 -11
- package/src/engine/registry.ts +2 -2
- package/src/engine/steps/read-find-many.ts +13 -13
- package/src/engine/steps/read-find-one.ts +7 -9
- package/src/engine/steps/unsafe-projection-delete.ts +4 -5
- package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
- package/src/engine/types/feature.ts +7 -2
- package/src/engine/types/fields.ts +4 -5
- package/src/engine/types/step.ts +10 -10
- package/src/engine/validate-projection-allowlist.ts +23 -3
- package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
- package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
- package/src/env/__tests__/compose-env-schema.test.ts +1 -1
- package/src/env/__tests__/dry-run.test.ts +1 -1
- package/src/errors/__tests__/classes.test.ts +1 -1
- package/src/errors/__tests__/write-failures.test.ts +1 -1
- package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
- package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
- package/src/es-ops/__tests__/runner.test.ts +29 -19
- package/src/es-ops/context.ts +9 -43
- package/src/es-ops/operations-schema.ts +2 -2
- package/src/es-ops/runner.ts +12 -26
- package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
- package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
- package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
- package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
- package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
- package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
- package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
- package/src/event-store/admin-api.ts +55 -83
- package/src/event-store/archive.ts +15 -39
- package/src/event-store/event-store.ts +92 -86
- package/src/event-store/events-schema.ts +2 -1
- package/src/event-store/index.ts +1 -0
- package/src/event-store/snapshot.ts +26 -24
- package/src/event-store/upcaster-dead-letter.ts +19 -18
- package/src/files/__tests__/content-disposition.test.ts +1 -1
- package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
- package/src/files/__tests__/file-handle.test.ts +1 -1
- package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
- package/src/files/__tests__/read-stream.test.ts +1 -1
- package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
- package/src/files/__tests__/write-stream.test.ts +1 -1
- package/src/files/__tests__/zip-stream.test.ts +1 -1
- package/src/files/file-ref-table.ts +2 -2
- package/src/files/file-routes.ts +7 -9
- package/src/files/storage-tracking.ts +9 -17
- package/src/i18n/__tests__/i18n.test.ts +1 -1
- package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
- package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
- package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
- package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
- package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
- package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
- package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
- package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
- package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
- package/src/migrations/projection-detection.ts +12 -1
- package/src/migrations/schema-drift.ts +7 -23
- package/src/observability/__tests__/console-provider.test.ts +1 -1
- package/src/observability/__tests__/metric-validator.test.ts +1 -1
- package/src/observability/__tests__/noop-provider.test.ts +1 -1
- package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
- package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-tracer.test.ts +1 -1
- package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
- package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
- package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
- package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
- package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
- package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
- package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
- package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
- package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
- package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
- package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
- package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
- package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
- package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
- package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
- package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
- package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
- package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
- package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
- package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
- package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
- package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
- package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
- package/src/pipeline/cascade-handler.ts +13 -21
- package/src/pipeline/dispatcher.ts +43 -48
- package/src/pipeline/event-consumer-state.ts +11 -2
- package/src/pipeline/event-dispatcher.ts +86 -146
- package/src/pipeline/event-retention.ts +14 -24
- package/src/pipeline/msp-rebuild.ts +54 -78
- package/src/pipeline/projection-rebuild.ts +65 -67
- package/src/pipeline/projection-state.ts +2 -2
- package/src/random/__tests__/generate.test.ts +13 -13
- package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
- package/src/redis/__tests__/redis-options.test.ts +1 -1
- package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
- package/src/search/__tests__/search-adapter.test.ts +1 -1
- package/src/secrets/__tests__/dek-cache.test.ts +1 -3
- package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/__tests__/leak-guard.test.ts +1 -1
- package/src/secrets/__tests__/rotation.test.ts +1 -1
- package/src/stack/db.ts +25 -48
- package/src/stack/push-entity-projection-tables.ts +2 -4
- package/src/stack/table-helpers.ts +98 -61
- package/src/stack/test-stack.ts +8 -7
- package/src/testing/__tests__/db-cleanup.test.ts +40 -0
- package/src/testing/__tests__/e2e-generator.test.ts +1 -1
- package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
- package/src/testing/db-cleanup.ts +44 -0
- package/src/testing/expect-error.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/multipart-helper.ts +94 -0
- package/src/testing/shared-entities.ts +5 -5
- package/src/time/__tests__/polyfill.test.ts +1 -1
- package/src/time/__tests__/tz-context.test.ts +1 -1
- package/src/utils/__tests__/assert.test.ts +1 -1
- package/src/utils/__tests__/env-parse.test.ts +1 -1
- package/CHANGELOG.md +0 -474
- package/src/db/__tests__/cursor.test.ts +0 -41
- package/src/db/__tests__/db-helpers.test.ts +0 -369
- package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
- package/src/db/__tests__/row-helpers.test.ts +0 -59
- package/src/engine/steps/_drizzle-boundary.ts +0 -19
- package/src/files/__tests__/file-field-column.integration.ts +0 -103
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { asRawClient, insertOne, selectMany } from "../../db/query";
|
|
3
3
|
import { createBooleanField, createEntity, createTextField } from "../../engine";
|
|
4
4
|
import {
|
|
5
5
|
createTestDb,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
unsafePushTables,
|
|
11
11
|
} from "../../stack";
|
|
12
12
|
import { table as pgTable, serial, text, timestamp } from "../dialect";
|
|
13
|
-
import {
|
|
13
|
+
import { buildEntityTable } from "../table-builder";
|
|
14
14
|
import { createTenantDb } from "../tenant-db";
|
|
15
15
|
|
|
16
16
|
// --- Entity table (has tenantId via buildBaseColumns) ---
|
|
@@ -25,7 +25,7 @@ const entity = createEntity({
|
|
|
25
25
|
softDelete: true,
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
const table =
|
|
28
|
+
const table = buildEntityTable("tenantDbItem", entity);
|
|
29
29
|
|
|
30
30
|
// --- System table (no tenantId — like job_runs) ---
|
|
31
31
|
|
|
@@ -58,19 +58,16 @@ describe("scoped mode (default)", () => {
|
|
|
58
58
|
test("auto-injects tenantId into values", async () => {
|
|
59
59
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
60
60
|
|
|
61
|
-
const
|
|
62
|
-
expect(
|
|
63
|
-
expect(
|
|
61
|
+
const row = await tdb.insertOne(table, { name: "Item 1" });
|
|
62
|
+
expect(row?.["tenantId"]).toBe(testTenantId(1));
|
|
63
|
+
expect(row?.["name"]).toBe("Item 1");
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("cannot override tenantId via values", async () => {
|
|
67
67
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
68
68
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
.values({ name: "Sneaky", tenantId: testTenantId(999) })
|
|
72
|
-
.returning();
|
|
73
|
-
expect(rows[0]?.["tenantId"]).toBe(testTenantId(1));
|
|
69
|
+
const row = await tdb.insertOne(table, { name: "Sneaky", tenantId: testTenantId(999) });
|
|
70
|
+
expect(row?.["tenantId"]).toBe(testTenantId(1));
|
|
74
71
|
});
|
|
75
72
|
});
|
|
76
73
|
|
|
@@ -79,11 +76,11 @@ describe("scoped mode (default)", () => {
|
|
|
79
76
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
80
77
|
const tdb2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
81
78
|
|
|
82
|
-
await tdb1.
|
|
83
|
-
await tdb2.
|
|
79
|
+
await tdb1.insertOne(table, { name: "T1 Scoped" });
|
|
80
|
+
await tdb2.insertOne(table, { name: "T2 Scoped" });
|
|
84
81
|
|
|
85
|
-
const rows1 = await tdb1.
|
|
86
|
-
const rows2 = await tdb2.
|
|
82
|
+
const rows1 = await tdb1.selectMany(table);
|
|
83
|
+
const rows2 = await tdb2.selectMany(table);
|
|
87
84
|
|
|
88
85
|
expect(rows1.every((r) => r!["tenantId"] === testTenantId(1))).toBe(true);
|
|
89
86
|
expect(rows2.every((r) => r!["tenantId"] === testTenantId(2))).toBe(true);
|
|
@@ -92,10 +89,10 @@ describe("scoped mode (default)", () => {
|
|
|
92
89
|
test("additional where conditions combine with tenant filter", async () => {
|
|
93
90
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
94
91
|
|
|
95
|
-
await tdb.
|
|
96
|
-
await tdb.
|
|
92
|
+
await tdb.insertOne(table, { name: "findme", status: "active" });
|
|
93
|
+
await tdb.insertOne(table, { name: "notme", status: "draft" });
|
|
97
94
|
|
|
98
|
-
const rows = await tdb.
|
|
95
|
+
const rows = await tdb.selectMany(table, { status: "active" });
|
|
99
96
|
|
|
100
97
|
expect(rows.length).toBeGreaterThanOrEqual(1);
|
|
101
98
|
expect(
|
|
@@ -106,12 +103,12 @@ describe("scoped mode (default)", () => {
|
|
|
106
103
|
test("select with columns", async () => {
|
|
107
104
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
108
105
|
|
|
109
|
-
await tdb.
|
|
106
|
+
await tdb.insertOne(table, { name: "ColSelect" });
|
|
110
107
|
|
|
111
|
-
const rows = await tdb
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
const rows = await asRawClient(tdb).unsafe<Record<string, unknown>>(
|
|
109
|
+
`SELECT id, name FROM "tenant_db_items" WHERE name = $1 LIMIT 10`,
|
|
110
|
+
["ColSelect"],
|
|
111
|
+
);
|
|
115
112
|
|
|
116
113
|
expect(rows.length).toBeGreaterThanOrEqual(1);
|
|
117
114
|
const row = rows[0]!;
|
|
@@ -124,13 +121,10 @@ describe("scoped mode (default)", () => {
|
|
|
124
121
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
125
122
|
|
|
126
123
|
for (let i = 0; i < 5; i++) {
|
|
127
|
-
await tdb
|
|
128
|
-
.insert(table)
|
|
129
|
-
.values({ name: `Limit${i}` })
|
|
130
|
-
.returning();
|
|
124
|
+
await tdb.insertOne(table, { name: `Limit${i}` });
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
const rows = await tdb.
|
|
127
|
+
const rows = await tdb.selectMany(table, {}, { limit: 2 });
|
|
134
128
|
expect(rows).toHaveLength(2);
|
|
135
129
|
});
|
|
136
130
|
});
|
|
@@ -140,22 +134,14 @@ describe("scoped mode (default)", () => {
|
|
|
140
134
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
141
135
|
const tdb2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
142
136
|
|
|
143
|
-
const
|
|
137
|
+
const row = await tdb1.insertOne(table, { name: "T1 Update" });
|
|
144
138
|
const id = row!["id"] as string;
|
|
145
139
|
|
|
146
|
-
const result = await tdb2
|
|
147
|
-
.update(table)
|
|
148
|
-
.set({ name: "Hacked" })
|
|
149
|
-
.where(eq(table["id"], id))
|
|
150
|
-
.returning();
|
|
140
|
+
const result = await tdb2.updateMany(table, { name: "Hacked" }, { id: id });
|
|
151
141
|
|
|
152
142
|
expect(result).toHaveLength(0);
|
|
153
143
|
|
|
154
|
-
const [updated] = await tdb1
|
|
155
|
-
.update(table)
|
|
156
|
-
.set({ name: "Updated" })
|
|
157
|
-
.where(eq(table["id"], id))
|
|
158
|
-
.returning();
|
|
144
|
+
const [updated] = await tdb1.updateMany(table, { name: "Updated" }, { id: id });
|
|
159
145
|
|
|
160
146
|
expect(updated!["name"]).toBe("Updated");
|
|
161
147
|
});
|
|
@@ -163,12 +149,12 @@ describe("scoped mode (default)", () => {
|
|
|
163
149
|
test("update without returning", async () => {
|
|
164
150
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
165
151
|
|
|
166
|
-
const
|
|
152
|
+
const row = await tdb.insertOne(table, { name: "NoReturn" });
|
|
167
153
|
const id = row!["id"] as string;
|
|
168
154
|
|
|
169
|
-
await tdb.
|
|
155
|
+
await tdb.updateMany(table, { name: "NoReturnUpdated" }, { id: id });
|
|
170
156
|
|
|
171
|
-
const [updated] = await tdb.
|
|
157
|
+
const [updated] = await tdb.selectMany(table, { id: id });
|
|
172
158
|
expect(updated!["name"]).toBe("NoReturnUpdated");
|
|
173
159
|
});
|
|
174
160
|
});
|
|
@@ -178,12 +164,12 @@ describe("scoped mode (default)", () => {
|
|
|
178
164
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
179
165
|
const tdb2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
180
166
|
|
|
181
|
-
const
|
|
167
|
+
const row = await tdb1.insertOne(table, { name: "T1 Delete" });
|
|
182
168
|
const id = row!["id"] as string;
|
|
183
169
|
|
|
184
|
-
await tdb2.
|
|
170
|
+
await tdb2.deleteMany(table, { id: id });
|
|
185
171
|
|
|
186
|
-
const rows = await tdb1.
|
|
172
|
+
const rows = await tdb1.selectMany(table, { id: id });
|
|
187
173
|
expect(rows).toHaveLength(1);
|
|
188
174
|
});
|
|
189
175
|
});
|
|
@@ -193,21 +179,17 @@ describe("scoped mode (default)", () => {
|
|
|
193
179
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
194
180
|
const tdb2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
195
181
|
|
|
196
|
-
const
|
|
182
|
+
const created = await tdb1.insertOne(table, { name: "Secret" });
|
|
197
183
|
const id = created!["id"] as string;
|
|
198
184
|
|
|
199
|
-
const seen = await tdb2.
|
|
185
|
+
const seen = await tdb2.selectMany(table, { id: id });
|
|
200
186
|
expect(seen).toHaveLength(0);
|
|
201
187
|
|
|
202
|
-
const updated = await tdb2
|
|
203
|
-
.update(table)
|
|
204
|
-
.set({ name: "Hacked" })
|
|
205
|
-
.where(eq(table["id"], id))
|
|
206
|
-
.returning();
|
|
188
|
+
const updated = await tdb2.updateMany(table, { name: "Hacked" }, { id: id });
|
|
207
189
|
expect(updated).toHaveLength(0);
|
|
208
190
|
|
|
209
|
-
await tdb2.
|
|
210
|
-
const stillThere = await tdb1.
|
|
191
|
+
await tdb2.deleteMany(table, { id: id });
|
|
192
|
+
const stillThere = await tdb1.selectMany(table, { id: id });
|
|
211
193
|
expect(stillThere).toHaveLength(1);
|
|
212
194
|
});
|
|
213
195
|
});
|
|
@@ -215,7 +197,7 @@ describe("scoped mode (default)", () => {
|
|
|
215
197
|
describe("reference data (tenantId = 0)", () => {
|
|
216
198
|
test("scoped select includes rows with tenantId = 0", async () => {
|
|
217
199
|
// Seed reference data with tenantId = 0 (like seedReferenceData does)
|
|
218
|
-
await testDb.db
|
|
200
|
+
await insertOne(testDb.db, table, {
|
|
219
201
|
name: "GlobalRef",
|
|
220
202
|
status: "ref",
|
|
221
203
|
tenantId: "00000000-0000-4000-8000-000000000000",
|
|
@@ -227,15 +209,15 @@ describe("scoped mode (default)", () => {
|
|
|
227
209
|
const tdb2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
228
210
|
|
|
229
211
|
// Both tenants can see the global reference row
|
|
230
|
-
const rows1 = await tdb1.
|
|
212
|
+
const rows1 = await tdb1.selectMany(table, { name: "GlobalRef" });
|
|
231
213
|
expect(rows1).toHaveLength(1);
|
|
232
214
|
|
|
233
|
-
const rows2 = await tdb2.
|
|
215
|
+
const rows2 = await tdb2.selectMany(table, { name: "GlobalRef" });
|
|
234
216
|
expect(rows2).toHaveLength(1);
|
|
235
217
|
});
|
|
236
218
|
|
|
237
219
|
test("scoped update does NOT affect tenantId = 0 rows", async () => {
|
|
238
|
-
await testDb.db
|
|
220
|
+
await insertOne(testDb.db, table, {
|
|
239
221
|
name: "RefNoUpdate",
|
|
240
222
|
status: "ref",
|
|
241
223
|
tenantId: "00000000-0000-4000-8000-000000000000",
|
|
@@ -245,25 +227,18 @@ describe("scoped mode (default)", () => {
|
|
|
245
227
|
|
|
246
228
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
247
229
|
|
|
248
|
-
const result = await tdb1
|
|
249
|
-
.update(table)
|
|
250
|
-
.set({ name: "Hacked" })
|
|
251
|
-
.where(eq(table["name"], "RefNoUpdate"))
|
|
252
|
-
.returning();
|
|
230
|
+
const result = await tdb1.updateMany(table, { name: "Hacked" }, { name: "RefNoUpdate" });
|
|
253
231
|
|
|
254
232
|
// Writes from a tenant scope must never touch reference rows (tenantId = 0).
|
|
255
233
|
// Reading them is fine, modifying them is a cross-tenant integrity bug.
|
|
256
234
|
expect(result).toHaveLength(0);
|
|
257
235
|
|
|
258
|
-
const [untouched] = await testDb.db
|
|
259
|
-
.select()
|
|
260
|
-
.from(table)
|
|
261
|
-
.where(eq(table["name"], "RefNoUpdate"));
|
|
236
|
+
const [untouched] = await selectMany(testDb.db, table, { name: "RefNoUpdate" });
|
|
262
237
|
expect(untouched!["name"]).toBe("RefNoUpdate");
|
|
263
238
|
});
|
|
264
239
|
|
|
265
240
|
test("scoped delete does NOT affect tenantId = 0 rows", async () => {
|
|
266
|
-
await testDb.db
|
|
241
|
+
await insertOne(testDb.db, table, {
|
|
267
242
|
name: "RefNoDelete",
|
|
268
243
|
status: "ref",
|
|
269
244
|
tenantId: "00000000-0000-4000-8000-000000000000",
|
|
@@ -272,12 +247,9 @@ describe("scoped mode (default)", () => {
|
|
|
272
247
|
});
|
|
273
248
|
|
|
274
249
|
const tdb1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
275
|
-
await tdb1.
|
|
250
|
+
await tdb1.deleteMany(table, { name: "RefNoDelete" });
|
|
276
251
|
|
|
277
|
-
const [stillThere] = await testDb.db
|
|
278
|
-
.select()
|
|
279
|
-
.from(table)
|
|
280
|
-
.where(eq(table["name"], "RefNoDelete"));
|
|
252
|
+
const [stillThere] = await selectMany(testDb.db, table, { name: "RefNoDelete" });
|
|
281
253
|
expect(stillThere).toBeDefined();
|
|
282
254
|
});
|
|
283
255
|
});
|
|
@@ -292,11 +264,11 @@ describe("system mode (r.systemScope())", () => {
|
|
|
292
264
|
const scoped1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
293
265
|
const scoped2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
294
266
|
|
|
295
|
-
await scoped1.
|
|
296
|
-
await scoped2.
|
|
267
|
+
await scoped1.insertOne(table, { name: "System-T1" });
|
|
268
|
+
await scoped2.insertOne(table, { name: "System-T2" });
|
|
297
269
|
|
|
298
270
|
const systemDb = createTenantDb(testDb.db, tenant1.tenantId, "system");
|
|
299
|
-
const rows = await systemDb.
|
|
271
|
+
const rows = await systemDb.selectMany(table);
|
|
300
272
|
|
|
301
273
|
const tenantIds = new Set(rows.map((r) => r!["tenantId"]));
|
|
302
274
|
// Must see rows from at least 2 different tenants
|
|
@@ -307,14 +279,14 @@ describe("system mode (r.systemScope())", () => {
|
|
|
307
279
|
const systemDb = createTenantDb(testDb.db, tenant1.tenantId, "system");
|
|
308
280
|
|
|
309
281
|
// Without explicit tenantId — uses the default (tenant1)
|
|
310
|
-
const
|
|
282
|
+
const defaultRow = await systemDb.insertOne(table, { name: "SystemDefault" });
|
|
311
283
|
expect(defaultRow!["tenantId"]).toBe(testTenantId(1));
|
|
312
284
|
|
|
313
285
|
// With explicit tenantId — handler's value wins
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
286
|
+
const overrideRow = await systemDb.insertOne(table, {
|
|
287
|
+
name: "SystemOverride",
|
|
288
|
+
tenantId: testTenantId(99),
|
|
289
|
+
});
|
|
318
290
|
expect(overrideRow!["tenantId"]).toBe(testTenantId(99));
|
|
319
291
|
});
|
|
320
292
|
|
|
@@ -326,55 +298,47 @@ describe("system mode (r.systemScope())", () => {
|
|
|
326
298
|
|
|
327
299
|
// In scoped mode, tenantId: 77 would be overridden to 1
|
|
328
300
|
const scopedDb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
301
|
+
const scopedRow = await scopedDb.insertOne(table, {
|
|
302
|
+
name: "ScopedForce",
|
|
303
|
+
tenantId: testTenantId(77),
|
|
304
|
+
});
|
|
333
305
|
expect(scopedRow!["tenantId"]).toBe(testTenantId(1)); // forced
|
|
334
306
|
|
|
335
307
|
// In unscoped mode, explicit tenantId wins
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
308
|
+
const unscopedRow = await systemDb.insertOne(table, {
|
|
309
|
+
name: "SystemExplicit",
|
|
310
|
+
tenantId: testTenantId(77),
|
|
311
|
+
});
|
|
340
312
|
expect(unscopedRow!["tenantId"]).toBe(testTenantId(77)); // handler wins
|
|
341
313
|
});
|
|
342
314
|
|
|
343
315
|
test("update affects rows from any tenant", async () => {
|
|
344
316
|
const scoped2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
345
|
-
const
|
|
317
|
+
const row = await scoped2.insertOne(table, { name: "T2-System-Upd" });
|
|
346
318
|
const id = row!["id"] as string;
|
|
347
319
|
|
|
348
320
|
// Scoped tenant 1 cannot update tenant 2's row
|
|
349
321
|
const scoped1 = createTenantDb(testDb.db, tenant1.tenantId);
|
|
350
|
-
const scopedResult = await scoped1
|
|
351
|
-
.update(table)
|
|
352
|
-
.set({ name: "ScopedFail" })
|
|
353
|
-
.where(eq(table["id"], id))
|
|
354
|
-
.returning();
|
|
322
|
+
const scopedResult = await scoped1.updateMany(table, { name: "ScopedFail" }, { id: id });
|
|
355
323
|
expect(scopedResult).toHaveLength(0);
|
|
356
324
|
|
|
357
325
|
// Unscoped CAN update tenant 2's row
|
|
358
326
|
const systemDb = createTenantDb(testDb.db, tenant1.tenantId, "system");
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
.set({ name: "SystemWin" })
|
|
362
|
-
.where(eq(table["id"], id))
|
|
363
|
-
.returning();
|
|
364
|
-
expect(updated!["name"]).toBe("SystemWin");
|
|
327
|
+
const updated = await systemDb.updateMany(table, { name: "SystemWin" }, { id: id });
|
|
328
|
+
expect(updated[0]!["name"]).toBe("SystemWin");
|
|
365
329
|
});
|
|
366
330
|
|
|
367
331
|
test("delete affects rows from any tenant", async () => {
|
|
368
332
|
const scoped2 = createTenantDb(testDb.db, tenant2.tenantId);
|
|
369
|
-
const
|
|
333
|
+
const row = await scoped2.insertOne(table, { name: "T2-System-Del" });
|
|
370
334
|
const id = row!["id"] as string;
|
|
371
335
|
|
|
372
336
|
// Unscoped can delete tenant 2's row from tenant 1 context
|
|
373
337
|
const systemDb = createTenantDb(testDb.db, tenant1.tenantId, "system");
|
|
374
|
-
await systemDb.
|
|
338
|
+
await systemDb.deleteMany(table, { id: id });
|
|
375
339
|
|
|
376
340
|
// Verify it's gone
|
|
377
|
-
const remaining = await scoped2.
|
|
341
|
+
const remaining = await scoped2.selectMany(table, { id: id });
|
|
378
342
|
expect(remaining).toHaveLength(0);
|
|
379
343
|
});
|
|
380
344
|
});
|
|
@@ -386,18 +350,18 @@ describe("system mode (r.systemScope())", () => {
|
|
|
386
350
|
describe("tables without tenantId column", () => {
|
|
387
351
|
test("select returns all rows (no tenant filter)", async () => {
|
|
388
352
|
// Insert two rows via raw db
|
|
389
|
-
await testDb.db
|
|
390
|
-
await testDb.db
|
|
353
|
+
await insertOne(testDb.db, systemTable, { label: "System-A" });
|
|
354
|
+
await insertOne(testDb.db, systemTable, { label: "System-B" });
|
|
391
355
|
|
|
392
356
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
393
|
-
const rows = await tdb.
|
|
357
|
+
const rows = await tdb.selectMany(systemTable);
|
|
394
358
|
expect(rows.length).toBeGreaterThanOrEqual(2);
|
|
395
359
|
});
|
|
396
360
|
|
|
397
361
|
test("insert does not inject tenantId", async () => {
|
|
398
362
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
399
363
|
|
|
400
|
-
const
|
|
364
|
+
const row = await tdb.insertOne(systemTable, { label: "NoTenantInjection" });
|
|
401
365
|
const data = row!;
|
|
402
366
|
expect(data["label"]).toBe("NoTenantInjection");
|
|
403
367
|
// No tenantId column at all — should not be in the result
|
|
@@ -407,33 +371,33 @@ describe("tables without tenantId column", () => {
|
|
|
407
371
|
test("select with where works without tenant filter", async () => {
|
|
408
372
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
409
373
|
|
|
410
|
-
await tdb.
|
|
374
|
+
await tdb.insertOne(systemTable, { label: "FindThis" });
|
|
411
375
|
|
|
412
|
-
const rows = await tdb.
|
|
376
|
+
const rows = await tdb.selectMany(systemTable, { label: "FindThis" });
|
|
413
377
|
expect(rows).toHaveLength(1);
|
|
414
378
|
});
|
|
415
379
|
|
|
416
380
|
test("update works without tenant filter", async () => {
|
|
417
381
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
418
382
|
|
|
419
|
-
const
|
|
383
|
+
const row = await tdb.insertOne(systemTable, { label: "BeforeUpd" });
|
|
420
384
|
const id = row!["id"] as number;
|
|
421
385
|
|
|
422
|
-
await tdb.
|
|
386
|
+
await tdb.updateMany(systemTable, { label: "AfterUpd" }, { id: id });
|
|
423
387
|
|
|
424
|
-
const [updated] = await tdb.
|
|
388
|
+
const [updated] = await tdb.selectMany(systemTable, { id: id });
|
|
425
389
|
expect(updated!["label"]).toBe("AfterUpd");
|
|
426
390
|
});
|
|
427
391
|
|
|
428
392
|
test("delete works without tenant filter", async () => {
|
|
429
393
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
430
394
|
|
|
431
|
-
const
|
|
395
|
+
const row = await tdb.insertOne(systemTable, { label: "ToDelete" });
|
|
432
396
|
const id = row!["id"] as number;
|
|
433
397
|
|
|
434
|
-
await tdb.
|
|
398
|
+
await tdb.deleteMany(systemTable, { id: id });
|
|
435
399
|
|
|
436
|
-
const remaining = await tdb.
|
|
400
|
+
const remaining = await tdb.selectMany(systemTable, { id: id });
|
|
437
401
|
expect(remaining).toHaveLength(0);
|
|
438
402
|
});
|
|
439
403
|
});
|
|
@@ -460,41 +424,41 @@ describe("tenantId property", () => {
|
|
|
460
424
|
describe("mass-update guard", () => {
|
|
461
425
|
test(".set().returning() without .where() rejects with a clear error", async () => {
|
|
462
426
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
463
|
-
await tdb.
|
|
464
|
-
await tdb.
|
|
427
|
+
await tdb.insertOne(table, { name: "MassUpdateVictim1" });
|
|
428
|
+
await tdb.insertOne(table, { name: "MassUpdateVictim2" });
|
|
465
429
|
|
|
466
|
-
|
|
467
|
-
|
|
430
|
+
// biome-ignore lint/suspicious/noExplicitAny: test cast
|
|
431
|
+
await expect((tdb as any).updateMany(table, { name: "Wiped" })).rejects.toThrow(
|
|
432
|
+
/without where would mass-update/,
|
|
468
433
|
);
|
|
469
434
|
|
|
470
435
|
// Rows must be untouched — the rejection happened before any SQL ran.
|
|
471
|
-
const untouched = await tdb.
|
|
436
|
+
const untouched = await tdb.selectMany(table);
|
|
472
437
|
const touched = untouched.filter((r) => r["name"] === "Wiped");
|
|
473
438
|
expect(touched).toHaveLength(0);
|
|
474
439
|
});
|
|
475
440
|
|
|
476
441
|
test(".set() awaited without .where() rejects too", async () => {
|
|
477
442
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
478
|
-
await tdb.
|
|
443
|
+
await tdb.insertOne(table, { name: "AwaitGuardVictim" });
|
|
479
444
|
|
|
480
|
-
|
|
481
|
-
|
|
445
|
+
// biome-ignore lint/suspicious/noExplicitAny: test cast
|
|
446
|
+
const promise = (tdb as any).updateMany(table, {
|
|
447
|
+
name: "WipedByAwait",
|
|
448
|
+
}) as unknown as Promise<void>;
|
|
449
|
+
await expect(promise).rejects.toThrow(/without where would mass-update/);
|
|
482
450
|
|
|
483
|
-
const untouched = await tdb.
|
|
451
|
+
const untouched = await tdb.selectMany(table);
|
|
484
452
|
const touched = untouched.filter((r) => r["name"] === "WipedByAwait");
|
|
485
453
|
expect(touched).toHaveLength(0);
|
|
486
454
|
});
|
|
487
455
|
|
|
488
456
|
test(".set().where(...).returning() still works (guard only triggers on missing where)", async () => {
|
|
489
457
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
490
|
-
const
|
|
458
|
+
const row = await tdb.insertOne(table, { name: "HappyPath" });
|
|
491
459
|
const id = row!["id"] as string;
|
|
492
460
|
|
|
493
|
-
const updated = await tdb
|
|
494
|
-
.update(table)
|
|
495
|
-
.set({ name: "HappyPathUpdated" })
|
|
496
|
-
.where(eq(table["id"], id))
|
|
497
|
-
.returning();
|
|
461
|
+
const updated = await tdb.updateMany(table, { name: "HappyPathUpdated" }, { id: id });
|
|
498
462
|
expect(updated[0]!["name"]).toBe("HappyPathUpdated");
|
|
499
463
|
});
|
|
500
464
|
});
|
|
@@ -514,7 +478,7 @@ describe("pre-flight signal cancellation", () => {
|
|
|
514
478
|
|
|
515
479
|
let thrown: unknown;
|
|
516
480
|
try {
|
|
517
|
-
await tdb.
|
|
481
|
+
await tdb.selectMany(table);
|
|
518
482
|
} catch (e) {
|
|
519
483
|
thrown = e;
|
|
520
484
|
}
|
|
@@ -535,7 +499,7 @@ describe("pre-flight signal cancellation", () => {
|
|
|
535
499
|
|
|
536
500
|
let insertThrown: unknown;
|
|
537
501
|
try {
|
|
538
|
-
await tdb.
|
|
502
|
+
await tdb.insertOne(table, { name: "x" });
|
|
539
503
|
} catch (e) {
|
|
540
504
|
insertThrown = e;
|
|
541
505
|
}
|
|
@@ -543,10 +507,7 @@ describe("pre-flight signal cancellation", () => {
|
|
|
543
507
|
|
|
544
508
|
let updateThrown: unknown;
|
|
545
509
|
try {
|
|
546
|
-
await tdb
|
|
547
|
-
.update(table)
|
|
548
|
-
.set({ name: "y" })
|
|
549
|
-
.where(eq(table["id"], "00000000-0000-0000-0000-000000000001"));
|
|
510
|
+
await tdb.updateMany(table, { name: "y" }, { id: "00000000-0000-0000-0000-000000000001" });
|
|
550
511
|
} catch (e) {
|
|
551
512
|
updateThrown = e;
|
|
552
513
|
}
|
|
@@ -554,7 +515,7 @@ describe("pre-flight signal cancellation", () => {
|
|
|
554
515
|
|
|
555
516
|
let deleteThrown: unknown;
|
|
556
517
|
try {
|
|
557
|
-
await tdb.
|
|
518
|
+
await tdb.deleteMany(table, { id: "00000000-0000-0000-0000-000000000001" });
|
|
558
519
|
} catch (e) {
|
|
559
520
|
deleteThrown = e;
|
|
560
521
|
}
|
|
@@ -576,14 +537,14 @@ describe("pre-flight signal cancellation", () => {
|
|
|
576
537
|
controller.signal,
|
|
577
538
|
);
|
|
578
539
|
|
|
579
|
-
const
|
|
540
|
+
const first = await tdb.insertOne(table, { name: "preflight-first" });
|
|
580
541
|
expect(first).toBeDefined();
|
|
581
542
|
|
|
582
543
|
controller.abort();
|
|
583
544
|
|
|
584
545
|
let secondThrown: unknown;
|
|
585
546
|
try {
|
|
586
|
-
await tdb.
|
|
547
|
+
await tdb.insertOne(table, { name: "preflight-second" });
|
|
587
548
|
} catch (e) {
|
|
588
549
|
secondThrown = e;
|
|
589
550
|
}
|
|
@@ -592,7 +553,7 @@ describe("pre-flight signal cancellation", () => {
|
|
|
592
553
|
// Proves the first row was actually committed and the second never
|
|
593
554
|
// made it — the abort prevented future work, didn't roll back done
|
|
594
555
|
// work.
|
|
595
|
-
const rows = await testDb.db
|
|
556
|
+
const rows = await selectMany(testDb.db, table);
|
|
596
557
|
const names = rows.map((r) => r["name"] as string);
|
|
597
558
|
expect(names).toContain("preflight-first");
|
|
598
559
|
expect(names).not.toContain("preflight-second");
|
|
@@ -600,7 +561,8 @@ describe("pre-flight signal cancellation", () => {
|
|
|
600
561
|
|
|
601
562
|
test("no signal passed: queries run normally (signal is opt-in)", async () => {
|
|
602
563
|
const tdb = createTenantDb(testDb.db, tenant1.tenantId);
|
|
603
|
-
const result = await tdb.
|
|
604
|
-
expect(result).
|
|
564
|
+
const result = await tdb.insertOne(table, { name: "no-signal" });
|
|
565
|
+
expect(result).toBeDefined();
|
|
566
|
+
expect(result?.["name"]).toBe("no-signal");
|
|
605
567
|
});
|
|
606
568
|
});
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
// also nicht. Erst die projection-INSERT verletzt den Index. Ohne F8:
|
|
14
14
|
// 500. Mit F8: writeFailure(UniqueViolationError) → 409.
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
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, selectMany } from "../../db/query";
|
|
18
19
|
import { createEntity, createTextField } from "../../engine";
|
|
19
20
|
import { createEventsTable } from "../../event-store";
|
|
20
|
-
import {
|
|
21
|
+
import { TestUsers, unsafeCreateEntityTable } from "../../stack";
|
|
22
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
21
23
|
import { createEventStoreExecutor } from "../event-store-executor";
|
|
22
|
-
import {
|
|
24
|
+
import { buildEntityTable } from "../table-builder";
|
|
23
25
|
import { createTenantDb, type TenantDb } from "../tenant-db";
|
|
24
26
|
|
|
25
27
|
const userEntity = createEntity({
|
|
@@ -39,14 +41,15 @@ const userEntity = createEntity({
|
|
|
39
41
|
{ columns: ["tenantId", "email"], unique: true, name: "read_unique_users_tenant_email_uniq" },
|
|
40
42
|
],
|
|
41
43
|
});
|
|
42
|
-
const table =
|
|
44
|
+
const table = buildEntityTable("unique-user", userEntity);
|
|
43
45
|
const exec = createEventStoreExecutor(table, userEntity, { entityName: "unique-user" });
|
|
44
46
|
|
|
45
|
-
let testDb:
|
|
47
|
+
let testDb: BunTestDb;
|
|
46
48
|
let tdb: TenantDb;
|
|
47
49
|
const admin = TestUsers.admin;
|
|
48
50
|
|
|
49
51
|
beforeAll(async () => {
|
|
52
|
+
await ensureTemporalPolyfill();
|
|
50
53
|
testDb = await createTestDb();
|
|
51
54
|
await unsafeCreateEntityTable(testDb.db, userEntity, "unique-user");
|
|
52
55
|
await createEventsTable(testDb.db);
|
|
@@ -58,7 +61,9 @@ afterAll(async () => {
|
|
|
58
61
|
});
|
|
59
62
|
|
|
60
63
|
beforeEach(async () => {
|
|
61
|
-
await testDb.db.
|
|
64
|
+
await asRawClient(testDb.db).unsafe(
|
|
65
|
+
`TRUNCATE kumiko_events, read_unique_users RESTART IDENTITY CASCADE`,
|
|
66
|
+
);
|
|
62
67
|
});
|
|
63
68
|
|
|
64
69
|
// =============================================================================
|
|
@@ -98,7 +103,7 @@ describe("F8 — entity-level unique-violation auf create", () => {
|
|
|
98
103
|
tdb,
|
|
99
104
|
);
|
|
100
105
|
expect(second.isSuccess).toBe(false);
|
|
101
|
-
const rows = await testDb.db
|
|
106
|
+
const rows = await selectMany(testDb.db, table);
|
|
102
107
|
expect(rows).toHaveLength(1);
|
|
103
108
|
expect((rows[0] as { displayName: string }).displayName).toBe("Bob 1");
|
|
104
109
|
});
|