@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
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
// event-dispatcher — at-least-once delivery, strictly ordered by events.id
|
|
7
7
|
// per MSP consumer, dead-letters on repeated handler failures.
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
9
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { integer as pgInteger, table as pgTable, uuid as pgUuid } from "../../db/dialect";
|
|
13
12
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
14
|
-
import {
|
|
13
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
14
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
15
15
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
16
16
|
import {
|
|
17
17
|
createTestUser,
|
|
@@ -28,13 +28,13 @@ const shipmentEntity = createEntity({
|
|
|
28
28
|
table: "read_msp_shipments",
|
|
29
29
|
fields: { customer: createTextField({ required: true }) },
|
|
30
30
|
});
|
|
31
|
-
const shipmentTable =
|
|
31
|
+
const shipmentTable = buildEntityTable("msp-shipment", shipmentEntity);
|
|
32
32
|
|
|
33
33
|
const refundEntity = createEntity({
|
|
34
34
|
table: "read_msp_refunds",
|
|
35
35
|
fields: { customer: createTextField({ required: true }) },
|
|
36
36
|
});
|
|
37
|
-
const refundTable =
|
|
37
|
+
const refundTable = buildEntityTable("msp-refund", refundEntity);
|
|
38
38
|
|
|
39
39
|
// Cross-cutting MSP: one row per customer, sums shipments − refunds. Key
|
|
40
40
|
// differences from a single-stream projection:
|
|
@@ -68,41 +68,19 @@ const mspFeature = defineFeature("msptest", (r) => {
|
|
|
68
68
|
apply: {
|
|
69
69
|
[shipmentBilled.name]: async (event, tx) => {
|
|
70
70
|
const p = event.payload as { customer: string; cents: number };
|
|
71
|
-
await tx
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
tenantId: event.tenantId,
|
|
76
|
-
shipments: 1,
|
|
77
|
-
refunds: 0,
|
|
78
|
-
netCents: p.cents,
|
|
79
|
-
})
|
|
80
|
-
.onConflictDoUpdate({
|
|
81
|
-
target: customerBalanceTable.customer,
|
|
82
|
-
set: {
|
|
83
|
-
shipments: sql`${customerBalanceTable.shipments} + 1`,
|
|
84
|
-
netCents: sql`${customerBalanceTable.netCents} + ${p.cents}`,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
71
|
+
await asRawClient(tx).unsafe(
|
|
72
|
+
`INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 1, 0, $3) ON CONFLICT (customer) DO UPDATE SET shipments = read_msp_customer_balance.shipments + 1, net_cents = read_msp_customer_balance.net_cents + $3`,
|
|
73
|
+
[p.customer, event.tenantId, p.cents],
|
|
74
|
+
);
|
|
87
75
|
},
|
|
88
76
|
[refundIssued.name]: async (event, tx) => {
|
|
89
77
|
const p = event.payload as { customer: string; cents: number };
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
refunds: 1,
|
|
97
|
-
netCents: -p.cents,
|
|
98
|
-
})
|
|
99
|
-
.onConflictDoUpdate({
|
|
100
|
-
target: customerBalanceTable.customer,
|
|
101
|
-
set: {
|
|
102
|
-
refunds: sql`${customerBalanceTable.refunds} + 1`,
|
|
103
|
-
netCents: sql`${customerBalanceTable.netCents} - ${p.cents}`,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
78
|
+
// -$3 ohne cast wird vom planner als "unary minus on unknown type"
|
|
79
|
+
// mehrdeutig — wir casten $3 explizit auf integer.
|
|
80
|
+
await asRawClient(tx).unsafe(
|
|
81
|
+
`INSERT INTO "read_msp_customer_balance" (customer, tenant_id, shipments, refunds, net_cents) VALUES ($1::uuid, $2::uuid, 0, 1, -$3::integer) ON CONFLICT (customer) DO UPDATE SET refunds = read_msp_customer_balance.refunds + 1, net_cents = read_msp_customer_balance.net_cents - $3::integer`,
|
|
82
|
+
[p.customer, event.tenantId, p.cents],
|
|
83
|
+
);
|
|
106
84
|
},
|
|
107
85
|
},
|
|
108
86
|
});
|
|
@@ -207,10 +185,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
207
185
|
// Drain the dispatcher — MSPs run async.
|
|
208
186
|
await stack.eventDispatcher?.runOnce();
|
|
209
187
|
|
|
210
|
-
const rows = await stack.db
|
|
211
|
-
.select()
|
|
212
|
-
.from(customerBalanceTable)
|
|
213
|
-
.orderBy(customerBalanceTable.customer);
|
|
188
|
+
const rows = await selectMany(stack.db, customerBalanceTable);
|
|
214
189
|
const byCustomer = new Map(rows.map((r) => [r.customer, r]));
|
|
215
190
|
|
|
216
191
|
expect(byCustomer.get(customerA)).toMatchObject({
|
|
@@ -239,10 +214,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
239
214
|
expect(pass2?.byConsumer[mspName]?.processed ?? 0).toBe(0);
|
|
240
215
|
|
|
241
216
|
// Row state is stable across the no-op pass.
|
|
242
|
-
const [row] = await stack.db
|
|
243
|
-
.select()
|
|
244
|
-
.from(customerBalanceTable)
|
|
245
|
-
.where(eq(customerBalanceTable.customer, cust));
|
|
217
|
+
const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
|
|
246
218
|
expect(row?.shipments).toBe(1);
|
|
247
219
|
expect(row?.netCents).toBe(42);
|
|
248
220
|
});
|
|
@@ -273,10 +245,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
273
245
|
);
|
|
274
246
|
await stack.eventDispatcher?.runOnce();
|
|
275
247
|
|
|
276
|
-
const rows = await stack.db
|
|
277
|
-
.select()
|
|
278
|
-
.from(customerBalanceTable)
|
|
279
|
-
.orderBy(customerBalanceTable.customer);
|
|
248
|
+
const rows = await selectMany(stack.db, customerBalanceTable);
|
|
280
249
|
const alpha = rows.find((r) => r.customer === customerAlpha);
|
|
281
250
|
const beta = rows.find((r) => r.customer === customerBeta);
|
|
282
251
|
|
|
@@ -298,10 +267,7 @@ describe("r.multiStreamProjection — Marten MultiStreamProjection equivalent",
|
|
|
298
267
|
|
|
299
268
|
// Only the shipment-billed event was folded in; the auto "created"
|
|
300
269
|
// event was silently skipped.
|
|
301
|
-
const [row] = await stack.db
|
|
302
|
-
.select()
|
|
303
|
-
.from(customerBalanceTable)
|
|
304
|
-
.where(eq(customerBalanceTable.customer, cust));
|
|
270
|
+
const [row] = await selectMany(stack.db, customerBalanceTable, { customer: cust });
|
|
305
271
|
expect(row?.shipments).toBe(1);
|
|
306
272
|
});
|
|
307
273
|
});
|
package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts}
RENAMED
|
@@ -13,27 +13,25 @@
|
|
|
13
13
|
// jitter does not. If this ever flakes in CI, drop to 3000 — the goal is
|
|
14
14
|
// "catastrophic regression detector", not "perf SLO".
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
table as drizzlePgTable,
|
|
21
|
-
uuid as drizzleUuid,
|
|
22
|
-
} from "../../db/dialect";
|
|
16
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
18
|
+
import { integer, table as pgTable, uuid as pgUuid } from "../../db/dialect";
|
|
19
|
+
import { asRawClient } from "../../db/query";
|
|
23
20
|
import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
|
|
24
21
|
import type { ProjectionDefinition } from "../../engine/types";
|
|
25
22
|
import { createEventsTable } from "../../event-store";
|
|
26
23
|
import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
|
|
27
|
-
import {
|
|
24
|
+
import { TestUsers, unsafePushTables } from "../../stack";
|
|
25
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
28
26
|
import { generateId as uuid } from "../../utils";
|
|
29
27
|
|
|
30
28
|
// Counter projection: every task.created bumps a counter, every
|
|
31
29
|
// task.updated is a no-op. Enough to exercise the apply path —
|
|
32
30
|
// rebuild cost is dominated by event iteration + apply dispatch,
|
|
33
31
|
// not the projection state shape.
|
|
34
|
-
const taskCountTable =
|
|
35
|
-
tenantId:
|
|
36
|
-
count:
|
|
32
|
+
const taskCountTable = pgTable("read_perf_rebuild_task_count", {
|
|
33
|
+
tenantId: pgUuid("tenant_id").primaryKey(),
|
|
34
|
+
count: integer("count").notNull().default(0),
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
const taskCountProjection: ProjectionDefinition = {
|
|
@@ -42,13 +40,10 @@ const taskCountProjection: ProjectionDefinition = {
|
|
|
42
40
|
table: taskCountTable,
|
|
43
41
|
apply: {
|
|
44
42
|
"task.created": async (event, tx) => {
|
|
45
|
-
await tx
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
target: taskCountTable.tenantId,
|
|
50
|
-
set: { count: sql`${taskCountTable.count} + 1` },
|
|
51
|
-
});
|
|
43
|
+
await asRawClient(tx).unsafe(
|
|
44
|
+
`INSERT INTO "read_perf_rebuild_task_count" (tenant_id, count) VALUES ($1, 1) ON CONFLICT (tenant_id) DO UPDATE SET count = read_perf_rebuild_task_count.count + 1`,
|
|
45
|
+
[event.tenantId],
|
|
46
|
+
);
|
|
52
47
|
},
|
|
53
48
|
"task.updated": async (_event, _tx) => {
|
|
54
49
|
// No-op apply — measuring event-iteration overhead, not per-event
|
|
@@ -69,11 +64,12 @@ const feature = defineFeature("perfrebuild", (r) => {
|
|
|
69
64
|
});
|
|
70
65
|
|
|
71
66
|
const admin = TestUsers.admin;
|
|
72
|
-
let testDb:
|
|
67
|
+
let testDb: BunTestDb;
|
|
73
68
|
const registry = createRegistry([feature]);
|
|
74
69
|
const qualifiedProjectionName = "perfrebuild:projection:task-count";
|
|
75
70
|
|
|
76
71
|
beforeAll(async () => {
|
|
72
|
+
await ensureTemporalPolyfill();
|
|
77
73
|
testDb = await createTestDb();
|
|
78
74
|
await createEventsTable(testDb.db);
|
|
79
75
|
await createProjectionStateTable(testDb.db);
|
|
@@ -85,8 +81,8 @@ afterAll(async () => {
|
|
|
85
81
|
});
|
|
86
82
|
|
|
87
83
|
beforeEach(async () => {
|
|
88
|
-
await testDb.db.
|
|
89
|
-
|
|
84
|
+
await asRawClient(testDb.db).unsafe(
|
|
85
|
+
`TRUNCATE kumiko_events, read_perf_rebuild_task_count, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
90
86
|
);
|
|
91
87
|
});
|
|
92
88
|
|
|
@@ -96,25 +92,31 @@ beforeEach(async () => {
|
|
|
96
92
|
async function seedEvents(count: number, depth: number): Promise<void> {
|
|
97
93
|
const userId = uuid();
|
|
98
94
|
// v1 creates
|
|
99
|
-
await testDb.db.
|
|
95
|
+
await asRawClient(testDb.db).unsafe(
|
|
96
|
+
`
|
|
100
97
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
101
|
-
SELECT gen_random_uuid(), 'task', $
|
|
98
|
+
SELECT gen_random_uuid(), 'task', $1::uuid, 1, 'task.created',
|
|
102
99
|
jsonb_build_object('title', 'Task ' || gs.n),
|
|
103
|
-
jsonb_build_object('userId', $
|
|
104
|
-
$
|
|
105
|
-
FROM generate_series(1, $
|
|
106
|
-
|
|
100
|
+
jsonb_build_object('userId', $2::text),
|
|
101
|
+
$3::text
|
|
102
|
+
FROM generate_series(1, $4) AS gs(n);
|
|
103
|
+
`,
|
|
104
|
+
[admin.tenantId, userId, userId, count],
|
|
105
|
+
);
|
|
107
106
|
// v2..depth updates
|
|
108
107
|
for (let v = 2; v <= depth; v++) {
|
|
109
|
-
await testDb.db.
|
|
108
|
+
await asRawClient(testDb.db).unsafe(
|
|
109
|
+
`
|
|
110
110
|
INSERT INTO kumiko_events (aggregate_id, aggregate_type, tenant_id, version, type, payload, metadata, created_by)
|
|
111
|
-
SELECT e.aggregate_id, 'task', $
|
|
112
|
-
jsonb_build_object('title', 'Task v' || $
|
|
113
|
-
jsonb_build_object('userId', $
|
|
114
|
-
$
|
|
111
|
+
SELECT e.aggregate_id, 'task', $1::uuid, $2, 'task.updated',
|
|
112
|
+
jsonb_build_object('title', 'Task v' || $3),
|
|
113
|
+
jsonb_build_object('userId', $4::text),
|
|
114
|
+
$5::text
|
|
115
115
|
FROM kumiko_events e
|
|
116
|
-
WHERE e.aggregate_type = 'task' AND e.version = $
|
|
117
|
-
|
|
116
|
+
WHERE e.aggregate_type = 'task' AND e.version = $6;
|
|
117
|
+
`,
|
|
118
|
+
[admin.tenantId, v, v, userId, userId, v - 1],
|
|
119
|
+
);
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Memory `feedback_no_fake_dispatcher`: real HTTP-Calls via setupTestStack,
|
|
10
10
|
// nicht createTestDispatcher.
|
|
11
11
|
|
|
12
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
12
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
15
15
|
import type { PostQueryHookFn } from "../../engine/types";
|
|
@@ -10,15 +10,11 @@
|
|
|
10
10
|
// - status lifecycle (idle → rebuilding → idle on success, → failed on throw)
|
|
11
11
|
// - never-rebuilt projection has sensible default state
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
integer as drizzleInteger,
|
|
17
|
-
table as drizzlePgTable,
|
|
18
|
-
uuid as drizzleUuid,
|
|
19
|
-
} from "../../db/dialect";
|
|
13
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
import { integer, table as pgTable, uuid } from "../../db/dialect";
|
|
20
15
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
21
|
-
import {
|
|
16
|
+
import { asRawClient, insertOne, selectMany } from "../../db/query";
|
|
17
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
22
18
|
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
23
19
|
import {
|
|
24
20
|
createEntity,
|
|
@@ -54,23 +50,19 @@ const itemEntity = createEntity({
|
|
|
54
50
|
},
|
|
55
51
|
softDelete: true,
|
|
56
52
|
});
|
|
57
|
-
const itemTable =
|
|
53
|
+
const itemTable = buildEntityTable("rebuild-item", itemEntity);
|
|
58
54
|
|
|
59
|
-
const itemsPerGroupTable =
|
|
60
|
-
groupId:
|
|
61
|
-
tenantId:
|
|
62
|
-
itemCount:
|
|
55
|
+
const itemsPerGroupTable = pgTable("read_rebuild_items_per_group", {
|
|
56
|
+
groupId: uuid("group_id").primaryKey(),
|
|
57
|
+
tenantId: uuid("tenant_id").notNull(),
|
|
58
|
+
itemCount: integer("item_count").notNull().default(0),
|
|
63
59
|
});
|
|
64
60
|
|
|
65
61
|
async function bump(tx: unknown, groupId: string, tenantId: string, delta: number): Promise<void> {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.onConflictDoUpdate({
|
|
71
|
-
target: itemsPerGroupTable.groupId,
|
|
72
|
-
set: { itemCount: sql`${itemsPerGroupTable.itemCount} + ${delta}` },
|
|
73
|
-
});
|
|
62
|
+
await asRawClient(tx).unsafe(
|
|
63
|
+
`INSERT INTO "read_rebuild_items_per_group" (group_id, tenant_id, item_count) VALUES ($1::uuid, $2::uuid, $3) ON CONFLICT (group_id) DO UPDATE SET item_count = read_rebuild_items_per_group.item_count + $3`,
|
|
64
|
+
[groupId, tenantId, delta],
|
|
65
|
+
);
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
type ItemCreated = { groupId: string };
|
|
@@ -121,8 +113,8 @@ afterAll(async () => {
|
|
|
121
113
|
});
|
|
122
114
|
|
|
123
115
|
beforeEach(async () => {
|
|
124
|
-
await testDb.db.
|
|
125
|
-
|
|
116
|
+
await asRawClient(testDb.db).unsafe(
|
|
117
|
+
`TRUNCATE kumiko_events, read_rebuild_items, read_rebuild_items_per_group, kumiko_projections RESTART IDENTITY CASCADE`,
|
|
126
118
|
);
|
|
127
119
|
});
|
|
128
120
|
|
|
@@ -138,10 +130,7 @@ async function appendCreatedEvent(groupId: string, name: string): Promise<void>
|
|
|
138
130
|
}
|
|
139
131
|
|
|
140
132
|
async function getCount(groupId: string): Promise<number | undefined> {
|
|
141
|
-
const [row] = await testDb.db
|
|
142
|
-
.select()
|
|
143
|
-
.from(itemsPerGroupTable)
|
|
144
|
-
.where(eq(itemsPerGroupTable.groupId, groupId));
|
|
133
|
+
const [row] = await selectMany(testDb.db, itemsPerGroupTable, { groupId: groupId });
|
|
145
134
|
return row?.itemCount;
|
|
146
135
|
}
|
|
147
136
|
|
|
@@ -174,9 +163,11 @@ describe("rebuildProjection — happy path", () => {
|
|
|
174
163
|
await appendCreatedEvent(group, "b");
|
|
175
164
|
|
|
176
165
|
// Seed the projection table with a stale/wrong value.
|
|
177
|
-
await testDb.db
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
await insertOne(testDb.db, itemsPerGroupTable, {
|
|
167
|
+
groupId: group,
|
|
168
|
+
tenantId: admin.tenantId,
|
|
169
|
+
itemCount: 999,
|
|
170
|
+
});
|
|
180
171
|
|
|
181
172
|
const result = await rebuildProjection(qualifiedProjectionName, {
|
|
182
173
|
db: testDb.db,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// drizzle-tables directly. Auto-filters by tenant_id when the projection
|
|
4
4
|
// table carries that column.
|
|
5
5
|
|
|
6
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
6
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import {
|
|
9
9
|
integer as pgInteger,
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
uuid as pgUuid,
|
|
13
13
|
} from "../../db/dialect";
|
|
14
14
|
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
15
|
-
import {
|
|
15
|
+
import { insertOne } from "../../db/query";
|
|
16
|
+
import { buildEntityTable } from "../../db/table-builder";
|
|
16
17
|
import { createEntity, createTextField, defineFeature } from "../../engine";
|
|
17
18
|
import {
|
|
18
19
|
resetEventStore,
|
|
@@ -26,7 +27,7 @@ const widgetEntity = createEntity({
|
|
|
26
27
|
table: "read_qp_widgets",
|
|
27
28
|
fields: { name: createTextField({ required: true }) },
|
|
28
29
|
});
|
|
29
|
-
const widgetTable =
|
|
30
|
+
const widgetTable = buildEntityTable("qp-widget", widgetEntity);
|
|
30
31
|
|
|
31
32
|
// Tenant-scoped projection — auto-filter by tenant_id.
|
|
32
33
|
const tenantScopedTable = pgTable("read_qp_widget_count_tenant", {
|
|
@@ -52,7 +53,7 @@ const qpFeature = defineFeature("qp", (r) => {
|
|
|
52
53
|
apply: {
|
|
53
54
|
"qp-widget.created": async (event, tx) => {
|
|
54
55
|
const p = event.payload as { name?: string };
|
|
55
|
-
await tx
|
|
56
|
+
await insertOne(tx, tenantScopedTable, {
|
|
56
57
|
widgetId: event.aggregateId,
|
|
57
58
|
tenantId: event.tenantId,
|
|
58
59
|
label: p.name ?? "?",
|
|
@@ -68,7 +69,7 @@ const qpFeature = defineFeature("qp", (r) => {
|
|
|
68
69
|
apply: {
|
|
69
70
|
"qp-widget.created": async (event, tx) => {
|
|
70
71
|
const p = event.payload as { name?: string };
|
|
71
|
-
await tx
|
|
72
|
+
await insertOne(tx, systemScopedTable, {
|
|
72
73
|
widgetId: event.aggregateId,
|
|
73
74
|
label: p.name ?? "?",
|
|
74
75
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { RedisKeys } from "../redis-keys";
|
|
3
|
+
|
|
4
|
+
describe("RedisKeys", () => {
|
|
5
|
+
test("uses unique kumiko-prefixed namespaces", () => {
|
|
6
|
+
const values = Object.values(RedisKeys);
|
|
7
|
+
expect(new Set(values).size).toBe(values.length);
|
|
8
|
+
for (const key of values) {
|
|
9
|
+
expect(key.startsWith("kumiko:")).toBe(true);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
});
|
package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { createTestRedis, type TestRedis } from "../../stack";
|
|
3
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
3
4
|
import { createEntityCache } from "../entity-cache";
|
|
4
5
|
import { createEventDedup } from "../event-dedup";
|
|
5
6
|
import { createIdempotencyGuard } from "../idempotency";
|
|
@@ -7,6 +8,7 @@ import { createIdempotencyGuard } from "../idempotency";
|
|
|
7
8
|
let testRedis: TestRedis;
|
|
8
9
|
|
|
9
10
|
beforeAll(async () => {
|
|
11
|
+
await ensureTemporalPolyfill();
|
|
10
12
|
testRedis = await createTestRedis();
|
|
11
13
|
});
|
|
12
14
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
1
|
import type { TableColumns } from "../db/dialect";
|
|
2
|
+
import { deleteMany, fetchOne, updateMany } from "../db/query";
|
|
3
3
|
import { OnDeleteStrategies, SystemHookNames, SystemHookPriorities } from "../engine/constants";
|
|
4
4
|
import type { PreDeleteHookFn, Registry } from "../engine/types";
|
|
5
5
|
import { ConflictError, FrameworkReasons } from "../errors";
|
|
@@ -44,12 +44,8 @@ export function createCascadeDeleteHook(
|
|
|
44
44
|
if (!targetTable) continue;
|
|
45
45
|
|
|
46
46
|
if (strategy === OnDeleteStrategies.restrict) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
.from(targetTable)
|
|
50
|
-
.where(eq(targetTable[relation.foreignKey], payload.id))
|
|
51
|
-
.limit(1);
|
|
52
|
-
if (rows.length > 0) {
|
|
47
|
+
const row = await fetchOne(db, targetTable, { [relation.foreignKey]: payload.id });
|
|
48
|
+
if (row) {
|
|
53
49
|
throw new ConflictError({
|
|
54
50
|
message: `${relation.target} has records referencing ${entityName}#${payload.id}`,
|
|
55
51
|
i18nKey: "errors.deleteRestricted",
|
|
@@ -64,14 +60,16 @@ export function createCascadeDeleteHook(
|
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
if (strategy === OnDeleteStrategies.cascade) {
|
|
67
|
-
await db
|
|
63
|
+
await deleteMany(db, targetTable, { [relation.foreignKey]: payload.id });
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
if (strategy === OnDeleteStrategies.setNull) {
|
|
71
|
-
await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
await updateMany(
|
|
68
|
+
db,
|
|
69
|
+
targetTable,
|
|
70
|
+
{ [relation.foreignKey]: null },
|
|
71
|
+
{ [relation.foreignKey]: payload.id },
|
|
72
|
+
);
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -79,17 +77,11 @@ export function createCascadeDeleteHook(
|
|
|
79
77
|
const throughTable = tables.get(relation.through.table);
|
|
80
78
|
if (!throughTable) continue;
|
|
81
79
|
// sourceKey points at the owner side (the entity being deleted).
|
|
82
|
-
// targetKey would point at the other side — filtering by it here
|
|
83
|
-
// would miss every through-row for this entity.
|
|
84
80
|
const sourceKey = relation.through.sourceKey;
|
|
85
81
|
|
|
86
82
|
if (strategy === OnDeleteStrategies.restrict) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
.from(throughTable)
|
|
90
|
-
.where(eq(throughTable[sourceKey], payload.id))
|
|
91
|
-
.limit(1);
|
|
92
|
-
if (rows.length > 0) {
|
|
83
|
+
const row = await fetchOne(db, throughTable, { [sourceKey]: payload.id });
|
|
84
|
+
if (row) {
|
|
93
85
|
throw new ConflictError({
|
|
94
86
|
message: `${relation.through.table} has records referencing ${entityName}#${payload.id}`,
|
|
95
87
|
i18nKey: "errors.deleteRestricted",
|
|
@@ -104,7 +96,7 @@ export function createCascadeDeleteHook(
|
|
|
104
96
|
}
|
|
105
97
|
|
|
106
98
|
if (strategy === OnDeleteStrategies.cascade) {
|
|
107
|
-
await db
|
|
99
|
+
await deleteMany(db, throughTable, { [sourceKey]: payload.id });
|
|
108
100
|
}
|
|
109
101
|
}
|
|
110
102
|
}
|
|
@@ -6,7 +6,13 @@ import type {
|
|
|
6
6
|
SessionUser,
|
|
7
7
|
WriteResult,
|
|
8
8
|
} from "../engine/types";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type FieldIssue,
|
|
11
|
+
InternalError,
|
|
12
|
+
isKumikoError,
|
|
13
|
+
type KumikoError,
|
|
14
|
+
type WriteErrorInfo,
|
|
15
|
+
} from "../errors";
|
|
10
16
|
|
|
11
17
|
export type FailedWriteResult = Extract<WriteResult, { isSuccess: false }>;
|
|
12
18
|
|
|
@@ -146,12 +152,7 @@ export function prefixValidationPath(info: WriteErrorInfo, prefix: string): Writ
|
|
|
146
152
|
if (info.code !== "validation_error") return info;
|
|
147
153
|
const details = info.details as // @cast-boundary error-details
|
|
148
154
|
| {
|
|
149
|
-
fields?: readonly
|
|
150
|
-
path: string;
|
|
151
|
-
code: string;
|
|
152
|
-
i18nKey: string;
|
|
153
|
-
params?: Readonly<Record<string, unknown>>;
|
|
154
|
-
}[];
|
|
155
|
+
fields?: readonly FieldIssue[];
|
|
155
156
|
}
|
|
156
157
|
| undefined;
|
|
157
158
|
const fields = details?.fields;
|