@cosmicdrift/kumiko-framework 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/__tests__/{anonymous-access.integration.ts → anonymous-access.integration.test.ts} +12 -9
- package/src/__tests__/{error-contract.integration.ts → error-contract.integration.test.ts} +5 -4
- package/src/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +3 -3
- package/src/__tests__/{full-stack.integration.ts → full-stack.integration.test.ts} +7 -16
- package/src/__tests__/{ownership.integration.ts → ownership.integration.test.ts} +3 -2
- package/src/__tests__/{raw-table.integration.ts → raw-table.integration.test.ts} +18 -30
- package/src/__tests__/{reference-data.integration.ts → reference-data.integration.test.ts} +24 -11
- package/src/__tests__/{transition-guard.integration.ts → transition-guard.integration.test.ts} +12 -10
- package/src/api/__tests__/api.test.ts +1 -1
- package/src/api/__tests__/auth-middleware-transport.test.ts +1 -1
- package/src/api/__tests__/auth-routes-cookie.test.ts +1 -1
- package/src/api/__tests__/{batch.integration.ts → batch.integration.test.ts} +30 -30
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/csrf-middleware.test.ts +1 -1
- package/src/api/__tests__/{dispatcher-live.integration.ts → dispatcher-live.integration.test.ts} +10 -9
- package/src/api/__tests__/metrics-endpoint.test.ts +1 -1
- package/src/api/__tests__/{nested-write.integration.ts → nested-write.integration.test.ts} +13 -16
- package/src/api/__tests__/readiness.test.ts +1 -1
- package/src/api/__tests__/request-id-middleware.test.ts +1 -1
- package/src/api/__tests__/sse-broker.test.ts +12 -12
- package/src/api/__tests__/sse-route.test.ts +1 -1
- package/src/api/readiness.ts +2 -2
- package/src/auth/__tests__/roles.test.ts +2 -2
- package/src/bun-db/__tests__/PATTERN.md +73 -0
- package/src/bun-db/__tests__/_helpers.ts +103 -0
- package/src/bun-db/__tests__/batch-methods.integration.test.ts +143 -0
- package/src/bun-db/__tests__/batch-methods.test.ts +20 -0
- package/src/bun-db/__tests__/bun-test-db.ts +19 -0
- package/src/bun-db/__tests__/bun-test-stack.ts +6 -0
- package/src/bun-db/__tests__/column-types.integration.test.ts +132 -0
- package/src/bun-db/__tests__/compound-types.integration.test.ts +134 -0
- package/src/bun-db/__tests__/jsonb-edge-cases.integration.test.ts +235 -0
- package/src/bun-db/__tests__/smoke.integration.test.ts +43 -0
- package/src/bun-db/__tests__/sql-methods.integration.test.ts +231 -0
- package/src/bun-db/__tests__/where-patterns.integration.test.ts +185 -0
- package/src/bun-db/connection.ts +84 -0
- package/src/bun-db/index.ts +31 -0
- package/src/bun-db/query.ts +845 -0
- package/src/compliance/__tests__/duration-spec.test.ts +1 -1
- package/src/compliance/__tests__/profiles.test.ts +1 -1
- package/src/compliance/__tests__/sub-processors.test.ts +1 -1
- package/src/db/__tests__/{apply-entity-event-tenant.integration.ts → apply-entity-event-tenant.integration.test.ts} +13 -11
- package/src/db/__tests__/big-int-field.test.ts +15 -14
- package/src/db/__tests__/column-ddl.integration.test.ts +113 -0
- package/src/db/__tests__/compound-types.test.ts +1 -1
- package/src/db/__tests__/{config-seed.integration.ts → config-seed.integration.test.ts} +32 -27
- package/src/db/__tests__/connection-options.test.ts +1 -1
- package/src/db/__tests__/dialect-instant.test.ts +1 -1
- package/src/db/__tests__/encryption.test.ts +1 -1
- package/src/db/__tests__/{drizzle-table-types.test.ts → entity-table-types.test.ts} +16 -16
- package/src/db/__tests__/{event-store-executor-list.integration.ts → event-store-executor-list.integration.test.ts} +12 -7
- package/src/db/__tests__/{event-store-executor.integration.ts → event-store-executor.integration.test.ts} +19 -12
- package/src/db/__tests__/{implicit-projection-equivalence.integration.ts → implicit-projection-equivalence.integration.test.ts} +35 -29
- package/src/db/__tests__/located-timestamp.test.ts +1 -1
- package/src/db/__tests__/money.test.ts +1 -1
- package/src/db/__tests__/{multi-row-insert.integration.ts → multi-row-insert.integration.test.ts} +18 -11
- package/src/db/__tests__/parse-auto-verb.test.ts +1 -1
- package/src/db/__tests__/{required-not-null-migration-safety.integration.ts → required-not-null-migration-safety.integration.test.ts} +28 -24
- package/src/db/__tests__/{schema-migration.integration.ts → schema-migration.integration.test.ts} +32 -28
- package/src/db/__tests__/sql-inventory.test.ts +56 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +30 -11
- package/src/db/__tests__/table-builder-required.test.ts +20 -22
- package/src/db/__tests__/{tenant-db.integration.ts → tenant-db.integration.test.ts} +106 -144
- package/src/db/__tests__/{unique-violation-mapping.integration.ts → unique-violation-mapping.integration.test.ts} +13 -8
- package/src/db/api.ts +46 -0
- package/src/db/apply-entity-event.ts +45 -36
- package/src/db/assert-exists-in.ts +5 -16
- package/src/db/bun-provider.ts +37 -0
- package/src/db/config-seed.ts +4 -4
- package/src/db/connection.ts +14 -57
- package/src/db/cursor.ts +5 -56
- package/src/db/dialect.ts +472 -99
- package/src/db/eagerload.ts +5 -12
- package/src/db/entity-table-meta.ts +390 -0
- package/src/db/event-store-executor.ts +158 -100
- package/src/db/index.ts +33 -5
- package/src/db/migrate-generator.ts +350 -0
- package/src/db/migrate-runner.ts +206 -0
- package/src/db/postgres-provider.ts +25 -0
- package/src/db/queries/entity-read.ts +15 -0
- package/src/db/queries/es-ops.ts +17 -0
- package/src/db/queries/event-consumer.ts +170 -0
- package/src/db/queries/event-store-admin.ts +127 -0
- package/src/db/queries/event-store.ts +155 -0
- package/src/db/queries/projection-rebuild.ts +59 -0
- package/src/db/queries/raw-sql.ts +15 -0
- package/src/db/queries/schema-drift.ts +35 -0
- package/src/db/queries/seed-context.ts +58 -0
- package/src/db/queries/table-ops.ts +11 -0
- package/src/db/queries/test-stack.ts +56 -0
- package/src/db/query-api.ts +22 -0
- package/src/db/query.ts +30 -0
- package/src/db/reference-data.ts +19 -22
- package/src/db/render-ddl.ts +57 -0
- package/src/db/row-helpers.ts +3 -52
- package/src/db/schema-inspection.ts +17 -4
- package/src/db/sql-inventory.ts +208 -0
- package/src/db/table-builder.ts +48 -40
- package/src/db/tenant-db.ts +105 -326
- package/src/engine/__tests__/auth-claims-registrar.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +3 -3
- package/src/engine/__tests__/boot-validator.test.ts +4 -3
- package/src/engine/__tests__/build-app-schema.test.ts +1 -1
- package/src/engine/__tests__/build-target.test.ts +1 -1
- package/src/engine/__tests__/claim-keys.test.ts +1 -1
- package/src/engine/__tests__/codemod-pipeline.test.ts +3 -3
- package/src/engine/__tests__/config-helpers.test.ts +1 -1
- package/src/engine/__tests__/effective-features.test.ts +1 -1
- package/src/engine/__tests__/engine.test.ts +1 -1
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +3 -3
- package/src/engine/__tests__/extends-registrar.test.ts +4 -4
- package/src/engine/__tests__/factories-long-text.test.ts +1 -1
- package/src/engine/__tests__/factories-time.test.ts +1 -1
- package/src/engine/__tests__/field-predicates.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +1 -1
- package/src/engine/__tests__/identifiers.test.ts +1 -1
- package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
- package/src/engine/__tests__/nav.test.ts +1 -1
- package/src/engine/__tests__/ownership.test.ts +10 -11
- package/src/engine/__tests__/parse-ref-target.test.ts +1 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
- package/src/engine/__tests__/{pipeline-handler.integration.ts → pipeline-handler.integration.test.ts} +38 -52
- package/src/engine/__tests__/{pipeline-observability.integration.ts → pipeline-observability.integration.test.ts} +1 -1
- package/src/engine/__tests__/{pipeline-performance.integration.ts → pipeline-performance.integration.test.ts} +1 -1
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +1 -1
- package/src/engine/__tests__/projection-helpers.test.ts +25 -17
- package/src/engine/__tests__/projection.test.ts +4 -4
- package/src/engine/__tests__/qualified-name.test.ts +1 -1
- package/src/engine/__tests__/raw-table.test.ts +9 -8
- package/src/engine/__tests__/resolve-config-or-param.test.ts +5 -5
- package/src/engine/__tests__/run-in.test.ts +1 -1
- package/src/engine/__tests__/schema-builder.test.ts +1 -1
- package/src/engine/__tests__/screen.test.ts +1 -1
- package/src/engine/__tests__/search-payload-extension.test.ts +3 -3
- package/src/engine/__tests__/state-machine.test.ts +1 -1
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +7 -7
- package/src/engine/__tests__/steps-aggregate-create.test.ts +4 -4
- package/src/engine/__tests__/steps-aggregate-update.test.ts +3 -3
- package/src/engine/__tests__/steps-call-feature.test.ts +5 -5
- package/src/engine/__tests__/steps-mail-send.test.ts +7 -7
- package/src/engine/__tests__/steps-read.test.ts +34 -40
- package/src/engine/__tests__/steps-resolver-utils.test.ts +6 -6
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +24 -19
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +28 -17
- package/src/engine/__tests__/steps-webhook-send.test.ts +6 -6
- package/src/engine/__tests__/steps-workflow.test.ts +7 -7
- package/src/engine/__tests__/system-user.test.ts +1 -1
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +4 -5
- package/src/engine/__tests__/validation-hooks.test.ts +1 -1
- package/src/engine/__tests__/visual-tree-patterns.test.ts +1 -1
- package/src/engine/boot-validator/entity-handler.ts +3 -3
- package/src/engine/boot-validator/ownership.ts +1 -1
- package/src/engine/define-feature.ts +1 -2
- package/src/engine/entity-handlers.ts +5 -5
- package/src/engine/factories.ts +1 -1
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patch.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/patcher.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +1 -1
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +1 -1
- package/src/engine/ownership.ts +113 -41
- package/src/engine/pattern-library/__tests__/library.test.ts +2 -2
- package/src/engine/projection-helpers.ts +2 -11
- package/src/engine/registry.ts +2 -2
- package/src/engine/steps/read-find-many.ts +13 -13
- package/src/engine/steps/read-find-one.ts +7 -9
- package/src/engine/steps/unsafe-projection-delete.ts +4 -5
- package/src/engine/steps/unsafe-projection-upsert.ts +63 -31
- package/src/engine/types/feature.ts +7 -2
- package/src/engine/types/fields.ts +4 -5
- package/src/engine/types/step.ts +10 -10
- package/src/engine/validate-projection-allowlist.ts +23 -3
- package/src/entrypoint/__tests__/{entrypoint-job-wiring.integration.ts → entrypoint-job-wiring.integration.test.ts} +4 -3
- package/src/entrypoint/__tests__/{split-deploy.integration.ts → split-deploy.integration.test.ts} +4 -3
- package/src/env/__tests__/compose-env-schema.test.ts +1 -1
- package/src/env/__tests__/dry-run.test.ts +1 -1
- package/src/errors/__tests__/classes.test.ts +1 -1
- package/src/errors/__tests__/write-failures.test.ts +1 -1
- package/src/es-ops/__tests__/{context.integration.ts → context.integration.test.ts} +43 -29
- package/src/es-ops/__tests__/{runner.integration.ts → runner.integration.test.ts} +25 -23
- package/src/es-ops/__tests__/runner.test.ts +29 -19
- package/src/es-ops/context.ts +9 -43
- package/src/es-ops/operations-schema.ts +2 -2
- package/src/es-ops/runner.ts +12 -26
- package/src/event-store/__tests__/{admin-api.integration.ts → admin-api.integration.test.ts} +71 -45
- package/src/event-store/__tests__/{event-store.integration.ts → event-store.integration.test.ts} +7 -5
- package/src/event-store/__tests__/{get-stream-version-perf.integration.ts → get-stream-version-perf.integration.test.ts} +5 -3
- package/src/event-store/__tests__/{perf.integration.ts → perf.integration.test.ts} +24 -16
- package/src/event-store/__tests__/{snapshot.integration.ts → snapshot.integration.test.ts} +34 -28
- package/src/event-store/__tests__/{upcaster-dead-letter.integration.ts → upcaster-dead-letter.integration.test.ts} +11 -12
- package/src/event-store/__tests__/{upcaster.integration.ts → upcaster.integration.test.ts} +19 -32
- package/src/event-store/admin-api.ts +55 -83
- package/src/event-store/archive.ts +15 -39
- package/src/event-store/event-store.ts +92 -86
- package/src/event-store/events-schema.ts +2 -1
- package/src/event-store/index.ts +1 -0
- package/src/event-store/snapshot.ts +26 -24
- package/src/event-store/upcaster-dead-letter.ts +19 -18
- package/src/files/__tests__/content-disposition.test.ts +1 -1
- package/src/files/__tests__/{file-field-pipeline.integration.ts → file-field-pipeline.integration.test.ts} +8 -5
- package/src/files/__tests__/file-handle.test.ts +1 -1
- package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +32 -17
- package/src/files/__tests__/read-stream.test.ts +1 -1
- package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts} +26 -30
- package/src/files/__tests__/write-stream.test.ts +1 -1
- package/src/files/__tests__/zip-stream.test.ts +1 -1
- package/src/files/file-ref-table.ts +2 -2
- package/src/files/file-routes.ts +7 -9
- package/src/files/storage-tracking.ts +9 -17
- package/src/i18n/__tests__/i18n.test.ts +1 -1
- package/src/jobs/__tests__/{job-event-trigger.integration.ts → job-event-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{job-multi-trigger.integration.ts → job-multi-trigger.integration.test.ts} +6 -3
- package/src/jobs/__tests__/{jobs.integration.ts → jobs.integration.test.ts} +5 -7
- package/src/lifecycle/__tests__/{lifecycle-server.integration.ts → lifecycle-server.integration.test.ts} +1 -1
- package/src/lifecycle/__tests__/lifecycle.test.ts +6 -6
- package/src/lifecycle/__tests__/signal-handlers.test.ts +6 -6
- package/src/logging/__tests__/pino-trace-bridge.test.ts +1 -1
- package/src/migrations/__tests__/compare-snapshots.test.ts +1 -1
- package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts} +34 -26
- package/src/migrations/__tests__/{detect-projections-to-rebuild.integration.ts → detect-projections-to-rebuild.integration.test.ts} +1 -1
- package/src/migrations/__tests__/rebuild-marker.test.ts +1 -1
- package/src/migrations/projection-detection.ts +12 -1
- package/src/migrations/schema-drift.ts +7 -23
- package/src/observability/__tests__/console-provider.test.ts +1 -1
- package/src/observability/__tests__/metric-validator.test.ts +1 -1
- package/src/observability/__tests__/noop-provider.test.ts +1 -1
- package/src/observability/__tests__/{observability.integration.ts → observability.integration.test.ts} +5 -8
- package/src/observability/__tests__/prometheus-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-meter.test.ts +1 -1
- package/src/observability/__tests__/recording-tracer.test.ts +1 -1
- package/src/observability/__tests__/sensitive-filter.test.ts +1 -1
- package/src/pipeline/__tests__/{archive-stream.integration.ts → archive-stream.integration.test.ts} +3 -3
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +9 -9
- package/src/pipeline/__tests__/{cascade-handler.integration.ts → cascade-handler.integration.test.ts} +18 -15
- package/src/pipeline/__tests__/cascade-handler.test.ts +1 -1
- package/src/pipeline/__tests__/{causation-chain.integration.ts → causation-chain.integration.test.ts} +12 -13
- package/src/pipeline/__tests__/{ctx-bridge.integration.ts → ctx-bridge.integration.test.ts} +12 -11
- package/src/pipeline/__tests__/dispatcher.test.ts +2 -2
- package/src/pipeline/__tests__/{distributed-lock.integration.ts → distributed-lock.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{domain-events-projections.integration.ts → domain-events-projections.integration.test.ts} +13 -15
- package/src/pipeline/__tests__/{event-dedup.integration.ts → event-dedup.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-define-event-strict.integration.ts → event-define-event-strict.integration.test.ts} +6 -16
- package/src/pipeline/__tests__/{event-dispatcher-lifecycle.integration.ts → event-dispatcher-lifecycle.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-multi-instance.integration.ts → event-dispatcher-multi-instance.integration.test.ts} +3 -2
- package/src/pipeline/__tests__/{event-dispatcher-pg-listen.integration.ts → event-dispatcher-pg-listen.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{event-dispatcher-recovery.integration.ts → event-dispatcher-recovery.integration.test.ts} +2 -2
- package/src/pipeline/__tests__/{event-dispatcher-second-audit.integration.ts → event-dispatcher-second-audit.integration.test.ts} +17 -16
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +14 -12
- package/src/pipeline/__tests__/{event-dispatcher.integration.ts → event-dispatcher.integration.test.ts} +8 -15
- package/src/pipeline/__tests__/{event-retention.integration.ts → event-retention.integration.test.ts} +28 -25
- package/src/pipeline/__tests__/{fetch-for-writing.integration.ts → fetch-for-writing.integration.test.ts} +6 -6
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +4 -4
- package/src/pipeline/__tests__/{load-aggregate-query.integration.ts → load-aggregate-query.integration.test.ts} +9 -5
- package/src/pipeline/__tests__/{msp-error-mode.integration.ts → msp-error-mode.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{msp-multi-hop.integration.ts → msp-multi-hop.integration.test.ts} +9 -8
- package/src/pipeline/__tests__/{msp-rebuild.integration.ts → msp-rebuild.integration.test.ts} +47 -55
- package/src/pipeline/__tests__/{multi-stream-projection.integration.ts → multi-stream-projection.integration.test.ts} +19 -53
- package/src/pipeline/__tests__/{perf-rebuild.integration.ts → perf-rebuild.integration.test.ts} +36 -34
- package/src/pipeline/__tests__/{post-query-hook.integration.ts → post-query-hook.integration.test.ts} +1 -1
- package/src/pipeline/__tests__/{projection-rebuild.integration.ts → projection-rebuild.integration.test.ts} +21 -30
- package/src/pipeline/__tests__/{query-projection.integration.ts → query-projection.integration.test.ts} +6 -5
- package/src/pipeline/__tests__/{redis-pipeline.integration.ts → redis-pipeline.integration.test.ts} +3 -1
- package/src/pipeline/cascade-handler.ts +13 -21
- package/src/pipeline/dispatcher.ts +43 -48
- package/src/pipeline/event-consumer-state.ts +11 -2
- package/src/pipeline/event-dispatcher.ts +86 -146
- package/src/pipeline/event-retention.ts +14 -24
- package/src/pipeline/msp-rebuild.ts +54 -78
- package/src/pipeline/projection-rebuild.ts +65 -67
- package/src/pipeline/projection-state.ts +2 -2
- package/src/random/__tests__/generate.test.ts +13 -13
- package/src/rate-limit/__tests__/{dispatcher-l3.integration.ts → dispatcher-l3.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{middleware.integration.ts → middleware.integration.test.ts} +1 -1
- package/src/rate-limit/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +1 -1
- package/src/redis/__tests__/redis-options.test.ts +1 -1
- package/src/search/__tests__/{meilisearch-adapter.integration.ts → meilisearch-adapter.integration.test.ts} +1 -1
- package/src/search/__tests__/search-adapter.test.ts +1 -1
- package/src/secrets/__tests__/dek-cache.test.ts +1 -3
- package/src/secrets/__tests__/env-master-key-provider.test.ts +1 -1
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/__tests__/leak-guard.test.ts +1 -1
- package/src/secrets/__tests__/rotation.test.ts +1 -1
- package/src/stack/db.ts +25 -48
- package/src/stack/push-entity-projection-tables.ts +2 -4
- package/src/stack/table-helpers.ts +98 -61
- package/src/stack/test-stack.ts +8 -7
- package/src/testing/__tests__/db-cleanup.test.ts +40 -0
- package/src/testing/__tests__/e2e-generator.test.ts +1 -1
- package/src/testing/__tests__/{ensure-entity-table.integration.ts → ensure-entity-table.integration.test.ts} +7 -14
- package/src/testing/db-cleanup.ts +44 -0
- package/src/testing/expect-error.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/multipart-helper.ts +94 -0
- package/src/testing/shared-entities.ts +5 -5
- package/src/time/__tests__/polyfill.test.ts +1 -1
- package/src/time/__tests__/tz-context.test.ts +1 -1
- package/src/utils/__tests__/assert.test.ts +1 -1
- package/src/utils/__tests__/env-parse.test.ts +1 -1
- package/CHANGELOG.md +0 -472
- package/src/db/__tests__/cursor.test.ts +0 -41
- package/src/db/__tests__/db-helpers.test.ts +0 -369
- package/src/db/__tests__/drizzle-helpers.integration.ts +0 -186
- package/src/db/__tests__/row-helpers.test.ts +0 -59
- package/src/engine/steps/_drizzle-boundary.ts +0 -19
- package/src/files/__tests__/file-field-column.integration.ts +0 -103
package/src/files/__tests__/{storage-tracking.integration.ts → storage-tracking.integration.test.ts}
RENAMED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
// 3. The table column types survive round-trip (bigint → number via
|
|
9
9
|
// Drizzle's mode:"number", so arithmetic in assertions Just Works).
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
import { asRawClient, selectMany } from "../../db/query";
|
|
13
13
|
import type { SessionUser } from "../../engine";
|
|
14
14
|
import { createTestUser, setupTestStack, type TestStack, TestUsers } from "../../stack";
|
|
15
|
+
import { buildMultipartBody, patchFileInstanceofForBunTest } from "../../testing";
|
|
15
16
|
import {
|
|
16
17
|
createInMemoryFileProvider,
|
|
17
18
|
filesStorageTrackingFeature,
|
|
@@ -36,6 +37,7 @@ const SMALL = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(16).fill(0)]); //
|
|
|
36
37
|
const LARGE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, ...Array(96).fill(0)]); // 100 bytes
|
|
37
38
|
|
|
38
39
|
beforeAll(async () => {
|
|
40
|
+
patchFileInstanceofForBunTest();
|
|
39
41
|
provider = createInMemoryFileProvider();
|
|
40
42
|
stack = await setupTestStack({
|
|
41
43
|
features: [filesStorageTrackingFeature],
|
|
@@ -54,8 +56,8 @@ beforeEach(async () => {
|
|
|
54
56
|
// each test starts from zero. kumiko_event_consumers registration is
|
|
55
57
|
// re-asserted below; truncating it forces ensureRegistered to seed the
|
|
56
58
|
// cursor at event.id = 0.
|
|
57
|
-
await stack.db.
|
|
58
|
-
|
|
59
|
+
await asRawClient(stack.db).unsafe(
|
|
60
|
+
`TRUNCATE kumiko_events, kumiko_event_consumers, file_refs, read_tenant_storage_usage RESTART IDENTITY CASCADE`,
|
|
59
61
|
);
|
|
60
62
|
await stack.eventDispatcher?.ensureRegistered();
|
|
61
63
|
});
|
|
@@ -64,23 +66,20 @@ async function upload(user: SessionUser, name: string, content: Uint8Array): Pro
|
|
|
64
66
|
const token = await stack.jwt.sign(user);
|
|
65
67
|
const formData = new FormData();
|
|
66
68
|
formData.append("file", new File([Buffer.from(content)], name, { type: "image/png" }));
|
|
69
|
+
const { body: multipartBody, contentType } = await buildMultipartBody(formData);
|
|
67
70
|
const res = await stack.app.request("/api/files", {
|
|
68
71
|
method: "POST",
|
|
69
|
-
headers: { Authorization: `Bearer ${token}
|
|
70
|
-
body:
|
|
72
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
|
|
73
|
+
body: multipartBody,
|
|
71
74
|
});
|
|
72
75
|
expect(res.status).toBe(201);
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
async function usageFor(tenantId: string): Promise<{ totalBytes: number; fileCount: number }> {
|
|
76
|
-
const [row] = await stack.db
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
.from(tenantStorageUsageTable)
|
|
82
|
-
.where(eq(tenantStorageUsageTable.tenantId, tenantId));
|
|
83
|
-
return row ?? { totalBytes: 0, fileCount: 0 };
|
|
79
|
+
const [row] = await selectMany(stack.db, tenantStorageUsageTable, { tenantId });
|
|
80
|
+
return row
|
|
81
|
+
? { totalBytes: Number(row["totalBytes"]) ?? 0, fileCount: Number(row["fileCount"]) ?? 0 }
|
|
82
|
+
: { totalBytes: 0, fileCount: 0 };
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
describe("tenant-storage-usage MSP", () => {
|
|
@@ -105,10 +104,7 @@ describe("tenant-storage-usage MSP", () => {
|
|
|
105
104
|
|
|
106
105
|
// Exactly one row per tenant — the UPSERT must not insert a second
|
|
107
106
|
// row for the second upload.
|
|
108
|
-
const rows = await stack.db
|
|
109
|
-
.select()
|
|
110
|
-
.from(tenantStorageUsageTable)
|
|
111
|
-
.where(eq(tenantStorageUsageTable.tenantId, admin.tenantId));
|
|
107
|
+
const rows = await selectMany(stack.db, tenantStorageUsageTable, { tenantId: admin.tenantId });
|
|
112
108
|
expect(rows).toHaveLength(1);
|
|
113
109
|
});
|
|
114
110
|
|
|
@@ -128,11 +124,10 @@ describe("tenant-storage-usage MSP", () => {
|
|
|
128
124
|
await upload(admin, "a.png", SMALL);
|
|
129
125
|
await stack.eventDispatcher?.runOnce();
|
|
130
126
|
|
|
131
|
-
const [first] = await stack.db
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
expect(first?.at).toBeInstanceOf(Temporal.Instant);
|
|
127
|
+
const [first] = await selectMany(stack.db, tenantStorageUsageTable, {
|
|
128
|
+
tenantId: admin.tenantId,
|
|
129
|
+
});
|
|
130
|
+
expect(first?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
|
|
136
131
|
|
|
137
132
|
// Postgres NOW() resolution is microseconds; a second upload a beat
|
|
138
133
|
// later must produce a strictly later timestamp (or at least not an
|
|
@@ -142,12 +137,13 @@ describe("tenant-storage-usage MSP", () => {
|
|
|
142
137
|
await upload(admin, "b.png", LARGE);
|
|
143
138
|
await stack.eventDispatcher?.runOnce();
|
|
144
139
|
|
|
145
|
-
const [second] = await stack.db
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
const [second] = await selectMany(stack.db, tenantStorageUsageTable, {
|
|
141
|
+
tenantId: admin.tenantId,
|
|
142
|
+
});
|
|
143
|
+
expect(second?.["lastUpdatedAt"]).toBeInstanceOf(Temporal.Instant);
|
|
144
|
+
if (!first?.["lastUpdatedAt"] || !second?.["lastUpdatedAt"]) throw new Error("missing rows");
|
|
145
|
+
expect(
|
|
146
|
+
Temporal.Instant.compare(second["lastUpdatedAt"], first["lastUpdatedAt"]),
|
|
147
|
+
).toBeGreaterThanOrEqual(0);
|
|
152
148
|
});
|
|
153
149
|
});
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
// AsyncIterable-source pinst die Streaming-Semantik (Caller streamt
|
|
13
13
|
// chunk-fuer-chunk, Provider niemals alles im Memory).
|
|
14
14
|
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
15
16
|
import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
16
17
|
import { tmpdir } from "node:os";
|
|
17
18
|
import { join } from "node:path";
|
|
18
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
19
19
|
import { createInMemoryFileProvider } from "../in-memory-provider";
|
|
20
20
|
import { createLocalProvider } from "../local-provider";
|
|
21
21
|
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
// kann — kein "passt nur in unserer eigenen reverse-engineerten
|
|
12
12
|
// Welt".
|
|
13
13
|
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
14
15
|
import { spawn } from "node:child_process";
|
|
15
16
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
16
17
|
import { tmpdir } from "node:os";
|
|
17
18
|
import { join } from "node:path";
|
|
18
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
19
19
|
import { getTemporal } from "../../time";
|
|
20
20
|
import { createZipStream, type ZipEntry } from "../zip-stream";
|
|
21
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import { instant, integer, table as pgTable, text, uuid } from "../db/dialect";
|
|
1
|
+
// sql now comes from native dialect
|
|
2
|
+
import { instant, integer, table as pgTable, sql, text, uuid } from "../db/dialect";
|
|
3
3
|
|
|
4
4
|
// `id` is a UUID (not serial): it doubles as the aggregate-id for the
|
|
5
5
|
// `fileRef` event stream — every upload appends exactly one
|
package/src/files/file-routes.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deleteMany, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { getUser } from "../api/auth-middleware";
|
|
5
|
-
import type { DbConnection } from "../db/connection";
|
|
5
|
+
import type { DbConnection, DbTx } from "../db/connection";
|
|
6
6
|
import type { EventDef } from "../engine/types";
|
|
7
7
|
import { isFileField, type Registry, type SessionUser, type TenantId } from "../engine/types";
|
|
8
8
|
import { append as appendEvent } from "../event-store/event-store";
|
|
@@ -172,8 +172,9 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
|
|
|
172
172
|
// Atomic: insert FileRef + append files:event:uploaded in one tx. Either
|
|
173
173
|
// both land or neither — no dangling FileRef without event, no event
|
|
174
174
|
// referencing a row that doesn't exist.
|
|
175
|
-
await db.
|
|
176
|
-
await
|
|
175
|
+
await db.begin(async (tx: DbTx) => {
|
|
176
|
+
const { insertOne } = await import("../bun-db/query");
|
|
177
|
+
await insertOne(tx, fileRefsTable, {
|
|
177
178
|
id: fileRefId,
|
|
178
179
|
tenantId: user.tenantId,
|
|
179
180
|
storageKey,
|
|
@@ -271,7 +272,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
|
|
|
271
272
|
// Upload. Umgekehrt liesse ein storage-success + db-fail eine Row mit
|
|
272
273
|
// permanent-broken Reference zurück — aus Sicht der API "Datei
|
|
273
274
|
// existiert" aber jeder Read 404t aus dem Provider.
|
|
274
|
-
await db
|
|
275
|
+
await deleteMany(db, fileRefsTable, { id: id });
|
|
275
276
|
await storageProvider.delete(fileRef.storageKey);
|
|
276
277
|
return c.json({ ok: true });
|
|
277
278
|
});
|
|
@@ -342,10 +343,7 @@ export function createFileRoutes(options: FileRoutesOptions): Hono {
|
|
|
342
343
|
});
|
|
343
344
|
|
|
344
345
|
async function loadFileForTenant(id: string, tenantId: TenantId): Promise<FileRef | null> {
|
|
345
|
-
const [row] = await db
|
|
346
|
-
.select()
|
|
347
|
-
.from(fileRefsTable)
|
|
348
|
-
.where(and(eq(fileRefsTable.id, id), eq(fileRefsTable.tenantId, tenantId)));
|
|
346
|
+
const [row] = await selectMany(db, fileRefsTable, { id, tenantId });
|
|
349
347
|
return (row as FileRef | undefined) ?? null; // @cast-boundary db-row
|
|
350
348
|
}
|
|
351
349
|
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// consumer-cursor row. Apps that want it pass filesStorageTrackingFeature
|
|
11
11
|
// into createApp / setupTestStack alongside their domain features.
|
|
12
12
|
|
|
13
|
-
import { sql } from "
|
|
14
|
-
import {
|
|
13
|
+
import { bigint, instant, integer, table as pgTable, sql, uuid } from "../db/dialect";
|
|
14
|
+
import { incrementCounter } from "../db/query";
|
|
15
15
|
import { defineFeature, typedPayload } from "../engine";
|
|
16
16
|
import { fileUploadedEvent } from "./file-routes";
|
|
17
17
|
|
|
@@ -39,21 +39,13 @@ export const filesStorageTrackingFeature = defineFeature("files-storage-tracking
|
|
|
39
39
|
// The SQL increment guarantees correctness under concurrent dispatcher
|
|
40
40
|
// runs (shouldn't happen with a single consumer, but the invariant is
|
|
41
41
|
// free and cheap — no reason to rely on serial delivery).
|
|
42
|
-
await
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.onConflictDoUpdate({
|
|
50
|
-
target: tenantStorageUsageTable.tenantId,
|
|
51
|
-
set: {
|
|
52
|
-
totalBytes: sql`${tenantStorageUsageTable.totalBytes} + ${payload.size}`,
|
|
53
|
-
fileCount: sql`${tenantStorageUsageTable.fileCount} + 1`,
|
|
54
|
-
lastUpdatedAt: sql`NOW()`,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
42
|
+
await incrementCounter(
|
|
43
|
+
tx,
|
|
44
|
+
tenantStorageUsageTable,
|
|
45
|
+
{ tenantId: event.tenantId, totalBytes: payload.size, fileCount: 1 },
|
|
46
|
+
{ totalBytes: payload.size, fileCount: 1 },
|
|
47
|
+
{ set: { lastUpdatedAt: sql`now()` } },
|
|
48
|
+
);
|
|
57
49
|
},
|
|
58
50
|
},
|
|
59
51
|
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
1
2
|
import type { Hono } from "hono";
|
|
2
|
-
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { buildServer, type JwtHelper } from "../../api";
|
|
5
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
5
6
|
import { createRegistry, defineFeature, type SessionUser } from "../../engine";
|
|
6
|
-
import {
|
|
7
|
+
import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
|
|
7
8
|
import { waitFor } from "../../testing";
|
|
9
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
8
10
|
import { createJobRunner, type JobRunner } from "../job-runner";
|
|
9
11
|
|
|
10
12
|
// --- Track job executions ---
|
|
@@ -64,7 +66,7 @@ const analyticsFeature = defineFeature("analytics", (r) => {
|
|
|
64
66
|
|
|
65
67
|
// --- Setup ---
|
|
66
68
|
|
|
67
|
-
let testDb:
|
|
69
|
+
let testDb: BunTestDb;
|
|
68
70
|
let testRedis: TestRedis;
|
|
69
71
|
let app: Hono;
|
|
70
72
|
let jwt: JwtHelper;
|
|
@@ -74,6 +76,7 @@ const adminUser = TestUsers.admin;
|
|
|
74
76
|
const JWT_SECRET = "event-trigger-test-secret-minimum-32-chars!!";
|
|
75
77
|
|
|
76
78
|
beforeAll(async () => {
|
|
79
|
+
await ensureTemporalPolyfill();
|
|
77
80
|
testDb = await createTestDb();
|
|
78
81
|
testRedis = await createTestRedis();
|
|
79
82
|
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
// Validator-Reject). Hier prüfen wir die runtime-dispatch-Pfade durch
|
|
7
7
|
// einen echten BullMQ-Worker.
|
|
8
8
|
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
9
10
|
import type { Hono } from "hono";
|
|
10
|
-
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { buildServer, type JwtHelper } from "../../api";
|
|
13
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
13
14
|
import { createRegistry, defineFeature, type SessionUser } from "../../engine";
|
|
14
|
-
import {
|
|
15
|
+
import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
|
|
15
16
|
import { waitFor } from "../../testing";
|
|
17
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
16
18
|
import { createJobRunner, type JobRunner } from "../job-runner";
|
|
17
19
|
|
|
18
20
|
const jobExecutions: Array<{ trigger: string; payload: Record<string, unknown> }> = [];
|
|
@@ -48,7 +50,7 @@ const orderFeature = defineFeature("multi", (r) => {
|
|
|
48
50
|
);
|
|
49
51
|
});
|
|
50
52
|
|
|
51
|
-
let testDb:
|
|
53
|
+
let testDb: BunTestDb;
|
|
52
54
|
let testRedis: TestRedis;
|
|
53
55
|
let app: Hono;
|
|
54
56
|
let jwt: JwtHelper;
|
|
@@ -58,6 +60,7 @@ const adminUser = TestUsers.admin;
|
|
|
58
60
|
const JWT_SECRET = "multi-trigger-test-secret-minimum-32-chars!!";
|
|
59
61
|
|
|
60
62
|
beforeAll(async () => {
|
|
63
|
+
await ensureTemporalPolyfill();
|
|
61
64
|
testDb = await createTestDb();
|
|
62
65
|
testRedis = await createTestRedis();
|
|
63
66
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { requestContext } from "../../api/request-context";
|
|
3
3
|
import { createRegistry, defineFeature } from "../../engine";
|
|
4
4
|
import type { AppContext, Registry } from "../../engine/types";
|
|
@@ -192,7 +192,7 @@ describe("scenario 2: scheduled job", () => {
|
|
|
192
192
|
|
|
193
193
|
// BullMQ's repeatable scheduler needs a second or two to register its
|
|
194
194
|
// first tick — a generous delay schedule covers the startup window.
|
|
195
|
-
test("cron job fires via BullMQ scheduler",
|
|
195
|
+
test("cron job fires via BullMQ scheduler", async () => {
|
|
196
196
|
clearLog();
|
|
197
197
|
await withRunner(async () => {
|
|
198
198
|
await waitFor(
|
|
@@ -278,7 +278,7 @@ describe("concurrency: skip", () => {
|
|
|
278
278
|
});
|
|
279
279
|
|
|
280
280
|
describe("concurrency: sequential", () => {
|
|
281
|
-
test("same-name dispatches run strictly one after the other",
|
|
281
|
+
test("same-name dispatches run strictly one after the other", async () => {
|
|
282
282
|
clearLog();
|
|
283
283
|
await withRunner(async (runner) => {
|
|
284
284
|
// Three rapid dispatches. Parallel mode would land all entries
|
|
@@ -318,7 +318,7 @@ describe("concurrency: sequential", () => {
|
|
|
318
318
|
});
|
|
319
319
|
});
|
|
320
320
|
|
|
321
|
-
test("lock is released even when the handler throws",
|
|
321
|
+
test("lock is released even when the handler throws", async () => {
|
|
322
322
|
clearLog();
|
|
323
323
|
await withRunner(async (runner) => {
|
|
324
324
|
// First dispatch fails. If the finally-path didn't release the lock,
|
|
@@ -355,9 +355,7 @@ describe("concurrency: sequential", () => {
|
|
|
355
355
|
});
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
-
test("lock release is value-matched: foreign tokens survive expiration races", {
|
|
359
|
-
timeout: 5_000,
|
|
360
|
-
}, async () => {
|
|
358
|
+
test("lock release is value-matched: foreign tokens survive expiration races", async () => {
|
|
361
359
|
// Pin the contract that distributed-lock's release script enforces:
|
|
362
360
|
// a release call from a worker whose token has already expired and
|
|
363
361
|
// been claimed by someone else must NOT delete the new owner's lock.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Full-stack proof for lifecycle ↔ buildServer wiring.
|
|
2
2
|
// Drives drain() directly — SIGTERM plumbing has its own unit test.
|
|
3
3
|
|
|
4
|
-
import { afterAll, afterEach, beforeAll, describe, expect, test } from "
|
|
4
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
5
5
|
import { defineFeature } from "../../engine";
|
|
6
6
|
import { setupTestStack, type TestStack } from "../../stack";
|
|
7
7
|
import { sharedWidgetEntity } from "../../testing";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import { createLifecycle } from "../lifecycle";
|
|
3
3
|
|
|
4
4
|
describe("lifecycle — state machine", () => {
|
|
@@ -20,7 +20,7 @@ describe("lifecycle — state machine", () => {
|
|
|
20
20
|
|
|
21
21
|
test("markReady from 'ready' is a no-op", () => {
|
|
22
22
|
const lc = createLifecycle({ startReady: true });
|
|
23
|
-
const listener =
|
|
23
|
+
const listener = mock();
|
|
24
24
|
lc.onStateChange(listener);
|
|
25
25
|
lc.markReady();
|
|
26
26
|
expect(listener).not.toHaveBeenCalled();
|
|
@@ -70,7 +70,7 @@ describe("lifecycle — shutdown hooks", () => {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
test("one failing hook does not block the others, and the error is logged", async () => {
|
|
73
|
-
const logger = { error:
|
|
73
|
+
const logger = { error: mock() };
|
|
74
74
|
const lc = createLifecycle({ startReady: true, logger });
|
|
75
75
|
const calls: string[] = [];
|
|
76
76
|
lc.registerShutdownHook("healthy-a", async () => {
|
|
@@ -171,7 +171,7 @@ describe("lifecycle — onStateChange", () => {
|
|
|
171
171
|
|
|
172
172
|
test("unsubscribe stops further callbacks", () => {
|
|
173
173
|
const lc = createLifecycle();
|
|
174
|
-
const cb =
|
|
174
|
+
const cb = mock();
|
|
175
175
|
const unsubscribe = lc.onStateChange(cb);
|
|
176
176
|
lc.markReady();
|
|
177
177
|
expect(cb).toHaveBeenCalledTimes(1);
|
|
@@ -184,9 +184,9 @@ describe("lifecycle — onStateChange", () => {
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
test("broken listener does not break others, and the error is logged", () => {
|
|
187
|
-
const logger = { error:
|
|
187
|
+
const logger = { error: mock() };
|
|
188
188
|
const lc = createLifecycle({ logger });
|
|
189
|
-
const healthy =
|
|
189
|
+
const healthy = mock();
|
|
190
190
|
lc.onStateChange(() => {
|
|
191
191
|
throw new Error("subscriber exploded");
|
|
192
192
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import { createLifecycle } from "../lifecycle";
|
|
3
3
|
import { attachSignalHandlers } from "../signal-handlers";
|
|
4
4
|
import { createTestLifecycle } from "./create-test-lifecycle";
|
|
@@ -6,7 +6,7 @@ import { createTestLifecycle } from "./create-test-lifecycle";
|
|
|
6
6
|
describe("attachSignalHandlers", () => {
|
|
7
7
|
test("SIGTERM triggers drain and calls exit(0)", async () => {
|
|
8
8
|
const lc = createLifecycle({ startReady: true });
|
|
9
|
-
const exit =
|
|
9
|
+
const exit = mock();
|
|
10
10
|
const hookCalls: string[] = [];
|
|
11
11
|
lc.registerShutdownHook("spy", async (signal) => {
|
|
12
12
|
hookCalls.push(signal);
|
|
@@ -25,7 +25,7 @@ describe("attachSignalHandlers", () => {
|
|
|
25
25
|
|
|
26
26
|
test("SIGINT path drains with the right signal label", async () => {
|
|
27
27
|
const lc = createLifecycle({ startReady: true });
|
|
28
|
-
const exit =
|
|
28
|
+
const exit = mock();
|
|
29
29
|
const seen: string[] = [];
|
|
30
30
|
lc.registerShutdownHook("spy", async (signal) => {
|
|
31
31
|
seen.push(signal);
|
|
@@ -44,7 +44,7 @@ describe("attachSignalHandlers", () => {
|
|
|
44
44
|
|
|
45
45
|
test("multiple SIGTERMs still call exit exactly once", async () => {
|
|
46
46
|
const lc = createLifecycle({ startReady: true });
|
|
47
|
-
const exit =
|
|
47
|
+
const exit = mock();
|
|
48
48
|
// Slow hook so we can fire additional signals while drain is in-flight.
|
|
49
49
|
lc.registerShutdownHook("slow", async () => {
|
|
50
50
|
await new Promise((r) => setTimeout(r, 30));
|
|
@@ -74,7 +74,7 @@ describe("attachSignalHandlers", () => {
|
|
|
74
74
|
throw new Error("drain itself exploded");
|
|
75
75
|
},
|
|
76
76
|
});
|
|
77
|
-
const exit =
|
|
77
|
+
const exit = mock();
|
|
78
78
|
const handle = attachSignalHandlers(brokenLifecycle, { exit, signals: ["SIGTERM"] });
|
|
79
79
|
try {
|
|
80
80
|
process.emit("SIGTERM");
|
|
@@ -87,7 +87,7 @@ describe("attachSignalHandlers", () => {
|
|
|
87
87
|
|
|
88
88
|
test("detach() removes the process listeners", () => {
|
|
89
89
|
const lc = createLifecycle({ startReady: true });
|
|
90
|
-
const exit =
|
|
90
|
+
const exit = mock();
|
|
91
91
|
const before = process.listenerCount("SIGTERM");
|
|
92
92
|
const handle = attachSignalHandlers(lc, { exit, signals: ["SIGTERM"] });
|
|
93
93
|
expect(process.listenerCount("SIGTERM")).toBe(before + 1);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Projection-Tabelle muss der Detector die richtigen Tabellen-Namen
|
|
4
4
|
// melden, damit migrate apply den richtigen Rebuild triggert.
|
|
5
5
|
|
|
6
|
-
import { describe, expect, test } from "
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
7
|
import { compareSnapshots } from "../projection-detection";
|
|
8
8
|
import type { Snapshot, SnapshotTable } from "../schema-drift";
|
|
9
9
|
|
package/src/migrations/__tests__/{detect-drift.integration.ts → detect-drift.integration.test.ts}
RENAMED
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
// hier blockiert Container-Starts; jeder False-Negative lässt
|
|
5
5
|
// Schema-Drift unentdeckt durch.
|
|
6
6
|
|
|
7
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
7
8
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { join } from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
|
|
12
|
+
import { asRawClient } from "../../db/query";
|
|
13
|
+
import { ensureTemporalPolyfill } from "../../time/polyfill";
|
|
13
14
|
import { detectDrift } from "../schema-drift";
|
|
14
15
|
|
|
15
|
-
let testDb:
|
|
16
|
+
let testDb: BunTestDb;
|
|
16
17
|
let migrationsDir: string;
|
|
17
18
|
|
|
18
19
|
beforeAll(async () => {
|
|
20
|
+
await ensureTemporalPolyfill();
|
|
19
21
|
testDb = await createTestDb();
|
|
20
22
|
});
|
|
21
23
|
|
|
@@ -82,8 +84,8 @@ function writeSnapshotSimple(idx: number, tableNames: string[]): void {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
async function ensureDrizzleMigrationsTable(): Promise<void> {
|
|
85
|
-
await testDb.db.
|
|
86
|
-
await testDb.db.
|
|
87
|
+
await asRawClient(testDb.db).unsafe(`CREATE SCHEMA IF NOT EXISTS drizzle`);
|
|
88
|
+
await asRawClient(testDb.db).unsafe(`
|
|
87
89
|
CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
|
|
88
90
|
id serial PRIMARY KEY,
|
|
89
91
|
hash text NOT NULL,
|
|
@@ -93,12 +95,13 @@ async function ensureDrizzleMigrationsTable(): Promise<void> {
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
async function dropDrizzleMigrationsTable(): Promise<void> {
|
|
96
|
-
await testDb.db.
|
|
98
|
+
await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drizzle.__drizzle_migrations`);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
async function insertAppliedMigration(hash: string): Promise<void> {
|
|
100
|
-
await testDb.db.
|
|
101
|
-
|
|
102
|
+
await asRawClient(testDb.db).unsafe(
|
|
103
|
+
`INSERT INTO drizzle.__drizzle_migrations (hash, created_at) VALUES ($1, $2)`,
|
|
104
|
+
[hash, Date.now()],
|
|
102
105
|
);
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -106,8 +109,8 @@ describe("detectDrift", () => {
|
|
|
106
109
|
beforeEach(async () => {
|
|
107
110
|
await dropDrizzleMigrationsTable();
|
|
108
111
|
// Cleanup test tables that might still exist from earlier runs
|
|
109
|
-
await testDb.db.
|
|
110
|
-
await testDb.db.
|
|
112
|
+
await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drift_test_users`);
|
|
113
|
+
await asRawClient(testDb.db).unsafe(`DROP TABLE IF EXISTS drift_test_orders`);
|
|
111
114
|
});
|
|
112
115
|
|
|
113
116
|
test("frische DB ohne __drizzle_migrations + 1 Migration im Journal → 1 pending + table missing", async () => {
|
|
@@ -124,7 +127,7 @@ describe("detectDrift", () => {
|
|
|
124
127
|
test("alle Migrations applied + alle Tabellen existieren → ok", async () => {
|
|
125
128
|
writeJournal([{ idx: 0, tag: "0000_init" }]);
|
|
126
129
|
writeSnapshotSimple(0, ["drift_test_users"]);
|
|
127
|
-
await testDb.db.
|
|
130
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
|
|
128
131
|
await ensureDrizzleMigrationsTable();
|
|
129
132
|
await insertAppliedMigration("hash-0000");
|
|
130
133
|
|
|
@@ -140,8 +143,8 @@ describe("detectDrift", () => {
|
|
|
140
143
|
{ idx: 1, tag: "0001_add_orders" },
|
|
141
144
|
]);
|
|
142
145
|
writeSnapshotSimple(1, ["drift_test_users", "drift_test_orders"]);
|
|
143
|
-
await testDb.db.
|
|
144
|
-
await testDb.db.
|
|
146
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
|
|
147
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_orders (id uuid PRIMARY KEY)`);
|
|
145
148
|
await ensureDrizzleMigrationsTable();
|
|
146
149
|
await insertAppliedMigration("hash-0000"); // nur eine applied
|
|
147
150
|
|
|
@@ -155,7 +158,7 @@ describe("detectDrift", () => {
|
|
|
155
158
|
test("alle Migrations applied aber Tabelle fehlt manuell → drift", async () => {
|
|
156
159
|
writeJournal([{ idx: 0, tag: "0000_init" }]);
|
|
157
160
|
writeSnapshotSimple(0, ["drift_test_users", "drift_test_orders"]);
|
|
158
|
-
await testDb.db.
|
|
161
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
|
|
159
162
|
// drift_test_orders bewusst NICHT angelegt (simuliert manuellen DROP)
|
|
160
163
|
await ensureDrizzleMigrationsTable();
|
|
161
164
|
await insertAppliedMigration("hash-0000");
|
|
@@ -179,7 +182,9 @@ describe("detectDrift", () => {
|
|
|
179
182
|
},
|
|
180
183
|
]);
|
|
181
184
|
// DB hat email NULLABLE — drift.
|
|
182
|
-
await testDb.db.
|
|
185
|
+
await asRawClient(testDb.db).unsafe(
|
|
186
|
+
`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, email text)`,
|
|
187
|
+
);
|
|
183
188
|
await ensureDrizzleMigrationsTable();
|
|
184
189
|
await insertAppliedMigration("hash-0000");
|
|
185
190
|
|
|
@@ -204,7 +209,7 @@ describe("detectDrift", () => {
|
|
|
204
209
|
},
|
|
205
210
|
]);
|
|
206
211
|
// DB hat KEINE email-Spalte.
|
|
207
|
-
await testDb.db.
|
|
212
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
|
|
208
213
|
await ensureDrizzleMigrationsTable();
|
|
209
214
|
await insertAppliedMigration("hash-0000");
|
|
210
215
|
|
|
@@ -226,8 +231,8 @@ describe("detectDrift", () => {
|
|
|
226
231
|
},
|
|
227
232
|
]);
|
|
228
233
|
// DB hat zusätzliche Spalte (z.B. manueller ALTER TABLE in Prod).
|
|
229
|
-
await testDb.db.
|
|
230
|
-
|
|
234
|
+
await asRawClient(testDb.db).unsafe(
|
|
235
|
+
`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, secret_legacy text)`,
|
|
231
236
|
);
|
|
232
237
|
await ensureDrizzleMigrationsTable();
|
|
233
238
|
await insertAppliedMigration("hash-0000");
|
|
@@ -251,7 +256,9 @@ describe("detectDrift", () => {
|
|
|
251
256
|
},
|
|
252
257
|
]);
|
|
253
258
|
// DB hat age als TEXT statt INTEGER.
|
|
254
|
-
await testDb.db.
|
|
259
|
+
await asRawClient(testDb.db).unsafe(
|
|
260
|
+
`CREATE TABLE drift_test_users (id uuid PRIMARY KEY, age text)`,
|
|
261
|
+
);
|
|
255
262
|
await ensureDrizzleMigrationsTable();
|
|
256
263
|
await insertAppliedMigration("hash-0000");
|
|
257
264
|
|
|
@@ -278,7 +285,7 @@ describe("detectDrift", () => {
|
|
|
278
285
|
},
|
|
279
286
|
},
|
|
280
287
|
]);
|
|
281
|
-
await testDb.db.
|
|
288
|
+
await asRawClient(testDb.db).unsafe(`
|
|
282
289
|
CREATE TABLE drift_test_users (
|
|
283
290
|
id uuid PRIMARY KEY,
|
|
284
291
|
email text NOT NULL,
|
|
@@ -297,10 +304,10 @@ describe("detectDrift", () => {
|
|
|
297
304
|
test("public.__drizzle_migrations Fallback (Pre-0.20-Drizzle)", async () => {
|
|
298
305
|
writeJournal([{ idx: 0, tag: "0000_init" }]);
|
|
299
306
|
writeSnapshotSimple(0, ["drift_test_users"]);
|
|
300
|
-
await testDb.db.
|
|
307
|
+
await asRawClient(testDb.db).unsafe(`CREATE TABLE drift_test_users (id uuid PRIMARY KEY)`);
|
|
301
308
|
// Legacy: Tabelle in public-Schema statt drizzle-Schema
|
|
302
309
|
await dropDrizzleMigrationsTable();
|
|
303
|
-
await testDb.db.
|
|
310
|
+
await asRawClient(testDb.db).unsafe(`
|
|
304
311
|
CREATE TABLE public.__drizzle_migrations (
|
|
305
312
|
id serial PRIMARY KEY,
|
|
306
313
|
hash text NOT NULL,
|
|
@@ -308,13 +315,14 @@ describe("detectDrift", () => {
|
|
|
308
315
|
)
|
|
309
316
|
`);
|
|
310
317
|
try {
|
|
311
|
-
await testDb.db.
|
|
312
|
-
|
|
318
|
+
await asRawClient(testDb.db).unsafe(
|
|
319
|
+
`INSERT INTO public.__drizzle_migrations (hash, created_at) VALUES ('hash-0000', $1)`,
|
|
320
|
+
[Date.now()],
|
|
313
321
|
);
|
|
314
322
|
const report = await detectDrift(testDb.db, migrationsDir);
|
|
315
323
|
expect(report.ok).toBe(true);
|
|
316
324
|
} finally {
|
|
317
|
-
await testDb.db.
|
|
325
|
+
await asRawClient(testDb.db).unsafe(`DROP TABLE public.__drizzle_migrations`);
|
|
318
326
|
}
|
|
319
327
|
});
|
|
320
328
|
});
|