@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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// pinned by parse.test.ts + render-roundtrip.test.ts) so a failure
|
|
6
6
|
// here narrows the cause to patch.ts itself.
|
|
7
7
|
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
8
9
|
import { Project, type SourceFile } from "ts-morph";
|
|
9
|
-
import { describe, expect, test } from "vitest";
|
|
10
10
|
import { parseSourceFile } from "../parse";
|
|
11
11
|
import { addPattern, applyChanges, type PatternId, removePattern, replacePattern } from "../patch";
|
|
12
12
|
import { createFeaturePatcher } from "../patcher";
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// arg layout the AI/Designer provides and creates a syntactically valid
|
|
9
9
|
// pattern.
|
|
10
10
|
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
11
12
|
import { Project, type SourceFile } from "ts-morph";
|
|
12
|
-
import { describe, expect, test } from "vitest";
|
|
13
13
|
import { parseSourceFile } from "../parse";
|
|
14
14
|
import { createFeaturePatcher } from "../patcher";
|
|
15
15
|
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// (where the pattern came from) and the rendered file (where it ends
|
|
9
9
|
// up at canonical positions). We compare every other field.
|
|
10
10
|
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
11
12
|
import { Project } from "ts-morph";
|
|
12
|
-
import { describe, expect, test } from "vitest";
|
|
13
13
|
import { parseSourceFile } from "../parse";
|
|
14
14
|
import type { FeaturePattern } from "../patterns";
|
|
15
15
|
import { renderFeatureFile, renderPattern } from "../render";
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// AST extractor + the Designer/AI consumers (renderPattern,
|
|
8
8
|
// getEditability, PATTERN_LIBRARY). Both halves must agree on shape.
|
|
9
9
|
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
10
11
|
import { Project } from "ts-morph";
|
|
11
|
-
import { describe, expect, test } from "vitest";
|
|
12
12
|
import { parseSourceFile } from "../parse";
|
|
13
13
|
import { getEditability } from "../patterns";
|
|
14
14
|
import { renderPattern } from "../render";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CallExpression, Node } from "ts-morph";
|
|
2
2
|
import { SyntaxKind } from "ts-morph";
|
|
3
|
+
import { isPlainObject } from "../../../utils/is-plain-object";
|
|
3
4
|
import type { ParseError } from "../parse";
|
|
4
5
|
|
|
5
6
|
export type ExtractOutput<TPattern> =
|
|
@@ -107,9 +108,7 @@ export function readDataLiteralNode(node: Node): unknown {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
export
|
|
111
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
112
|
-
}
|
|
111
|
+
export { isPlainObject } from "../../../utils/is-plain-object";
|
|
113
112
|
|
|
114
113
|
export function readPropertyKey(propAssign: import("ts-morph").PropertyAssignment): string {
|
|
115
114
|
const nameNode = propAssign.getNameNode();
|
package/src/engine/ownership.ts
CHANGED
|
@@ -12,14 +12,22 @@
|
|
|
12
12
|
// { from: "claim:<featureQn>",
|
|
13
13
|
// column?: "..." } → row[column ?? claim.shortName] === user.claims[claim.qn]
|
|
14
14
|
// (string[] claim → inArray)
|
|
15
|
-
// { where: (user,
|
|
15
|
+
// { where: (user, ctx) => SqlFragment } → escape hatch, raw parameterised SQL
|
|
16
16
|
//
|
|
17
17
|
// Construction: use the `from(ref, column?)` helper. It returns a FromRule
|
|
18
18
|
// ready to drop into an access map.
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { toSnakeCase } from "../db/table-builder";
|
|
21
21
|
import type { SessionUser } from "./types";
|
|
22
22
|
|
|
23
|
+
// Parameterised SQL fragment — produced by buildOwnershipClause + by the
|
|
24
|
+
// WhereRule escape-hatch. Caller weaves `sqlText` into a larger statement,
|
|
25
|
+
// renumbering placeholders if needed (FragmentBuilder below).
|
|
26
|
+
export type SqlFragment = {
|
|
27
|
+
readonly sqlText: string;
|
|
28
|
+
readonly params: readonly unknown[];
|
|
29
|
+
};
|
|
30
|
+
|
|
23
31
|
// Reference spec supported by `from()`:
|
|
24
32
|
// "user:id" → user.id
|
|
25
33
|
// "user:tenantId" → user.tenantId (rarely needed — TenantDb scopes anyway)
|
|
@@ -49,9 +57,18 @@ export type FromRule = {
|
|
|
49
57
|
readonly column: string;
|
|
50
58
|
};
|
|
51
59
|
|
|
60
|
+
// Context passed to a WhereRule escape-hatch. The author returns a SqlFragment
|
|
61
|
+
// whose placeholders start at `paramStart` ($N, $N+1, ...); the framework
|
|
62
|
+
// concatenates the fragment into the larger query.
|
|
63
|
+
export type WhereRuleContext<TTable = unknown> = {
|
|
64
|
+
readonly table: TTable;
|
|
65
|
+
readonly tableName: string;
|
|
66
|
+
readonly paramStart: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
52
69
|
export type WhereRule<TTable = unknown> = {
|
|
53
70
|
readonly kind: "where";
|
|
54
|
-
readonly where: (user: SessionUser,
|
|
71
|
+
readonly where: (user: SessionUser, ctx: WhereRuleContext<TTable>) => SqlFragment;
|
|
55
72
|
};
|
|
56
73
|
|
|
57
74
|
// "all" collapses to a primitive so map authors can write `Admin: "all"`
|
|
@@ -244,51 +261,94 @@ export function userCanCreateFieldRow(
|
|
|
244
261
|
// "empty" → user has a role mapped but no rule accepts any row (missing
|
|
245
262
|
// claim, empty array, role not in map). Skip the DB call entirely
|
|
246
263
|
// — returning [] is equivalent and avoids a pointless roundtrip.
|
|
247
|
-
// "sql" → apply
|
|
264
|
+
// "sql" → apply the parameterised fragment as an AND on the query.
|
|
265
|
+
// Caller is responsible for renumbering placeholders when
|
|
266
|
+
// concatenating with other fragments (see `shiftParams` below).
|
|
248
267
|
//
|
|
249
268
|
// "empty" vs. "pass" is the critical distinction for a safe default:
|
|
250
|
-
// undefined/pass = allow, empty = deny-by-construction.
|
|
251
|
-
// the exact leak direction advisor flagged; the disjoint type prevents it.
|
|
269
|
+
// undefined/pass = allow, empty = deny-by-construction.
|
|
252
270
|
export type OwnershipClause =
|
|
253
271
|
| { readonly kind: "pass" }
|
|
254
272
|
| { readonly kind: "empty" }
|
|
255
|
-
| { readonly kind: "sql"; readonly
|
|
273
|
+
| { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
|
|
256
274
|
|
|
257
275
|
const PASS_CLAUSE: OwnershipClause = { kind: "pass" };
|
|
258
276
|
const EMPTY_CLAUSE: OwnershipClause = { kind: "empty" };
|
|
259
277
|
|
|
260
|
-
|
|
261
|
-
|
|
278
|
+
const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
|
|
279
|
+
const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
|
|
280
|
+
|
|
281
|
+
function tableNameOf(table: unknown): string {
|
|
282
|
+
if (table !== null && typeof table === "object") {
|
|
283
|
+
const sym = (table as Record<symbol, unknown>)[KUMIKO_NAME_SYMBOL];
|
|
284
|
+
if (typeof sym === "string") return sym;
|
|
285
|
+
}
|
|
286
|
+
return "<unknown>";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Resolve a JS-field name on the table to its underlying SQL column name.
|
|
290
|
+
// Drizzle tables carry the mapping under Symbol.for("kumiko:schema:Columns");
|
|
291
|
+
// we read it without importing drizzle-orm at runtime.
|
|
292
|
+
function columnSqlName(table: unknown, field: string): string | null {
|
|
293
|
+
if (table === null || typeof table !== "object") return null;
|
|
294
|
+
const cols = (table as Record<symbol, unknown>)[KUMIKO_COLUMNS_SYMBOL];
|
|
295
|
+
if (cols && typeof cols === "object") {
|
|
296
|
+
const col = (cols as Record<string, unknown>)[field];
|
|
297
|
+
if (col && typeof col === "object") {
|
|
298
|
+
const nameVal = (col as Record<string, unknown>)["name"];
|
|
299
|
+
if (typeof nameVal === "string") return nameVal;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Field may already be the SQL column name on plain objects (tests, etc.).
|
|
303
|
+
if ((table as Record<string, unknown>)[field] !== undefined) {
|
|
304
|
+
return toSnakeCase(field);
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function quoteIdent(name: string): string {
|
|
310
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Shift `$N` placeholder numbers in an embedded fragment so they line up
|
|
314
|
+
// with the outer query's param array.
|
|
315
|
+
export function shiftParams(fragment: SqlFragment, shift: number): SqlFragment {
|
|
316
|
+
if (shift === 0) return fragment;
|
|
317
|
+
const sqlText = fragment.sqlText.replace(/\$(\d+)/g, (_, num) => `$${Number(num) + shift}`);
|
|
318
|
+
return { sqlText, params: fragment.params };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Build an ownership clause for entity-level READ access. Caller weaves
|
|
322
|
+
// the result into a raw-SQL WHERE (see event-store-executor list/getById).
|
|
262
323
|
//
|
|
263
|
-
// `table` is the
|
|
264
|
-
//
|
|
265
|
-
//
|
|
324
|
+
// `table` is the (drizzle or compatible) table object; we extract column
|
|
325
|
+
// SQL names via the kumiko:schema:Columns symbol. Unknown column on a from-rule
|
|
326
|
+
// is a boot-time misconfiguration; at request time we treat it as empty
|
|
327
|
+
// (safe default) rather than passing silently.
|
|
266
328
|
export function buildOwnershipClause(
|
|
267
329
|
user: SessionUser,
|
|
268
330
|
accessMap: OwnershipMap | undefined,
|
|
269
|
-
|
|
270
|
-
|
|
331
|
+
table: unknown,
|
|
332
|
+
paramStart = 1,
|
|
271
333
|
): OwnershipClause {
|
|
272
334
|
if (!accessMap || Object.keys(accessMap).length === 0) return PASS_CLAUSE;
|
|
273
335
|
|
|
274
|
-
const clauses:
|
|
336
|
+
const clauses: SqlFragment[] = [];
|
|
275
337
|
let anyRoleMatched = false;
|
|
276
338
|
let everyRuleCollapsedToEmpty = true;
|
|
339
|
+
let nextParamIdx = paramStart;
|
|
277
340
|
|
|
278
341
|
for (const role of user.roles) {
|
|
279
342
|
const rule = accessMap[role];
|
|
280
343
|
if (!rule) continue;
|
|
281
344
|
anyRoleMatched = true;
|
|
282
|
-
// "all" = no filter at all for this role; short-circuit.
|
|
283
345
|
if (rule === "all") return PASS_CLAUSE;
|
|
284
|
-
const resolved =
|
|
346
|
+
const resolved = ruleToFragment(rule, user, table, nextParamIdx);
|
|
285
347
|
if (resolved.kind === "sql") {
|
|
286
|
-
clauses.push(resolved.
|
|
348
|
+
clauses.push({ sqlText: resolved.sqlText, params: resolved.params });
|
|
349
|
+
nextParamIdx += resolved.params.length;
|
|
287
350
|
everyRuleCollapsedToEmpty = false;
|
|
288
351
|
}
|
|
289
|
-
// "empty" contribution from one role doesn't short-circuit: another
|
|
290
|
-
// role might still contribute an OR-branch. But if ALL branches are
|
|
291
|
-
// empty, the result is empty.
|
|
292
352
|
}
|
|
293
353
|
|
|
294
354
|
if (!anyRoleMatched) return EMPTY_CLAUSE;
|
|
@@ -296,42 +356,54 @@ export function buildOwnershipClause(
|
|
|
296
356
|
if (clauses.length === 1) {
|
|
297
357
|
const only = clauses[0];
|
|
298
358
|
if (!only) return EMPTY_CLAUSE;
|
|
299
|
-
return { kind: "sql",
|
|
359
|
+
return { kind: "sql", sqlText: `(${only.sqlText})`, params: only.params };
|
|
300
360
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
return { kind: "sql",
|
|
361
|
+
const sqlText = clauses.map((c) => `(${c.sqlText})`).join(" OR ");
|
|
362
|
+
const params: unknown[] = [];
|
|
363
|
+
for (const c of clauses) for (const p of c.params) params.push(p);
|
|
364
|
+
return { kind: "sql", sqlText: `(${sqlText})`, params };
|
|
305
365
|
}
|
|
306
366
|
|
|
307
|
-
type
|
|
367
|
+
type RuleFragmentResult =
|
|
368
|
+
| { readonly kind: "empty" }
|
|
369
|
+
| { readonly kind: "sql"; readonly sqlText: string; readonly params: readonly unknown[] };
|
|
308
370
|
|
|
309
|
-
function
|
|
371
|
+
function ruleToFragment(
|
|
310
372
|
rule: OwnershipRule,
|
|
311
373
|
user: SessionUser,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
):
|
|
374
|
+
table: unknown,
|
|
375
|
+
paramStart: number,
|
|
376
|
+
): RuleFragmentResult {
|
|
315
377
|
if (rule === "all") {
|
|
316
|
-
|
|
317
|
-
// fallback.
|
|
318
|
-
return { kind: "sql", sql: sql`true` };
|
|
378
|
+
return { kind: "sql", sqlText: "TRUE", params: [] };
|
|
319
379
|
}
|
|
320
380
|
if (rule.kind === "where") {
|
|
321
|
-
|
|
381
|
+
const frag = rule.where(user, {
|
|
382
|
+
table,
|
|
383
|
+
tableName: tableNameOf(table),
|
|
384
|
+
paramStart,
|
|
385
|
+
});
|
|
386
|
+
return { kind: "sql", sqlText: frag.sqlText, params: frag.params };
|
|
322
387
|
}
|
|
323
388
|
// FromRule
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
// time we treat as empty (fail-closed).
|
|
327
|
-
if (!column) return { kind: "empty" };
|
|
389
|
+
const colName = columnSqlName(table, rule.column);
|
|
390
|
+
if (!colName) return { kind: "empty" };
|
|
328
391
|
|
|
329
392
|
const value = resolveUserValue(rule, user);
|
|
330
393
|
if (value === undefined || value === null) return { kind: "empty" };
|
|
331
394
|
|
|
332
395
|
if (Array.isArray(value)) {
|
|
333
396
|
if (value.length === 0) return { kind: "empty" };
|
|
334
|
-
|
|
397
|
+
const placeholders = value.map((_, i) => `$${paramStart + i}`).join(", ");
|
|
398
|
+
return {
|
|
399
|
+
kind: "sql",
|
|
400
|
+
sqlText: `${quoteIdent(colName)} IN (${placeholders})`,
|
|
401
|
+
params: value,
|
|
402
|
+
};
|
|
335
403
|
}
|
|
336
|
-
return {
|
|
404
|
+
return {
|
|
405
|
+
kind: "sql",
|
|
406
|
+
sqlText: `${quoteIdent(colName)} = $${paramStart}`,
|
|
407
|
+
params: [value],
|
|
408
|
+
};
|
|
337
409
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// sample pattern (sanity check that entity-fields-editor points at
|
|
8
8
|
// `definition.fields`, not `fields`).
|
|
9
9
|
|
|
10
|
-
import { describe, expect, expectTypeOf, test } from "
|
|
10
|
+
import { describe, expect, expectTypeOf, test } from "bun:test";
|
|
11
11
|
import {
|
|
12
12
|
type FeaturePattern,
|
|
13
13
|
type FeaturePatternKind,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
// All FeaturePatternKind discriminator values, hand-listed so the test
|
|
25
25
|
// fails CI when a new pattern is added without a library entry. Match
|
|
26
26
|
// against the FeaturePattern union via type-test below.
|
|
27
|
-
const ALL_KINDS:
|
|
27
|
+
const ALL_KINDS: FeaturePatternKind[] = [
|
|
28
28
|
"requires",
|
|
29
29
|
"optionalRequires",
|
|
30
30
|
"readsConfig",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
1
|
import type { DbRunner } from "../db/connection";
|
|
2
|
+
import { updateMany } from "../db/query";
|
|
3
3
|
import type { StoredEvent } from "../event-store/event-store";
|
|
4
4
|
import type { MultiStreamApplyContext } from "../pipeline/multi-stream-apply-context";
|
|
5
5
|
import type { MultiStreamApplyFn, ProjectionTable, SingleStreamApplyFn } from "./types/projection";
|
|
@@ -71,15 +71,6 @@ export function setFields(
|
|
|
71
71
|
}
|
|
72
72
|
return async (event, tx) => {
|
|
73
73
|
const values = typeof fields === "function" ? fields(event) : fields;
|
|
74
|
-
|
|
75
|
-
// does not know user table shapes). Drizzle's tx.update().set() is
|
|
76
|
-
// strict about the concrete row, so we feed it the erased value; the
|
|
77
|
-
// type-safety guarantee for `values` lives at the setFields call-site.
|
|
78
|
-
// biome-ignore lint/suspicious/noExplicitAny: see note above.
|
|
79
|
-
const set = values as any; // @cast-boundary engine-bridge
|
|
80
|
-
await tx
|
|
81
|
-
.update(table)
|
|
82
|
-
.set(set)
|
|
83
|
-
.where(eq(idCol as never, event.aggregateId)); // @cast-boundary db-operator
|
|
74
|
+
await updateMany(tx, table, values, { id: event.aggregateId });
|
|
84
75
|
};
|
|
85
76
|
}
|
package/src/engine/registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyEntityEvent } from "../db/apply-entity-event";
|
|
2
|
-
import {
|
|
2
|
+
import { buildEntityTable } from "../db/table-builder";
|
|
3
3
|
import { buildMetricName, validateMetricName } from "../observability";
|
|
4
4
|
import { type QnType, qualifyEntityName } from "./qualified-name";
|
|
5
5
|
import type {
|
|
@@ -40,6 +40,7 @@ import type {
|
|
|
40
40
|
TranslationKeys,
|
|
41
41
|
TreeActionDef,
|
|
42
42
|
TreeChildrenSubscribe,
|
|
43
|
+
UnmanagedTableDef,
|
|
43
44
|
WorkspaceDefinition,
|
|
44
45
|
WriteHandlerDef,
|
|
45
46
|
} from "./types";
|
|
@@ -67,7 +68,7 @@ function buildImplicitProjection(
|
|
|
67
68
|
qualify: typeof qualifyEntityName,
|
|
68
69
|
): ProjectionDefinition {
|
|
69
70
|
const name = qualify(featureName, "projection", `${entityName}${IMPLICIT_PROJECTION_SUFFIX}`);
|
|
70
|
-
const drizzleTable =
|
|
71
|
+
const drizzleTable = buildEntityTable(entityName, entity);
|
|
71
72
|
// applyEntityEvent gibt ApplyResult zurück; SingleStreamApplyFn erwartet
|
|
72
73
|
// Promise<void>. Im rebuild-Pfad ist die Row irrelevant — wir discarden.
|
|
73
74
|
const handler = async (
|
|
@@ -169,6 +170,10 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
|
|
|
169
170
|
// enforced at ingest below (collisions would race two CREATE TABLE
|
|
170
171
|
// statements at the same physical name and break boot).
|
|
171
172
|
const rawTableMap = new Map<string, RawTableDef>();
|
|
173
|
+
// Unmanaged tables — declared via r.unmanagedTable() (EntityTableMeta).
|
|
174
|
+
// Cousin of rawTables: same uniqueness-by-tableName invariant, different
|
|
175
|
+
// storage shape (post-drizzle migrate-runner consumes EntityTableMeta).
|
|
176
|
+
const unmanagedTableMap = new Map<string, UnmanagedTableDef>();
|
|
172
177
|
// Auth-claims hooks — tagged with featureName so the login resolver can
|
|
173
178
|
// auto-prefix each hook's returned keys with "<feature>:".
|
|
174
179
|
const authClaimsHooks: AuthClaimsHookDef[] = [];
|
|
@@ -543,6 +548,20 @@ export function createRegistry(features: readonly FeatureDefinition[]): Registry
|
|
|
543
548
|
rawTableMap.set(rawName, { ...rawDef, featureName: feature.name });
|
|
544
549
|
}
|
|
545
550
|
|
|
551
|
+
// Unmanaged tables — same cross-feature uniqueness invariant as rawTables.
|
|
552
|
+
// Two features registering the same physical tableName would race two
|
|
553
|
+
// CREATE TABLE statements via migrate-runner.
|
|
554
|
+
for (const [umName, umDef] of Object.entries(feature.unmanagedTables ?? {})) {
|
|
555
|
+
const existing = unmanagedTableMap.get(umName);
|
|
556
|
+
if (existing) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`Unmanaged-table "${umName}" registered by both feature "${existing.featureName}" and ` +
|
|
559
|
+
`"${feature.name}". Pick a feature-prefixed tableName to disambiguate.`,
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
unmanagedTableMap.set(umName, { ...umDef, featureName: feature.name });
|
|
563
|
+
}
|
|
564
|
+
|
|
546
565
|
// Claim keys: aggregated by qualified name. Two features cannot collide
|
|
547
566
|
// here (qualified by feature name), but we still guard for explicit
|
|
548
567
|
// correctness — the only way to hit this is a hand-built FeatureDefinition
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
// r.step.read.findMany — load multiple rows from a projection table.
|
|
2
2
|
//
|
|
3
|
-
// Sibling to read.findOne — same tenant-filter caveat (caller-owned)
|
|
4
|
-
//
|
|
5
|
-
// landed under steps.<name>.
|
|
3
|
+
// Sibling to read.findOne — same tenant-filter caveat (caller-owned).
|
|
4
|
+
// Resolves to a row-array (possibly empty), landed under steps.<name>.
|
|
6
5
|
//
|
|
7
6
|
// Optional `limit` — defaults to no-limit (caller-chosen, NOT a
|
|
8
7
|
// guard-rail). Most legitimate uses iterate via r.step.forEach (M.1.6)
|
|
9
8
|
// over the result, where unbounded arrays would be the bug. Set
|
|
10
9
|
// `limit` explicitly when the row-count could grow without bound.
|
|
11
10
|
|
|
12
|
-
import
|
|
11
|
+
import { selectMany, type WhereObject } from "../../db/query";
|
|
13
12
|
import { defineStep } from "../define-step";
|
|
14
13
|
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
15
|
-
import { asQueryTarget } from "./_drizzle-boundary";
|
|
16
14
|
import { resolveOptional } from "./_resolver-utils";
|
|
17
15
|
|
|
18
16
|
type ReadFindManyArgs = {
|
|
19
17
|
readonly name: string;
|
|
20
|
-
readonly table:
|
|
21
|
-
readonly where?: StepResolver<
|
|
18
|
+
readonly table: unknown;
|
|
19
|
+
readonly where?: StepResolver<WhereObject | undefined>;
|
|
22
20
|
readonly limit?: number;
|
|
23
21
|
};
|
|
24
22
|
|
|
@@ -28,10 +26,12 @@ defineStep<ReadFindManyArgs, readonly Record<string, unknown>[]>({
|
|
|
28
26
|
resultKey: (args) => args.name,
|
|
29
27
|
run: async (args, ctx: PipelineCtx) => {
|
|
30
28
|
const where = resolveOptional(args.where, ctx);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const rows = await selectMany(
|
|
30
|
+
ctx.db.raw,
|
|
31
|
+
args.table,
|
|
32
|
+
where,
|
|
33
|
+
args.limit !== undefined ? { limit: args.limit } : undefined,
|
|
34
|
+
);
|
|
35
35
|
return rows as readonly Record<string, unknown>[];
|
|
36
36
|
},
|
|
37
37
|
});
|
|
@@ -39,8 +39,8 @@ defineStep<ReadFindManyArgs, readonly Record<string, unknown>[]>({
|
|
|
39
39
|
export function buildReadFindManyStep(
|
|
40
40
|
name: string,
|
|
41
41
|
opts: {
|
|
42
|
-
readonly table:
|
|
43
|
-
readonly where?: StepResolver<
|
|
42
|
+
readonly table: unknown;
|
|
43
|
+
readonly where?: StepResolver<WhereObject | undefined>;
|
|
44
44
|
readonly limit?: number;
|
|
45
45
|
},
|
|
46
46
|
): StepInstance {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// r.step.read.findOne — load a single row from a projection table.
|
|
2
2
|
//
|
|
3
|
-
// Thin wrapper on
|
|
3
|
+
// Thin wrapper on selectMany(db, table, where, { limit: 1 }) (bun-db).
|
|
4
4
|
// Resolves to the first row or null. Tenant-isolation: the caller's
|
|
5
5
|
// `where` clause is responsible for any tenantId filter — read.findOne
|
|
6
6
|
// does NOT auto-inject one (different from ctx.queryProjection which
|
|
@@ -20,16 +20,15 @@
|
|
|
20
20
|
// fine for "find by uuid", a footgun for "find by tenantId". No
|
|
21
21
|
// runtime check; reviewer responsibility.
|
|
22
22
|
|
|
23
|
-
import
|
|
23
|
+
import { selectMany, type WhereObject } from "../../db/query";
|
|
24
24
|
import { defineStep } from "../define-step";
|
|
25
25
|
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
26
|
-
import { asQueryTarget } from "./_drizzle-boundary";
|
|
27
26
|
import { resolveRequired } from "./_resolver-utils";
|
|
28
27
|
|
|
29
28
|
type ReadFindOneArgs = {
|
|
30
29
|
readonly name: string;
|
|
31
|
-
readonly table:
|
|
32
|
-
readonly where: StepResolver<
|
|
30
|
+
readonly table: unknown;
|
|
31
|
+
readonly where: StepResolver<WhereObject | undefined>;
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
|
|
@@ -38,8 +37,7 @@ defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
|
|
|
38
37
|
resultKey: (args) => args.name,
|
|
39
38
|
run: async (args, ctx: PipelineCtx) => {
|
|
40
39
|
const where = resolveRequired(args.where, ctx);
|
|
41
|
-
const
|
|
42
|
-
const rows = where === undefined ? await query.limit(1) : await query.where(where).limit(1);
|
|
40
|
+
const rows = await selectMany(ctx.db.raw, args.table, where, { limit: 1 });
|
|
43
41
|
return (rows[0] as Record<string, unknown> | undefined) ?? null;
|
|
44
42
|
},
|
|
45
43
|
});
|
|
@@ -47,8 +45,8 @@ defineStep<ReadFindOneArgs, Record<string, unknown> | null>({
|
|
|
47
45
|
export function buildReadFindOneStep(
|
|
48
46
|
name: string,
|
|
49
47
|
opts: {
|
|
50
|
-
readonly table:
|
|
51
|
-
readonly where: StepResolver<
|
|
48
|
+
readonly table: unknown;
|
|
49
|
+
readonly where: StepResolver<WhereObject | undefined>;
|
|
52
50
|
},
|
|
53
51
|
): StepInstance {
|
|
54
52
|
return {
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
// must commit in the same TX as the aggregate-mutation that triggered
|
|
17
17
|
// it (stronger consistency than an async projection). Reviewer judges.
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import { deleteMany, type WhereObject } from "../../db/query";
|
|
20
20
|
import { defineStep } from "../define-step";
|
|
21
21
|
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
22
|
-
import { asQueryTarget } from "./_drizzle-boundary";
|
|
23
22
|
import { resolveRequired } from "./_resolver-utils";
|
|
24
23
|
|
|
25
24
|
// `where` is REQUIRED — table-wide DELETE without a clause is a TRUNCATE
|
|
@@ -28,8 +27,8 @@ import { resolveRequired } from "./_resolver-utils";
|
|
|
28
27
|
// `r.step.unsafeProjectionTruncate` step rather than loosening this
|
|
29
28
|
// type to `SQL | undefined`.
|
|
30
29
|
type UnsafeProjectionDeleteArgs = {
|
|
31
|
-
readonly table:
|
|
32
|
-
readonly where: StepResolver<
|
|
30
|
+
readonly table: unknown;
|
|
31
|
+
readonly where: StepResolver<WhereObject>;
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
defineStep<UnsafeProjectionDeleteArgs, void>({
|
|
@@ -37,7 +36,7 @@ defineStep<UnsafeProjectionDeleteArgs, void>({
|
|
|
37
36
|
defaultFailureStrategy: "throw",
|
|
38
37
|
run: async (args, ctx: PipelineCtx) => {
|
|
39
38
|
const where = resolveRequired(args.where, ctx);
|
|
40
|
-
await ctx.db.
|
|
39
|
+
await deleteMany(ctx.db.raw, args.table, where);
|
|
41
40
|
},
|
|
42
41
|
});
|
|
43
42
|
|