@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
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// EntityTableMeta — plain-data Schema-Meta für eine Read-Model-Tabelle.
|
|
2
|
+
// Single source of truth statt verheirateter drizzle-pgTable-Builder.
|
|
3
|
+
//
|
|
4
|
+
// Phase 3a (Drizzle-Replacement Plan): Type + Generator existieren parallel
|
|
5
|
+
// zu buildEntityTable. Konsumenten bleiben auf EntityTable (via Adapter
|
|
6
|
+
// `entityTableMetaToEntityTable`), bis Phase 4 die Query-API auf Bun.sql
|
|
7
|
+
// umstellt.
|
|
8
|
+
//
|
|
9
|
+
// Designed für zwei Quellen:
|
|
10
|
+
// 1. **Managed** — EntityDefinition via buildEntityTableMeta(name, entity).
|
|
11
|
+
// Standard-Pfad mit base-columns (id, tenant_id, version, inserted_at,
|
|
12
|
+
// modified_at, inserted_by_id, modified_by_id, ggf. softDelete-Cols),
|
|
13
|
+
// automatischer tenant_id-Index, audit-fähig.
|
|
14
|
+
// 2. **Unmanaged** — defineUnmanagedTable(input). Escape-Hatch für Tabellen
|
|
15
|
+
// die NICHT durch das Entity-System gemanagt werden — keine erzwungenen
|
|
16
|
+
// base-columns, kein Standard-Audit-Trail. App-Author trägt Verantwortung
|
|
17
|
+
// für Tenant-Scoping, Version-Tracking, audit-by-Spalten. Verwendung
|
|
18
|
+
// auf Sondercases beschränken (child-projection-tables ohne tenant,
|
|
19
|
+
// append-only-logs mit serial PK, aggregate-ID ohne DEFAULT, …).
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
EntityDefinition,
|
|
23
|
+
EntityIndexDef,
|
|
24
|
+
EntityRelations,
|
|
25
|
+
FieldDefinition,
|
|
26
|
+
} from "../engine/types";
|
|
27
|
+
import { READ_MODEL_PREFIX, toSnakeCase, toTableName } from "./table-builder";
|
|
28
|
+
|
|
29
|
+
// PG-Type-Repertoire das die Read-Model-Tabellen brauchen. Bewusst
|
|
30
|
+
// schmal — keine Vendor-spezifischen Typen (TSVECTOR, HSTORE, etc.).
|
|
31
|
+
// App-Author die solche braucht greift in der reviewten SQL-Migration
|
|
32
|
+
// hand-edit ein, nicht im Generator.
|
|
33
|
+
export type PgType =
|
|
34
|
+
| "uuid"
|
|
35
|
+
| "text"
|
|
36
|
+
| "boolean"
|
|
37
|
+
| "integer"
|
|
38
|
+
| "bigint"
|
|
39
|
+
| "serial"
|
|
40
|
+
| "bigserial"
|
|
41
|
+
| "jsonb"
|
|
42
|
+
| "timestamptz"
|
|
43
|
+
| "timestamptz(3)";
|
|
44
|
+
|
|
45
|
+
export type ColumnMeta = {
|
|
46
|
+
readonly name: string; // snake_case PG column name
|
|
47
|
+
readonly pgType: PgType;
|
|
48
|
+
readonly notNull: boolean;
|
|
49
|
+
// Raw SQL-default-expression (e.g. `now()`, `gen_random_uuid()`,
|
|
50
|
+
// `'[]'::jsonb`). undefined = no DEFAULT clause.
|
|
51
|
+
readonly defaultSql?: string;
|
|
52
|
+
readonly primaryKey?: boolean;
|
|
53
|
+
readonly identity?: boolean;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type IndexMeta = {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly columns: readonly string[]; // snake_case PG column names
|
|
59
|
+
readonly unique?: boolean;
|
|
60
|
+
// Raw SQL-where-expression for partial indexes. Caller is responsible
|
|
61
|
+
// for safety — emitted verbatim.
|
|
62
|
+
readonly whereSql?: string;
|
|
63
|
+
// Set wenn die EntityDefinition ein partial-Index hat (def.where als
|
|
64
|
+
// drizzle SQL-AST), der vom Generator nicht zuverlässig renderbar ist.
|
|
65
|
+
// Renderer emittiert das Statement dann AUSKOMMENTIERT mit Warn-Hint —
|
|
66
|
+
// App-Author muss das WHERE manuell im generierten SQL hinzufügen.
|
|
67
|
+
readonly needsManualWhere?: boolean;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type CompositePrimaryKeyMeta = {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly columns: readonly string[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type EntityTableMeta = {
|
|
76
|
+
readonly tableName: string;
|
|
77
|
+
readonly columns: readonly ColumnMeta[];
|
|
78
|
+
readonly indexes: readonly IndexMeta[];
|
|
79
|
+
// For tables with composite PK (no single id column, e.g. snapshots
|
|
80
|
+
// keyed by aggregate_id+version). When set, no column should have
|
|
81
|
+
// primaryKey:true; the constraint is emitted at table-level.
|
|
82
|
+
readonly compositePrimaryKey?: CompositePrimaryKeyMeta;
|
|
83
|
+
// Source-hint für Diagnose/Tests — nicht funktional verwendet.
|
|
84
|
+
// "managed" = aus EntityDefinition (mit base-columns + audit-trail).
|
|
85
|
+
// "unmanaged" = via defineUnmanagedTable — kein Standard-Audit, App
|
|
86
|
+
// trägt Verantwortung. Migration-Generator + Tooling können den
|
|
87
|
+
// discriminator nutzen um Warnungen zu rendern ("X tables are unmanaged").
|
|
88
|
+
readonly source: "managed" | "unmanaged";
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Standard base-columns für event-sourced Read-Model-Tabellen. Spiegelt
|
|
92
|
+
// `buildBaseColumns()` aus table-builder.ts (drizzle-Variante).
|
|
93
|
+
function fullBaseColumns(idType: "uuid" | "serial", softDelete: boolean): readonly ColumnMeta[] {
|
|
94
|
+
const idCol: ColumnMeta =
|
|
95
|
+
idType === "uuid"
|
|
96
|
+
? {
|
|
97
|
+
name: "id",
|
|
98
|
+
pgType: "uuid",
|
|
99
|
+
notNull: true,
|
|
100
|
+
defaultSql: "gen_random_uuid()",
|
|
101
|
+
primaryKey: true,
|
|
102
|
+
}
|
|
103
|
+
: { name: "id", pgType: "serial", notNull: true, primaryKey: true };
|
|
104
|
+
|
|
105
|
+
const cols: ColumnMeta[] = [
|
|
106
|
+
idCol,
|
|
107
|
+
{ name: "tenant_id", pgType: "uuid", notNull: true },
|
|
108
|
+
{ name: "version", pgType: "integer", notNull: true, defaultSql: "1" },
|
|
109
|
+
{ name: "inserted_at", pgType: "timestamptz", notNull: true, defaultSql: "now()" },
|
|
110
|
+
{ name: "modified_at", pgType: "timestamptz", notNull: false },
|
|
111
|
+
{ name: "inserted_by_id", pgType: "text", notNull: false },
|
|
112
|
+
{ name: "modified_by_id", pgType: "text", notNull: false },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
if (softDelete) {
|
|
116
|
+
cols.push(
|
|
117
|
+
{ name: "is_deleted", pgType: "boolean", notNull: true, defaultSql: "false" },
|
|
118
|
+
{ name: "deleted_at", pgType: "timestamptz", notNull: false },
|
|
119
|
+
{ name: "deleted_by_id", pgType: "text", notNull: false },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return cols;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function quoteSql(literal: string): string {
|
|
126
|
+
return `'${literal.replace(/'/g, "''")}'`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function fieldDefaultLiteral(field: FieldDefinition): string | undefined {
|
|
130
|
+
if (!("default" in field) || field.default === undefined) return undefined;
|
|
131
|
+
const v = field.default;
|
|
132
|
+
if (typeof v === "string") return quoteSql(v);
|
|
133
|
+
if (typeof v === "number") return String(v);
|
|
134
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Spiegelt `fieldToColumns()` aus table-builder.ts (Drizzle-Variante).
|
|
139
|
+
// Lock-step: jeder Field-Type produziert dieselben PG-Spalten wie heute.
|
|
140
|
+
function fieldToColumnMeta(
|
|
141
|
+
name: string,
|
|
142
|
+
field: FieldDefinition,
|
|
143
|
+
entity: EntityDefinition,
|
|
144
|
+
): readonly ColumnMeta[] {
|
|
145
|
+
const snake = toSnakeCase(name);
|
|
146
|
+
switch (field.type) {
|
|
147
|
+
case "text":
|
|
148
|
+
case "longText": {
|
|
149
|
+
const def = fieldDefaultLiteral(field);
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
name: snake,
|
|
153
|
+
pgType: "text",
|
|
154
|
+
notNull: field.required === true,
|
|
155
|
+
...(def !== undefined && { defaultSql: def }),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
case "boolean": {
|
|
160
|
+
const def = fieldDefaultLiteral(field);
|
|
161
|
+
const hasDefault = def !== undefined;
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
name: snake,
|
|
165
|
+
pgType: "boolean",
|
|
166
|
+
notNull: hasDefault || field.required === true,
|
|
167
|
+
...(hasDefault && { defaultSql: def }),
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
case "select": {
|
|
172
|
+
const def = fieldDefaultLiteral(field);
|
|
173
|
+
return [
|
|
174
|
+
{
|
|
175
|
+
name: snake,
|
|
176
|
+
pgType: "text",
|
|
177
|
+
notNull: field.required === true,
|
|
178
|
+
...(def !== undefined && { defaultSql: def }),
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
case "multiSelect":
|
|
183
|
+
return [{ name: snake, pgType: "jsonb", notNull: true, defaultSql: "'[]'::jsonb" }];
|
|
184
|
+
case "number": {
|
|
185
|
+
const def = fieldDefaultLiteral(field);
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
name: snake,
|
|
189
|
+
pgType: "integer",
|
|
190
|
+
notNull: field.required === true,
|
|
191
|
+
...(def !== undefined && { defaultSql: def }),
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
case "bigInt": {
|
|
196
|
+
const def = fieldDefaultLiteral(field);
|
|
197
|
+
return [
|
|
198
|
+
{
|
|
199
|
+
name: snake,
|
|
200
|
+
pgType: "bigint",
|
|
201
|
+
notNull: field.required === true,
|
|
202
|
+
...(def !== undefined && { defaultSql: def }),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
case "reference":
|
|
207
|
+
if (field.multiple === true) {
|
|
208
|
+
return [{ name: snake, pgType: "jsonb", notNull: true, defaultSql: "'[]'::jsonb" }];
|
|
209
|
+
}
|
|
210
|
+
return [{ name: snake, pgType: "uuid", notNull: field.required === true }];
|
|
211
|
+
case "money": {
|
|
212
|
+
const cur = entity.defaultCurrency ?? "EUR";
|
|
213
|
+
return [
|
|
214
|
+
{ name: snake, pgType: "bigint", notNull: field.required === true },
|
|
215
|
+
{
|
|
216
|
+
name: `${snake}_currency`,
|
|
217
|
+
pgType: "text",
|
|
218
|
+
notNull: true,
|
|
219
|
+
defaultSql: quoteSql(cur),
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
case "embedded":
|
|
224
|
+
return [{ name: snake, pgType: "jsonb", notNull: true, defaultSql: "'{}'::jsonb" }];
|
|
225
|
+
case "jsonb":
|
|
226
|
+
return [{ name: snake, pgType: "jsonb", notNull: true, defaultSql: "'{}'::jsonb" }];
|
|
227
|
+
case "date":
|
|
228
|
+
case "timestamp":
|
|
229
|
+
return [{ name: snake, pgType: "timestamptz", notNull: field.required === true }];
|
|
230
|
+
case "tz":
|
|
231
|
+
return [{ name: snake, pgType: "text", notNull: field.required === true }];
|
|
232
|
+
case "locatedTimestamp":
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
name: `${snake}_utc`,
|
|
236
|
+
pgType: "timestamptz",
|
|
237
|
+
notNull: field.required === true,
|
|
238
|
+
},
|
|
239
|
+
{ name: `${snake}_tz`, pgType: "text", notNull: field.required === true },
|
|
240
|
+
];
|
|
241
|
+
case "file":
|
|
242
|
+
case "image":
|
|
243
|
+
return [{ name: snake, pgType: "uuid", notNull: field.required === true }];
|
|
244
|
+
case "files":
|
|
245
|
+
case "images":
|
|
246
|
+
return [];
|
|
247
|
+
default:
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function resolveTableName(
|
|
253
|
+
entityName: string,
|
|
254
|
+
entity: EntityDefinition,
|
|
255
|
+
featureName: string | undefined,
|
|
256
|
+
): string {
|
|
257
|
+
const baseName = entity.table ?? toTableName(entityName);
|
|
258
|
+
if (!featureName) return baseName;
|
|
259
|
+
if (baseName.startsWith(READ_MODEL_PREFIX)) {
|
|
260
|
+
return `${READ_MODEL_PREFIX}${featureName}_${baseName.slice(READ_MODEL_PREFIX.length)}`;
|
|
261
|
+
}
|
|
262
|
+
return `${featureName}_${baseName}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export type BuildEntityTableMetaOptions = {
|
|
266
|
+
readonly featureName?: string;
|
|
267
|
+
readonly relations?: EntityRelations;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export function buildEntityTableMeta(
|
|
271
|
+
entityName: string,
|
|
272
|
+
entity: EntityDefinition,
|
|
273
|
+
options?: BuildEntityTableMetaOptions,
|
|
274
|
+
): EntityTableMeta {
|
|
275
|
+
const tableName = resolveTableName(entityName, entity, options?.featureName);
|
|
276
|
+
const idType = entity.idType ?? "uuid";
|
|
277
|
+
|
|
278
|
+
// Base-columns first, then user-fields. User-fields with the same
|
|
279
|
+
// pg-name as a base-column OVERRIDE the base-column (last-wins, gleiches
|
|
280
|
+
// Verhalten wie drizzle's `{ ...base, ...fields }` Spread im table-
|
|
281
|
+
// builder). Use-case: user-session hat `tenantId` als field um access-
|
|
282
|
+
// control aufzudrücken, fileRef hat `insertedAt` als field für sortable/
|
|
283
|
+
// filterable-marker. Die DB-Spalte bleibt die gleiche, nur Application-
|
|
284
|
+
// Metadata auf der Field-Seite ändert sich.
|
|
285
|
+
const baseCols = fullBaseColumns(idType, entity.softDelete === true);
|
|
286
|
+
const colByName = new Map<string, ColumnMeta>();
|
|
287
|
+
for (const c of baseCols) colByName.set(c.name, c);
|
|
288
|
+
|
|
289
|
+
const fieldNameToSnake = new Map<string, string>();
|
|
290
|
+
for (const [name, field] of Object.entries(entity.fields)) {
|
|
291
|
+
const fieldCols = fieldToColumnMeta(name, field, entity);
|
|
292
|
+
for (const c of fieldCols) colByName.set(c.name, c);
|
|
293
|
+
if (fieldCols.length === 1 && fieldCols[0]) fieldNameToSnake.set(name, fieldCols[0].name);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Preserve base-col order, then any new user-col-names in fields-order.
|
|
297
|
+
const columns: ColumnMeta[] = [];
|
|
298
|
+
const seen = new Set<string>();
|
|
299
|
+
for (const c of baseCols) {
|
|
300
|
+
const final = colByName.get(c.name);
|
|
301
|
+
if (final && !seen.has(final.name)) {
|
|
302
|
+
columns.push(final);
|
|
303
|
+
seen.add(final.name);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const c of colByName.values()) {
|
|
307
|
+
if (!seen.has(c.name)) {
|
|
308
|
+
columns.push(c);
|
|
309
|
+
seen.add(c.name);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const indexes: IndexMeta[] = [{ name: `${tableName}_tenant_id_idx`, columns: ["tenant_id"] }];
|
|
314
|
+
|
|
315
|
+
// FK-Indexes: file/image-Felder + belongsTo-Relations
|
|
316
|
+
const fkSnakeNames = new Set<string>();
|
|
317
|
+
for (const [name, field] of Object.entries(entity.fields)) {
|
|
318
|
+
if (field.type === "file" || field.type === "image") fkSnakeNames.add(toSnakeCase(name));
|
|
319
|
+
}
|
|
320
|
+
if (options?.relations) {
|
|
321
|
+
for (const rel of Object.values(options.relations)) {
|
|
322
|
+
if (rel.type === "belongsTo") {
|
|
323
|
+
const snake = fieldNameToSnake.get(rel.foreignKey) ?? toSnakeCase(rel.foreignKey);
|
|
324
|
+
fkSnakeNames.add(snake);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const snake of fkSnakeNames) {
|
|
329
|
+
indexes.push({ name: `${tableName}_${snake}_idx`, columns: [snake] });
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Explizit deklarierte indexes (EntityIndexDef). `def.where` ist heute
|
|
333
|
+
// ein drizzle SQL-AST — wir können daraus keinen zuverlässigen Raw-SQL-
|
|
334
|
+
// String rendern (queryChunks sind internal). Wenn ein where gesetzt
|
|
335
|
+
// ist, markieren wir den IndexMeta mit needsManualWhere=true; der DDL-
|
|
336
|
+
// Renderer emittiert das Statement dann als AUSKOMMENTIERT mit Warn-
|
|
337
|
+
// Hinweis. App-Author muss das im generierten SQL-File hand-editieren.
|
|
338
|
+
for (const def of (entity.indexes ?? []) as readonly EntityIndexDef[]) {
|
|
339
|
+
const cols = def.columns.map(
|
|
340
|
+
(fieldName) => fieldNameToSnake.get(fieldName) ?? toSnakeCase(fieldName),
|
|
341
|
+
);
|
|
342
|
+
const suffix = def.unique === true ? "unique" : "idx";
|
|
343
|
+
const indexName = def.name ?? `${tableName}_${cols.join("_")}_${suffix}`;
|
|
344
|
+
indexes.push({
|
|
345
|
+
name: indexName,
|
|
346
|
+
columns: cols,
|
|
347
|
+
...(def.unique === true && { unique: true }),
|
|
348
|
+
...(def.where !== undefined && { needsManualWhere: true }),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
tableName,
|
|
354
|
+
columns,
|
|
355
|
+
indexes,
|
|
356
|
+
source: "managed",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Escape-Hatch für Tabellen die NICHT durch das Entity-System gemanagt
|
|
361
|
+
// werden. Kein Audit-Trail (keine version, inserted_at, modified_by etc.),
|
|
362
|
+
// kein automatischer tenant_id-Index, kein softDelete-Support.
|
|
363
|
+
//
|
|
364
|
+
// **Vorsicht-vor-Use:** wenn du das hier benutzt, gibst du das Standard-
|
|
365
|
+
// Audit-Pattern auf. Begründe im Code WARUM (child-projection ohne tenant-
|
|
366
|
+
// scope, aggregate-id-PK ohne DEFAULT, append-only-log mit serial PK,
|
|
367
|
+
// performance-critical hot-path ohne version-check, …). Reviewer sollten
|
|
368
|
+
// jede neue defineUnmanagedTable-Stelle prüfen.
|
|
369
|
+
//
|
|
370
|
+
// Heutige use-cases im framework:
|
|
371
|
+
// - `read_delivery_attempts` — id kommt aus dem Aggregate-Stream
|
|
372
|
+
// - `read_job_run_logs` — child-table, serial PK, kein tenant-scope
|
|
373
|
+
export type UnmanagedTableInput = {
|
|
374
|
+
readonly tableName: string;
|
|
375
|
+
readonly columns: readonly ColumnMeta[];
|
|
376
|
+
readonly indexes?: readonly IndexMeta[];
|
|
377
|
+
readonly compositePrimaryKey?: CompositePrimaryKeyMeta;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export function defineUnmanagedTable(input: UnmanagedTableInput): EntityTableMeta {
|
|
381
|
+
return {
|
|
382
|
+
tableName: input.tableName,
|
|
383
|
+
columns: input.columns,
|
|
384
|
+
indexes: input.indexes ?? [],
|
|
385
|
+
...(input.compositePrimaryKey !== undefined && {
|
|
386
|
+
compositePrimaryKey: input.compositePrimaryKey,
|
|
387
|
+
}),
|
|
388
|
+
source: "unmanaged",
|
|
389
|
+
};
|
|
390
|
+
}
|