@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
package/src/db/dialect.ts
CHANGED
|
@@ -1,89 +1,477 @@
|
|
|
1
|
-
//
|
|
2
|
-
// No other file in the framework should import from "drizzle-orm/pg-core" directly.
|
|
3
|
-
|
|
4
|
-
import { customType } from "drizzle-orm/pg-core";
|
|
5
|
-
|
|
6
|
-
export type {
|
|
7
|
-
PgSelect as SelectQuery,
|
|
8
|
-
PgTableWithColumns as TableColumns,
|
|
9
|
-
} from "drizzle-orm/pg-core";
|
|
10
|
-
export {
|
|
11
|
-
bigint,
|
|
12
|
-
bigserial,
|
|
13
|
-
boolean,
|
|
14
|
-
index,
|
|
15
|
-
integer,
|
|
16
|
-
jsonb,
|
|
17
|
-
numeric,
|
|
18
|
-
pgTable as table,
|
|
19
|
-
primaryKey,
|
|
20
|
-
serial,
|
|
21
|
-
text,
|
|
22
|
-
timestamp,
|
|
23
|
-
uniqueIndex,
|
|
24
|
-
uuid,
|
|
25
|
-
} from "drizzle-orm/pg-core";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Money column: BIGINT storing the integer minor unit (cents for EUR, yen
|
|
29
|
-
* for JPY). Range ±9.2e18 in the DB; JS `number` is safe to 2^53 ≈ 9e15
|
|
30
|
-
* minor units = ~90 trillion EUR. INTEGER would cap at ~21 million EUR per
|
|
31
|
-
* row which is too tight for real-world invoices, bank balances, or bills
|
|
32
|
-
* of sale — hence bigint.
|
|
33
|
-
*/
|
|
34
|
-
export const moneyAmount = customType<{ data: number; driverData: string | number }>({
|
|
35
|
-
dataType() {
|
|
36
|
-
return "bigint";
|
|
37
|
-
},
|
|
38
|
-
fromDriver(value: string | number): number {
|
|
39
|
-
// node-postgres returns BIGINT as string by default; Bun's pg returns
|
|
40
|
-
// number. Cast via Number() either way — safe because we stay under 2^53.
|
|
41
|
-
return typeof value === "number" ? value : Number(value);
|
|
42
|
-
},
|
|
43
|
-
toDriver(value: number): number {
|
|
44
|
-
return value;
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Instant column: TIMESTAMPTZ storing a UTC instant, surfaced to JS as
|
|
50
|
-
* `Temporal.Instant`. Replaces the old dual-mode situation (`mode:"date"`
|
|
51
|
-
* for base fields vs `mode:"string"` for user-defined timestamp fields)
|
|
52
|
-
* with a single round-trip type. See sprint-f-temporal.md for the migration.
|
|
53
|
-
*
|
|
54
|
-
* Driver-data is the ISO-8601 string the postgres driver actually exchanges.
|
|
55
|
-
* `fromDriver` reads what the driver returns (postgres-js gives strings for
|
|
56
|
-
* timestamptz) and parses through Temporal. `toDriver` writes the canonical
|
|
57
|
-
* Temporal.Instant.toString() — the spike confirmed `eq/lte/gt/orderBy/
|
|
58
|
-
* returning` accept Temporal.Instant directly without manual `.toString()`
|
|
59
|
-
* at the call site.
|
|
60
|
-
*
|
|
61
|
-
* Boot-order note: `Temporal` must exist on globalThis before any
|
|
62
|
-
* fromDriver/toDriver call. `ensureTemporalPolyfill()` runs at framework
|
|
63
|
-
* boot. The closures here are lazy — they fire on read/write, not on
|
|
64
|
-
* module load — so importing this file before the polyfill is safe.
|
|
65
|
-
*
|
|
66
|
-
* Optional `precision` (0..6) — fractional-second digits. Default 6 matches
|
|
67
|
-
* PG's default `timestamptz`. Pass 3 for the events-table (ms precision —
|
|
68
|
-
* matches what asOf-queries can compare reliably). Affects only CREATE TABLE
|
|
69
|
-
* DDL via drizzle-kit; runtime parse handles any precision via Temporal.
|
|
70
|
-
*/
|
|
71
|
-
// Forgiving overload: payloads from custom write-handlers sometimes
|
|
72
|
-
// arrive as ISO strings rather than Temporal.Instant (Zod insert-
|
|
73
|
-
// schemas use z.iso.datetime, not a Temporal validator). Coerce here
|
|
74
|
-
// at the boundary so the DB always sees a normalised string — and
|
|
75
|
-
// Temporal.Instant.from throws on bad input, which is the right failure
|
|
76
|
-
// mode (vs. the obscure "x.toString is not a function" crash that hit
|
|
77
|
-
// feature authors before this overload existed).
|
|
1
|
+
// Native dialect — replaces drizzle-orm/pg-core re-exports.
|
|
78
2
|
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
3
|
+
// Produces table objects that simultaneously expose:
|
|
4
|
+
// 1. EntityTableMeta shape (source/columns/indexes) for bun-db's
|
|
5
|
+
// extractTableInfo + the migrate-runner's renderTableDdl
|
|
6
|
+
// 2. drizzle-compatible Symbol metadata so callers that introspect
|
|
7
|
+
// Symbol.for("kumiko:schema:Name") / Symbol.for("kumiko:schema:Columns") keep
|
|
8
|
+
// working (no caller updates needed)
|
|
9
|
+
// 3. Top-level column-handle properties (table.id, table.tenantId, ...)
|
|
10
|
+
// so legacy code that does `table[field].name` still resolves to the
|
|
11
|
+
// snake_case SQL column name.
|
|
85
12
|
//
|
|
86
|
-
//
|
|
13
|
+
// The framework no longer imports drizzle-orm at runtime — schema-files
|
|
14
|
+
// use only this module.
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
ColumnMeta,
|
|
18
|
+
CompositePrimaryKeyMeta,
|
|
19
|
+
EntityTableMeta,
|
|
20
|
+
IndexMeta,
|
|
21
|
+
PgType,
|
|
22
|
+
} from "./entity-table-meta";
|
|
23
|
+
|
|
24
|
+
// Public type aliases — historical compat for callers that used to import
|
|
25
|
+
// these from drizzle-orm/pg-core. SelectQuery is no longer a meaningful
|
|
26
|
+
// shape (no chain builder); TableColumns is the new SchemaTable union.
|
|
27
|
+
// biome-ignore lint/suspicious/noExplicitAny: variadic table shape
|
|
28
|
+
export type TableColumns<_T = any> = SchemaTable;
|
|
29
|
+
// biome-ignore lint/suspicious/noExplicitAny: legacy type — chain API is gone
|
|
30
|
+
export type SelectQuery = any;
|
|
31
|
+
|
|
32
|
+
// Column handle exposed on the SchemaTable. The `name` is the SQL column
|
|
33
|
+
// name (snake_case); legacy code accesses `table.fieldName.name` to
|
|
34
|
+
// produce raw SQL.
|
|
35
|
+
export type ColumnHandle = {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly pgType: PgType;
|
|
38
|
+
readonly getSQLType: () => string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
|
|
42
|
+
const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
|
|
43
|
+
|
|
44
|
+
// SchemaTable — opaque shape with both EntityTableMeta + Symbol-based
|
|
45
|
+
// introspection. Returned by `table(...)`.
|
|
46
|
+
export type SchemaTable = EntityTableMeta & {
|
|
47
|
+
readonly [KUMIKO_NAME_SYMBOL]: string;
|
|
48
|
+
readonly [KUMIKO_COLUMNS_SYMBOL]: Record<string, ColumnHandle>;
|
|
49
|
+
readonly [field: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function pgTypeToSqlType(pgType: PgType): string {
|
|
53
|
+
switch (pgType) {
|
|
54
|
+
case "uuid":
|
|
55
|
+
return "uuid";
|
|
56
|
+
case "text":
|
|
57
|
+
return "text";
|
|
58
|
+
case "boolean":
|
|
59
|
+
return "boolean";
|
|
60
|
+
case "integer":
|
|
61
|
+
return "integer";
|
|
62
|
+
case "bigint":
|
|
63
|
+
return "bigint";
|
|
64
|
+
case "serial":
|
|
65
|
+
return "serial";
|
|
66
|
+
case "bigserial":
|
|
67
|
+
return "bigserial";
|
|
68
|
+
case "jsonb":
|
|
69
|
+
return "jsonb";
|
|
70
|
+
case "timestamptz":
|
|
71
|
+
return "timestamp with time zone";
|
|
72
|
+
case "timestamptz(3)":
|
|
73
|
+
return "timestamp(3) with time zone";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _toSnakeCase(name: string): string {
|
|
78
|
+
return name.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- Column builder ----
|
|
82
|
+
//
|
|
83
|
+
// Returned by uuid()/text()/etc. Chainable: notNull/primaryKey/default/unique.
|
|
84
|
+
// Internal state captured in the builder; finalised when handed to table().
|
|
85
|
+
// `name` is set explicitly on the first call (uuid("user_id")) and may be
|
|
86
|
+
// overridden by `withCamel(jsField)` so the handle exposes both the SQL name
|
|
87
|
+
// AND the JS field-name for type inference.
|
|
88
|
+
|
|
89
|
+
type ColumnFinal = {
|
|
90
|
+
readonly sqlName: string;
|
|
91
|
+
readonly pgType: PgType;
|
|
92
|
+
readonly notNull: boolean;
|
|
93
|
+
readonly primaryKey: boolean;
|
|
94
|
+
readonly unique: boolean;
|
|
95
|
+
readonly identity: boolean;
|
|
96
|
+
readonly defaultSql?: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type ColumnBuilder<TValue = unknown> = {
|
|
100
|
+
readonly __column: true;
|
|
101
|
+
readonly finalise: () => ColumnFinal;
|
|
102
|
+
notNull(): ColumnBuilder<TValue>;
|
|
103
|
+
primaryKey(): ColumnBuilder<TValue>;
|
|
104
|
+
default(
|
|
105
|
+
value: TValue | SqlExpression | readonly unknown[] | number | string | boolean | null,
|
|
106
|
+
): ColumnBuilder<TValue>;
|
|
107
|
+
defaultRandom(): ColumnBuilder<TValue>;
|
|
108
|
+
defaultNow(): ColumnBuilder<TValue>;
|
|
109
|
+
generatedAlwaysAsIdentity(): ColumnBuilder<TValue>;
|
|
110
|
+
unique(name?: string): ColumnBuilder<TValue>;
|
|
111
|
+
$type<T>(): ColumnBuilder<T>;
|
|
112
|
+
$onUpdate(fn: () => unknown): ColumnBuilder<TValue>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function buildColumn(sqlName: string, pgType: PgType): ColumnBuilder<unknown> {
|
|
116
|
+
let notNull = false;
|
|
117
|
+
let primaryKey = false;
|
|
118
|
+
let unique = false;
|
|
119
|
+
let identity = false;
|
|
120
|
+
let defaultSql: string | undefined;
|
|
121
|
+
|
|
122
|
+
function literalDefault(value: unknown): string | null {
|
|
123
|
+
if (value === undefined) return null;
|
|
124
|
+
if (value === null) return "NULL";
|
|
125
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
126
|
+
if (typeof value === "number") return String(value);
|
|
127
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
128
|
+
if (typeof value === "bigint") return value.toString();
|
|
129
|
+
if (
|
|
130
|
+
value &&
|
|
131
|
+
typeof value === "object" &&
|
|
132
|
+
"kind" in value &&
|
|
133
|
+
(value as { kind: string }).kind === "sql-expr"
|
|
134
|
+
) {
|
|
135
|
+
return (value as SqlExpression).text;
|
|
136
|
+
}
|
|
137
|
+
if (typeof value === "function") return null; // function-defaults stay JS-side
|
|
138
|
+
// Object/array → jsonb literal
|
|
139
|
+
const serialised = JSON.stringify(value);
|
|
140
|
+
if (serialised === undefined) return null;
|
|
141
|
+
return `'${serialised.replace(/'/g, "''")}'::jsonb`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const builder: ColumnBuilder<unknown> = {
|
|
145
|
+
__column: true,
|
|
146
|
+
finalise(): ColumnFinal {
|
|
147
|
+
return {
|
|
148
|
+
sqlName,
|
|
149
|
+
pgType,
|
|
150
|
+
notNull,
|
|
151
|
+
primaryKey,
|
|
152
|
+
unique,
|
|
153
|
+
identity,
|
|
154
|
+
...(defaultSql !== undefined && { defaultSql }),
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
notNull() {
|
|
158
|
+
notNull = true;
|
|
159
|
+
return builder;
|
|
160
|
+
},
|
|
161
|
+
primaryKey() {
|
|
162
|
+
primaryKey = true;
|
|
163
|
+
notNull = true;
|
|
164
|
+
return builder;
|
|
165
|
+
},
|
|
166
|
+
default(value: unknown) {
|
|
167
|
+
const rendered = literalDefault(value);
|
|
168
|
+
defaultSql = rendered === null ? undefined : rendered;
|
|
169
|
+
return builder;
|
|
170
|
+
},
|
|
171
|
+
defaultRandom() {
|
|
172
|
+
defaultSql = "gen_random_uuid()";
|
|
173
|
+
return builder;
|
|
174
|
+
},
|
|
175
|
+
defaultNow() {
|
|
176
|
+
defaultSql = "now()";
|
|
177
|
+
return builder;
|
|
178
|
+
},
|
|
179
|
+
generatedAlwaysAsIdentity() {
|
|
180
|
+
identity = true;
|
|
181
|
+
defaultSql = undefined;
|
|
182
|
+
return builder;
|
|
183
|
+
},
|
|
184
|
+
unique(_name?: string) {
|
|
185
|
+
unique = true;
|
|
186
|
+
return builder;
|
|
187
|
+
},
|
|
188
|
+
$type<T>() {
|
|
189
|
+
return builder as unknown as ColumnBuilder<T>;
|
|
190
|
+
},
|
|
191
|
+
$onUpdate(_fn: () => unknown) {
|
|
192
|
+
// Runtime $onUpdate is a no-op in the schema layer — the framework's
|
|
193
|
+
// event-driven projection write path sets modified_at explicitly.
|
|
194
|
+
return builder;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
return builder;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- Column factories ----
|
|
201
|
+
|
|
202
|
+
export function uuid(name: string): ColumnBuilder<string> {
|
|
203
|
+
return buildColumn(name, "uuid") as ColumnBuilder<string>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function text(name: string): ColumnBuilder<string> {
|
|
207
|
+
return buildColumn(name, "text") as ColumnBuilder<string>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function boolean(name: string): ColumnBuilder<boolean> {
|
|
211
|
+
return buildColumn(name, "boolean") as ColumnBuilder<boolean>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function integer(name: string): ColumnBuilder<number> {
|
|
215
|
+
return buildColumn(name, "integer") as ColumnBuilder<number>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function serial(name: string): ColumnBuilder<number> {
|
|
219
|
+
return buildColumn(name, "serial") as ColumnBuilder<number>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function bigint(
|
|
223
|
+
name: string,
|
|
224
|
+
_opts?: { mode?: "bigint" | "number" },
|
|
225
|
+
): ColumnBuilder<bigint> {
|
|
226
|
+
return buildColumn(name, "bigint") as ColumnBuilder<bigint>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function bigserial(
|
|
230
|
+
name: string,
|
|
231
|
+
_opts?: { mode?: "bigint" | "number" },
|
|
232
|
+
): ColumnBuilder<bigint> {
|
|
233
|
+
return buildColumn(name, "bigserial") as ColumnBuilder<bigint>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function jsonb(name: string): ColumnBuilder<Record<string, unknown>> {
|
|
237
|
+
return buildColumn(name, "jsonb") as ColumnBuilder<Record<string, unknown>>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Legacy alias kept for compat — timestamptz with no precision.
|
|
241
|
+
export function timestamp(
|
|
242
|
+
name: string,
|
|
243
|
+
_opts?: { withTimezone?: boolean; mode?: "string" | "date" },
|
|
244
|
+
): ColumnBuilder<Temporal.Instant | string> {
|
|
245
|
+
return buildColumn(name, "timestamptz") as ColumnBuilder<Temporal.Instant | string>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// numeric → text (we don't currently use Decimal); kept for compat with
|
|
249
|
+
// legacy schema imports that won't actually instantiate at runtime.
|
|
250
|
+
export function numeric(
|
|
251
|
+
name: string,
|
|
252
|
+
_opts?: { precision?: number; scale?: number },
|
|
253
|
+
): ColumnBuilder<string> {
|
|
254
|
+
return buildColumn(name, "text") as ColumnBuilder<string>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function instant(
|
|
258
|
+
name: string,
|
|
259
|
+
opts?: { precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6 },
|
|
260
|
+
): ColumnBuilder<Temporal.Instant> {
|
|
261
|
+
const pgType: PgType = opts?.precision === 3 ? "timestamptz(3)" : "timestamptz";
|
|
262
|
+
return buildColumn(name, pgType) as ColumnBuilder<Temporal.Instant>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// moneyAmount kept as a customType-style API but produces a bigint column.
|
|
266
|
+
export const moneyAmount = (name: string): ColumnBuilder<number> =>
|
|
267
|
+
buildColumn(name, "bigint") as ColumnBuilder<number>;
|
|
268
|
+
|
|
269
|
+
// ---- Index + primaryKey helpers ----
|
|
270
|
+
|
|
271
|
+
export type IndexBuilder = {
|
|
272
|
+
readonly __index: true;
|
|
273
|
+
on(...cols: ColumnHandle[]): IndexBuilderWithCols;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export type IndexBuilderWithCols = {
|
|
277
|
+
readonly __index: true;
|
|
278
|
+
readonly name: string;
|
|
279
|
+
readonly unique: boolean;
|
|
280
|
+
readonly columns: readonly string[];
|
|
281
|
+
where(expr: SqlExpression): IndexBuilderWithCols;
|
|
282
|
+
readonly whereSql?: string;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
function makeIndex(name: string, unique: boolean): IndexBuilder {
|
|
286
|
+
return {
|
|
287
|
+
__index: true,
|
|
288
|
+
on(...cols: ColumnHandle[]): IndexBuilderWithCols {
|
|
289
|
+
const colNames = cols.map((c) => c.name);
|
|
290
|
+
let whereSql: string | undefined;
|
|
291
|
+
const finalised: IndexBuilderWithCols = {
|
|
292
|
+
__index: true,
|
|
293
|
+
name,
|
|
294
|
+
unique,
|
|
295
|
+
columns: colNames,
|
|
296
|
+
get whereSql() {
|
|
297
|
+
return whereSql;
|
|
298
|
+
},
|
|
299
|
+
where(expr: SqlExpression) {
|
|
300
|
+
whereSql = expr.text;
|
|
301
|
+
return finalised;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
return finalised;
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function index(name: string): IndexBuilder {
|
|
310
|
+
return makeIndex(name, false);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function uniqueIndex(name: string): IndexBuilder {
|
|
314
|
+
return makeIndex(name, true);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export type PrimaryKeyDescriptor = {
|
|
318
|
+
readonly __pk: true;
|
|
319
|
+
readonly columns: readonly string[];
|
|
320
|
+
readonly name?: string;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export function primaryKey(opts: {
|
|
324
|
+
columns: readonly ColumnHandle[];
|
|
325
|
+
name?: string;
|
|
326
|
+
}): PrimaryKeyDescriptor {
|
|
327
|
+
return {
|
|
328
|
+
__pk: true,
|
|
329
|
+
columns: opts.columns.map((c) => c.name),
|
|
330
|
+
...(opts.name !== undefined && { name: opts.name }),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---- sql template ----
|
|
335
|
+
//
|
|
336
|
+
// A constrained sql template tag. Returns a SqlExpression carrying the
|
|
337
|
+
// composed text + params. Used in DEFAULT expressions in schema files
|
|
338
|
+
// (sql`now()`, sql`gen_random_uuid()`, sql`0`).
|
|
339
|
+
//
|
|
340
|
+
// Limits: no nested SqlExpression composition (drizzle's recursive
|
|
341
|
+
// `sql\`${other}\``) — schema-files use single-level expressions only.
|
|
342
|
+
|
|
343
|
+
export type SqlExpression = {
|
|
344
|
+
readonly kind: "sql-expr";
|
|
345
|
+
readonly text: string;
|
|
346
|
+
readonly params: readonly unknown[];
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export function sql(strings: TemplateStringsArray, ...values: readonly unknown[]): SqlExpression {
|
|
350
|
+
const parts: string[] = [];
|
|
351
|
+
const params: unknown[] = [];
|
|
352
|
+
for (let i = 0; i < strings.length; i++) {
|
|
353
|
+
parts.push(strings[i] ?? "");
|
|
354
|
+
if (i < values.length) {
|
|
355
|
+
const v = values[i];
|
|
356
|
+
if (v && typeof v === "object" && "kind" in v && v.kind === "sql-expr") {
|
|
357
|
+
parts.push((v as SqlExpression).text);
|
|
358
|
+
} else {
|
|
359
|
+
parts.push(String(v));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { kind: "sql-expr", text: parts.join(""), params };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
sql.raw = (text: string): SqlExpression => ({ kind: "sql-expr", text, params: [] });
|
|
367
|
+
|
|
368
|
+
// ---- table() — the schema-table factory ----
|
|
369
|
+
//
|
|
370
|
+
// Produces a SchemaTable with:
|
|
371
|
+
// - EntityTableMeta shape (source/tableName/columns/indexes)
|
|
372
|
+
// - Drizzle Symbol metadata for compat with bun-db's introspection
|
|
373
|
+
// - Top-level column-handle properties (table.fieldName → ColumnHandle)
|
|
374
|
+
//
|
|
375
|
+
// Second arg is an object whose keys are JS field-names; values are
|
|
376
|
+
// ColumnBuilder. Third arg is the constraints/index callback receiving
|
|
377
|
+
// a record of ColumnHandle (so existing `(t) => ({ idx: index(...).on(t.col) })`
|
|
378
|
+
// patterns work).
|
|
379
|
+
|
|
380
|
+
export type ColumnMap = Record<string, ColumnBuilder<unknown>>;
|
|
381
|
+
|
|
382
|
+
type IndexOrPk = IndexBuilderWithCols | PrimaryKeyDescriptor;
|
|
383
|
+
|
|
384
|
+
export function table<TCols extends ColumnMap>(
|
|
385
|
+
tableName: string,
|
|
386
|
+
cols: TCols,
|
|
387
|
+
optsFn?: (
|
|
388
|
+
t: { [K in keyof TCols]: ColumnHandle },
|
|
389
|
+
) => Record<string, IndexOrPk> | ReadonlyArray<IndexOrPk>,
|
|
390
|
+
): SchemaTable {
|
|
391
|
+
// Finalise columns + build the column-handle map.
|
|
392
|
+
const handles: Record<string, ColumnHandle> = {};
|
|
393
|
+
const columnMetas: ColumnMeta[] = [];
|
|
394
|
+
const indexes: IndexMeta[] = [];
|
|
395
|
+
for (const [field, builder] of Object.entries(cols)) {
|
|
396
|
+
const final = builder.finalise();
|
|
397
|
+
const handle: ColumnHandle = {
|
|
398
|
+
name: final.sqlName,
|
|
399
|
+
pgType: final.pgType,
|
|
400
|
+
getSQLType: () => pgTypeToSqlType(final.pgType),
|
|
401
|
+
};
|
|
402
|
+
handles[field] = handle;
|
|
403
|
+
const meta: ColumnMeta = {
|
|
404
|
+
name: final.sqlName,
|
|
405
|
+
pgType: final.pgType,
|
|
406
|
+
notNull: final.notNull,
|
|
407
|
+
...(final.primaryKey && { primaryKey: true }),
|
|
408
|
+
...(final.identity && { identity: true }),
|
|
409
|
+
...(final.defaultSql !== undefined && { defaultSql: final.defaultSql }),
|
|
410
|
+
};
|
|
411
|
+
columnMetas.push(meta);
|
|
412
|
+
|
|
413
|
+
// Per-column .unique() → single-column unique index
|
|
414
|
+
if (final.unique) {
|
|
415
|
+
indexes.push({
|
|
416
|
+
name: `${tableName}_${final.sqlName}_unique`,
|
|
417
|
+
columns: [final.sqlName],
|
|
418
|
+
unique: true,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Evaluate index/pk callback with the column handles.
|
|
424
|
+
let compositePrimaryKey: CompositePrimaryKeyMeta | undefined;
|
|
425
|
+
if (optsFn) {
|
|
426
|
+
const tHandle = handles as { [K in keyof TCols]: ColumnHandle };
|
|
427
|
+
const opts = optsFn(tHandle);
|
|
428
|
+
const entries: Array<[string, IndexOrPk | undefined]> = Array.isArray(opts)
|
|
429
|
+
? (opts as ReadonlyArray<IndexOrPk>).map((v, i) => [String(i), v])
|
|
430
|
+
: Object.entries(opts);
|
|
431
|
+
for (const [key, value] of entries) {
|
|
432
|
+
if (!value) continue;
|
|
433
|
+
if ("__pk" in value && value.__pk === true) {
|
|
434
|
+
compositePrimaryKey = {
|
|
435
|
+
name: value.name ?? `${tableName}_pk`,
|
|
436
|
+
columns: value.columns,
|
|
437
|
+
};
|
|
438
|
+
} else if ("__index" in value && value.__index === true) {
|
|
439
|
+
const idx = value as IndexBuilderWithCols;
|
|
440
|
+
indexes.push({
|
|
441
|
+
name: idx.name,
|
|
442
|
+
columns: idx.columns,
|
|
443
|
+
...(idx.unique && { unique: true }),
|
|
444
|
+
...(idx.whereSql !== undefined && { whereSql: idx.whereSql }),
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
// Inline unique-column declaration via .unique() — not implemented yet.
|
|
448
|
+
// Schema files don't currently use that path; pinned by the table-types
|
|
449
|
+
// test fixture if it's ever added.
|
|
450
|
+
const _k = key;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Build the SchemaTable. Object.assign to layer the column handles + symbols
|
|
456
|
+
// onto the EntityTableMeta-shaped object so introspection works.
|
|
457
|
+
const base: EntityTableMeta = {
|
|
458
|
+
tableName,
|
|
459
|
+
columns: columnMetas,
|
|
460
|
+
indexes,
|
|
461
|
+
...(compositePrimaryKey !== undefined && { compositePrimaryKey }),
|
|
462
|
+
source: "unmanaged",
|
|
463
|
+
};
|
|
464
|
+
const out = Object.assign({}, base, handles, {
|
|
465
|
+
[KUMIKO_NAME_SYMBOL]: tableName,
|
|
466
|
+
[KUMIKO_COLUMNS_SYMBOL]: handles,
|
|
467
|
+
}) as SchemaTable;
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Helper used by `instantToDriver` callers in legacy code — kept identical
|
|
472
|
+
// to the previous behaviour. The native dialect handles parse/serialize
|
|
473
|
+
// implicitly via the Bun driver; this function is a defensive coerce at
|
|
474
|
+
// the API boundary.
|
|
87
475
|
export function instantToDriver(value: Temporal.Instant | string): string {
|
|
88
476
|
if (typeof value === "string") {
|
|
89
477
|
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
@@ -92,18 +480,3 @@ export function instantToDriver(value: Temporal.Instant | string): string {
|
|
|
92
480
|
}
|
|
93
481
|
return value.toString();
|
|
94
482
|
}
|
|
95
|
-
|
|
96
|
-
const instantBuilder = (config?: { precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6 }) =>
|
|
97
|
-
customType<{ data: Temporal.Instant; driverData: string }>({
|
|
98
|
-
dataType() {
|
|
99
|
-
const p = config?.precision;
|
|
100
|
-
return p !== undefined ? `timestamp(${p}) with time zone` : "timestamptz";
|
|
101
|
-
},
|
|
102
|
-
fromDriver(value: string): Temporal.Instant {
|
|
103
|
-
return Temporal.Instant.from(value);
|
|
104
|
-
},
|
|
105
|
-
toDriver: instantToDriver,
|
|
106
|
-
});
|
|
107
|
-
export function instant(name: string, config?: { precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6 }) {
|
|
108
|
-
return instantBuilder(config)(name);
|
|
109
|
-
}
|
package/src/db/eagerload.ts
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
// keine framework-engine-Internals und kann auch von custom
|
|
24
24
|
// query-handlern manuell aufgerufen werden.
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { selectMany } from "../db/query";
|
|
27
27
|
import type { EntityDefinition, FieldDefinition, ReferenceFieldDef } from "../engine/types";
|
|
28
|
-
import {
|
|
28
|
+
import { buildEntityTable } from "./table-builder";
|
|
29
29
|
import type { TenantDb } from "./tenant-db";
|
|
30
30
|
|
|
31
31
|
// Minimaler Registry-Lookup-Contract: pro entity-name → EntityDefinition.
|
|
@@ -121,18 +121,11 @@ export async function enrichWithReferences(
|
|
|
121
121
|
// UUID zurück, kein Crash.
|
|
122
122
|
return { fieldName: rf.fieldName, multiple: rf.multiple, map: new Map() };
|
|
123
123
|
}
|
|
124
|
-
const refTable =
|
|
125
|
-
const idCol = refTable["id"];
|
|
126
|
-
if (idCol === undefined) {
|
|
127
|
-
return { fieldName: rf.fieldName, multiple: rf.multiple, map: new Map() };
|
|
128
|
-
}
|
|
124
|
+
const refTable = buildEntityTable(rf.refEntityName, refEntity);
|
|
129
125
|
const idArray = [...ids];
|
|
130
|
-
const refRows = (await db
|
|
131
|
-
.select()
|
|
132
|
-
.from(refTable)
|
|
133
|
-
.where(inArray(idCol, idArray as never /* @cast-boundary db-operator */))) as Array<
|
|
126
|
+
const refRows = (await selectMany(db, refTable, { id: idArray })) as Array<
|
|
134
127
|
Record<string, unknown>
|
|
135
|
-
>;
|
|
128
|
+
>;
|
|
136
129
|
const map = new Map<string, Record<string, unknown>>();
|
|
137
130
|
for (const r of refRows) {
|
|
138
131
|
const id = r["id"];
|