@cosmicdrift/kumiko-framework 0.13.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 +7 -7
- 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 -472
- 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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { sql } from "../../db/dialect";
|
|
3
|
+
import type { EntityTableMeta } from "../../db/entity-table-meta";
|
|
4
|
+
import {
|
|
5
|
+
asRawClient,
|
|
6
|
+
countWhere,
|
|
7
|
+
deleteManyBatched,
|
|
8
|
+
incrementCounter,
|
|
9
|
+
insertMany,
|
|
10
|
+
selectMany,
|
|
11
|
+
upsertByPk,
|
|
12
|
+
upsertOnConflict,
|
|
13
|
+
} from "../query";
|
|
14
|
+
import { closeDb, getDb, renderCreateTable, uniqueTableName, withTable } from "./_helpers";
|
|
15
|
+
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
await closeDb();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const scoreCols = [
|
|
21
|
+
{ name: "score", pgType: "integer" as const, notNull: true, defaultSql: "0" },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
function makeCodePkMeta(tableName: string): EntityTableMeta {
|
|
25
|
+
return {
|
|
26
|
+
tableName,
|
|
27
|
+
source: "unmanaged",
|
|
28
|
+
indexes: [],
|
|
29
|
+
columns: [
|
|
30
|
+
{ name: "code", pgType: "text", notNull: true, primaryKey: true },
|
|
31
|
+
{ name: "score", pgType: "integer", notNull: true, defaultSql: "0" },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function withCodeTable<T>(
|
|
37
|
+
fn: (ctx: { db: unknown; meta: EntityTableMeta }) => Promise<T>,
|
|
38
|
+
): Promise<T> {
|
|
39
|
+
const db = await getDb();
|
|
40
|
+
const tableName = uniqueTableName("upsert");
|
|
41
|
+
const meta = makeCodePkMeta(tableName);
|
|
42
|
+
await asRawClient(db).unsafe(renderCreateTable(meta));
|
|
43
|
+
try {
|
|
44
|
+
return await fn({ db, meta });
|
|
45
|
+
} finally {
|
|
46
|
+
await asRawClient(db).unsafe(`DROP TABLE IF EXISTS "${tableName}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("countWhere", () => {
|
|
51
|
+
test("counts all rows when where is empty", async () => {
|
|
52
|
+
await withTable(scoreCols, async ({ db, meta }) => {
|
|
53
|
+
await insertMany(db, meta, [{ score: 1 }, { score: 2 }]);
|
|
54
|
+
expect(await countWhere(db, meta)).toBe(2);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("counts filtered rows", async () => {
|
|
59
|
+
await withTable(scoreCols, async ({ db, meta }) => {
|
|
60
|
+
await insertMany(db, meta, [{ score: 1 }, { score: 5 }, { score: 5 }]);
|
|
61
|
+
expect(await countWhere(db, meta, { score: 5 })).toBe(2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("upsertOnConflict / upsertByPk", () => {
|
|
67
|
+
test("upsertByPk inserts then updates on conflict", async () => {
|
|
68
|
+
await withCodeTable(async ({ db, meta }) => {
|
|
69
|
+
await upsertByPk(db, meta, { code: "alpha", score: 1 });
|
|
70
|
+
await upsertByPk(db, meta, { code: "alpha", score: 9 });
|
|
71
|
+
const rows = await selectMany<{ code: string; score: number }>(db, meta);
|
|
72
|
+
expect(rows).toHaveLength(1);
|
|
73
|
+
expect(rows[0]?.score).toBe(9);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("upsertOnConflict with explicit conflictKeys", async () => {
|
|
78
|
+
await withCodeTable(async ({ db, meta }) => {
|
|
79
|
+
await upsertOnConflict(db, meta, { code: "beta", score: 2 }, { conflictKeys: ["code"] });
|
|
80
|
+
await upsertOnConflict(
|
|
81
|
+
db,
|
|
82
|
+
meta,
|
|
83
|
+
{ code: "beta", score: 7 },
|
|
84
|
+
{ conflictKeys: ["code"], update: { score: 7 } },
|
|
85
|
+
);
|
|
86
|
+
const row = (await selectMany<{ score: number }>(db, meta, { code: "beta" }))[0];
|
|
87
|
+
expect(row?.score).toBe(7);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("incrementCounter", () => {
|
|
93
|
+
test("atomically increments numeric columns on conflict", async () => {
|
|
94
|
+
await withCodeTable(async ({ db, meta }) => {
|
|
95
|
+
await incrementCounter(db, meta, { code: "t1", score: 10 }, { score: 10 });
|
|
96
|
+
await incrementCounter(db, meta, { code: "t1", score: 0 }, { score: 5 });
|
|
97
|
+
const row = (await selectMany<{ score: number }>(db, meta, { code: "t1" }))[0];
|
|
98
|
+
expect(row?.score).toBe(15);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("supports set on conflict with sql expression", async () => {
|
|
103
|
+
await withCodeTable(async ({ db, meta }) => {
|
|
104
|
+
const withUpdated: EntityTableMeta = {
|
|
105
|
+
...meta,
|
|
106
|
+
columns: [
|
|
107
|
+
...meta.columns,
|
|
108
|
+
{ name: "updated_at", pgType: "timestamptz", notNull: true, defaultSql: "now()" },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
await asRawClient(db).unsafe(`DROP TABLE IF EXISTS "${meta.tableName}"`);
|
|
112
|
+
await asRawClient(db).unsafe(renderCreateTable(withUpdated));
|
|
113
|
+
await incrementCounter(
|
|
114
|
+
db,
|
|
115
|
+
withUpdated,
|
|
116
|
+
{ code: "x", score: 1 },
|
|
117
|
+
{ score: 1 },
|
|
118
|
+
{ conflictKeys: ["code"], set: { updatedAt: sql`NOW()` } },
|
|
119
|
+
);
|
|
120
|
+
const rows = await selectMany(db, withUpdated, { code: "x" });
|
|
121
|
+
expect(rows.length).toBe(1);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("deleteManyBatched", () => {
|
|
127
|
+
test("deletes in chunks until no rows match", async () => {
|
|
128
|
+
await withTable(scoreCols, async ({ db, meta }) => {
|
|
129
|
+
await insertMany(
|
|
130
|
+
db,
|
|
131
|
+
meta,
|
|
132
|
+
Array.from({ length: 10 }, (_, i) => ({ score: i < 7 ? 1 : 9 })),
|
|
133
|
+
);
|
|
134
|
+
expect(await countWhere(db, meta, { score: 1 })).toBe(7);
|
|
135
|
+
|
|
136
|
+
const result = await deleteManyBatched(db, meta, { score: 1 }, { limit: 3 });
|
|
137
|
+
expect(result.deleted).toBe(7);
|
|
138
|
+
expect(result.batches).toBe(3);
|
|
139
|
+
expect(await countWhere(db, meta, { score: 1 })).toBe(0);
|
|
140
|
+
expect(await countWhere(db, meta, { score: 9 })).toBe(3);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { deleteManyBatched } from "../query";
|
|
3
|
+
|
|
4
|
+
describe("deleteManyBatched (mock)", () => {
|
|
5
|
+
test("requires non-empty where", async () => {
|
|
6
|
+
const meta = {
|
|
7
|
+
source: "unmanaged" as const,
|
|
8
|
+
tableName: "read_items",
|
|
9
|
+
indexes: [],
|
|
10
|
+
columns: [
|
|
11
|
+
{ name: "id", pgType: "uuid", notNull: true, primaryKey: true },
|
|
12
|
+
{ name: "flag", pgType: "boolean", notNull: true },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
const db = { unsafe: async () => [] as unknown[] };
|
|
16
|
+
await expect(deleteManyBatched(db, meta, {}, { limit: 10 })).rejects.toThrow(
|
|
17
|
+
"where clause required",
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Provider-agnostic Test-DB: delegiert an createTestDb (postgres-js default).
|
|
2
|
+
// Welcher Provider tatsächlich genutzt wird steuert DB_PROVIDER env-var,
|
|
3
|
+
// gelesen in db/api.ts createConnection — global, einmalig zu Boot-Zeit.
|
|
4
|
+
//
|
|
5
|
+
// "Bun" im Namen ist historisch (Pattern-Discovery-Phase) — der Body ist
|
|
6
|
+
// provider-neutral. Aliase BunTestDb/createBunTestDb existieren damit
|
|
7
|
+
// die migrierten Test-Files importierbar bleiben ohne weitere Refactors.
|
|
8
|
+
|
|
9
|
+
import { type CreateTestDbOptions, createTestDb, type TestDb } from "../../stack/db";
|
|
10
|
+
|
|
11
|
+
export type { TestDb };
|
|
12
|
+
export type BunTestDb = TestDb;
|
|
13
|
+
|
|
14
|
+
export async function createBunTestDb(baseUrl?: string): Promise<TestDb> {
|
|
15
|
+
const opts: CreateTestDbOptions = baseUrl ? { baseUrl } : {};
|
|
16
|
+
return createTestDb(opts);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { createTestDb };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Provider-agnostic Test-Stack — re-exported from stack/test-stack.ts.
|
|
2
|
+
// setupBunTestStack / BunTestStack sind Aliase für setupTestStack / TestStack.
|
|
3
|
+
// "Bun" ist historisch, jetzt provider-neutral (DB_PROVIDER env).
|
|
4
|
+
|
|
5
|
+
export type { TestStack as BunTestStack } from "../../stack/test-stack";
|
|
6
|
+
export { setupTestStack as setupBunTestStack } from "../../stack/test-stack";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Column-Type-Roundtrip-Matrix: jeder pgType, der von Bun.SQL über
|
|
2
|
+
// bun-db unterstützt werden muss — insert + fetch + JS-Wert-Vergleich.
|
|
3
|
+
//
|
|
4
|
+
// Beweist dass jeder Spalten-Typ aus EntityTableMeta in den bun-db
|
|
5
|
+
// query-layer rein- und rausgeht ohne dass JS-Werte sich subtil ändern
|
|
6
|
+
// (Type-coercion-bug, BigInt-precision-loss, jsonb-string-statt-object,
|
|
7
|
+
// Temporal-Drift bei timestamptz, etc.).
|
|
8
|
+
|
|
9
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
10
|
+
import { fetchOne, insertOne } from "../query";
|
|
11
|
+
import { closeDb, withTable } from "./_helpers";
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await closeDb();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("column-type roundtrip", () => {
|
|
18
|
+
test("text", async () => {
|
|
19
|
+
await withTable([{ name: "val", pgType: "text", notNull: true }], async ({ db, meta }) => {
|
|
20
|
+
const ins = await insertOne<{ id: string; val: string }>(db, meta, { val: "hello" });
|
|
21
|
+
const row = await fetchOne<{ id: string; val: string }>(db, meta, { id: ins!.id });
|
|
22
|
+
expect(row?.val).toBe("hello");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("text with unicode + emoji", async () => {
|
|
27
|
+
await withTable([{ name: "val", pgType: "text", notNull: true }], async ({ db, meta }) => {
|
|
28
|
+
const ins = await insertOne<{ id: string; val: string }>(db, meta, {
|
|
29
|
+
val: "Müller 🦊 中文",
|
|
30
|
+
});
|
|
31
|
+
const row = await fetchOne<{ id: string; val: string }>(db, meta, { id: ins!.id });
|
|
32
|
+
expect(row?.val).toBe("Müller 🦊 中文");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("text empty string (not null)", async () => {
|
|
37
|
+
await withTable([{ name: "val", pgType: "text", notNull: false }], async ({ db, meta }) => {
|
|
38
|
+
const ins = await insertOne<{ id: string; val: string }>(db, meta, { val: "" });
|
|
39
|
+
const row = await fetchOne<{ id: string; val: string }>(db, meta, { id: ins!.id });
|
|
40
|
+
expect(row?.val).toBe("");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("text null in nullable column", async () => {
|
|
45
|
+
await withTable([{ name: "val", pgType: "text", notNull: false }], async ({ db, meta }) => {
|
|
46
|
+
const ins = await insertOne<{ id: string; val: string | null }>(db, meta, { val: null });
|
|
47
|
+
const row = await fetchOne<{ id: string; val: string | null }>(db, meta, { id: ins!.id });
|
|
48
|
+
expect(row?.val).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("boolean true + false", async () => {
|
|
53
|
+
await withTable([{ name: "flag", pgType: "boolean", notNull: true }], async ({ db, meta }) => {
|
|
54
|
+
const t = await insertOne<{ id: string; flag: boolean }>(db, meta, { flag: true });
|
|
55
|
+
const f = await insertOne<{ id: string; flag: boolean }>(db, meta, { flag: false });
|
|
56
|
+
const tr = await fetchOne<{ flag: boolean }>(db, meta, { id: t!.id });
|
|
57
|
+
const fr = await fetchOne<{ flag: boolean }>(db, meta, { id: f!.id });
|
|
58
|
+
expect(tr?.flag).toBe(true);
|
|
59
|
+
expect(fr?.flag).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("integer ±max", async () => {
|
|
64
|
+
await withTable([{ name: "n", pgType: "integer", notNull: true }], async ({ db, meta }) => {
|
|
65
|
+
const max = await insertOne<{ id: string; n: number }>(db, meta, { n: 2147483647 });
|
|
66
|
+
const min = await insertOne<{ id: string; n: number }>(db, meta, { n: -2147483648 });
|
|
67
|
+
const zero = await insertOne<{ id: string; n: number }>(db, meta, { n: 0 });
|
|
68
|
+
expect((await fetchOne<{ n: number }>(db, meta, { id: max!.id }))?.n).toBe(2147483647);
|
|
69
|
+
expect((await fetchOne<{ n: number }>(db, meta, { id: min!.id }))?.n).toBe(-2147483648);
|
|
70
|
+
expect((await fetchOne<{ n: number }>(db, meta, { id: zero!.id }))?.n).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("bigint ±max (as bigint)", async () => {
|
|
75
|
+
await withTable([{ name: "n", pgType: "bigint", notNull: true }], async ({ db, meta }) => {
|
|
76
|
+
// JS Number-Max = 2^53-1. Postgres bigint = ±2^63-1.
|
|
77
|
+
// bun-db boundary coerziert string→bigint (siehe commit 0be2db9b).
|
|
78
|
+
const bigPos = 9007199254740993n;
|
|
79
|
+
const ins = await insertOne<{ id: string; n: bigint }>(db, meta, { n: bigPos });
|
|
80
|
+
const row = await fetchOne<{ id: string; n: bigint }>(db, meta, { id: ins!.id });
|
|
81
|
+
expect(row?.n).toBe(bigPos);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("uuid roundtrip", async () => {
|
|
86
|
+
await withTable([{ name: "ref", pgType: "uuid", notNull: true }], async ({ db, meta }) => {
|
|
87
|
+
const target = "00000000-0000-4000-8000-000000000001";
|
|
88
|
+
const ins = await insertOne<{ id: string; ref: string }>(db, meta, { ref: target });
|
|
89
|
+
const row = await fetchOne<{ ref: string }>(db, meta, { id: ins!.id });
|
|
90
|
+
expect(row?.ref).toBe(target);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("timestamptz roundtrip via ISO string", async () => {
|
|
95
|
+
await withTable(
|
|
96
|
+
[{ name: "ts", pgType: "timestamptz", notNull: true }],
|
|
97
|
+
async ({ db, meta }) => {
|
|
98
|
+
const iso = "2026-05-24T08:30:00.000Z";
|
|
99
|
+
const ins = await insertOne<{ id: string; ts: unknown }>(db, meta, { ts: iso });
|
|
100
|
+
const row = await fetchOne<{ id: string; ts: Date | string }>(db, meta, { id: ins!.id });
|
|
101
|
+
// bun-db gibt Temporal.Instant zurück (siehe ec2c7fbf). Wir
|
|
102
|
+
// checken über instant.toString() bzw. Date-equivalence.
|
|
103
|
+
const fetched = row?.ts;
|
|
104
|
+
const ms =
|
|
105
|
+
fetched instanceof Date ? fetched.getTime() : new Date(String(fetched)).getTime();
|
|
106
|
+
expect(ms).toBe(new Date(iso).getTime());
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("jsonb empty object", async () => {
|
|
112
|
+
await withTable(
|
|
113
|
+
[{ name: "data", pgType: "jsonb", notNull: true, defaultSql: "'{}'::jsonb" }],
|
|
114
|
+
async ({ db, meta }) => {
|
|
115
|
+
const ins = await insertOne<{ id: string; data: object }>(db, meta, { data: {} });
|
|
116
|
+
const row = await fetchOne<{ data: object }>(db, meta, { id: ins!.id });
|
|
117
|
+
expect(row?.data).toEqual({});
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("jsonb empty array", async () => {
|
|
123
|
+
await withTable(
|
|
124
|
+
[{ name: "data", pgType: "jsonb", notNull: true, defaultSql: "'[]'::jsonb" }],
|
|
125
|
+
async ({ db, meta }) => {
|
|
126
|
+
const ins = await insertOne<{ id: string; data: unknown[] }>(db, meta, { data: [] });
|
|
127
|
+
const row = await fetchOne<{ data: unknown[] }>(db, meta, { id: ins!.id });
|
|
128
|
+
expect(row?.data).toEqual([]);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// compound-types.integration.ts — Roundtrip-Tests für die entity-table-meta
|
|
2
|
+
// Compound-Field-Types die 2+ Spalten produzieren.
|
|
3
|
+
//
|
|
4
|
+
// money (field.type "money"):
|
|
5
|
+
// <snake> bigint — Cent-Betrag (JS bigint, kein precision-loss)
|
|
6
|
+
// <snake>_currency text — ISO-4217-Code
|
|
7
|
+
// Read-keys (nach camelCase-Coercion in coerceRow): price / priceCurrency
|
|
8
|
+
//
|
|
9
|
+
// locatedTimestamp (field.type "locatedTimestamp"):
|
|
10
|
+
// <snake>_utc timestamptz — UTC-Zeitstempel (→ Temporal.Instant nach coerceRow)
|
|
11
|
+
// <snake>_tz text — IANA-Timezone-Name
|
|
12
|
+
// Read-keys nach camelCase-Coercion: dueAtUtc / dueAtTz
|
|
13
|
+
//
|
|
14
|
+
// Schreiben: snake_case-Keys werden von columnOf() korrekt gemappt.
|
|
15
|
+
// Lesen: coerceRow() renamed snake_case-DB-columns zu camelCase JS-keys.
|
|
16
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
17
|
+
import { fetchOne, insertOne } from "../query";
|
|
18
|
+
import { closeDb, withTable } from "./_helpers";
|
|
19
|
+
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await closeDb();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const moneyCols = [
|
|
25
|
+
{ name: "price", pgType: "bigint" as const, notNull: true },
|
|
26
|
+
{ name: "price_currency", pgType: "text" as const, notNull: true, defaultSql: "'EUR'" },
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const locatedTsCols = [
|
|
30
|
+
{ name: "due_at_utc", pgType: "timestamptz" as const, notNull: true },
|
|
31
|
+
{ name: "due_at_tz", pgType: "text" as const, notNull: true },
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
describe("money — roundtrip", () => {
|
|
35
|
+
test("EUR-Betrag: insert + fetchOne gibt gleichen bigint + currency zurück", async () => {
|
|
36
|
+
await withTable(moneyCols, async ({ db, meta }) => {
|
|
37
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
38
|
+
price: 1234n,
|
|
39
|
+
price_currency: "EUR",
|
|
40
|
+
});
|
|
41
|
+
const row = await fetchOne<{ price: bigint; priceCurrency: string }>(db, meta, {
|
|
42
|
+
id: ins!.id,
|
|
43
|
+
});
|
|
44
|
+
expect(row!.price).toBe(1234n);
|
|
45
|
+
expect(row!.priceCurrency).toBe("EUR");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("CHF-Currency roundtrip", async () => {
|
|
50
|
+
await withTable(moneyCols, async ({ db, meta }) => {
|
|
51
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
52
|
+
price: 9900n,
|
|
53
|
+
price_currency: "CHF",
|
|
54
|
+
});
|
|
55
|
+
const row = await fetchOne<{ price: bigint; priceCurrency: string }>(db, meta, {
|
|
56
|
+
id: ins!.id,
|
|
57
|
+
});
|
|
58
|
+
expect(row!.priceCurrency).toBe("CHF");
|
|
59
|
+
expect(row!.price).toBe(9900n);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("JPY-Currency roundtrip (kein Cent, ganzer Yen)", async () => {
|
|
64
|
+
await withTable(moneyCols, async ({ db, meta }) => {
|
|
65
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
66
|
+
price: 150000n,
|
|
67
|
+
price_currency: "JPY",
|
|
68
|
+
});
|
|
69
|
+
const row = await fetchOne<{ price: bigint; priceCurrency: string }>(db, meta, {
|
|
70
|
+
id: ins!.id,
|
|
71
|
+
});
|
|
72
|
+
expect(row!.priceCurrency).toBe("JPY");
|
|
73
|
+
expect(row!.price).toBe(150000n);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("großer Betrag (> 2^53 cents — bigint-Boundary, precision-loss-Guard)", async () => {
|
|
78
|
+
await withTable(moneyCols, async ({ db, meta }) => {
|
|
79
|
+
// JS Number max-safe ist 9007199254740991. Wert jenseits dieser Grenze
|
|
80
|
+
// würde bei Number-Rückgabe still precision verlieren. bigint-Roundtrip
|
|
81
|
+
// muss exakt bleiben.
|
|
82
|
+
const bigCents = 9007199254740992n; // 2^53 — jenseits JS-Number-Grenze
|
|
83
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
84
|
+
price: bigCents,
|
|
85
|
+
price_currency: "EUR",
|
|
86
|
+
});
|
|
87
|
+
const row = await fetchOne<{ price: bigint }>(db, meta, { id: ins!.id });
|
|
88
|
+
expect(row!.price).toBe(bigCents);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("locatedTimestamp — roundtrip", () => {
|
|
94
|
+
test("UTC-Zeitstempel + Timezone-Name roundtrip (Europe/Berlin)", async () => {
|
|
95
|
+
await withTable(locatedTsCols, async ({ db, meta }) => {
|
|
96
|
+
const iso = "2026-05-24T08:30:00.000Z";
|
|
97
|
+
const tz = "Europe/Berlin";
|
|
98
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
99
|
+
due_at_utc: iso,
|
|
100
|
+
due_at_tz: tz,
|
|
101
|
+
});
|
|
102
|
+
const row = await fetchOne<{ dueAtUtc: Temporal.Instant; dueAtTz: string }>(db, meta, {
|
|
103
|
+
id: ins!.id,
|
|
104
|
+
});
|
|
105
|
+
// bun-db coerciert timestamptz → Temporal.Instant (coerceRow in query.ts)
|
|
106
|
+
expect(typeof row!.dueAtUtc.epochNanoseconds).toBe("bigint");
|
|
107
|
+
expect(row!.dueAtUtc.epochMilliseconds).toBe(new Date(iso).getTime());
|
|
108
|
+
expect(row!.dueAtTz).toBe(tz);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("Timezone America/New_York bleibt als String erhalten", async () => {
|
|
113
|
+
await withTable(locatedTsCols, async ({ db, meta }) => {
|
|
114
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
115
|
+
due_at_utc: "2026-01-15T13:00:00.000Z",
|
|
116
|
+
due_at_tz: "America/New_York",
|
|
117
|
+
});
|
|
118
|
+
const row = await fetchOne<{ dueAtTz: string }>(db, meta, { id: ins!.id });
|
|
119
|
+
expect(row!.dueAtTz).toBe("America/New_York");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("UTC-Zeitstempel am Tag-Anfang (Mitternacht)", async () => {
|
|
124
|
+
await withTable(locatedTsCols, async ({ db, meta }) => {
|
|
125
|
+
const iso = "2026-01-01T00:00:00.000Z";
|
|
126
|
+
const ins = await insertOne<{ id: string }>(db, meta, {
|
|
127
|
+
due_at_utc: iso,
|
|
128
|
+
due_at_tz: "UTC",
|
|
129
|
+
});
|
|
130
|
+
const row = await fetchOne<{ dueAtUtc: Temporal.Instant }>(db, meta, { id: ins!.id });
|
|
131
|
+
expect(row!.dueAtUtc.epochMilliseconds).toBe(new Date(iso).getTime());
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|