@cosmicdrift/kumiko-framework 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
- package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
- package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
- package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
- package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
- package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
- package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
- package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
- package/src/api/__tests__/api.test.ts +1 -1
- package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
- package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
- package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/csrf-middleware.test.ts +1 -1
- package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
- package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
- package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
- package/src/api/__tests__/readiness.test.ts +1 -1
- package/src/api/__tests__/request-id-middleware.test.ts +1 -1
- package/src/api/__tests__/sse-broker.test.ts +12 -12
- package/src/api/__tests__/sse-route.test.ts +1 -1
- package/src/api/readiness.ts +2 -2
- package/src/auth/__tests__/roles.test.ts +2 -2
- package/src/bun-db/__tests__/PATTERN.md +73 -0
- package/src/bun-db/__tests__/_helpers.ts +103 -0
- package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
- package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
- package/src/bun-db/__tests__/bun-test-db.ts +19 -0
- package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
- package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
- package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
- package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
- package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
- package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
- package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
- package/src/bun-db/connection.ts +84 -0
- package/src/bun-db/index.ts +31 -0
- package/src/bun-db/query.ts +845 -0
- package/src/compliance/__tests__/duration-spec.test.ts +1 -1
- package/src/compliance/__tests__/profiles.test.ts +1 -1
- package/src/compliance/__tests__/sub-processors.test.ts +1 -1
- package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
- package/src/db/__tests__/big-int-field.test.ts +15 -14
- package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
- package/src/db/__tests__/compound-types.test.ts +1 -1
- package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
- package/src/db/__tests__/connection-options.test.ts +1 -1
- package/src/db/__tests__/dialect-instant.test.ts +1 -1
- package/src/db/__tests__/encryption.test.ts +1 -1
- package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
- package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
- package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
- package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
- package/src/db/__tests__/located-timestamp.test.ts +1 -1
- package/src/db/__tests__/money.test.ts +1 -1
- package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
- package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
- package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
- package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
- package/src/db/__tests__/sql-inventory.test.ts +56 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
- package/src/db/__tests__/table-builder-required.test.ts +20 -22
- package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
- package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
- package/src/db/api.ts +46 -0
- package/src/db/apply-entity-event.ts +45 -36
- package/src/db/assert-exists-in.ts +5 -16
- package/src/db/bun-provider.ts +37 -0
- package/src/db/config-seed.ts +4 -4
- package/src/db/connection.ts +14 -57
- package/src/db/cursor.ts +5 -56
- package/src/db/dialect.ts +472 -99
- package/src/db/eagerload.ts +5 -12
- package/src/db/entity-table-meta.ts +390 -0
- package/src/db/event-store-executor.ts +158 -100
- package/src/db/index.ts +33 -5
- package/src/db/migrate-generator.ts +350 -0
- package/src/db/migrate-runner.ts +206 -0
- package/src/db/postgres-provider.ts +25 -0
- package/src/db/queries/entity-read.ts +15 -0
- package/src/db/queries/es-ops.ts +17 -0
- package/src/db/queries/event-consumer.ts +170 -0
- package/src/db/queries/event-store-admin.ts +127 -0
- package/src/db/queries/event-store.ts +155 -0
- package/src/db/queries/projection-rebuild.ts +59 -0
- package/src/db/queries/raw-sql.ts +15 -0
- package/src/db/queries/schema-drift.ts +35 -0
- package/src/db/queries/seed-context.ts +58 -0
- package/src/db/queries/table-ops.ts +11 -0
- package/src/db/queries/test-stack.ts +56 -0
- package/src/db/query-api.ts +22 -0
- package/src/db/query.ts +30 -0
- package/src/db/reference-data.ts +19 -22
- package/src/db/render-ddl.ts +57 -0
- package/src/db/row-helpers.ts +3 -52
- package/src/db/schema-inspection.ts +17 -4
- package/src/db/sql-inventory.ts +208 -0
- package/src/db/table-builder.ts +48 -40
- package/src/db/tenant-db.ts +105 -326
- package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
- package/src/engine/__tests__/boot-validator.test.ts +4 -3
- package/src/engine/__tests__/build-app-schema.test.ts +1 -1
- package/src/engine/__tests__/build-target.test.ts +1 -1
- package/src/engine/__tests__/claim-keys.test.ts +1 -1
- package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
- package/src/engine/__tests__/config-helpers.test.ts +1 -1
- package/src/engine/__tests__/effective-features.test.ts +1 -1
- package/src/engine/__tests__/engine.test.ts +1 -1
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +3 -3
- package/src/engine/__tests__/extends-registrar.test.ts +4 -4
- package/src/engine/__tests__/factories-long-text.test.ts +1 -1
- package/src/engine/__tests__/factories-time.test.ts +1 -1
- package/src/engine/__tests__/field-predicates.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +1 -1
- package/src/engine/__tests__/identifiers.test.ts +1 -1
- package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
- package/src/engine/__tests__/nav.test.ts +1 -1
- package/src/engine/__tests__/ownership.test.ts +10 -11
- package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
- package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
- package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
- package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +1 -1
- package/src/engine/__tests__/projection-helpers.test.ts +25 -17
- package/src/engine/__tests__/projection.test.ts +4 -4
- package/src/engine/__tests__/qualified-name.test.ts +1 -1
- package/src/engine/__tests__/raw-table.test.ts +9 -8
- package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
- package/src/engine/__tests__/run-in.test.ts +1 -1
- package/src/engine/__tests__/schema-builder.test.ts +1 -1
- package/src/engine/__tests__/screen.test.ts +1 -1
- package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
- package/src/engine/__tests__/state-machine.test.ts +1 -1
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
- package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
- package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
- package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
- package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
- package/src/engine/__tests__/steps-read.test.ts +34 -40
- package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
- package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
- package/src/engine/__tests__/steps-workflow.test.ts +7 -7
- package/src/engine/__tests__/system-user.test.ts +1 -1
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
- package/src/engine/__tests__/validation-hooks.test.ts +1 -1
- package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
- package/src/engine/boot-validator/entity-handler.ts +3 -3
- package/src/engine/boot-validator/ownership.ts +1 -1
- package/src/engine/define-feature.ts +1 -2
- package/src/engine/entity-handlers.ts +5 -5
- package/src/engine/factories.ts +1 -1
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
- package/src/engine/ownership.ts +113 -41
- package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
- package/src/engine/projection-helpers.ts +2 -11
- package/src/engine/registry.ts +2 -2
- package/src/engine/steps/read-find-many.ts +13 -13
- package/src/engine/steps/read-find-one.ts +7 -9
- package/src/engine/steps/unsafe-projection-delete.ts +4 -5
- package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
- package/src/engine/types/feature.ts +7 -2
- package/src/engine/types/fields.ts +4 -5
- package/src/engine/types/step.ts +10 -10
- package/src/engine/validate-projection-allowlist.ts +23 -3
- package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
- package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
- package/src/env/__tests__/compose-env-schema.test.ts +1 -1
- package/src/env/__tests__/dry-run.test.ts +1 -1
- package/src/errors/__tests__/classes.test.ts +1 -1
- package/src/errors/__tests__/write-failures.test.ts +1 -1
- package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
- package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
- package/src/es-ops/__tests__/runner.test.ts +29 -19
- package/src/es-ops/context.ts +9 -43
- package/src/es-ops/operations-schema.ts +2 -2
- package/src/es-ops/runner.ts +12 -26
- package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
- package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
- package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
- package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
- package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
- package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
- package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
- package/src/event-store/admin-api.ts +55 -83
- package/src/event-store/archive.ts +15 -39
- package/src/event-store/event-store.ts +92 -86
- package/src/event-store/events-schema.ts +2 -1
- package/src/event-store/index.ts +1 -0
- package/src/event-store/snapshot.ts +26 -24
- package/src/event-store/upcaster-dead-letter.ts +19 -18
- package/src/files/__tests__/content-disposition.test.ts +1 -1
- package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
- package/src/files/__tests__/file-handle.test.ts +1 -1
- package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
- package/src/files/__tests__/read-stream.test.ts +1 -1
- package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
- package/src/files/__tests__/write-stream.test.ts +1 -1
- package/src/files/__tests__/zip-stream.test.ts +1 -1
- package/src/files/file-ref-table.ts +2 -2
- package/src/files/file-routes.ts +7 -9
- package/src/files/storage-tracking.ts +9 -17
- package/src/i18n/__tests__/i18n.test.ts +1 -1
- package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
- package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
- package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
- package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
- package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
- package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
- package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
- package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
- package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
- package/src/migrations/projection-detection.ts +12 -1
- package/src/migrations/schema-drift.ts +7 -23
- package/src/observability/__tests__/console-provider.test.ts +1 -1
- package/src/observability/__tests__/metric-validator.test.ts +1 -1
- package/src/observability/__tests__/noop-provider.test.ts +1 -1
- package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
- package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-tracer.test.ts +1 -1
- package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
- package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
- package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
- package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
- package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
- package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
- package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
- package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
- package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
- package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
- package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
- package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
- package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
- package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
- package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
- package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
- package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
- package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
- package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
- package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
- package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
- package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
- package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
- package/src/pipeline/cascade-handler.ts +13 -21
- package/src/pipeline/dispatcher.ts +43 -48
- package/src/pipeline/event-consumer-state.ts +11 -2
- package/src/pipeline/event-dispatcher.ts +86 -146
- package/src/pipeline/event-retention.ts +14 -24
- package/src/pipeline/msp-rebuild.ts +54 -78
- package/src/pipeline/projection-rebuild.ts +65 -67
- package/src/pipeline/projection-state.ts +2 -2
- package/src/random/__tests__/generate.test.ts +13 -13
- package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
- package/src/redis/__tests__/redis-options.test.ts +1 -1
- package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
- package/src/search/__tests__/search-adapter.test.ts +1 -1
- package/src/secrets/__tests__/dek-cache.test.ts +1 -3
- package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/__tests__/leak-guard.test.ts +1 -1
- package/src/secrets/__tests__/rotation.test.ts +1 -1
- package/src/stack/db.ts +25 -48
- package/src/stack/push-entity-projection-tables.ts +2 -4
- package/src/stack/table-helpers.ts +98 -61
- package/src/stack/test-stack.ts +8 -7
- package/src/testing/__tests__/db-cleanup.test.ts +40 -0
- package/src/testing/__tests__/e2e-generator.test.ts +1 -1
- package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
- package/src/testing/db-cleanup.ts +44 -0
- package/src/testing/expect-error.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/multipart-helper.ts +94 -0
- package/src/testing/shared-entities.ts +5 -5
- package/src/time/__tests__/polyfill.test.ts +1 -1
- package/src/time/__tests__/tz-context.test.ts +1 -1
- package/src/utils/__tests__/assert.test.ts +1 -1
- package/src/utils/__tests__/env-parse.test.ts +1 -1
- package/CHANGELOG.md +0 -474
- package/src/db/__tests__/cursor.test.ts +0 -41
- package/src/db/__tests__/db-helpers.test.ts +0 -369
- package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
- package/src/db/__tests__/row-helpers.test.ts +0 -59
- package/src/engine/steps/_drizzle-boundary.ts +0 -19
- package/src/files/__tests__/file-field-column.integration.ts +0 -103
package/src/db/api.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// DB-API: Unified Types und Connection-Factory.
|
|
2
|
+
// Provider-agnostic code verwendet ausschliesslich diese Types.
|
|
3
|
+
// Provider-Implementierungen: postgres-provider.ts, bun-provider.ts.
|
|
4
|
+
//
|
|
5
|
+
// `asRawClient(db)` aus bun-db/query.ts normalisiert beide Provider
|
|
6
|
+
// zu { unsafe, begin } — business code ist provider-neutral.
|
|
7
|
+
//
|
|
8
|
+
// Default: postgres-js. Set DB_PROVIDER=bun für Bun.SQL (experimentell).
|
|
9
|
+
|
|
10
|
+
export type DbConnectionOptions = {
|
|
11
|
+
readonly maxConnections?: number;
|
|
12
|
+
readonly idleTimeoutSeconds?: number;
|
|
13
|
+
readonly connectTimeoutSeconds?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Connection-Handle: db für Queries, client für Legacy-Zugriff (LISTEN/NOTIFY-Peer
|
|
17
|
+
// bei Bun.SQL), close für Pool-Shutdown.
|
|
18
|
+
export type DbConnection = {
|
|
19
|
+
/** Provider Connection — Calls gehen über asRawClient() oder direkt. */
|
|
20
|
+
// biome-ignore lint/suspicious/noExplicitAny: cross-provider connection — postgres-js | Bun.SQL
|
|
21
|
+
readonly db: any;
|
|
22
|
+
/** Legacy postgres-js Client (für LISTEN peer) */
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: postgres-js client
|
|
24
|
+
readonly client: any;
|
|
25
|
+
/** Optionaler Bun.SQL LISTEN peer */
|
|
26
|
+
// biome-ignore lint/suspicious/noExplicitAny: postgres-js LISTEN peer
|
|
27
|
+
readonly listenClient?: any;
|
|
28
|
+
/** Pool schliessen */
|
|
29
|
+
close: () => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let _provider: undefined | (() => Promise<typeof import("./postgres-provider")>);
|
|
33
|
+
|
|
34
|
+
export async function createConnection(
|
|
35
|
+
url: string,
|
|
36
|
+
options: DbConnectionOptions = {},
|
|
37
|
+
): Promise<DbConnection> {
|
|
38
|
+
const p = process.env["DB_PROVIDER"];
|
|
39
|
+
if (p === "bun" || p === "bun-sql") {
|
|
40
|
+
const { createBunConnection } = await import("./bun-provider");
|
|
41
|
+
return createBunConnection(url, options);
|
|
42
|
+
}
|
|
43
|
+
// postgres-js: sync, kein async import nötig
|
|
44
|
+
const { createPgConnection } = await import("./postgres-provider");
|
|
45
|
+
return createPgConnection(url, options);
|
|
46
|
+
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
// - "skipped" → Event ist kein Auto-Verb (Domain-Event auf demselben
|
|
48
48
|
// Aggregate). Caller no-op.
|
|
49
49
|
|
|
50
|
-
import {
|
|
50
|
+
import { deleteMany, insertOne, updateMany } from "../db/query";
|
|
51
51
|
import type { EntityDefinition } from "../engine/types";
|
|
52
52
|
import { InternalError } from "../errors";
|
|
53
53
|
import type { StoredEvent } from "../event-store";
|
|
@@ -112,57 +112,65 @@ export async function applyEntityEvent(
|
|
|
112
112
|
message: `applyEntityEvent: payload.tenantId set but invalid (${JSON.stringify(payloadTenantId)}). Tenant-isolation-kritisch: silent fallback auf event.tenantId würde Cross-Tenant-Drift erzeugen.`,
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
const
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
.returning();
|
|
126
|
-
return { kind: "applied", verb, row: (row as DbRow | undefined) ?? null };
|
|
115
|
+
const row = await insertOne<DbRow>(tx, table, {
|
|
116
|
+
...event.payload,
|
|
117
|
+
tenantId,
|
|
118
|
+
id: event.aggregateId,
|
|
119
|
+
version: event.version,
|
|
120
|
+
insertedAt: event.createdAt,
|
|
121
|
+
insertedById: event.createdBy,
|
|
122
|
+
});
|
|
123
|
+
return { kind: "applied", verb, row: row ?? null };
|
|
127
124
|
}
|
|
128
125
|
|
|
129
126
|
case "updated": {
|
|
130
127
|
// payload-Shape: { changes, previous } — siehe event-store-executor.ts.
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
const rawChanges = (event.payload["changes"] ?? {}) as Record<string, unknown>; // @cast-boundary engine-payload
|
|
129
|
+
// Plural file fields (files/images) have no entity-table column — the
|
|
130
|
+
// array of UUIDs lives in the event payload only. Strip them from the
|
|
131
|
+
// UPDATE so Postgres doesn't error with "column X does not exist".
|
|
132
|
+
const changes: Record<string, unknown> = {};
|
|
133
|
+
for (const [key, value] of Object.entries(rawChanges)) {
|
|
134
|
+
const fieldType = entity.fields[key]?.type;
|
|
135
|
+
if (fieldType === "files" || fieldType === "images") continue;
|
|
136
|
+
changes[key] = value;
|
|
137
|
+
}
|
|
138
|
+
const rows = await updateMany<DbRow>(
|
|
139
|
+
tx,
|
|
140
|
+
table,
|
|
141
|
+
{
|
|
135
142
|
...changes,
|
|
136
143
|
version: event.version,
|
|
137
144
|
modifiedAt: event.createdAt,
|
|
138
145
|
modifiedById: event.createdBy,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { kind: "applied", verb, row:
|
|
146
|
+
},
|
|
147
|
+
{ id: event.aggregateId },
|
|
148
|
+
);
|
|
149
|
+
return { kind: "applied", verb, row: rows[0] ?? null };
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
case "deleted": {
|
|
146
153
|
if (softDelete) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const rows = await updateMany<DbRow>(
|
|
155
|
+
tx,
|
|
156
|
+
table,
|
|
157
|
+
{
|
|
150
158
|
isDeleted: true,
|
|
151
159
|
deletedAt: event.createdAt,
|
|
152
160
|
deletedById: event.createdBy,
|
|
153
161
|
version: event.version,
|
|
154
162
|
modifiedAt: event.createdAt,
|
|
155
163
|
modifiedById: event.createdBy,
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return { kind: "applied", verb, row:
|
|
164
|
+
},
|
|
165
|
+
{ id: event.aggregateId },
|
|
166
|
+
);
|
|
167
|
+
return { kind: "applied", verb, row: rows[0] ?? null };
|
|
160
168
|
}
|
|
161
169
|
// Hard-Delete: DELETE-Statement gibt keine returning-Row her und
|
|
162
170
|
// der Live-Pfad nutzt eh `existing` (pre-delete-Snapshot) für die
|
|
163
171
|
// Response. Beim Replay ist das fine, der Caller braucht die Row
|
|
164
172
|
// nicht weiter.
|
|
165
|
-
await tx
|
|
173
|
+
await deleteMany(tx, table, { id: event.aggregateId });
|
|
166
174
|
return { kind: "applied", verb, row: null };
|
|
167
175
|
}
|
|
168
176
|
|
|
@@ -170,19 +178,20 @@ export async function applyEntityEvent(
|
|
|
170
178
|
// Restore ist nur bei softDelete sinnvoll. Hard-Delete-Entities sollten
|
|
171
179
|
// keine restored-Events erhalten — falls doch, defensive skip.
|
|
172
180
|
if (!softDelete) return { kind: "skipped" };
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
const rows = await updateMany<DbRow>(
|
|
182
|
+
tx,
|
|
183
|
+
table,
|
|
184
|
+
{
|
|
176
185
|
isDeleted: false,
|
|
177
186
|
deletedAt: null,
|
|
178
187
|
deletedById: null,
|
|
179
188
|
version: event.version,
|
|
180
189
|
modifiedAt: event.createdAt,
|
|
181
190
|
modifiedById: event.createdBy,
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return { kind: "applied", verb, row:
|
|
191
|
+
},
|
|
192
|
+
{ id: event.aggregateId },
|
|
193
|
+
);
|
|
194
|
+
return { kind: "applied", verb, row: rows[0] ?? null };
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchOne } from "../db/query";
|
|
2
2
|
import type { TenantId } from "../engine/types/identifiers";
|
|
3
3
|
import { NotFoundError } from "../errors";
|
|
4
4
|
import type { DbConnection } from "./connection";
|
|
@@ -28,22 +28,11 @@ export async function assertExistsIn(
|
|
|
28
28
|
entityName?: string;
|
|
29
29
|
},
|
|
30
30
|
): Promise<NotFoundError | null> {
|
|
31
|
-
const
|
|
31
|
+
const where: Record<string, unknown> = { [options.field]: options.value };
|
|
32
|
+
if (options.tenantId !== undefined) where["tenantId"] = options.tenantId;
|
|
33
|
+
if (options.where) Object.assign(where, options.where);
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
conditions.push(eq(entity["tenantId"], options.tenantId));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (options.where) {
|
|
38
|
-
for (const [key, val] of Object.entries(options.where)) {
|
|
39
|
-
conditions.push(eq(entity[key], val));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const [row] = await db
|
|
44
|
-
.select()
|
|
45
|
-
.from(entity)
|
|
46
|
-
.where(and(...conditions) as SQL); // @cast-boundary db-operator
|
|
35
|
+
const row = await fetchOne(db, entity, where);
|
|
47
36
|
|
|
48
37
|
if (!row) {
|
|
49
38
|
const entityName = options.entityName ?? String(options.field).replace(/Id$/, "");
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Bun.SQL Provider — experimentell.
|
|
2
|
+
// Default: postgres-js. Dieser Provider nur aktiv wenn DB_PROVIDER=bun.
|
|
3
|
+
// Bekannter Bug: Extended-Query-Protocol Caching verursacht
|
|
4
|
+
// PostgresError "bind message has 11 result formats but query has 1 columns"
|
|
5
|
+
// bei sequentiellen Queries mit unterschiedlicher Spaltenzahl innerhalb
|
|
6
|
+
// derselben Connection.
|
|
7
|
+
//
|
|
8
|
+
// Bun.SQL hat kein LISTEN — postgres-js-Peer für event-dispatcher.
|
|
9
|
+
|
|
10
|
+
import postgres from "postgres";
|
|
11
|
+
import type { DbConnection, DbConnectionOptions } from "./api";
|
|
12
|
+
|
|
13
|
+
export function createBunConnection(url: string, options: DbConnectionOptions = {}): DbConnection {
|
|
14
|
+
const bunOpts: { max?: number; idleTimeout?: number; connectionTimeout?: number } = {};
|
|
15
|
+
if (options.maxConnections !== undefined) bunOpts.max = options.maxConnections;
|
|
16
|
+
if (options.idleTimeoutSeconds !== undefined) bunOpts.idleTimeout = options.idleTimeoutSeconds;
|
|
17
|
+
if (options.connectTimeoutSeconds !== undefined)
|
|
18
|
+
bunOpts.connectionTimeout = options.connectTimeoutSeconds;
|
|
19
|
+
const db = new Bun.SQL(url, bunOpts);
|
|
20
|
+
|
|
21
|
+
// LISTEN peer — 1 connection reicht für NOTIFY-Wakeups
|
|
22
|
+
const pgOpts: Parameters<typeof postgres>[1] = { max: 1 };
|
|
23
|
+
if (options.idleTimeoutSeconds !== undefined) pgOpts.idle_timeout = options.idleTimeoutSeconds;
|
|
24
|
+
if (options.connectTimeoutSeconds !== undefined)
|
|
25
|
+
pgOpts.connect_timeout = options.connectTimeoutSeconds;
|
|
26
|
+
const listenClient = postgres(url, pgOpts);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
db,
|
|
30
|
+
client: listenClient,
|
|
31
|
+
listenClient,
|
|
32
|
+
close: async () => {
|
|
33
|
+
await db.end();
|
|
34
|
+
await listenClient.end();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
package/src/db/config-seed.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { ConfigSeedDef, Registry } from "../engine/types";
|
|
|
5
5
|
import type { DbConnection } from "./connection";
|
|
6
6
|
import type { EncryptionProvider } from "./encryption";
|
|
7
7
|
import { createEventStoreExecutor } from "./event-store-executor";
|
|
8
|
-
import type {
|
|
8
|
+
import type { EntityTable } from "./table-builder";
|
|
9
9
|
import { createTenantDb } from "./tenant-db";
|
|
10
10
|
|
|
11
11
|
// Namespace UUID for deterministic seed aggregate IDs. Same namespace +
|
|
@@ -24,10 +24,10 @@ const CONFIG_SEED_NS = "6f1e9d8c-2a5b-4c7d-9e3f-1a2b3c4d5e6f";
|
|
|
24
24
|
* Idempotent, race-safe via DB-level unique constraints, and visible to
|
|
25
25
|
* multi-stream-projection subscribers as normal configValue.created events.
|
|
26
26
|
*/
|
|
27
|
-
export async function seedConfigValues(
|
|
27
|
+
export async function seedConfigValues<E extends EntityDefinition>(
|
|
28
28
|
seeds: readonly ConfigSeedDef[],
|
|
29
|
-
table:
|
|
30
|
-
entity:
|
|
29
|
+
table: EntityTable<E>,
|
|
30
|
+
entity: E,
|
|
31
31
|
registry: Registry,
|
|
32
32
|
db: DbConnection,
|
|
33
33
|
encryption?: EncryptionProvider,
|
package/src/db/connection.ts
CHANGED
|
@@ -1,67 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
// DB-Connection-Types: provider-agnostic via db/api.ts.
|
|
2
|
+
// createConnection delegiert an postgres-provider (default) oder bun-provider (DB_PROVIDER=bun).
|
|
2
3
|
import postgres from "postgres";
|
|
3
4
|
import { readPositiveIntEnv } from "../utils/env-parse";
|
|
4
5
|
|
|
5
|
-
export type
|
|
6
|
+
export { createConnection, type DbConnectionOptions } from "./api";
|
|
6
7
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Code paths that operate on either a connection or an active transaction
|
|
13
|
-
// (e.g. TenantDb, dispatcher pipeline) accept both.
|
|
8
|
+
// Legacy Types — für Aufrufer die direkt diese Module importieren
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: Bun.SQL global type
|
|
10
|
+
export type DbConnection = ReturnType<typeof postgres> | any;
|
|
11
|
+
// biome-ignore lint/suspicious/noExplicitAny: postgres-js namespace lookup
|
|
12
|
+
export type DbTx = postgres.TransactionSql<any> | any;
|
|
14
13
|
export type DbRunner = DbConnection | DbTx;
|
|
15
|
-
|
|
16
|
-
// Dynamic Drizzle tables (buildDrizzleTable with `any` column schema) lose
|
|
17
|
-
// their per-column types at the Drizzle boundary. Query results come back as
|
|
18
|
-
// arbitrary records. `DbRow` marks those typing-loss sites so readers see the
|
|
19
|
-
// limitation without re-spelling `Record<string, unknown>` at every callsite.
|
|
20
|
-
// Use `DbRow` for rows read via dynamic tables; a concrete entity-row type
|
|
21
|
-
// is preferred whenever the table is statically typed.
|
|
22
14
|
export type DbRow = Record<string, unknown>;
|
|
23
|
-
|
|
24
|
-
// The raw postgres.js client. Exposed alongside the Drizzle wrapper so the
|
|
25
|
-
// event-dispatcher (or other components that need LISTEN / pg-specific
|
|
26
|
-
// features Drizzle doesn't surface) can subscribe without re-opening a
|
|
27
|
-
// connection from the URL.
|
|
28
15
|
export type PgClient = ReturnType<typeof postgres>;
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
// framework explicitly supports. Omitted keys fall back to postgres.js
|
|
32
|
-
// defaults (max=10, idle_timeout=PGIDLE_TIMEOUT env, connect_timeout=
|
|
33
|
-
// PGCONNECT_TIMEOUT env). See `docs/plans/architecture/scaling.md` for
|
|
34
|
-
// sizing guidance per deployment shape.
|
|
35
|
-
export type DbConnectionOptions = {
|
|
36
|
-
// Max concurrent connections in the pool. postgres.js defaults to 10 —
|
|
37
|
-
// fine for a single app process against a small DB. Multi-worker or
|
|
38
|
-
// high-concurrency API deploys should scale this with `num_workers *
|
|
39
|
-
// per-request-concurrency` and stay below the DB's own max_connections
|
|
40
|
-
// (typical managed postgres: 100–400).
|
|
41
|
-
readonly maxConnections?: number;
|
|
42
|
-
// Seconds before an idle connection is closed. Null/undefined → keep
|
|
43
|
-
// connections warm forever (postgres.js default when the env var is
|
|
44
|
-
// unset). Managed pgBouncer tiers usually want this explicitly set to
|
|
45
|
-
// something like 30–60 so a single burst doesn't hold connections
|
|
46
|
-
// indefinitely.
|
|
47
|
-
readonly idleTimeoutSeconds?: number;
|
|
48
|
-
// Seconds to wait while establishing a new connection. Fails the query
|
|
49
|
-
// with a timeout error rather than hanging indefinitely when the DB is
|
|
50
|
-
// unreachable — critical for `/health/ready` to actually flip to 503
|
|
51
|
-
// within its 2s probe budget.
|
|
52
|
-
readonly connectTimeoutSeconds?: number;
|
|
53
|
-
};
|
|
17
|
+
export type PgListenClient = ReturnType<typeof postgres>;
|
|
54
18
|
|
|
19
|
+
// Legacy: postgres-js only. Neue Aufrufer: createConnection() aus api.ts.
|
|
55
20
|
export function createDbConnection(
|
|
56
21
|
url: string,
|
|
57
|
-
options: DbConnectionOptions = {},
|
|
22
|
+
options: import("./api").DbConnectionOptions = {},
|
|
58
23
|
): {
|
|
59
24
|
db: DbConnection;
|
|
60
25
|
client: PgClient;
|
|
61
26
|
close: () => Promise<void>;
|
|
62
27
|
} {
|
|
63
|
-
// Only forward fields the caller set — empty object otherwise preserves
|
|
64
|
-
// postgres.js's env-var-driven defaults (PGIDLE_TIMEOUT / PGCONNECT_TIMEOUT).
|
|
65
28
|
const pgOptions: Parameters<typeof postgres>[1] = {};
|
|
66
29
|
if (options.maxConnections !== undefined) pgOptions.max = options.maxConnections;
|
|
67
30
|
if (options.idleTimeoutSeconds !== undefined) pgOptions.idle_timeout = options.idleTimeoutSeconds;
|
|
@@ -70,10 +33,9 @@ export function createDbConnection(
|
|
|
70
33
|
}
|
|
71
34
|
|
|
72
35
|
const client = postgres(url, pgOptions);
|
|
73
|
-
const db = drizzle(client);
|
|
74
36
|
|
|
75
37
|
return {
|
|
76
|
-
db,
|
|
38
|
+
db: client,
|
|
77
39
|
client,
|
|
78
40
|
close: async () => {
|
|
79
41
|
await client.end();
|
|
@@ -81,15 +43,10 @@ export function createDbConnection(
|
|
|
81
43
|
};
|
|
82
44
|
}
|
|
83
45
|
|
|
84
|
-
// Parse the supported env vars into a DbConnectionOptions object. Useful
|
|
85
|
-
// for a main.ts that wants to read DATABASE_POOL_MAX / DATABASE_POOL_
|
|
86
|
-
// IDLE_TIMEOUT / DATABASE_POOL_CONNECT_TIMEOUT without re-implementing
|
|
87
|
-
// the number-coercion + validation. Unrecognised / non-numeric values
|
|
88
|
-
// throw — misconfig surfaces at boot, not mid-request.
|
|
89
46
|
export function dbConnectionOptionsFromEnv(
|
|
90
47
|
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
91
|
-
): DbConnectionOptions {
|
|
92
|
-
const opts: DbConnectionOptions & {
|
|
48
|
+
): import("./api").DbConnectionOptions {
|
|
49
|
+
const opts: import("./api").DbConnectionOptions & {
|
|
93
50
|
maxConnections?: number;
|
|
94
51
|
idleTimeoutSeconds?: number;
|
|
95
52
|
connectTimeoutSeconds?: number;
|
package/src/db/cursor.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { and, asc, desc, eq, gt, inArray, type SQL, sql } from "drizzle-orm";
|
|
2
1
|
import type { EntityId, TenantId } from "../engine/types/identifiers";
|
|
3
|
-
import type { SelectQuery as PgSelect } from "./dialect";
|
|
4
2
|
|
|
5
3
|
export type CursorQueryOptions = {
|
|
6
4
|
tenantId: TenantId;
|
|
@@ -9,24 +7,20 @@ export type CursorQueryOptions = {
|
|
|
9
7
|
filterIds?: readonly EntityId[];
|
|
10
8
|
sort?: string;
|
|
11
9
|
sortDirection?: "asc" | "desc";
|
|
12
|
-
extraWhere?: SQL;
|
|
13
10
|
};
|
|
14
11
|
|
|
15
12
|
export type CursorResult<T> = {
|
|
16
13
|
rows: T[];
|
|
17
14
|
nextCursor: string | null;
|
|
18
15
|
/** Optional total row count — nur present wenn der Caller `totalCount: true`
|
|
19
|
-
* in der Query setzt.
|
|
20
|
-
* Scroll und Default-Lists lassen den extra COUNT(*) weg. */
|
|
16
|
+
* in der Query setzt. */
|
|
21
17
|
total?: number;
|
|
22
18
|
};
|
|
23
19
|
|
|
24
|
-
// String-basiert damit
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// (time-ordered Prefix); UUIDv4 nicht — wer den nutzt, kriegt
|
|
29
|
-
// inkorrekte cursor-Reihenfolge, das ist erwartet (Default ist v7).
|
|
20
|
+
// String-basiert damit UUIDs (Default seit Sprint F) + Integer-Auto-Increment
|
|
21
|
+
// durch denselben Cursor-Pfad laufen. UUIDv7 erfüllt die lex-Monotonie
|
|
22
|
+
// (time-ordered Prefix); UUIDv4 nicht — wer den nutzt, kriegt inkorrekte
|
|
23
|
+
// cursor-Reihenfolge (Default ist v7).
|
|
30
24
|
export function encodeCursor(id: string | number): string {
|
|
31
25
|
return Buffer.from(String(id)).toString("base64url");
|
|
32
26
|
}
|
|
@@ -36,48 +30,3 @@ export function decodeCursor(cursor: string): string {
|
|
|
36
30
|
if (decoded === "") throw new Error(`Invalid cursor: ${cursor}`);
|
|
37
31
|
return decoded;
|
|
38
32
|
}
|
|
39
|
-
|
|
40
|
-
export function applyCursorQuery<T extends PgSelect>(
|
|
41
|
-
query: T,
|
|
42
|
-
// biome-ignore lint/suspicious/noExplicitAny: Drizzle dynamic tables lose column types
|
|
43
|
-
table: any,
|
|
44
|
-
options: CursorQueryOptions,
|
|
45
|
-
): T {
|
|
46
|
-
const conditions: SQL[] = [eq(table.tenantId, options.tenantId)];
|
|
47
|
-
|
|
48
|
-
if (table.isDeleted) {
|
|
49
|
-
conditions.push(eq(table.isDeleted, false));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (options.cursor) {
|
|
53
|
-
conditions.push(gt(table.id, decodeCursor(options.cursor)));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (options.filterIds !== undefined) {
|
|
57
|
-
if (options.filterIds.length === 0) {
|
|
58
|
-
// No matching IDs — return empty result via raw `false`. Statisch
|
|
59
|
-
// false ist type-agnostisch (int-PK / uuid-PK egal); ein eq(id, "")
|
|
60
|
-
// oder eq(id, -1) würde je nach Spalten-Type einen Cast-Error
|
|
61
|
-
// werfen.
|
|
62
|
-
conditions.push(sql`false`);
|
|
63
|
-
} else {
|
|
64
|
-
conditions.push(inArray(table.id, options.filterIds as readonly string[])); // @cast-boundary db-operator
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (options.extraWhere) {
|
|
69
|
-
conditions.push(options.extraWhere);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const limit = options.limit ?? 50;
|
|
73
|
-
|
|
74
|
-
let result = query.where(and(...conditions)).limit(limit);
|
|
75
|
-
|
|
76
|
-
if (options.sort && table[options.sort]) {
|
|
77
|
-
const column = table[options.sort];
|
|
78
|
-
result =
|
|
79
|
-
options.sortDirection === "desc" ? result.orderBy(desc(column)) : result.orderBy(asc(column));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result as T; // @cast-boundary engine-bridge
|
|
83
|
-
}
|