@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
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
// items-create.integration im Showcase abgedeckt — nicht ausreichend
|
|
5
5
|
// für Framework-Code der von jeder App genutzt wird.
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
9
|
+
import { asRawClient } from "../../db/query";
|
|
9
10
|
import { createEntity, createNumberField, createTextField } from "../../engine";
|
|
10
11
|
import { createEventsTable } from "../../event-store";
|
|
11
|
-
import {
|
|
12
|
+
import { TestUsers, unsafeCreateEntityTable } from "../../stack";
|
|
13
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
12
14
|
import { createEventStoreExecutor } from "../event-store-executor";
|
|
13
|
-
import {
|
|
15
|
+
import { buildEntityTable } from "../table-builder";
|
|
14
16
|
import { createTenantDb, type TenantDb } from "../tenant-db";
|
|
15
17
|
|
|
16
18
|
const entity = createEntity({
|
|
@@ -20,13 +22,14 @@ const entity = createEntity({
|
|
|
20
22
|
rank: createNumberField({ sortable: true }),
|
|
21
23
|
},
|
|
22
24
|
});
|
|
23
|
-
const table =
|
|
25
|
+
const table = buildEntityTable("pagerItem", entity);
|
|
24
26
|
|
|
25
|
-
let testDb:
|
|
27
|
+
let testDb: BunTestDb;
|
|
26
28
|
let tdb: TenantDb;
|
|
27
29
|
const admin = TestUsers.admin;
|
|
28
30
|
|
|
29
31
|
beforeAll(async () => {
|
|
32
|
+
await ensureTemporalPolyfill();
|
|
30
33
|
testDb = await createTestDb();
|
|
31
34
|
await unsafeCreateEntityTable(testDb.db, entity, "pagerItem");
|
|
32
35
|
await createEventsTable(testDb.db);
|
|
@@ -38,7 +41,9 @@ afterAll(async () => {
|
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
beforeEach(async () => {
|
|
41
|
-
await testDb.db.
|
|
44
|
+
await asRawClient(testDb.db).unsafe(
|
|
45
|
+
`TRUNCATE kumiko_events, read_pager_items RESTART IDENTITY CASCADE`,
|
|
46
|
+
);
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
describe("event-store-executor.list — offset + totalCount (Tier 2.6d)", () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { asRawClient } from "../../db/query";
|
|
3
3
|
import { createBooleanField, createEntity, createTextField } from "../../engine";
|
|
4
4
|
import { createEventsTable } from "../../event-store";
|
|
5
5
|
import {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
unsafeCreateEntityTable,
|
|
11
11
|
} from "../../stack";
|
|
12
12
|
import { createEventStoreExecutor } from "../event-store-executor";
|
|
13
|
-
import {
|
|
13
|
+
import { buildEntityTable } from "../table-builder";
|
|
14
14
|
import { createTenantDb, type TenantDb } from "../tenant-db";
|
|
15
15
|
|
|
16
16
|
const entity = createEntity({
|
|
@@ -22,7 +22,7 @@ const entity = createEntity({
|
|
|
22
22
|
},
|
|
23
23
|
softDelete: true,
|
|
24
24
|
});
|
|
25
|
-
const table =
|
|
25
|
+
const table = buildEntityTable("esExecUser", entity);
|
|
26
26
|
|
|
27
27
|
let testDb: TestDb;
|
|
28
28
|
let tdb: TenantDb;
|
|
@@ -40,7 +40,9 @@ afterAll(async () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
beforeEach(async () => {
|
|
43
|
-
await testDb.db.
|
|
43
|
+
await asRawClient(testDb.db).unsafe(
|
|
44
|
+
`TRUNCATE kumiko_events, read_es_exec_users RESTART IDENTITY CASCADE`,
|
|
45
|
+
);
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
describe("event-store-executor", () => {
|
|
@@ -117,7 +119,7 @@ const sensitiveEntity = createEntity({
|
|
|
117
119
|
},
|
|
118
120
|
softDelete: true,
|
|
119
121
|
});
|
|
120
|
-
const sensitiveTable =
|
|
122
|
+
const sensitiveTable = buildEntityTable("esExecSensitive", sensitiveEntity);
|
|
121
123
|
|
|
122
124
|
describe("event-store-executor — sensitive fields", () => {
|
|
123
125
|
const crud = createEventStoreExecutor(sensitiveTable, sensitiveEntity, {
|
|
@@ -129,8 +131,8 @@ describe("event-store-executor — sensitive fields", () => {
|
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
beforeEach(async () => {
|
|
132
|
-
await testDb.db.
|
|
133
|
-
|
|
134
|
+
await asRawClient(testDb.db).unsafe(
|
|
135
|
+
`TRUNCATE kumiko_events, read_es_exec_sensitive RESTART IDENTITY CASCADE`,
|
|
134
136
|
);
|
|
135
137
|
});
|
|
136
138
|
|
|
@@ -138,12 +140,17 @@ describe("event-store-executor — sensitive fields", () => {
|
|
|
138
140
|
type: string;
|
|
139
141
|
payload: TPayload;
|
|
140
142
|
}> {
|
|
141
|
-
const rows = await testDb.db.
|
|
142
|
-
|
|
143
|
-
);
|
|
143
|
+
const rows = (await asRawClient(testDb.db).unsafe(
|
|
144
|
+
`SELECT type, payload FROM kumiko_events ORDER BY id DESC LIMIT 1`,
|
|
145
|
+
)) as Array<{ type: string; payload: unknown }>;
|
|
144
146
|
const row = rows[0];
|
|
145
147
|
if (!row) throw new Error("no events in store");
|
|
146
|
-
return
|
|
148
|
+
return {
|
|
149
|
+
type: row["type"],
|
|
150
|
+
payload: (typeof row["payload"] === "string"
|
|
151
|
+
? JSON.parse(row["payload"])
|
|
152
|
+
: row["payload"]) as TPayload,
|
|
153
|
+
};
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
test("create event payload excludes sensitive fields but entity row keeps them", async () => {
|
|
@@ -15,16 +15,17 @@
|
|
|
15
15
|
// 5. Snapshot erneut nehmen
|
|
16
16
|
// 6. deep-equal: identische Rows in identischer Reihenfolge
|
|
17
17
|
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
19
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
20
20
|
import { createBooleanField, createEntity, createTextField, defineFeature } from "../../engine";
|
|
21
21
|
import { createRegistry } from "../../engine/registry";
|
|
22
22
|
import { createEventsTable } from "../../event-store";
|
|
23
23
|
import { rebuildProjection } from "../../pipeline";
|
|
24
24
|
import { createProjectionStateTable } from "../../pipeline/projection-state";
|
|
25
|
-
import {
|
|
25
|
+
import { TestUsers, unsafeCreateEntityTable } from "../../stack";
|
|
26
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
26
27
|
import { createEventStoreExecutor } from "../event-store-executor";
|
|
27
|
-
import {
|
|
28
|
+
import { buildEntityTable } from "../table-builder";
|
|
28
29
|
import { createTenantDb, type TenantDb } from "../tenant-db";
|
|
29
30
|
|
|
30
31
|
const userEntity = createEntity({
|
|
@@ -41,13 +42,14 @@ const userFeature = defineFeature("implicittest", (r) => {
|
|
|
41
42
|
r.entity("user", userEntity);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
const userTable =
|
|
45
|
+
const userTable = buildEntityTable("user", userEntity);
|
|
45
46
|
|
|
46
|
-
let testDb:
|
|
47
|
+
let testDb: BunTestDb;
|
|
47
48
|
let tdb: TenantDb;
|
|
48
49
|
const adminUser = TestUsers.admin;
|
|
49
50
|
|
|
50
51
|
beforeAll(async () => {
|
|
52
|
+
await ensureTemporalPolyfill();
|
|
51
53
|
testDb = await createTestDb();
|
|
52
54
|
await unsafeCreateEntityTable(testDb.db, userEntity, "user");
|
|
53
55
|
await createEventsTable(testDb.db);
|
|
@@ -60,13 +62,18 @@ afterAll(async () => {
|
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
beforeEach(async () => {
|
|
63
|
-
await testDb.db.
|
|
64
|
-
|
|
65
|
+
await asRawClient(testDb.db).unsafe(
|
|
66
|
+
`TRUNCATE kumiko_events, read_implicit_users, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
65
67
|
);
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
async function snapshotTable(): Promise<readonly Record<string, unknown>[]> {
|
|
69
|
-
const rows = await
|
|
71
|
+
const rows = await selectMany(
|
|
72
|
+
testDb.db,
|
|
73
|
+
userTable,
|
|
74
|
+
{},
|
|
75
|
+
{ orderBy: { col: "id", direction: "asc" } },
|
|
76
|
+
);
|
|
70
77
|
return rows as readonly Record<string, unknown>[];
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -224,7 +231,7 @@ describe("implicit-projection / Live==Rebuild equivalence", () => {
|
|
|
224
231
|
// sensitive-Spalte oder verschlüsseltem Event-Payload), bricht der Test
|
|
225
232
|
// und zwingt zu Aufmerksamkeit.
|
|
226
233
|
|
|
227
|
-
import {
|
|
234
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
228
235
|
|
|
229
236
|
const sensitiveTable = "read_implicit_sensitive_users";
|
|
230
237
|
|
|
@@ -240,7 +247,7 @@ const sensitiveFeature = defineFeature("implicitsensitive", (r) => {
|
|
|
240
247
|
r.entity("sensitive-user", sensitiveEntity);
|
|
241
248
|
});
|
|
242
249
|
|
|
243
|
-
const
|
|
250
|
+
const sensitiveEntityTable = buildEntityTable("sensitive-user", sensitiveEntity);
|
|
244
251
|
|
|
245
252
|
describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
|
|
246
253
|
beforeAll(async () => {
|
|
@@ -248,15 +255,13 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
|
|
|
248
255
|
});
|
|
249
256
|
|
|
250
257
|
beforeEach(async () => {
|
|
251
|
-
await testDb.db.
|
|
252
|
-
|
|
253
|
-
`TRUNCATE ${sensitiveTable}, kumiko_events, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
254
|
-
),
|
|
258
|
+
await asRawClient(testDb.db).unsafe(
|
|
259
|
+
`TRUNCATE ${sensitiveTable}, kumiko_events, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
255
260
|
);
|
|
256
261
|
});
|
|
257
262
|
|
|
258
263
|
test("Live schreibt sensitive-Felder, Rebuild lässt sie NULL (Welle-3-Roadmap)", async () => {
|
|
259
|
-
const crud = createEventStoreExecutor(
|
|
264
|
+
const crud = createEventStoreExecutor(sensitiveEntityTable, sensitiveEntity, {
|
|
260
265
|
entityName: "sensitive-user",
|
|
261
266
|
});
|
|
262
267
|
|
|
@@ -269,20 +274,22 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
|
|
|
269
274
|
);
|
|
270
275
|
if (!created.isSuccess) throw new Error("setup failed");
|
|
271
276
|
|
|
272
|
-
const [liveRow] = await testDb.db
|
|
273
|
-
.
|
|
274
|
-
|
|
275
|
-
.where(eq(sensitiveDrizzleTable["id"], created.data.id as string));
|
|
277
|
+
const [liveRow] = await selectMany(testDb.db, sensitiveEntityTable, {
|
|
278
|
+
id: created.data.id as string,
|
|
279
|
+
});
|
|
276
280
|
expect(liveRow?.["apiKey"]).toBe("secret-token-abc");
|
|
277
281
|
expect(liveRow?.["email"]).toBe("x@test.de");
|
|
278
282
|
|
|
279
283
|
// 2. Verifiziere dass das Event-Log das Feld NICHT enthält (stripped).
|
|
280
|
-
const
|
|
281
|
-
|
|
284
|
+
const { eventsTable } = await import("../../event-store");
|
|
285
|
+
const [event] = await selectMany(
|
|
286
|
+
testDb.db,
|
|
287
|
+
eventsTable,
|
|
288
|
+
{ aggregateId: created.data.id },
|
|
289
|
+
{ orderBy: { col: "version", direction: "asc" } },
|
|
282
290
|
);
|
|
283
|
-
expect(
|
|
284
|
-
expect(
|
|
285
|
-
expect(events[0]?.payload?.["email"]).toBe("x@test.de");
|
|
291
|
+
expect(event?.payload?.["apiKey"]).toBeUndefined();
|
|
292
|
+
expect(event?.payload?.["email"]).toBe("x@test.de");
|
|
286
293
|
|
|
287
294
|
// 3. Rebuild über die ImplicitProjection. Read-Tabelle wird aus
|
|
288
295
|
// event.payload neu materialisiert — apiKey ist nicht im Log,
|
|
@@ -293,10 +300,9 @@ describe("implicit-projection / dokumentierte Sensitive-Drift", () => {
|
|
|
293
300
|
registry,
|
|
294
301
|
});
|
|
295
302
|
|
|
296
|
-
const [rebuiltRow] = await testDb.db
|
|
297
|
-
.
|
|
298
|
-
|
|
299
|
-
.where(eq(sensitiveDrizzleTable["id"], created.data.id as string));
|
|
303
|
+
const [rebuiltRow] = await selectMany(testDb.db, sensitiveEntityTable, {
|
|
304
|
+
id: created.data.id as string,
|
|
305
|
+
});
|
|
300
306
|
expect(rebuiltRow?.["email"]).toBe("x@test.de");
|
|
301
307
|
// DAS ist die Drift: sensitive Feld ist nach Rebuild weg.
|
|
302
308
|
expect(rebuiltRow?.["apiKey"]).toBeNull();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pure Unit-Tests für die flatten/rehydrate Helpers — Auto-Convert für
|
|
2
2
|
// locatedTimestamp-Felder. Keine DB, kein Stack, nur Daten-Transform.
|
|
3
3
|
|
|
4
|
-
import { describe, expect, test } from "
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
5
|
import { createEntity, createLocatedTimestampField, createTextField } from "../../engine";
|
|
6
6
|
import type { EntityDefinition } from "../../engine/types";
|
|
7
7
|
import { flattenLocatedTimestamp, rehydrateLocatedTimestamp } from "../located-timestamp";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { EntityTableMeta } from "../entity-table-meta";
|
|
3
|
+
import {
|
|
4
|
+
diffSnapshots,
|
|
5
|
+
generateMigration,
|
|
6
|
+
renderMigrationSql,
|
|
7
|
+
snapshotFromMetas,
|
|
8
|
+
} from "../migrate-generator";
|
|
9
|
+
|
|
10
|
+
function meta(
|
|
11
|
+
tableName: string,
|
|
12
|
+
extraColumn?: EntityTableMeta["columns"][number],
|
|
13
|
+
): EntityTableMeta {
|
|
14
|
+
return {
|
|
15
|
+
tableName,
|
|
16
|
+
source: "unmanaged",
|
|
17
|
+
indexes: [],
|
|
18
|
+
columns: [
|
|
19
|
+
{ name: "id", pgType: "uuid", notNull: true, primaryKey: true },
|
|
20
|
+
...(extraColumn ? [extraColumn] : []),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("snapshotFromMetas", () => {
|
|
26
|
+
test("sorts tables by name for stable snapshots", () => {
|
|
27
|
+
const snap = snapshotFromMetas([meta("zebras"), meta("apples")]);
|
|
28
|
+
expect(snap.tables.map((t) => t.tableName)).toEqual(["apples", "zebras"]);
|
|
29
|
+
expect(snap.version).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("diffSnapshots", () => {
|
|
34
|
+
test("null prev → all tables are new", () => {
|
|
35
|
+
const next = snapshotFromMetas([meta("tasks")]);
|
|
36
|
+
const diff = diffSnapshots(null, next);
|
|
37
|
+
expect(diff.newTables.map((t) => t.tableName)).toEqual(["tasks"]);
|
|
38
|
+
expect(diff.droppedTables).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("detects dropped table and new column", () => {
|
|
42
|
+
const prev = snapshotFromMetas([meta("tasks"), meta("legacy")]);
|
|
43
|
+
const next = snapshotFromMetas([
|
|
44
|
+
meta("tasks", { name: "title", pgType: "text", notNull: true }),
|
|
45
|
+
]);
|
|
46
|
+
const diff = diffSnapshots(prev, next);
|
|
47
|
+
expect(diff.droppedTables).toEqual(["legacy"]);
|
|
48
|
+
expect(diff.changedTables[0]?.newColumns.map((c) => c.name)).toEqual(["title"]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("renderMigrationSql / generateMigration", () => {
|
|
53
|
+
test("emits CREATE TABLE for new tables", () => {
|
|
54
|
+
const diff = diffSnapshots(null, snapshotFromMetas([meta("tasks")]));
|
|
55
|
+
const sql = renderMigrationSql(diff, { name: "init", sequenceNumber: 1 });
|
|
56
|
+
expect(sql).toContain('CREATE TABLE IF NOT EXISTS "tasks"');
|
|
57
|
+
expect(sql).toContain("Migration 0001_init");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("generateMigration bundles snapshot + sql", () => {
|
|
61
|
+
const out = generateMigration({
|
|
62
|
+
metas: [meta("tasks")],
|
|
63
|
+
prevSnapshot: null,
|
|
64
|
+
name: "init",
|
|
65
|
+
sequenceNumber: 1,
|
|
66
|
+
});
|
|
67
|
+
expect(out.snapshot.tables).toHaveLength(1);
|
|
68
|
+
expect(out.sqlContent).toContain("0001_init");
|
|
69
|
+
expect(out.filename).toBe("0001_init.sql");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { splitSqlStatements } from "../migrate-runner";
|
|
3
|
+
|
|
4
|
+
describe("splitSqlStatements", () => {
|
|
5
|
+
test("splits on semicolons and strips line comments", () => {
|
|
6
|
+
const sql = `
|
|
7
|
+
CREATE TABLE "a" (id uuid); -- inline comment
|
|
8
|
+
CREATE TABLE "b" (id uuid);
|
|
9
|
+
`;
|
|
10
|
+
expect(splitSqlStatements(sql)).toEqual([
|
|
11
|
+
'CREATE TABLE "a" (id uuid);',
|
|
12
|
+
'CREATE TABLE "b" (id uuid);',
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("filters empty segments", () => {
|
|
17
|
+
expect(splitSqlStatements("-- only comments\n; ;")).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pure Unit-Tests für money flatten/rehydrate Helpers.
|
|
2
2
|
|
|
3
|
-
import { describe, expect, test } from "
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
4
|
import { createEntity, createMoneyField, createTextField } from "../../engine";
|
|
5
5
|
import type { EntityDefinition } from "../../engine/types";
|
|
6
6
|
import { flattenMoney, rehydrateMoney } from "../money";
|
package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts}
RENAMED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
// have a real footgun, or whether the showcase bug had a different root
|
|
8
8
|
// cause.
|
|
9
9
|
|
|
10
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
11
|
-
import {
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
11
|
+
import { insertOne, selectMany } from "../../db/query";
|
|
12
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
12
13
|
import { createEntity, createTextField } from "../../engine";
|
|
13
14
|
import { setupTestStack, type TestStack, unsafeCreateEntityTable } from "../../stack";
|
|
14
15
|
|
|
@@ -19,7 +20,7 @@ const linkEntity = createEntity({
|
|
|
19
20
|
rightId: createTextField({ required: true }),
|
|
20
21
|
},
|
|
21
22
|
});
|
|
22
|
-
const linkTable =
|
|
23
|
+
const linkTable = buildEntityTable("link", linkEntity);
|
|
23
24
|
|
|
24
25
|
let stack: TestStack;
|
|
25
26
|
|
|
@@ -41,7 +42,7 @@ describe("instant() customType is forgiving with ISO strings", () => {
|
|
|
41
42
|
table: "mri_ts",
|
|
42
43
|
fields: { name: createTextField({ required: true }) },
|
|
43
44
|
});
|
|
44
|
-
const tsTable =
|
|
45
|
+
const tsTable = buildEntityTable("ts-row", tsEntity);
|
|
45
46
|
|
|
46
47
|
test("INSERT accepts an ISO string for an instant column (forgiving path)", async () => {
|
|
47
48
|
await unsafeCreateEntityTable(stack.db, tsEntity, "ts-row");
|
|
@@ -50,12 +51,12 @@ describe("instant() customType is forgiving with ISO strings", () => {
|
|
|
50
51
|
// would call .toString() on a string and produce a malformed driver
|
|
51
52
|
// value that PG rejects.
|
|
52
53
|
const isoString = "2026-01-15T12:00:00Z";
|
|
53
|
-
await stack.db
|
|
54
|
+
await insertOne(stack.db, tsTable, {
|
|
54
55
|
name: "x",
|
|
55
56
|
tenantId: "00000000-0000-4000-8000-000000000001",
|
|
56
57
|
insertedAt: isoString as unknown as Temporal.Instant,
|
|
57
58
|
});
|
|
58
|
-
const rows = await stack.db
|
|
59
|
+
const rows = await selectMany(stack.db, tsTable);
|
|
59
60
|
expect(rows).toHaveLength(1);
|
|
60
61
|
expect(rows[0]?.["insertedAt"]).toBeInstanceOf(Temporal.Instant);
|
|
61
62
|
});
|
|
@@ -63,11 +64,17 @@ describe("instant() customType is forgiving with ISO strings", () => {
|
|
|
63
64
|
|
|
64
65
|
describe("multi-row INSERT", () => {
|
|
65
66
|
test("two rows with no id supplied → both rows persist (PG gen_random_uuid per row)", async () => {
|
|
66
|
-
await stack.db
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
await insertOne(stack.db, linkTable, {
|
|
68
|
+
leftId: "L1",
|
|
69
|
+
rightId: "R1",
|
|
70
|
+
tenantId: "00000000-0000-4000-8000-000000000001",
|
|
71
|
+
});
|
|
72
|
+
await insertOne(stack.db, linkTable, {
|
|
73
|
+
leftId: "L2",
|
|
74
|
+
rightId: "R2",
|
|
75
|
+
tenantId: "00000000-0000-4000-8000-000000000001",
|
|
76
|
+
});
|
|
77
|
+
const rows = await selectMany(stack.db, linkTable);
|
|
71
78
|
expect(rows).toHaveLength(2);
|
|
72
79
|
// Each row got its own id from the PG default.
|
|
73
80
|
const ids = new Set(rows.map((r) => r["id"] as string));
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Wenn parseAutoVerb für ein Domain-Event versehentlich einen Verb
|
|
8
8
|
// returnt, würde die ImplicitProjection den falschen Handler firen.
|
|
9
9
|
|
|
10
|
-
import { describe, expect, test } from "
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
11
11
|
import type { StoredEvent } from "../../event-store";
|
|
12
12
|
import { parseAutoVerb } from "../apply-entity-event";
|
|
13
13
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { constraintOf, extractPgError, isTableAlreadyExists, isUniqueViolation } from "../pg-error";
|
|
3
|
+
|
|
4
|
+
describe("extractPgError", () => {
|
|
5
|
+
test("reads code from top-level postgres-js error", () => {
|
|
6
|
+
const info = extractPgError({ code: "23505", constraint_name: "users_email_uq" });
|
|
7
|
+
expect(info).toEqual({ code: "23505", constraint_name: "users_email_uq" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("unwraps DrizzleQueryError.cause", () => {
|
|
11
|
+
const info = extractPgError({
|
|
12
|
+
message: "wrapper",
|
|
13
|
+
cause: { code: "23505", constraint_name: "uq" },
|
|
14
|
+
});
|
|
15
|
+
expect(info?.code).toBe("23505");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns null for non-objects", () => {
|
|
19
|
+
expect(extractPgError("nope")).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("isUniqueViolation", () => {
|
|
24
|
+
test("true for SQLSTATE 23505", () => {
|
|
25
|
+
expect(isUniqueViolation({ code: "23505" })).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("false otherwise", () => {
|
|
29
|
+
expect(isUniqueViolation({ code: "23503" })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("isTableAlreadyExists", () => {
|
|
34
|
+
test("true for SQLSTATE 42P07", () => {
|
|
35
|
+
expect(isTableAlreadyExists({ code: "42P07" })).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("constraintOf", () => {
|
|
40
|
+
test("returns constraint_name when present", () => {
|
|
41
|
+
expect(constraintOf({ constraint_name: "users_email_uq" })).toBe("users_email_uq");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
// solchen Migration eine Sanity-Query auf NULL-Counts in den betroffenen
|
|
17
17
|
// Spalten laufen, oder DB drop'pen wenn der State Demo-State ist.
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
20
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
21
|
+
import { asRawClient } from "../../db/query";
|
|
22
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
22
23
|
|
|
23
|
-
let testDb:
|
|
24
|
+
let testDb: BunTestDb;
|
|
24
25
|
|
|
25
26
|
beforeAll(async () => {
|
|
27
|
+
await ensureTemporalPolyfill();
|
|
26
28
|
testDb = await createTestDb();
|
|
27
29
|
});
|
|
28
30
|
|
|
@@ -31,53 +33,53 @@ afterAll(async () => {
|
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
beforeEach(async () => {
|
|
34
|
-
await testDb.db.
|
|
36
|
+
await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS migration_safety_test`);
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
describe("ALTER TABLE SET NOT NULL — Daten-Sicherheits-Verhalten", () => {
|
|
38
40
|
test("SET NOT NULL kracht wenn die Spalte NULL-Zeilen enthält", async () => {
|
|
39
|
-
await testDb.db.
|
|
41
|
+
await asRawClient(testDb.db).unsafe(`
|
|
40
42
|
CREATE TABLE migration_safety_test (
|
|
41
43
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
44
|
key text
|
|
43
45
|
)
|
|
44
46
|
`);
|
|
45
47
|
// NULL-Zeile einschleusen — simuliert prod state vor dem Drift-Fix.
|
|
46
|
-
await testDb.db.
|
|
48
|
+
await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
|
|
47
49
|
|
|
48
50
|
let caught: unknown;
|
|
49
51
|
try {
|
|
50
|
-
await testDb.db.
|
|
52
|
+
await asRawClient(testDb.db).unsafe(
|
|
53
|
+
`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
|
|
54
|
+
);
|
|
51
55
|
} catch (err) {
|
|
52
56
|
caught = err;
|
|
53
57
|
}
|
|
54
58
|
expect(caught).toBeDefined();
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// `.message`. Wir prüfen pragmatisch beide Pfade.
|
|
59
|
-
const cause = (caught as { cause?: unknown }).cause;
|
|
60
|
-
const causeCode = (cause as { code?: string } | undefined)?.code;
|
|
61
|
-
expect(causeCode).toBe("23502");
|
|
59
|
+
// Postgres-js throws PostgresError directly (no drizzle wrapper anymore).
|
|
60
|
+
const code = (caught as { code?: string } | undefined)?.code;
|
|
61
|
+
expect(code).toBe("23502");
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test("SET NOT NULL läuft sauber durch wenn alle Zeilen Werte haben", async () => {
|
|
65
|
-
await testDb.db.
|
|
65
|
+
await asRawClient(testDb.db).unsafe(`
|
|
66
66
|
CREATE TABLE migration_safety_test (
|
|
67
67
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
68
68
|
key text
|
|
69
69
|
)
|
|
70
70
|
`);
|
|
71
|
-
await testDb.db.
|
|
72
|
-
await testDb.db.
|
|
71
|
+
await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES ('foo')`);
|
|
72
|
+
await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES ('bar')`);
|
|
73
73
|
|
|
74
74
|
// Sollte ohne Throw durchlaufen.
|
|
75
|
-
await testDb.db.
|
|
75
|
+
await asRawClient(testDb.db).unsafe(
|
|
76
|
+
`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
|
|
77
|
+
);
|
|
76
78
|
|
|
77
79
|
// Verifizieren: zukünftige NULL-Inserts werden jetzt blockiert.
|
|
78
80
|
let caught: unknown;
|
|
79
81
|
try {
|
|
80
|
-
await testDb.db.
|
|
82
|
+
await asRawClient(testDb.db).unsafe(`INSERT INTO migration_safety_test (key) VALUES (NULL)`);
|
|
81
83
|
} catch (err) {
|
|
82
84
|
caught = err;
|
|
83
85
|
}
|
|
@@ -88,17 +90,19 @@ describe("ALTER TABLE SET NOT NULL — Daten-Sicherheits-Verhalten", () => {
|
|
|
88
90
|
// Frisch erstellt, keine Zeilen — der Fall in dem `migrate apply` nach
|
|
89
91
|
// einem DB-drop läuft. Dieser Pfad muss IMMER grün sein, sonst wäre
|
|
90
92
|
// jeder Greenfield-Deploy kaputt.
|
|
91
|
-
await testDb.db.
|
|
93
|
+
await asRawClient(testDb.db).unsafe(`
|
|
92
94
|
CREATE TABLE migration_safety_test (
|
|
93
95
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
94
96
|
key text
|
|
95
97
|
)
|
|
96
98
|
`);
|
|
97
|
-
await testDb.db.
|
|
99
|
+
await asRawClient(testDb.db).unsafe(
|
|
100
|
+
`ALTER TABLE migration_safety_test ALTER COLUMN key SET NOT NULL`,
|
|
101
|
+
);
|
|
98
102
|
|
|
99
103
|
// Beweis: information_schema zeigt die Spalte jetzt als NOT NULL.
|
|
100
|
-
const rows = await testDb.db.
|
|
101
|
-
|
|
104
|
+
const rows = await asRawClient(testDb.db).unsafe<{ is_nullable: string }>(
|
|
105
|
+
`SELECT is_nullable FROM information_schema.columns WHERE table_name = 'migration_safety_test' AND column_name = 'key'`,
|
|
102
106
|
);
|
|
103
107
|
expect(rows[0]?.is_nullable).toBe("NO");
|
|
104
108
|
});
|