@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
|
@@ -11,56 +11,88 @@
|
|
|
11
11
|
// rejected by boot-validation — domain mutation MUST go through
|
|
12
12
|
// r.step.aggregate.*.
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import type { PgColumn } from "drizzle-orm/pg-core";
|
|
14
|
+
import { executeRawQuery } from "../../db/queries/raw-sql";
|
|
16
15
|
import { defineStep } from "../define-step";
|
|
17
16
|
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
18
|
-
import { asQueryTarget } from "./_drizzle-boundary";
|
|
19
17
|
import { resolveRequired } from "./_resolver-utils";
|
|
20
18
|
|
|
21
19
|
type UnsafeProjectionUpsertArgs = {
|
|
22
|
-
readonly table:
|
|
20
|
+
readonly table: unknown;
|
|
23
21
|
readonly on: readonly string[];
|
|
24
22
|
readonly row: StepResolver<Record<string, unknown>>;
|
|
25
23
|
};
|
|
26
24
|
|
|
25
|
+
// @cast-boundary drizzle-bridge — reads table name + column snake_case
|
|
26
|
+
// names from drizzle Symbol-based metadata without importing drizzle-orm.
|
|
27
|
+
const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
|
|
28
|
+
const KUMIKO_COLUMNS_SYMBOL = Symbol.for("kumiko:schema:Columns");
|
|
29
|
+
|
|
30
|
+
function resolveTableName(table: unknown): string {
|
|
31
|
+
if (typeof table !== "object" || table === null) {
|
|
32
|
+
throw new Error("unsafeProjectionUpsert: table is not an object");
|
|
33
|
+
}
|
|
34
|
+
const name = (table as Record<symbol, unknown>)[KUMIKO_NAME_SYMBOL];
|
|
35
|
+
if (typeof name !== "string") {
|
|
36
|
+
throw new Error("unsafeProjectionUpsert: table has no kumiko:schema:Name symbol");
|
|
37
|
+
}
|
|
38
|
+
return name;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveColumnName(table: unknown, field: string): string {
|
|
42
|
+
if (typeof table !== "object" || table === null) return field;
|
|
43
|
+
const cols = (table as Record<symbol, unknown>)[KUMIKO_COLUMNS_SYMBOL];
|
|
44
|
+
if (typeof cols !== "object" || cols === null) return field;
|
|
45
|
+
const col = (cols as Record<string, unknown>)[field];
|
|
46
|
+
if (typeof col === "object" && col !== null) {
|
|
47
|
+
const nameVal = (col as Record<string, unknown>)["name"];
|
|
48
|
+
if (typeof nameVal === "string") return nameVal;
|
|
49
|
+
}
|
|
50
|
+
return field;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function quoteIdent(name: string): string {
|
|
54
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
55
|
+
}
|
|
56
|
+
|
|
27
57
|
defineStep<UnsafeProjectionUpsertArgs, void>({
|
|
28
58
|
kind: "unsafeProjectionUpsert",
|
|
29
59
|
defaultFailureStrategy: "throw",
|
|
30
60
|
run: async (args, ctx: PipelineCtx) => {
|
|
31
61
|
const resolvedRow = resolveRequired(args.row, ctx);
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
throw new Error(`unsafeProjectionUpsert: column "${key}" not found on target table`);
|
|
63
|
+
// Validate conflict-key columns exist in the row.
|
|
64
|
+
for (const key of args.on) {
|
|
65
|
+
if (!(key in resolvedRow)) {
|
|
66
|
+
throw new Error(`unsafeProjectionUpsert: column "${key}" not found in row`);
|
|
38
67
|
}
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tableName = resolveTableName(args.table);
|
|
71
|
+
const entries = Object.entries(resolvedRow);
|
|
72
|
+
const params: unknown[] = [];
|
|
41
73
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
const colNames = entries.map(([k]) => quoteIdent(resolveColumnName(args.table, k)));
|
|
75
|
+
const placeholders = entries.map((_, i) => `$${i + 1}`);
|
|
76
|
+
for (const [, v] of entries) params.push(v);
|
|
77
|
+
|
|
78
|
+
const conflictCols = args.on
|
|
79
|
+
.map((k) => quoteIdent(resolveColumnName(args.table, k)))
|
|
80
|
+
.join(", ");
|
|
81
|
+
|
|
82
|
+
// SET clause excludes conflict-key columns.
|
|
83
|
+
const setClauses: string[] = [];
|
|
84
|
+
let paramIdx = entries.length + 1;
|
|
85
|
+
for (const [k, v] of entries) {
|
|
86
|
+
if (args.on.includes(k)) continue;
|
|
87
|
+
setClauses.push(`${quoteIdent(resolveColumnName(args.table, k))} = $${paramIdx++}`);
|
|
88
|
+
params.push(v);
|
|
47
89
|
}
|
|
48
90
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// `as never` (not `as any`) — never is contravariantly assignable to
|
|
55
|
-
// every drizzle Insert-shape; explicit "this bypass cannot be made
|
|
56
|
-
// type-safe without lifting <TTable extends Table>" marker.
|
|
57
|
-
await ctx.db
|
|
58
|
-
.insert(asQueryTarget(args.table))
|
|
59
|
-
.values(resolvedRow as never)
|
|
60
|
-
.onConflictDoUpdate({
|
|
61
|
-
target: conflictTargets as unknown as PgColumn[],
|
|
62
|
-
set: updateSet as never,
|
|
63
|
-
});
|
|
91
|
+
const sqlText =
|
|
92
|
+
`INSERT INTO ${quoteIdent(tableName)} (${colNames.join(", ")}) VALUES (${placeholders.join(", ")}) ` +
|
|
93
|
+
`ON CONFLICT (${conflictCols}) DO UPDATE SET ${setClauses.join(", ")}`;
|
|
94
|
+
|
|
95
|
+
await executeRawQuery(ctx.db.raw, sqlText, params);
|
|
64
96
|
},
|
|
65
97
|
});
|
|
66
98
|
|
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
4
|
+
// PgTable historically came from drizzle-orm/pg-core; the native dialect
|
|
5
|
+
// no longer carries drizzle internal class types. Every caller really
|
|
6
|
+
// needs "an opaque table-object with Symbol-based introspection".
|
|
7
|
+
type PgTable = unknown;
|
|
8
|
+
|
|
3
9
|
import type { QueryHandlerDefinition, WriteHandlerDefinition } from "../define-handler";
|
|
4
10
|
import type {
|
|
5
11
|
ConfigKeyDefinition,
|
|
@@ -143,6 +149,23 @@ export type RawTableDef = RawTableEntry & {
|
|
|
143
149
|
readonly featureName: string;
|
|
144
150
|
};
|
|
145
151
|
|
|
152
|
+
// --- Unmanaged tables (declared by features via r.unmanagedTable()) ---
|
|
153
|
+
|
|
154
|
+
/** Per-feature unmanaged-table registration. `meta` is the
|
|
155
|
+
* `EntityTableMeta` (framework-native shape used by `migrate-runner`).
|
|
156
|
+
* The `reason` justifies the bypass at the registration site — same
|
|
157
|
+
* contract as `r.rawTable`. */
|
|
158
|
+
export type UnmanagedTableEntry = {
|
|
159
|
+
readonly name: string;
|
|
160
|
+
readonly meta: EntityTableMeta;
|
|
161
|
+
readonly reason: string;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Registry-aggregated unmanaged-table — adds the owning feature name. */
|
|
165
|
+
export type UnmanagedTableDef = UnmanagedTableEntry & {
|
|
166
|
+
readonly featureName: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
146
169
|
// --- Feature Definition (output of defineFeature) ---
|
|
147
170
|
|
|
148
171
|
export type FeatureDefinition = {
|
|
@@ -176,7 +199,7 @@ export type FeatureDefinition = {
|
|
|
176
199
|
// F3 search-payload-extension — per-entity contributors that add flat fields
|
|
177
200
|
// to the search-index payload during indexing. Keyed by entityName. Wrapped
|
|
178
201
|
// in OwnedFn for feature-toggle filtering (consistent with postQuery-Hooks).
|
|
179
|
-
readonly searchPayloadExtensions
|
|
202
|
+
readonly searchPayloadExtensions?: Readonly<
|
|
180
203
|
Record<string, readonly OwnedFn<SearchPayloadContributorFn>[]>
|
|
181
204
|
>;
|
|
182
205
|
readonly configKeys: Readonly<Record<string, ConfigKeyDefinition>>;
|
|
@@ -269,6 +292,12 @@ export type FeatureDefinition = {
|
|
|
269
292
|
// system. Keyed by feature-local short name. The registry attaches
|
|
270
293
|
// featureName on aggregation, lifting RawTableEntry → RawTableDef.
|
|
271
294
|
readonly rawTables: Readonly<Record<string, RawTableEntry>>;
|
|
295
|
+
// Unmanaged tables declared via r.unmanagedTable() — `EntityTableMeta`
|
|
296
|
+
// shape (post-drizzle), keyed by feature-local table-name. Cousin of
|
|
297
|
+
// rawTables: same bypass-justification contract, different storage
|
|
298
|
+
// shape. `kumiko schema generate` aggregates these alongside
|
|
299
|
+
// r.entity()-derived metas to build the full schema.
|
|
300
|
+
readonly unmanagedTables: Readonly<Record<string, UnmanagedTableEntry>>;
|
|
272
301
|
// Optional Zod-schema for env-vars this feature reads at runtime.
|
|
273
302
|
// Declared via `r.envSchema(z.object({...}))`. `composeEnvSchema` reads
|
|
274
303
|
// this to build one app-wide schema for boot-validation + dry-run
|
|
@@ -577,6 +606,22 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
577
606
|
// declare data via `r.entity()` instead.
|
|
578
607
|
rawTable(name: string, table: PgTable, options: RawTableOptions): void;
|
|
579
608
|
|
|
609
|
+
// Declare an "unmanaged" framework-native table (post-drizzle).
|
|
610
|
+
// EntityTableMeta carries the same column-shape that r.entity() builds,
|
|
611
|
+
// minus the audit-trail + base-columns scaffolding — used for read-side
|
|
612
|
+
// projections of event-streams (delivery-attempts, job-run-logs) where
|
|
613
|
+
// r.entity()'s aggregate-lifecycle assumptions don't fit.
|
|
614
|
+
//
|
|
615
|
+
// The `meta` argument is the result of `defineUnmanagedTable(...)` from
|
|
616
|
+
// `@cosmicdrift/kumiko-framework/db`. Reason-justification + audit-trail
|
|
617
|
+
// contract identical to `r.rawTable`.
|
|
618
|
+
//
|
|
619
|
+
// Why this exists separate from `r.rawTable`: rawTable carries a Drizzle
|
|
620
|
+
// `PgTable` (legacy), unmanagedTable carries the new `EntityTableMeta`
|
|
621
|
+
// shape that `migrate-runner` consumes. After the full drizzle-cut they
|
|
622
|
+
// will likely merge; for now they coexist.
|
|
623
|
+
unmanagedTable(meta: EntityTableMeta, options: RawTableOptions): void;
|
|
624
|
+
|
|
580
625
|
// Register the tree-actions schema for this feature — a map of
|
|
581
626
|
// action-name → action-definition (with optional typed args). At-most-
|
|
582
627
|
// one call per feature.
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
// accepted at the type layer during migration: features that pass an
|
|
6
6
|
// array are auto-normalized to { [role]: "all" } at registry build.
|
|
7
7
|
// Long-term: string[] disappears.
|
|
8
|
-
import type { SQL } from "drizzle-orm";
|
|
9
8
|
import type { OwnershipMap } from "../ownership";
|
|
10
9
|
|
|
11
10
|
export type FieldAccess = {
|
|
@@ -475,7 +474,7 @@ export function isFileField(field: FieldDefinition | undefined): field is AnyFil
|
|
|
475
474
|
export type TransitionMap = Readonly<Record<string, readonly string[]>>;
|
|
476
475
|
|
|
477
476
|
/** Composite-Index auf einer Entity. Spalten werden via field-Name
|
|
478
|
-
* referenziert (camelCase).
|
|
477
|
+
* referenziert (camelCase). buildEntityTable mapped sie auf snake_case-
|
|
479
478
|
* Spaltennamen und benennt den Index nach Convention:
|
|
480
479
|
*
|
|
481
480
|
* <table>_<col1>_<col2>_idx (non-unique)
|
|
@@ -484,7 +483,7 @@ export type TransitionMap = Readonly<Record<string, readonly string[]>>;
|
|
|
484
483
|
* Eine `name`-Override ist erlaubt — Convention-Bruch in Bestandscode
|
|
485
484
|
* vermeidet Migration-Churn beim Refactor.
|
|
486
485
|
*
|
|
487
|
-
* Single-column indices über `tenantId` sind redundant (
|
|
486
|
+
* Single-column indices über `tenantId` sind redundant (buildEntityTable
|
|
488
487
|
* legt die immer automatisch an); die Boot-Validation warnt. */
|
|
489
488
|
export type EntityIndexDef = {
|
|
490
489
|
readonly columns: readonly [string, ...string[]];
|
|
@@ -503,7 +502,7 @@ export type EntityIndexDef = {
|
|
|
503
502
|
* man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
|
|
504
503
|
* statt voller Tabelle).
|
|
505
504
|
*/
|
|
506
|
-
readonly where?:
|
|
505
|
+
readonly where?: unknown;
|
|
507
506
|
};
|
|
508
507
|
|
|
509
508
|
export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
|
|
@@ -517,7 +516,7 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
|
|
|
517
516
|
/** Allowed state transitions per field. Boot validates against select options. */
|
|
518
517
|
readonly transitions?: Readonly<Record<string, TransitionMap>>;
|
|
519
518
|
/** Composite-Indices über mehrere Felder. Single-column FK-Indices und
|
|
520
|
-
* der tenant_id-Index werden weiterhin automatisch von
|
|
519
|
+
* der tenant_id-Index werden weiterhin automatisch von buildEntityTable
|
|
521
520
|
* angelegt — diese Liste ist nur für Custom-Indices die der Author
|
|
522
521
|
* explizit deklariert (z.B. `{ unique: true, columns: ["key", "tenantId", "userId"] }`). */
|
|
523
522
|
readonly indexes?: readonly EntityIndexDef[];
|
package/src/engine/types/step.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// (see TS-typing notes in the design doc). M.1 uses unsafeAppendEvent
|
|
10
10
|
// semantics under the hood for r.step.aggregate.appendEvent.
|
|
11
11
|
|
|
12
|
-
import type { SQL, Table } from "drizzle-orm";
|
|
13
12
|
import type { EventStoreExecutor } from "../../db/event-store-executor";
|
|
13
|
+
import type { WhereObject } from "../../db/query";
|
|
14
14
|
import type { KumikoEventTypeMap } from "./event-type-map";
|
|
15
15
|
import type { HandlerContext, WriteEvent, WriteResult } from "./handlers";
|
|
16
16
|
import type { SaveContext } from "./hooks";
|
|
@@ -200,31 +200,31 @@ export type StepNamespace = {
|
|
|
200
200
|
// and NOT registered as an aggregate-table via r.entity. See
|
|
201
201
|
// step-vocabulary.md "Was unsafeProjection.* überspringt".
|
|
202
202
|
readonly unsafeProjectionUpsert: (args: {
|
|
203
|
-
readonly table:
|
|
203
|
+
readonly table: unknown;
|
|
204
204
|
readonly on: readonly string[];
|
|
205
205
|
readonly row: StepResolver<Record<string, unknown>>;
|
|
206
206
|
}) => StepInstance;
|
|
207
207
|
// Sibling: delete row(s) from a read-side projection table. Same
|
|
208
208
|
// boot-validation contract as unsafeProjectionUpsert.
|
|
209
209
|
readonly unsafeProjectionDelete: (args: {
|
|
210
|
-
readonly table:
|
|
211
|
-
readonly where: StepResolver<
|
|
210
|
+
readonly table: unknown;
|
|
211
|
+
readonly where: StepResolver<WhereObject>;
|
|
212
212
|
}) => StepInstance;
|
|
213
|
-
// Read sub-namespace — thin wrapper on
|
|
214
|
-
// tenant-filter (does NOT auto-inject like ctx.queryProjection does).
|
|
213
|
+
// Read sub-namespace — thin wrapper on selectMany/fetchOne (bun-db).
|
|
214
|
+
// Caller-owned tenant-filter (does NOT auto-inject like ctx.queryProjection does).
|
|
215
215
|
readonly read: {
|
|
216
216
|
readonly findOne: (
|
|
217
217
|
name: string,
|
|
218
218
|
opts: {
|
|
219
|
-
readonly table:
|
|
220
|
-
readonly where: StepResolver<
|
|
219
|
+
readonly table: unknown;
|
|
220
|
+
readonly where: StepResolver<WhereObject | undefined>;
|
|
221
221
|
},
|
|
222
222
|
) => StepInstance;
|
|
223
223
|
readonly findMany: (
|
|
224
224
|
name: string,
|
|
225
225
|
opts: {
|
|
226
|
-
readonly table:
|
|
227
|
-
readonly where?: StepResolver<
|
|
226
|
+
readonly table: unknown;
|
|
227
|
+
readonly where?: StepResolver<WhereObject | undefined>;
|
|
228
228
|
readonly limit?: number;
|
|
229
229
|
},
|
|
230
230
|
) => StepInstance;
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
// caught by this validator. A future lint-rule will enforce the contract
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
|
|
26
|
-
import { getTableName, type Table } from "drizzle-orm";
|
|
27
26
|
import { getStep } from "./define-step";
|
|
28
27
|
import { buildPipelineSteps } from "./pipeline";
|
|
29
28
|
import type { FeatureDefinition, SessionUser, TenantId, WriteEvent } from "./types";
|
|
@@ -56,7 +55,28 @@ function* walkAllSteps(steps: readonly StepInstance[]): Generator<StepInstance,
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
// @cast-boundary drizzle-bridge — reads table name from drizzle Symbol
|
|
59
|
+
// without importing drizzle-orm (bun-db pattern, see bun-db/query.ts).
|
|
60
|
+
const KUMIKO_NAME_SYMBOL = Symbol.for("kumiko:schema:Name");
|
|
61
|
+
|
|
62
|
+
function resolveTableNameFromStep(table: unknown): string {
|
|
63
|
+
if (typeof table === "object" && table !== null) {
|
|
64
|
+
// EntityTableMeta discriminator
|
|
65
|
+
if (
|
|
66
|
+
"source" in table &&
|
|
67
|
+
"tableName" in table &&
|
|
68
|
+
typeof (table as Record<string, unknown>)["tableName"] === "string"
|
|
69
|
+
) {
|
|
70
|
+
return (table as Record<string, unknown>)["tableName"] as string;
|
|
71
|
+
}
|
|
72
|
+
// drizzle pgTable
|
|
73
|
+
const name = (table as Record<symbol, unknown>)[KUMIKO_NAME_SYMBOL];
|
|
74
|
+
if (typeof name === "string") return name;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`validate-projection-allowlist: cannot resolve table name from ${String(table)}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type UnsafeProjectionStepArgs = { readonly table: unknown };
|
|
60
80
|
|
|
61
81
|
const DUMMY_USER: SessionUser = {
|
|
62
82
|
id: "00000000-0000-0000-0000-000000000000",
|
|
@@ -137,7 +157,7 @@ export function validateProjectionAllowlist(features: readonly FeatureDefinition
|
|
|
137
157
|
`without a \`table\` argument.`,
|
|
138
158
|
);
|
|
139
159
|
}
|
|
140
|
-
const tableName =
|
|
160
|
+
const tableName = resolveTableNameFromStep(stepArgs.table);
|
|
141
161
|
|
|
142
162
|
const aggregateOwner = aggregateTables.get(tableName);
|
|
143
163
|
if (aggregateOwner) {
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
// so a future refactor that drops the merge fails here instead of silently
|
|
13
13
|
// regressing to the Welle-2.5 state.
|
|
14
14
|
|
|
15
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
17
18
|
import { createRegistry, defineFeature } from "../../engine";
|
|
18
19
|
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
19
20
|
import { createEventConsumerStateTable } from "../../pipeline";
|
|
20
|
-
import {
|
|
21
|
+
import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
|
|
21
22
|
import { waitFor } from "../../testing";
|
|
22
23
|
import { createAllInOneEntrypoint } from "../index";
|
|
23
24
|
|
|
@@ -72,7 +73,7 @@ const mixedLaneFeature = defineFeature("mixed", (r) => {
|
|
|
72
73
|
const JWT = "entrypoint-wiring-test-secret-must-be-32-chars!";
|
|
73
74
|
const adminUser = TestUsers.admin;
|
|
74
75
|
|
|
75
|
-
let testDb:
|
|
76
|
+
let testDb: BunTestDb;
|
|
76
77
|
let testRedis: TestRedis;
|
|
77
78
|
|
|
78
79
|
beforeAll(async () => {
|
package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts}
RENAMED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
// guard — buildServer always wires an SSE consumer so this only
|
|
12
12
|
// fires with systemConsumers explicitly disabled).
|
|
13
13
|
|
|
14
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
16
17
|
import { createRegistry, defineFeature } from "../../engine";
|
|
17
18
|
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
18
19
|
import { createEventConsumerStateTable } from "../../pipeline";
|
|
19
|
-
import {
|
|
20
|
+
import { createTestRedis, type TestRedis } from "../../stack";
|
|
20
21
|
import { createAllInOneEntrypoint, createApiEntrypoint, createWorkerEntrypoint } from "../index";
|
|
21
22
|
|
|
22
23
|
const splitFeature = defineFeature("split", (r) => {
|
|
@@ -38,7 +39,7 @@ function uniquePrefix(label: string): string {
|
|
|
38
39
|
return `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
let testDb:
|
|
42
|
+
let testDb: BunTestDb;
|
|
42
43
|
let testRedis: TestRedis;
|
|
43
44
|
|
|
44
45
|
beforeAll(async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from "
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { defineFeature } from "../../engine/define-feature";
|
|
4
4
|
import { camelCase, composeEnvSchema, KumikoBootError, parseEnv, pulumiConfigKey } from "../index";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { NotFoundError } from "../classes";
|
|
3
|
+
import { FrameworkReasons } from "../reasons";
|
|
4
|
+
import { buildInvalidTransitionDetails } from "../transition-details";
|
|
5
|
+
import { reraiseAsKumikoError, toWriteErrorInfo, writeFailure } from "../write-error-info";
|
|
6
|
+
|
|
7
|
+
describe("writeFailure", () => {
|
|
8
|
+
test("wraps KumikoError into WriteFailure envelope", () => {
|
|
9
|
+
const failure = writeFailure(new NotFoundError("invoice", "inv-1"));
|
|
10
|
+
expect(failure.isSuccess).toBe(false);
|
|
11
|
+
expect(failure.error.code).toBe("not_found");
|
|
12
|
+
expect(failure.error.httpStatus).toBe(404);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("reraiseAsKumikoError", () => {
|
|
17
|
+
test("round-trips WriteErrorInfo through KumikoError", () => {
|
|
18
|
+
const info = toWriteErrorInfo(new NotFoundError("task", 7));
|
|
19
|
+
const err = reraiseAsKumikoError(info);
|
|
20
|
+
expect(err.code).toBe("not_found");
|
|
21
|
+
expect(err.httpStatus).toBe(404);
|
|
22
|
+
expect(err.message).toContain("task");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("buildInvalidTransitionDetails", () => {
|
|
27
|
+
test("builds structured from/to/allowed + message", () => {
|
|
28
|
+
const details = buildInvalidTransitionDetails("draft", "paid", ["sent"]);
|
|
29
|
+
expect(details).toMatchObject({
|
|
30
|
+
from: "draft",
|
|
31
|
+
to: "paid",
|
|
32
|
+
allowed: ["sent"],
|
|
33
|
+
});
|
|
34
|
+
expect(details.message).toContain("draft");
|
|
35
|
+
expect(details.message).toContain("sent");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("FrameworkReasons", () => {
|
|
40
|
+
test("exposes stable snake_case reason codes", () => {
|
|
41
|
+
expect(FrameworkReasons.invalidTransition).toBe("invalid_transition");
|
|
42
|
+
expect(FrameworkReasons.staleState).toBe("stale_state");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { FieldIssue as FrameworkFieldIssue } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import type { FieldIssue as HeadlessFieldIssue } from "@cosmicdrift/kumiko-headless";
|
|
4
|
+
|
|
5
|
+
describe("FieldIssue cross-package contract", () => {
|
|
6
|
+
test("framework and headless FieldIssue shapes are assignable", () => {
|
|
7
|
+
const frameworkIssue: FrameworkFieldIssue = {
|
|
8
|
+
path: "title",
|
|
9
|
+
code: "too_small",
|
|
10
|
+
i18nKey: "errors.validation.too_small",
|
|
11
|
+
params: { minimum: 1 },
|
|
12
|
+
};
|
|
13
|
+
const headlessIssue: HeadlessFieldIssue = frameworkIssue;
|
|
14
|
+
expect(headlessIssue.path).toBe("title");
|
|
15
|
+
});
|
|
16
|
+
});
|
package/src/errors/classes.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
+
import { toSnakeCase } from "../utils/case";
|
|
2
|
+
import type { FieldIssue } from "./field-issue";
|
|
1
3
|
import { type ErrorOpts, KumikoError } from "./kumiko-error";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
// hook-derived validation errors so the client sees one list.
|
|
5
|
-
export type ValidationFieldIssue = {
|
|
6
|
-
readonly path: string;
|
|
7
|
-
readonly code: string;
|
|
8
|
-
readonly i18nKey: string;
|
|
9
|
-
readonly params?: Readonly<Record<string, unknown>>;
|
|
10
|
-
};
|
|
5
|
+
export type { FieldIssue, ValidationFieldIssue } from "./field-issue";
|
|
11
6
|
|
|
12
7
|
export type ValidationDetails = {
|
|
13
|
-
readonly fields: readonly
|
|
8
|
+
readonly fields: readonly FieldIssue[];
|
|
14
9
|
};
|
|
15
10
|
|
|
16
11
|
export class ValidationError extends KumikoError {
|
|
@@ -89,7 +84,7 @@ export class NotFoundError extends KumikoError {
|
|
|
89
84
|
// The reason string follows `<snake_entity>_not_found` — keeps a stable,
|
|
90
85
|
// client-friendly tag that survives wire serialization even if the entity
|
|
91
86
|
// name is later renamed for display purposes.
|
|
92
|
-
const reason = `${
|
|
87
|
+
const reason = `${toSnakeCase(entity)}_not_found`;
|
|
93
88
|
const details: NotFoundDetails & { reason: string } = { reason, entity, id: idStr };
|
|
94
89
|
super({
|
|
95
90
|
message: idStr !== undefined ? `${entity} ${idStr} not found` : `${entity} not found`,
|
|
@@ -101,15 +96,6 @@ export class NotFoundError extends KumikoError {
|
|
|
101
96
|
}
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
// Accepts camelCase OR kebab-case entity names and produces snake_case for
|
|
105
|
-
// the reason tag. New code uses kebab; legacy camelCase still flows through.
|
|
106
|
-
function toSnake(s: string): string {
|
|
107
|
-
return s
|
|
108
|
-
.replace(/-/g, "_")
|
|
109
|
-
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
110
|
-
.toLowerCase();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
99
|
// Generic 409. Features that need a narrower shape should subclass (see
|
|
114
100
|
// VersionConflictError) — this way the HTTP layer stays uniform while callers
|
|
115
101
|
// can still instanceof on the concrete subtype in handlers.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Canonical per-field validation issue shape — shared between server-side
|
|
2
|
+
// ValidationError, Zod-bridge, and client-side DispatcherError.details.fields.
|
|
3
|
+
export type FieldIssue = {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly i18nKey: string;
|
|
7
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** @deprecated Use `FieldIssue` — kept for existing imports. */
|
|
11
|
+
export type ValidationFieldIssue = FieldIssue;
|
package/src/errors/index.ts
CHANGED
package/src/errors/zod-bridge.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZodError, ZodIssue } from "zod";
|
|
2
|
-
import { ValidationError
|
|
2
|
+
import { ValidationError } from "./classes";
|
|
3
|
+
import type { FieldIssue } from "./field-issue";
|
|
3
4
|
|
|
4
5
|
// Zod issues carry a .code and sometimes issue-specific params (min, max, etc).
|
|
5
6
|
// We surface those under `params` so the client can render "must be at least N"
|
|
@@ -24,7 +25,7 @@ const ISSUE_PARAM_KEYS = [
|
|
|
24
25
|
] as const;
|
|
25
26
|
|
|
26
27
|
export function validationErrorFromZod(error: ZodError): ValidationError {
|
|
27
|
-
const fields = error.issues.map<
|
|
28
|
+
const fields = error.issues.map<FieldIssue>((issue) => {
|
|
28
29
|
const params = extractIssueParams(issue);
|
|
29
30
|
return {
|
|
30
31
|
path: issue.path.map(String).join(".") || "(root)",
|