@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
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import { table, text, uuid } from "../../db/dialect";
|
|
4
3
|
import { getStep } from "../define-step";
|
|
5
4
|
import { buildUnsafeProjectionDeleteStep } from "../steps/unsafe-projection-delete";
|
|
6
5
|
import type { PipelineCtx } from "../types/step";
|
|
7
6
|
|
|
8
|
-
const testTable =
|
|
7
|
+
const testTable = table("test_projection", {
|
|
9
8
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
10
9
|
tenantId: uuid("tenant_id").notNull(),
|
|
11
10
|
label: text("label"),
|
|
12
11
|
});
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// bun-db path: step calls deleteMany(ctx.db.raw, table, where) which lands on
|
|
14
|
+
// asRawClient(ctx.db.raw).unsafe(sqlText, params).
|
|
15
|
+
const unsafeMock = mock(async (_sqlText: string, _params: unknown[]): Promise<unknown[]> => []);
|
|
16
|
+
const rawDb = { unsafe: unsafeMock, begin: mock() };
|
|
17
|
+
const ctxDb = { raw: rawDb };
|
|
16
18
|
|
|
17
19
|
const mockCtx = {
|
|
18
|
-
db:
|
|
20
|
+
db: ctxDb,
|
|
19
21
|
event: { type: "test", payload: {} },
|
|
20
22
|
steps: {},
|
|
21
23
|
scope: {},
|
|
@@ -25,16 +27,16 @@ describe("buildUnsafeProjectionDeleteStep", () => {
|
|
|
25
27
|
it("returns a StepInstance with kind unsafeProjectionDelete", () => {
|
|
26
28
|
const step = buildUnsafeProjectionDeleteStep({
|
|
27
29
|
table: testTable,
|
|
28
|
-
where: () =>
|
|
30
|
+
where: () => ({}),
|
|
29
31
|
});
|
|
30
32
|
expect(step.kind).toBe("unsafeProjectionDelete");
|
|
31
33
|
expect(step.args).toMatchObject({ table: testTable });
|
|
32
34
|
});
|
|
33
35
|
|
|
34
|
-
it("accepts a static
|
|
36
|
+
it("accepts a static where clause", () => {
|
|
35
37
|
const step = buildUnsafeProjectionDeleteStep({
|
|
36
38
|
table: testTable,
|
|
37
|
-
where:
|
|
39
|
+
where: { id: "x" },
|
|
38
40
|
});
|
|
39
41
|
expect(step.args).toHaveProperty("table");
|
|
40
42
|
});
|
|
@@ -42,28 +44,31 @@ describe("buildUnsafeProjectionDeleteStep", () => {
|
|
|
42
44
|
|
|
43
45
|
describe("unsafeProjectionDelete run", () => {
|
|
44
46
|
beforeEach(() => {
|
|
45
|
-
|
|
46
|
-
mockDb.delete.mockReturnValue(mockDeleteBuilder);
|
|
47
|
+
mock.clearAllMocks();
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
it("
|
|
50
|
+
it("issues DELETE ... WHERE ... with the resolved where clause", async () => {
|
|
50
51
|
const stepDef = getStep("unsafeProjectionDelete");
|
|
51
52
|
expect(stepDef).toBeDefined();
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
await stepDef!.run({ table: testTable, where: whereClause }, mockCtx);
|
|
54
|
+
await stepDef!.run({ table: testTable, where: { id: "abc" } }, mockCtx);
|
|
55
55
|
|
|
56
|
-
expect(
|
|
57
|
-
|
|
56
|
+
expect(unsafeMock).toHaveBeenCalledTimes(1);
|
|
57
|
+
const [sqlText, params] = unsafeMock.mock.calls[0]!;
|
|
58
|
+
expect(sqlText).toMatch(/DELETE FROM "test_projection"/);
|
|
59
|
+
expect(sqlText).toMatch(/"id" = \$1/);
|
|
60
|
+
expect(params).toEqual(["abc"]);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
63
|
it("resolves a function where-clause before calling delete", async () => {
|
|
61
64
|
const stepDef = getStep("unsafeProjectionDelete");
|
|
62
65
|
|
|
63
|
-
const whereFn =
|
|
66
|
+
const whereFn = mock(() => ({ tenantId: "t1" }));
|
|
64
67
|
await stepDef!.run({ table: testTable, where: whereFn }, mockCtx);
|
|
65
68
|
|
|
66
69
|
expect(whereFn).toHaveBeenCalledWith(mockCtx);
|
|
67
|
-
expect(
|
|
70
|
+
expect(unsafeMock).toHaveBeenCalledTimes(1);
|
|
71
|
+
const [, params] = unsafeMock.mock.calls[0]!;
|
|
72
|
+
expect(params).toEqual(["t1"]);
|
|
68
73
|
});
|
|
69
74
|
});
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import { table, text, uuid } from "../../db/dialect";
|
|
3
3
|
import { getStep } from "../define-step";
|
|
4
4
|
import { buildUnsafeProjectionUpsertStep } from "../steps/unsafe-projection-upsert";
|
|
5
5
|
import type { PipelineCtx } from "../types/step";
|
|
6
6
|
|
|
7
|
-
const testTable =
|
|
7
|
+
const testTable = table("test_projection", {
|
|
8
8
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
9
9
|
tenantId: uuid("tenant_id").notNull(),
|
|
10
10
|
externalId: text("external_id").notNull().unique(),
|
|
11
11
|
label: text("label"),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// New bun-db path: step uses asRawClient(ctx.db.raw).unsafe(sqlText, params).
|
|
15
|
+
// Capture the raw SQL string + params per call instead of the old
|
|
16
|
+
// insert/values/onConflictDoUpdate chain.
|
|
17
|
+
const unsafeMock = mock(async (_sqlText: string, _params: unknown[]) => []);
|
|
18
|
+
const beginMock = mock(async (fn: (tx: unknown) => Promise<unknown>) => fn({}));
|
|
19
|
+
const rawDb = { unsafe: unsafeMock, begin: beginMock };
|
|
20
|
+
const ctxDb = { raw: rawDb };
|
|
17
21
|
|
|
18
22
|
const mockCtx = {
|
|
19
|
-
db:
|
|
23
|
+
db: ctxDb,
|
|
20
24
|
event: { type: "test", payload: {} },
|
|
21
25
|
steps: {},
|
|
22
26
|
scope: {},
|
|
@@ -46,7 +50,7 @@ describe("buildUnsafeProjectionUpsertStep", () => {
|
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
it("accepts a function row resolver", () => {
|
|
49
|
-
const resolver =
|
|
53
|
+
const resolver = mock(() => ({ tenantId: "t1", externalId: "e1" }));
|
|
50
54
|
const step = buildUnsafeProjectionUpsertStep({
|
|
51
55
|
table: testTable,
|
|
52
56
|
on: ["externalId"],
|
|
@@ -67,7 +71,7 @@ describe("buildUnsafeProjectionUpsertStep", () => {
|
|
|
67
71
|
|
|
68
72
|
describe("unsafeProjectionUpsert run", () => {
|
|
69
73
|
beforeEach(() => {
|
|
70
|
-
|
|
74
|
+
mock.clearAllMocks();
|
|
71
75
|
});
|
|
72
76
|
|
|
73
77
|
it("throws when a conflict-key column does not exist on the table", async () => {
|
|
@@ -92,14 +96,19 @@ describe("unsafeProjectionUpsert run", () => {
|
|
|
92
96
|
|
|
93
97
|
await stepDef!.run({ table: testTable, on: ["externalId"], row }, mockCtx);
|
|
94
98
|
|
|
95
|
-
expect(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
expect(unsafeMock).toHaveBeenCalledTimes(1);
|
|
100
|
+
const [sqlText, params] = unsafeMock.mock.calls[0]!;
|
|
101
|
+
expect(sqlText).toMatch(/INSERT INTO "test_projection"/);
|
|
102
|
+
expect(sqlText).toMatch(/ON CONFLICT \("external_id"\) DO UPDATE SET/);
|
|
103
|
+
// SET clause excludes the conflict-key column ("external_id") but
|
|
104
|
+
// includes the other columns (tenant_id, label).
|
|
105
|
+
expect(sqlText).toMatch(/"tenant_id" = \$/);
|
|
106
|
+
expect(sqlText).toMatch(/"label" = \$/);
|
|
107
|
+
expect(sqlText).not.toMatch(/"external_id" = \$\d+,/);
|
|
108
|
+
expect(params).toEqual(["t1", "e1", "hello", "t1", "hello"]);
|
|
100
109
|
});
|
|
101
110
|
|
|
102
|
-
it("calls
|
|
111
|
+
it("calls INSERT ... ON CONFLICT DO UPDATE with the resolved row + conflict targets", async () => {
|
|
103
112
|
const stepDef = getStep("unsafeProjectionUpsert");
|
|
104
113
|
|
|
105
114
|
await stepDef!.run(
|
|
@@ -111,7 +120,9 @@ describe("unsafeProjectionUpsert run", () => {
|
|
|
111
120
|
mockCtx,
|
|
112
121
|
);
|
|
113
122
|
|
|
114
|
-
expect(
|
|
115
|
-
|
|
123
|
+
expect(unsafeMock).toHaveBeenCalled();
|
|
124
|
+
const [sqlText] = unsafeMock.mock.calls[0]!;
|
|
125
|
+
expect(sqlText).toMatch(/INSERT INTO "test_projection"/);
|
|
126
|
+
expect(sqlText).toMatch(/ON CONFLICT \("external_id"\) DO UPDATE/);
|
|
116
127
|
});
|
|
117
128
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it,
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
2
|
import { getStep } from "../define-step";
|
|
3
3
|
import {
|
|
4
4
|
STEP_DISPATCH_AGGREGATE_TYPE,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { buildWebhookSendStep } from "../steps/webhook-send";
|
|
8
8
|
import type { PipelineCtx } from "../types/step";
|
|
9
9
|
|
|
10
|
-
const mockUnsafeAppendEvent =
|
|
10
|
+
const mockUnsafeAppendEvent = mock();
|
|
11
11
|
|
|
12
12
|
const mockCtx = {
|
|
13
13
|
unsafeAppendEvent: mockUnsafeAppendEvent,
|
|
@@ -50,7 +50,7 @@ describe("buildWebhookSendStep", () => {
|
|
|
50
50
|
|
|
51
51
|
describe("webhook.send run", () => {
|
|
52
52
|
beforeEach(() => {
|
|
53
|
-
|
|
53
|
+
mock.clearAllMocks();
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it("appends a step.dispatch-requested system event with the webhook spec", async () => {
|
|
@@ -67,7 +67,7 @@ describe("webhook.send run", () => {
|
|
|
67
67
|
mockCtx,
|
|
68
68
|
);
|
|
69
69
|
|
|
70
|
-
expect(mockUnsafeAppendEvent).
|
|
70
|
+
expect(mockUnsafeAppendEvent).toHaveBeenCalledTimes(1);
|
|
71
71
|
const eventArg = mockUnsafeAppendEvent.mock.calls[0]![0];
|
|
72
72
|
|
|
73
73
|
expect(eventArg.aggregateType).toBe(STEP_DISPATCH_AGGREGATE_TYPE);
|
|
@@ -79,8 +79,8 @@ describe("webhook.send run", () => {
|
|
|
79
79
|
|
|
80
80
|
it("resolves function-based url and body resolvers", async () => {
|
|
81
81
|
const stepDef = getStep("webhook.send");
|
|
82
|
-
const urlFn =
|
|
83
|
-
const bodyFn =
|
|
82
|
+
const urlFn = mock(() => "https://hooks.example/dynamic");
|
|
83
|
+
const bodyFn = mock(() => ({ key: "value" }));
|
|
84
84
|
|
|
85
85
|
await stepDef!.run(
|
|
86
86
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it,
|
|
1
|
+
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
2
|
import { getStep } from "../define-step";
|
|
3
3
|
import {
|
|
4
4
|
SUSPEND_SENTINEL,
|
|
@@ -11,7 +11,7 @@ import { buildWaitStep } from "../steps/wait";
|
|
|
11
11
|
import { buildWaitForEventStep } from "../steps/wait-for-event";
|
|
12
12
|
import type { PipelineCtx } from "../types/step";
|
|
13
13
|
|
|
14
|
-
const mockUnsafeAppendEvent =
|
|
14
|
+
const mockUnsafeAppendEvent = mock();
|
|
15
15
|
|
|
16
16
|
const workflowCtx = {
|
|
17
17
|
unsafeAppendEvent: mockUnsafeAppendEvent,
|
|
@@ -46,7 +46,7 @@ describe("buildWaitStep", () => {
|
|
|
46
46
|
|
|
47
47
|
describe("workflow.wait run", () => {
|
|
48
48
|
beforeEach(() => {
|
|
49
|
-
|
|
49
|
+
mock.clearAllMocks();
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it("throws when used outside defineWorkflow (no ctx.workflow)", async () => {
|
|
@@ -64,7 +64,7 @@ describe("workflow.wait run", () => {
|
|
|
64
64
|
const result = await stepDef!.run({ for: "PT1H" }, workflowCtx);
|
|
65
65
|
|
|
66
66
|
expect(result).toBe(SUSPEND_SENTINEL);
|
|
67
|
-
expect(mockUnsafeAppendEvent).
|
|
67
|
+
expect(mockUnsafeAppendEvent).toHaveBeenCalledTimes(1);
|
|
68
68
|
const eventArg = mockUnsafeAppendEvent.mock.calls[0]![0];
|
|
69
69
|
expect(eventArg.aggregateType).toBe(WORKFLOW_AGGREGATE_TYPE);
|
|
70
70
|
expect(eventArg.type).toBe(WORKFLOW_WAITING_TYPE);
|
|
@@ -108,7 +108,7 @@ describe("buildWaitForEventStep", () => {
|
|
|
108
108
|
|
|
109
109
|
describe("workflow.waitForEvent run", () => {
|
|
110
110
|
beforeEach(() => {
|
|
111
|
-
|
|
111
|
+
mock.clearAllMocks();
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it("throws when used outside defineWorkflow", async () => {
|
|
@@ -129,7 +129,7 @@ describe("workflow.waitForEvent run", () => {
|
|
|
129
129
|
);
|
|
130
130
|
|
|
131
131
|
expect(result).toBe(SUSPEND_SENTINEL);
|
|
132
|
-
expect(mockUnsafeAppendEvent).
|
|
132
|
+
expect(mockUnsafeAppendEvent).toHaveBeenCalledTimes(1);
|
|
133
133
|
const eventArg = mockUnsafeAppendEvent.mock.calls[0]![0];
|
|
134
134
|
expect(eventArg.aggregateType).toBe(WORKFLOW_AGGREGATE_TYPE);
|
|
135
135
|
expect(eventArg.type).toBe(WORKFLOW_WAITING_FOR_EVENT_TYPE);
|
|
@@ -162,7 +162,7 @@ describe("buildRetryStep", () => {
|
|
|
162
162
|
|
|
163
163
|
describe("workflow.retry run", () => {
|
|
164
164
|
beforeEach(() => {
|
|
165
|
-
|
|
165
|
+
mock.clearAllMocks();
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
it("throws when used outside defineWorkflow", async () => {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Unit tests for r.unmanagedTable() — the EntityTableMeta cousin of
|
|
2
|
+
// r.rawTable. Same audit-trail contract, different storage shape (post-
|
|
3
|
+
// drizzle migrate-runner). See define-feature.ts / DX-4.
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { defineUnmanagedTable } from "../../db/entity-table-meta";
|
|
7
|
+
import { defineFeature } from "../define-feature";
|
|
8
|
+
import { createRegistry } from "../registry";
|
|
9
|
+
|
|
10
|
+
const probeMeta = defineUnmanagedTable({
|
|
11
|
+
tableName: "ut_probe",
|
|
12
|
+
columns: [{ name: "id", pgType: "text", notNull: true, primaryKey: true }],
|
|
13
|
+
});
|
|
14
|
+
const probeMetaTwo = defineUnmanagedTable({
|
|
15
|
+
tableName: "ut_probe_two",
|
|
16
|
+
columns: [{ name: "id", pgType: "text", notNull: true, primaryKey: true }],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("r.unmanagedTable — declaration", () => {
|
|
20
|
+
test("rejects duplicate registrations within one feature", () => {
|
|
21
|
+
expect(() =>
|
|
22
|
+
defineFeature("probe", (r) => {
|
|
23
|
+
r.unmanagedTable(probeMeta, { reason: "test" });
|
|
24
|
+
r.unmanagedTable(probeMeta, { reason: "test" });
|
|
25
|
+
}),
|
|
26
|
+
).toThrow(/already registered/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("rejects empty reason", () => {
|
|
30
|
+
expect(() =>
|
|
31
|
+
defineFeature("probe", (r) => {
|
|
32
|
+
r.unmanagedTable(probeMeta, { reason: "" });
|
|
33
|
+
}),
|
|
34
|
+
).toThrow(/options\.reason must be a non-empty string/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("rejects whitespace-only reason", () => {
|
|
38
|
+
expect(() =>
|
|
39
|
+
defineFeature("probe", (r) => {
|
|
40
|
+
r.unmanagedTable(probeMeta, { reason: " " });
|
|
41
|
+
}),
|
|
42
|
+
).toThrow(/options\.reason must be a non-empty string/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("accepts valid registration and stores meta + reason", () => {
|
|
46
|
+
const feature = defineFeature("probe", (r) => {
|
|
47
|
+
r.unmanagedTable(probeMeta, {
|
|
48
|
+
reason: "read-side projection of an event-stream",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
expect(feature.unmanagedTables).toHaveProperty("ut_probe");
|
|
52
|
+
expect(feature.unmanagedTables["ut_probe"]?.reason).toBe(
|
|
53
|
+
"read-side projection of an event-stream",
|
|
54
|
+
);
|
|
55
|
+
expect(feature.unmanagedTables["ut_probe"]?.meta).toBe(probeMeta);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("two unmanaged tables on one feature register under their tableName", () => {
|
|
59
|
+
const feature = defineFeature("dual", (r) => {
|
|
60
|
+
r.unmanagedTable(probeMeta, { reason: "one" });
|
|
61
|
+
r.unmanagedTable(probeMetaTwo, { reason: "two" });
|
|
62
|
+
});
|
|
63
|
+
expect(Object.keys(feature.unmanagedTables).sort()).toEqual(["ut_probe", "ut_probe_two"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("absent unmanagedTables on a feature is ok", () => {
|
|
67
|
+
const feat = defineFeature("plain", () => {
|
|
68
|
+
// no r.unmanagedTable calls
|
|
69
|
+
});
|
|
70
|
+
expect(feat.unmanagedTables).toEqual({});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("createRegistry — unmanagedTable aggregation", () => {
|
|
75
|
+
test("rejects cross-feature tableName collisions at boot", () => {
|
|
76
|
+
// Two features can't share the same physical tableName — migrate-runner
|
|
77
|
+
// would race two CREATE TABLE statements. Boot-validator catches it.
|
|
78
|
+
const featA = defineFeature("a", (r) => {
|
|
79
|
+
r.unmanagedTable(probeMeta, { reason: "first" });
|
|
80
|
+
});
|
|
81
|
+
const featB = defineFeature("b", (r) => {
|
|
82
|
+
r.unmanagedTable(probeMeta, { reason: "second" });
|
|
83
|
+
});
|
|
84
|
+
expect(() => createRegistry([featA, featB])).toThrow(
|
|
85
|
+
/Unmanaged-table "ut_probe" registered by both feature "a" and "b"/,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("two features with distinct tableNames register cleanly", () => {
|
|
90
|
+
const featA = defineFeature("a", (r) => {
|
|
91
|
+
r.unmanagedTable(probeMeta, { reason: "first" });
|
|
92
|
+
});
|
|
93
|
+
const featB = defineFeature("b", (r) => {
|
|
94
|
+
r.unmanagedTable(probeMetaTwo, { reason: "second" });
|
|
95
|
+
});
|
|
96
|
+
expect(() => createRegistry([featA, featB])).not.toThrow();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
// is included here, plus the self-registration via defineStep({ subPaths })
|
|
4
4
|
// gate (Followup #15).
|
|
5
5
|
|
|
6
|
+
import { describe, expect, it } from "bun:test";
|
|
6
7
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { eq } from "drizzle-orm";
|
|
8
|
-
import { pgTable, text, uuid } from "drizzle-orm/pg-core";
|
|
9
|
-
import { describe, expect, it } from "vitest";
|
|
10
8
|
import { z } from "zod";
|
|
9
|
+
import { table as pgTable, text, uuid } from "../../db/dialect";
|
|
11
10
|
import { defineFeature } from "../define-feature";
|
|
12
11
|
import { defineWriteHandler } from "../define-handler";
|
|
13
12
|
import { defineStep } from "../define-step";
|
|
@@ -117,7 +116,7 @@ describe("validateProjectionAllowlist", () => {
|
|
|
117
116
|
perform: pipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
118
117
|
r.step.unsafeProjectionDelete({
|
|
119
118
|
table: widgetsTable,
|
|
120
|
-
where: () =>
|
|
119
|
+
where: () => ({ id: "anything" }),
|
|
121
120
|
}),
|
|
122
121
|
r.step.return({ isSuccess: true as const, data: { ok: true } }),
|
|
123
122
|
]),
|
|
@@ -140,7 +139,7 @@ describe("validateProjectionAllowlist", () => {
|
|
|
140
139
|
perform: pipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
141
140
|
r.step.unsafeProjectionDelete({
|
|
142
141
|
table: demoLogTable,
|
|
143
|
-
where: () =>
|
|
142
|
+
where: () => ({ id: "anything" }),
|
|
144
143
|
}),
|
|
145
144
|
r.step.return({ isSuccess: true as const, data: { ok: true } }),
|
|
146
145
|
]),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from "
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createEntity, createRegistry, createTextField, defineFeature } from "../index";
|
|
3
3
|
import type { ValidationError } from "../validation";
|
|
4
4
|
import { runValidation } from "../validation";
|
|
@@ -177,7 +177,7 @@ export function validateLocatedTimestamps(feature: FeatureDefinition): void {
|
|
|
177
177
|
// in der multi-Variante). Catched at boot, lange bevor drizzle-kit beim
|
|
178
178
|
// generate-Run zickt.
|
|
179
179
|
//
|
|
180
|
-
// `tenantId` als einzige Spalte ist redundant —
|
|
180
|
+
// `tenantId` als einzige Spalte ist redundant — buildEntityTable legt
|
|
181
181
|
// den Index sowieso automatisch an. Wir lassen die Composite-Form erlaubt
|
|
182
182
|
// (`["tenantId", "key"]` ist sinnvoll), nur die rein-tenantId-Single-
|
|
183
183
|
// column-Form blockieren wir.
|
|
@@ -225,13 +225,13 @@ export function validateEntityIndexes(feature: FeatureDefinition): void {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
// UNIQUE-constraint auf tenantId ist semantisch (1:1 tenant→entity)
|
|
228
|
-
// und NICHT redundant —
|
|
228
|
+
// und NICHT redundant — buildEntityTable's auto-Index ist nur ein
|
|
229
229
|
// Performance-Hint, kein constraint. Nur die rein-tenantId-Single-
|
|
230
230
|
// column-non-unique-Form blockieren.
|
|
231
231
|
if (def.columns.length === 1 && def.columns[0] === "tenantId" && !def.unique) {
|
|
232
232
|
throw new Error(
|
|
233
233
|
`${where}: single-column index on "tenantId" is redundant — ` +
|
|
234
|
-
`
|
|
234
|
+
`buildEntityTable always creates one automatically. Remove this entry.`,
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
}
|
|
@@ -16,7 +16,7 @@ export function validateOwnershipRules(
|
|
|
16
16
|
for (const [entityName, entity] of Object.entries(feature.entities)) {
|
|
17
17
|
const columnNames = new Set<string>(Object.keys(entity.fields));
|
|
18
18
|
// Framework-managed columns that rules are allowed to reference too.
|
|
19
|
-
// These are the base columns
|
|
19
|
+
// These are the base columns buildEntityTable adds unconditionally.
|
|
20
20
|
const frameworkColumns = ["id", "tenantId", "version", "insertedAt", "modifiedAt"];
|
|
21
21
|
for (const col of frameworkColumns) columnNames.add(col);
|
|
22
22
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PgTable } from "drizzle-orm/pg-core";
|
|
2
1
|
import type { ZodType, z } from "zod";
|
|
2
|
+
import type { EntityTableMeta } from "../db/entity-table-meta";
|
|
3
3
|
import { toTableName } from "../db/table-builder";
|
|
4
4
|
import { LifecycleHookTypes } from "./constants";
|
|
5
5
|
import type { QueryHandlerDefinition, WriteHandlerDefinition } from "./define-handler";
|
|
@@ -62,6 +62,7 @@ import type {
|
|
|
62
62
|
TreeActionDef,
|
|
63
63
|
TreeActionsHandle,
|
|
64
64
|
TreeChildrenSubscribe,
|
|
65
|
+
UnmanagedTableEntry,
|
|
65
66
|
ValidationHookFn,
|
|
66
67
|
WriteHandlerDef,
|
|
67
68
|
WriteHandlerFn,
|
|
@@ -137,6 +138,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
137
138
|
const projections: Record<string, ProjectionDefinition> = {};
|
|
138
139
|
const multiStreamProjections: Record<string, MultiStreamProjectionDefinition> = {};
|
|
139
140
|
const rawTables: Record<string, RawTableEntry> = {};
|
|
141
|
+
const unmanagedTables: Record<string, UnmanagedTableEntry> = {};
|
|
140
142
|
const authClaimsHooks: AuthClaimsFn[] = [];
|
|
141
143
|
const claimKeys: Record<string, ClaimKeyDefinition> = {};
|
|
142
144
|
const screens: Record<string, ScreenDefinition> = {};
|
|
@@ -759,7 +761,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
759
761
|
httpRoutes[key] = definition;
|
|
760
762
|
},
|
|
761
763
|
|
|
762
|
-
rawTable(rawTableName: string, table:
|
|
764
|
+
rawTable(rawTableName: string, table: unknown, options: RawTableOptions): void {
|
|
763
765
|
// Same kebab guard as r.projection / r.screen / r.nav so authoring-time
|
|
764
766
|
// mistakes surface at the feature file, not deep in registry boot.
|
|
765
767
|
if (!isKebabSegment(rawTableName)) {
|
|
@@ -792,6 +794,38 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
792
794
|
};
|
|
793
795
|
},
|
|
794
796
|
|
|
797
|
+
unmanagedTable(meta: EntityTableMeta, options: RawTableOptions): void {
|
|
798
|
+
// Name comes from the meta itself — apps already give the table a
|
|
799
|
+
// name when calling defineUnmanagedTable, no need to repeat it.
|
|
800
|
+
const tableName = meta.tableName;
|
|
801
|
+
if (!isKebabSegment(tableName.replace(/_/g, "-"))) {
|
|
802
|
+
// EntityTableMeta uses snake_case for tableName (matches Postgres
|
|
803
|
+
// convention); we just guard against truly broken input.
|
|
804
|
+
throw new Error(
|
|
805
|
+
`[Feature ${name}] Unmanaged-table name "${tableName}" must be a ` +
|
|
806
|
+
`valid identifier (lowercase letters, digits, underscores; start with a letter).`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
if (unmanagedTables[tableName]) {
|
|
810
|
+
throw new Error(
|
|
811
|
+
`[Feature ${name}] r.unmanagedTable("${tableName}") already registered. ` +
|
|
812
|
+
`Unmanaged-table names must be unique per feature.`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (typeof options.reason !== "string" || options.reason.trim().length === 0) {
|
|
816
|
+
throw new Error(
|
|
817
|
+
`[Feature ${name}] r.unmanagedTable("${tableName}"): options.reason must be a ` +
|
|
818
|
+
`non-empty string. The reason justifies the audit-trail bypass — ` +
|
|
819
|
+
`if you can't write one, declare data via r.entity() instead.`,
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
unmanagedTables[tableName] = {
|
|
823
|
+
name: tableName,
|
|
824
|
+
meta,
|
|
825
|
+
reason: options.reason,
|
|
826
|
+
};
|
|
827
|
+
},
|
|
828
|
+
|
|
795
829
|
claimKey<T extends ClaimKeyType>(
|
|
796
830
|
shortName: string,
|
|
797
831
|
options: { readonly type: T },
|
|
@@ -906,6 +940,7 @@ export function defineFeature<const TName extends string, TExports = undefined>(
|
|
|
906
940
|
workspaces,
|
|
907
941
|
httpRoutes,
|
|
908
942
|
rawTables,
|
|
943
|
+
unmanagedTables,
|
|
909
944
|
...(treeActions !== undefined && { treeActions }),
|
|
910
945
|
...(treeProvider !== undefined && { treeProvider }),
|
|
911
946
|
...(envSchema !== undefined && { envSchema }),
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
enrichWithReferences,
|
|
8
8
|
} from "../db/eagerload";
|
|
9
9
|
import { createEventStoreExecutor, type EventStoreExecutor } from "../db/event-store-executor";
|
|
10
|
-
import {
|
|
10
|
+
import { buildEntityTable } from "../db/table-builder";
|
|
11
11
|
import { assertUnreachable } from "../utils";
|
|
12
12
|
import { buildInsertSchema, buildUpdateSchema } from "./schema-builder";
|
|
13
13
|
import type { AccessRule, EntityDefinition, QueryHandlerDef, WriteHandlerDef } from "./types";
|
|
@@ -133,7 +133,7 @@ export function defineEntityWriteHandler(
|
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
const table =
|
|
136
|
+
const table = buildEntityTable(entityName, entity);
|
|
137
137
|
const executor = createEventStoreExecutor(table, entity, { entityName });
|
|
138
138
|
|
|
139
139
|
let schema: ZodType;
|
|
@@ -182,7 +182,7 @@ export function defineEntityQueryHandler(
|
|
|
182
182
|
): QueryHandlerDef {
|
|
183
183
|
const { entityName, verb } = parseHandlerName(name, QUERY_VERBS);
|
|
184
184
|
|
|
185
|
-
const table =
|
|
185
|
+
const table = buildEntityTable(entityName, entity);
|
|
186
186
|
const executor = createEventStoreExecutor(table, entity, { entityName });
|
|
187
187
|
|
|
188
188
|
let schema: ZodType;
|
|
@@ -319,14 +319,14 @@ type AnyTable = TableColumns<any>;
|
|
|
319
319
|
//
|
|
320
320
|
// const { table, executor } = createEntityExecutor("counter", counterEntity);
|
|
321
321
|
//
|
|
322
|
-
// Keep using the explicit
|
|
322
|
+
// Keep using the explicit buildEntityTable / createEventStoreExecutor duo
|
|
323
323
|
// when you need search-adapter / entity-cache options on the executor — this
|
|
324
324
|
// helper covers the zero-config case.
|
|
325
325
|
export function createEntityExecutor(
|
|
326
326
|
entityName: string,
|
|
327
327
|
entity: EntityDefinition,
|
|
328
328
|
): { readonly table: AnyTable; readonly executor: EventStoreExecutor } {
|
|
329
|
-
const table =
|
|
329
|
+
const table = buildEntityTable(entityName, entity);
|
|
330
330
|
const executor = createEventStoreExecutor(table, entity, { entityName });
|
|
331
331
|
return { table, executor };
|
|
332
332
|
}
|
package/src/engine/factories.ts
CHANGED
|
@@ -25,7 +25,7 @@ import type {
|
|
|
25
25
|
|
|
26
26
|
// Generic über `R extends true | false` (statt `boolean`) damit
|
|
27
27
|
// `createTextField({ required: true })` literal `required: true` im
|
|
28
|
-
// Return-Type behält. `boolean` würde widenen —
|
|
28
|
+
// Return-Type behält. `boolean` würde widenen — EntityTable<E>'s
|
|
29
29
|
// Mapped-Type könnte dann nicht zwischen `required: true` und
|
|
30
30
|
// `required: false` dispatchen, jede Column würde zu nullable
|
|
31
31
|
// degradieren. Default `R = false` matcht den runtime-default. Pattern
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// `// kumiko-feature-version: 1` — sodass künftige Format-Bumps
|
|
17
17
|
// gezielt migriert werden können (separater Migrator pro Version).
|
|
18
18
|
|
|
19
|
+
import { describe, expect, test } from "bun:test";
|
|
19
20
|
import { Project } from "ts-morph";
|
|
20
|
-
import { describe, expect, test } from "vitest";
|
|
21
21
|
import { parseSourceFile } from "../parse";
|
|
22
22
|
|
|
23
23
|
const CANONICAL_FEATURE = `
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// That is fine for framework-internal code, but the AI Builder must
|
|
8
8
|
// NEVER produce code with ParseErrors. This test pins the contract.
|
|
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
|
|
|
14
14
|
const INLINE_FEATURE = `
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
// generated features and Designer-authored features will follow the
|
|
24
24
|
// inline contract by construction.
|
|
25
25
|
|
|
26
|
+
import { describe, expect, test } from "bun:test";
|
|
26
27
|
import { resolve } from "node:path";
|
|
27
|
-
import { describe, expect, test } from "vitest";
|
|
28
28
|
import { parseFeatureFile } from "../parse";
|
|
29
29
|
import type { FeaturePattern } from "../patterns";
|
|
30
30
|
|
|
@@ -119,7 +119,7 @@ describe("parseFeatureFile against real Kumiko features", () => {
|
|
|
119
119
|
for (const error of result.errors) {
|
|
120
120
|
expect(error.methodName).toMatch(/^[a-zA-Z]+$/);
|
|
121
121
|
expect(error.reason.length).toBeGreaterThan(10);
|
|
122
|
-
expect(error.source.file).toContain(feature.path.split("/").pop());
|
|
122
|
+
expect(error.source.file as string).toContain(feature.path.split("/").pop()!);
|
|
123
123
|
expect(error.source.start.line).toBeGreaterThan(0);
|
|
124
124
|
expect(error.source.raw).toContain("r.");
|
|
125
125
|
}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// and surfaced as UnknownPattern with the correct methodName, so the
|
|
11
11
|
// Designer/AI know the call exists.
|
|
12
12
|
|
|
13
|
+
import { describe, expect, test } from "bun:test";
|
|
13
14
|
import { Project } from "ts-morph";
|
|
14
|
-
import { describe, expect, test } from "vitest";
|
|
15
15
|
import { parseSourceFile } from "../parse";
|
|
16
16
|
|
|
17
17
|
function createProject() {
|