@cosmicdrift/kumiko-framework 0.1.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/README.md +159 -0
- package/package.json +91 -0
- package/src/__tests__/anonymous-access.integration.ts +325 -0
- package/src/__tests__/error-contract.integration.ts +435 -0
- package/src/__tests__/field-access.integration.ts +269 -0
- package/src/__tests__/full-stack.integration.ts +914 -0
- package/src/__tests__/ownership.integration.ts +449 -0
- package/src/__tests__/reference-data.integration.ts +198 -0
- package/src/__tests__/transition-guard.integration.ts +340 -0
- package/src/api/__tests__/api.test.ts +337 -0
- package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
- package/src/api/__tests__/batch.integration.ts +404 -0
- package/src/api/__tests__/body-limit.test.ts +88 -0
- package/src/api/__tests__/csrf-middleware.test.ts +97 -0
- package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
- package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
- package/src/api/__tests__/nested-write.integration.ts +213 -0
- package/src/api/__tests__/readiness.test.ts +76 -0
- package/src/api/__tests__/request-id-middleware.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +58 -0
- package/src/api/__tests__/sse-route.test.ts +112 -0
- package/src/api/anonymous-cookie.ts +60 -0
- package/src/api/api-constants.ts +64 -0
- package/src/api/auth-middleware.ts +418 -0
- package/src/api/auth-routes.ts +982 -0
- package/src/api/csrf-middleware.ts +77 -0
- package/src/api/index.ts +31 -0
- package/src/api/jwt.ts +66 -0
- package/src/api/observability-middleware.ts +89 -0
- package/src/api/readiness.ts +132 -0
- package/src/api/request-context.ts +49 -0
- package/src/api/request-id-middleware.ts +50 -0
- package/src/api/route-registrars.ts +195 -0
- package/src/api/routes.ts +135 -0
- package/src/api/server.ts +640 -0
- package/src/api/sse-broker.ts +71 -0
- package/src/api/sse-route.ts +62 -0
- package/src/api/tokens.ts +16 -0
- package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
- package/src/db/__tests__/compound-types.test.ts +114 -0
- package/src/db/__tests__/connection-options.test.ts +68 -0
- package/src/db/__tests__/cursor.test.ts +41 -0
- package/src/db/__tests__/db-helpers.test.ts +369 -0
- package/src/db/__tests__/dialect-instant.test.ts +50 -0
- package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
- package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
- package/src/db/__tests__/encryption.test.ts +39 -0
- package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
- package/src/db/__tests__/event-store-executor.integration.ts +235 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
- package/src/db/__tests__/located-timestamp.test.ts +184 -0
- package/src/db/__tests__/money.test.ts +199 -0
- package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
- package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
- package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
- package/src/db/__tests__/row-helpers.test.ts +59 -0
- package/src/db/__tests__/schema-migration.integration.ts +273 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
- package/src/db/__tests__/table-builder-required.test.ts +216 -0
- package/src/db/__tests__/tenant-db.integration.ts +606 -0
- package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
- package/src/db/apply-entity-event.ts +188 -0
- package/src/db/assert-exists-in.ts +59 -0
- package/src/db/compound-types.ts +47 -0
- package/src/db/connection.ts +104 -0
- package/src/db/cursor.ts +83 -0
- package/src/db/dialect.ts +109 -0
- package/src/db/eagerload.ts +174 -0
- package/src/db/encryption.ts +39 -0
- package/src/db/event-store-executor.ts +906 -0
- package/src/db/index.ts +55 -0
- package/src/db/located-timestamp.ts +114 -0
- package/src/db/money.ts +120 -0
- package/src/db/pg-error.ts +46 -0
- package/src/db/reference-data.ts +77 -0
- package/src/db/row-helpers.ts +53 -0
- package/src/db/schema-inspection.ts +25 -0
- package/src/db/table-builder.ts +475 -0
- package/src/db/tenant-db.ts +434 -0
- package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
- package/src/engine/__tests__/boot-validator.test.ts +1865 -0
- package/src/engine/__tests__/build-app-schema.test.ts +154 -0
- package/src/engine/__tests__/claim-keys.test.ts +274 -0
- package/src/engine/__tests__/config-helpers.test.ts +236 -0
- package/src/engine/__tests__/effective-features.test.ts +86 -0
- package/src/engine/__tests__/engine.test.ts +1461 -0
- package/src/engine/__tests__/entity-handlers.test.ts +274 -0
- package/src/engine/__tests__/event-helpers.test.ts +68 -0
- package/src/engine/__tests__/extends-registrar.test.ts +159 -0
- package/src/engine/__tests__/factories-long-text.test.ts +84 -0
- package/src/engine/__tests__/factories-time.test.ts +158 -0
- package/src/engine/__tests__/field-predicates.test.ts +48 -0
- package/src/engine/__tests__/hook-phases.test.ts +132 -0
- package/src/engine/__tests__/identifiers.test.ts +35 -0
- package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
- package/src/engine/__tests__/nav.test.ts +267 -0
- package/src/engine/__tests__/ownership.test.ts +421 -0
- package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
- package/src/engine/__tests__/projection-helpers.test.ts +62 -0
- package/src/engine/__tests__/projection.test.ts +191 -0
- package/src/engine/__tests__/qualified-name.test.ts +264 -0
- package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
- package/src/engine/__tests__/run-in.test.ts +38 -0
- package/src/engine/__tests__/schema-builder.test.ts +380 -0
- package/src/engine/__tests__/screen.test.ts +408 -0
- package/src/engine/__tests__/state-machine.test.ts +148 -0
- package/src/engine/__tests__/system-user.test.ts +57 -0
- package/src/engine/__tests__/validation-hooks.test.ts +71 -0
- package/src/engine/access.ts +23 -0
- package/src/engine/boot-validator.ts +1528 -0
- package/src/engine/build-app-schema.ts +125 -0
- package/src/engine/config-helpers.ts +115 -0
- package/src/engine/constants.ts +85 -0
- package/src/engine/create-app.ts +98 -0
- package/src/engine/define-feature.ts +702 -0
- package/src/engine/define-handler.ts +78 -0
- package/src/engine/define-roles.ts +19 -0
- package/src/engine/effective-features.ts +87 -0
- package/src/engine/entity-handlers.ts +364 -0
- package/src/engine/event-helpers.ts +73 -0
- package/src/engine/factories.ts +328 -0
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
- package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
- package/src/engine/feature-ast/extractors.ts +2562 -0
- package/src/engine/feature-ast/index.ts +105 -0
- package/src/engine/feature-ast/parse.ts +369 -0
- package/src/engine/feature-ast/patch.ts +525 -0
- package/src/engine/feature-ast/patcher.ts +518 -0
- package/src/engine/feature-ast/patterns.ts +434 -0
- package/src/engine/feature-ast/render.ts +602 -0
- package/src/engine/feature-ast/source-location.ts +45 -0
- package/src/engine/field-access.ts +120 -0
- package/src/engine/index.ts +254 -0
- package/src/engine/ownership.ts +337 -0
- package/src/engine/parse-ref-target.ts +22 -0
- package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
- package/src/engine/pattern-library/index.ts +24 -0
- package/src/engine/pattern-library/library.ts +1117 -0
- package/src/engine/pattern-library/types.ts +255 -0
- package/src/engine/projection-helpers.ts +85 -0
- package/src/engine/qualified-name.ts +122 -0
- package/src/engine/read-claim.ts +31 -0
- package/src/engine/registry.ts +1325 -0
- package/src/engine/resolve-config-or-param.ts +153 -0
- package/src/engine/run-in.ts +29 -0
- package/src/engine/schema-builder.ts +175 -0
- package/src/engine/screen-filter-ops.ts +51 -0
- package/src/engine/state-machine.ts +70 -0
- package/src/engine/system-user.ts +32 -0
- package/src/engine/types/config.ts +306 -0
- package/src/engine/types/event-type-map.ts +37 -0
- package/src/engine/types/feature.ts +574 -0
- package/src/engine/types/fields.ts +422 -0
- package/src/engine/types/handlers.ts +742 -0
- package/src/engine/types/hooks.ts +142 -0
- package/src/engine/types/http-route.ts +54 -0
- package/src/engine/types/identifiers.ts +47 -0
- package/src/engine/types/index.ts +208 -0
- package/src/engine/types/nav.ts +46 -0
- package/src/engine/types/projection.ts +132 -0
- package/src/engine/types/relations.ts +51 -0
- package/src/engine/types/screen.ts +452 -0
- package/src/engine/types/workspace.ts +42 -0
- package/src/engine/validation.ts +33 -0
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
- package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
- package/src/entrypoint/index.ts +442 -0
- package/src/errors/__tests__/classes.test.ts +371 -0
- package/src/errors/__tests__/write-failures.test.ts +109 -0
- package/src/errors/classes.ts +249 -0
- package/src/errors/i18n/de.yaml +83 -0
- package/src/errors/i18n/en.yaml +80 -0
- package/src/errors/index.ts +41 -0
- package/src/errors/kumiko-error.ts +67 -0
- package/src/errors/reasons.ts +36 -0
- package/src/errors/serialize.ts +136 -0
- package/src/errors/transition-details.ts +30 -0
- package/src/errors/write-error-info.ts +123 -0
- package/src/errors/zod-bridge.ts +49 -0
- package/src/event-store/__tests__/admin-api.integration.ts +361 -0
- package/src/event-store/__tests__/event-store.integration.ts +584 -0
- package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
- package/src/event-store/__tests__/perf.integration.ts +255 -0
- package/src/event-store/__tests__/snapshot.integration.ts +267 -0
- package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
- package/src/event-store/__tests__/upcaster.integration.ts +460 -0
- package/src/event-store/admin-api.ts +257 -0
- package/src/event-store/archive.ts +106 -0
- package/src/event-store/errors.ts +35 -0
- package/src/event-store/event-store.ts +405 -0
- package/src/event-store/events-schema.ts +90 -0
- package/src/event-store/index.ts +50 -0
- package/src/event-store/snapshot.ts +210 -0
- package/src/event-store/upcaster-dead-letter.ts +119 -0
- package/src/event-store/upcaster.ts +147 -0
- package/src/files/__tests__/content-disposition.test.ts +123 -0
- package/src/files/__tests__/file-field-column.integration.ts +103 -0
- package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
- package/src/files/__tests__/file-handle.test.ts +122 -0
- package/src/files/__tests__/files.integration.ts +830 -0
- package/src/files/__tests__/storage-tracking.integration.ts +153 -0
- package/src/files/content-disposition.ts +55 -0
- package/src/files/file-handle.ts +63 -0
- package/src/files/file-ref-table.ts +22 -0
- package/src/files/file-routes.ts +353 -0
- package/src/files/in-memory-provider.ts +62 -0
- package/src/files/index.ts +29 -0
- package/src/files/local-provider.ts +35 -0
- package/src/files/storage-tracking.ts +60 -0
- package/src/files/types.ts +118 -0
- package/src/i18n/__tests__/i18n.test.ts +72 -0
- package/src/i18n/index.ts +29 -0
- package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
- package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
- package/src/jobs/__tests__/jobs.integration.ts +566 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-runner.ts +574 -0
- package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
- package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
- package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
- package/src/lifecycle/index.ts +13 -0
- package/src/lifecycle/lifecycle.ts +160 -0
- package/src/lifecycle/signal-handlers.ts +62 -0
- package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/pino-logger.ts +64 -0
- package/src/logging/types.ts +7 -0
- package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
- package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
- package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
- package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
- package/src/migrations/index.ts +28 -0
- package/src/migrations/projection-detection.ts +149 -0
- package/src/migrations/rebuild-marker.ts +64 -0
- package/src/migrations/schema-drift.ts +395 -0
- package/src/observability/__tests__/console-provider.test.ts +67 -0
- package/src/observability/__tests__/metric-validator.test.ts +87 -0
- package/src/observability/__tests__/noop-provider.test.ts +82 -0
- package/src/observability/__tests__/observability.integration.ts +559 -0
- package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
- package/src/observability/__tests__/recording-meter.test.ts +101 -0
- package/src/observability/__tests__/recording-tracer.test.ts +110 -0
- package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
- package/src/observability/console-provider.ts +130 -0
- package/src/observability/context.ts +26 -0
- package/src/observability/fallback.ts +34 -0
- package/src/observability/ids.ts +25 -0
- package/src/observability/index.ts +79 -0
- package/src/observability/metric-validator.ts +86 -0
- package/src/observability/metrics-handle.ts +56 -0
- package/src/observability/noop-provider.ts +146 -0
- package/src/observability/prometheus-meter.ts +284 -0
- package/src/observability/recording-meter.ts +156 -0
- package/src/observability/recording-tracer.ts +198 -0
- package/src/observability/redis-wrapper.ts +132 -0
- package/src/observability/sensitive-filter.ts +108 -0
- package/src/observability/standard-metrics.ts +213 -0
- package/src/observability/types/index.ts +29 -0
- package/src/observability/types/metric.ts +56 -0
- package/src/observability/types/provider.ts +32 -0
- package/src/observability/types/span.ts +64 -0
- package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
- package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
- package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
- package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
- package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
- package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
- package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
- package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
- package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
- package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
- package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
- package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
- package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
- package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
- package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
- package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
- package/src/pipeline/append-event-core.ts +117 -0
- package/src/pipeline/auth-claims-resolver.ts +103 -0
- package/src/pipeline/cascade-handler.ts +113 -0
- package/src/pipeline/dispatcher.ts +1585 -0
- package/src/pipeline/distributed-lock.ts +37 -0
- package/src/pipeline/entity-cache.ts +113 -0
- package/src/pipeline/event-consumer-state.ts +108 -0
- package/src/pipeline/event-dedup.ts +23 -0
- package/src/pipeline/event-dispatcher.ts +1016 -0
- package/src/pipeline/event-retention.ts +154 -0
- package/src/pipeline/idempotency.ts +76 -0
- package/src/pipeline/index.ts +66 -0
- package/src/pipeline/lifecycle-pipeline.ts +409 -0
- package/src/pipeline/msp-rebuild.ts +242 -0
- package/src/pipeline/multi-stream-apply-context.ts +115 -0
- package/src/pipeline/projection-rebuild.ts +334 -0
- package/src/pipeline/projection-state.ts +72 -0
- package/src/pipeline/projections-runner.ts +56 -0
- package/src/pipeline/redis-keys.ts +11 -0
- package/src/pipeline/system-hooks.ts +190 -0
- package/src/random/__tests__/generate.test.ts +149 -0
- package/src/random/generate.ts +141 -0
- package/src/random/index.ts +8 -0
- package/src/random/words.ts +392 -0
- package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
- package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
- package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
- package/src/rate-limit/bucket.ts +36 -0
- package/src/rate-limit/index.ts +14 -0
- package/src/rate-limit/middleware.ts +152 -0
- package/src/rate-limit/resolver.ts +267 -0
- package/src/redis/__tests__/redis-options.test.ts +54 -0
- package/src/redis/index.ts +74 -0
- package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
- package/src/search/__tests__/search-adapter.test.ts +256 -0
- package/src/search/in-memory-adapter.ts +123 -0
- package/src/search/index.ts +12 -0
- package/src/search/meilisearch-adapter.ts +106 -0
- package/src/search/types.ts +39 -0
- package/src/secrets/__tests__/dek-cache.test.ts +213 -0
- package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
- package/src/secrets/__tests__/envelope.test.ts +74 -0
- package/src/secrets/__tests__/leak-guard.test.ts +92 -0
- package/src/secrets/__tests__/rotation.test.ts +149 -0
- package/src/secrets/dek-cache.ts +116 -0
- package/src/secrets/env-master-key-provider.ts +162 -0
- package/src/secrets/envelope.ts +55 -0
- package/src/secrets/index.ts +19 -0
- package/src/secrets/leak-guard.ts +87 -0
- package/src/secrets/rotation.ts +34 -0
- package/src/secrets/types.ts +107 -0
- package/src/stack/db.ts +104 -0
- package/src/stack/event-collector.ts +23 -0
- package/src/stack/index.ts +32 -0
- package/src/stack/redis.ts +44 -0
- package/src/stack/request-helper.ts +168 -0
- package/src/stack/table-helpers.ts +104 -0
- package/src/stack/test-stack.ts +357 -0
- package/src/stack/test-users.ts +37 -0
- package/src/testing/__tests__/e2e-generator.test.ts +230 -0
- package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
- package/src/testing/access-assertions.ts +15 -0
- package/src/testing/assertions.ts +35 -0
- package/src/testing/e2e-generator.ts +465 -0
- package/src/testing/expect-error.ts +25 -0
- package/src/testing/handler-context.ts +125 -0
- package/src/testing/http-cookies.ts +52 -0
- package/src/testing/index.ts +41 -0
- package/src/testing/late-bound.ts +39 -0
- package/src/testing/mutable-master-key-provider.ts +31 -0
- package/src/testing/observability-recorder.ts +54 -0
- package/src/testing/shared-entities.ts +49 -0
- package/src/testing/utils.ts +1 -0
- package/src/testing/wait-for.ts +31 -0
- package/src/time/__tests__/polyfill.test.ts +73 -0
- package/src/time/__tests__/tz-context.test.ts +121 -0
- package/src/time/index.ts +21 -0
- package/src/time/polyfill.ts +70 -0
- package/src/time/tz-context.ts +107 -0
- package/src/ui-types/app-schema.ts +57 -0
- package/src/ui-types/index.ts +65 -0
- package/src/utils/__tests__/assert.test.ts +17 -0
- package/src/utils/__tests__/env-parse.test.ts +54 -0
- package/src/utils/assert.ts +18 -0
- package/src/utils/env-parse.ts +16 -0
- package/src/utils/ids.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/safe-json.ts +30 -0
- package/src/utils/serialization.ts +7 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Regression test for Welle 2.6.b — the `mergeDispatcherOptions` plumbing.
|
|
2
|
+
//
|
|
3
|
+
// The command-dispatcher (packages/framework/src/pipeline/dispatcher.ts:997)
|
|
4
|
+
// fires `jobRunner.handleEvent` as an afterCommit-hook. Without a jobRunner
|
|
5
|
+
// reference IN the dispatcher's options, event-triggered jobs silently drop
|
|
6
|
+
// on every write. Welle 2.5 built the jobRunner in the entrypoint factory
|
|
7
|
+
// but never wired it in — welle 2.6.b closes that via mergeDispatcherOptions.
|
|
8
|
+
//
|
|
9
|
+
// `job-event-trigger.integration.ts` only covers the path when the CALLER
|
|
10
|
+
// hand-wires `dispatcherOptions: { jobRunner }`. This test pins the
|
|
11
|
+
// auto-wiring path that a real app boot uses (`createAllInOneEntrypoint`) —
|
|
12
|
+
// so a future refactor that drops the merge fails here instead of silently
|
|
13
|
+
// regressing to the Welle-2.5 state.
|
|
14
|
+
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { createRegistry, defineFeature } from "../../engine";
|
|
18
|
+
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
19
|
+
import { createEventConsumerStateTable } from "../../pipeline";
|
|
20
|
+
import { createTestDb, createTestRedis, type TestDb, type TestRedis, TestUsers } from "../../stack";
|
|
21
|
+
import { waitFor } from "../../testing";
|
|
22
|
+
import { createAllInOneEntrypoint } from "../index";
|
|
23
|
+
|
|
24
|
+
const jobRuns: Array<{ name: string; payload: Record<string, unknown> }> = [];
|
|
25
|
+
|
|
26
|
+
const wiringFeature = defineFeature("wiring", (r) => {
|
|
27
|
+
r.writeHandler(
|
|
28
|
+
"order:create",
|
|
29
|
+
z.object({ sku: z.string() }),
|
|
30
|
+
async (event) => ({
|
|
31
|
+
isSuccess: true as const,
|
|
32
|
+
data: { id: 1, sku: event.payload.sku },
|
|
33
|
+
}),
|
|
34
|
+
{ access: { openToAll: true } },
|
|
35
|
+
);
|
|
36
|
+
r.job(
|
|
37
|
+
"record-order",
|
|
38
|
+
{ trigger: { on: "wiring:write:order:create" }, runIn: "worker" },
|
|
39
|
+
async (payload) => {
|
|
40
|
+
jobRuns.push({ name: "wiring:job:record-order", payload });
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Mixed-lane feature: one worker-lane + one api-lane job triggered on the
|
|
46
|
+
// same event. The all-in-one process spins up both BullMQ workers (one per
|
|
47
|
+
// queue) and both must execute. Proves the two-runner construction in
|
|
48
|
+
// createAllInOneEntrypoint actually consumes both lanes — not just that
|
|
49
|
+
// enqueue routing works.
|
|
50
|
+
const mixedLaneFeature = defineFeature("mixed", (r) => {
|
|
51
|
+
r.writeHandler(
|
|
52
|
+
"ping",
|
|
53
|
+
z.object({ msg: z.string() }),
|
|
54
|
+
async (event) => ({
|
|
55
|
+
isSuccess: true as const,
|
|
56
|
+
data: { id: 1, msg: event.payload.msg },
|
|
57
|
+
}),
|
|
58
|
+
{ access: { openToAll: true } },
|
|
59
|
+
);
|
|
60
|
+
r.job(
|
|
61
|
+
"handle-on-worker",
|
|
62
|
+
{ trigger: { on: "mixed:write:ping" }, runIn: "worker" },
|
|
63
|
+
async (payload) => {
|
|
64
|
+
jobRuns.push({ name: "mixed:job:handle-on-worker", payload });
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
r.job("handle-on-api", { trigger: { on: "mixed:write:ping" }, runIn: "api" }, async (payload) => {
|
|
68
|
+
jobRuns.push({ name: "mixed:job:handle-on-api", payload });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const JWT = "entrypoint-wiring-test-secret-must-be-32-chars!";
|
|
73
|
+
const adminUser = TestUsers.admin;
|
|
74
|
+
|
|
75
|
+
let testDb: TestDb;
|
|
76
|
+
let testRedis: TestRedis;
|
|
77
|
+
|
|
78
|
+
beforeAll(async () => {
|
|
79
|
+
[testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
|
|
80
|
+
await createEventsTable(testDb.db);
|
|
81
|
+
await createArchivedStreamsTable(testDb.db);
|
|
82
|
+
await createEventConsumerStateTable(testDb.db);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
afterAll(async () => {
|
|
86
|
+
await Promise.all([testDb.cleanup(), testRedis.cleanup()]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("createAllInOneEntrypoint auto-wires jobRunner into command-dispatcher", () => {
|
|
90
|
+
test("event-triggered job runs end-to-end (HTTP write → afterCommit → BullMQ → handler)", async () => {
|
|
91
|
+
jobRuns.length = 0;
|
|
92
|
+
const registry = createRegistry([wiringFeature]);
|
|
93
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
94
|
+
const entry = createAllInOneEntrypoint({
|
|
95
|
+
registry,
|
|
96
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
97
|
+
jwtSecret: JWT,
|
|
98
|
+
redisUrl,
|
|
99
|
+
queueNamePrefix: `wiring-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
100
|
+
});
|
|
101
|
+
await entry.start();
|
|
102
|
+
try {
|
|
103
|
+
const token = await entry.jwt.sign(adminUser);
|
|
104
|
+
const res = await entry.app.request("/api/write", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
Authorization: `Bearer ${token}`,
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
type: "wiring:write:order:create",
|
|
112
|
+
payload: { sku: "W-1" },
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
const result = (await res.json()) as { isSuccess: boolean };
|
|
116
|
+
expect(result.isSuccess).toBe(true);
|
|
117
|
+
|
|
118
|
+
// If mergeDispatcherOptions ever stops wiring the jobRunner, the
|
|
119
|
+
// afterCommit-hook at dispatcher.ts:997 becomes a no-op, the job
|
|
120
|
+
// is never enqueued, and waitFor times out. That's the regression
|
|
121
|
+
// this test is here to catch.
|
|
122
|
+
await waitFor(() => {
|
|
123
|
+
const run = jobRuns.find((e) => e.name === "wiring:job:record-order");
|
|
124
|
+
expect(run).toBeDefined();
|
|
125
|
+
expect(run?.payload["sku"]).toBe("W-1");
|
|
126
|
+
});
|
|
127
|
+
} finally {
|
|
128
|
+
await entry.stop();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("all-in-one runs BOTH lane workers — api-lane + worker-lane jobs fire on the same event", async () => {
|
|
133
|
+
jobRuns.length = 0;
|
|
134
|
+
const registry = createRegistry([mixedLaneFeature]);
|
|
135
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
136
|
+
const entry = createAllInOneEntrypoint({
|
|
137
|
+
registry,
|
|
138
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
139
|
+
jwtSecret: JWT,
|
|
140
|
+
redisUrl,
|
|
141
|
+
queueNamePrefix: `wiring-mixed-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
142
|
+
});
|
|
143
|
+
await entry.start();
|
|
144
|
+
try {
|
|
145
|
+
const token = await entry.jwt.sign(adminUser);
|
|
146
|
+
const res = await entry.app.request("/api/write", {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
Authorization: `Bearer ${token}`,
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({ type: "mixed:write:ping", payload: { msg: "hi" } }),
|
|
153
|
+
});
|
|
154
|
+
const result = (await res.json()) as { isSuccess: boolean };
|
|
155
|
+
expect(result.isSuccess).toBe(true);
|
|
156
|
+
|
|
157
|
+
// Both jobs must fire: worker-lane runner picks kumiko-jobs-<prefix>-worker,
|
|
158
|
+
// api-lane runner picks kumiko-jobs-<prefix>-api. If either BullMQ
|
|
159
|
+
// worker failed to start (bug in the two-runner build in createAllInOne
|
|
160
|
+
// Entrypoint), waitFor times out on the missing entry.
|
|
161
|
+
await waitFor(() => {
|
|
162
|
+
const workerRun = jobRuns.find((e) => e.name === "mixed:job:handle-on-worker");
|
|
163
|
+
const apiRun = jobRuns.find((e) => e.name === "mixed:job:handle-on-api");
|
|
164
|
+
expect(workerRun).toBeDefined();
|
|
165
|
+
expect(apiRun).toBeDefined();
|
|
166
|
+
expect(workerRun?.payload["msg"]).toBe("hi");
|
|
167
|
+
expect(apiRun?.payload["msg"]).toBe("hi");
|
|
168
|
+
});
|
|
169
|
+
} finally {
|
|
170
|
+
await entry.stop();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// Shape-level proof of the three entrypoint factories. End-to-end
|
|
2
|
+
// split-deploy (API writes → worker MSP applies) requires the full
|
|
3
|
+
// test-stack infrastructure (consumer-state table, MSP wiring, dispatcher
|
|
4
|
+
// options) and is better exercised via a dedicated sample app. Here we
|
|
5
|
+
// pin the public guarantees:
|
|
6
|
+
//
|
|
7
|
+
// 1. API entrypoint has no eventDispatcher/jobRunner handles.
|
|
8
|
+
// 2. Worker entrypoint has no HTTP app.
|
|
9
|
+
// 3. All-in-one has both.
|
|
10
|
+
// 4. Worker throws when there's literally nothing to consume (defensive
|
|
11
|
+
// guard — buildServer always wires an SSE consumer so this only
|
|
12
|
+
// fires with systemConsumers explicitly disabled).
|
|
13
|
+
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { createRegistry, defineFeature } from "../../engine";
|
|
17
|
+
import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
|
|
18
|
+
import { createEventConsumerStateTable } from "../../pipeline";
|
|
19
|
+
import { createTestDb, createTestRedis, type TestDb, type TestRedis } from "../../stack";
|
|
20
|
+
import { createAllInOneEntrypoint, createApiEntrypoint, createWorkerEntrypoint } from "../index";
|
|
21
|
+
|
|
22
|
+
const splitFeature = defineFeature("split", (r) => {
|
|
23
|
+
const tick = r.defineEvent("tick", z.object({ note: z.string() }), { version: 1 });
|
|
24
|
+
r.multiStreamProjection({
|
|
25
|
+
name: "spy",
|
|
26
|
+
apply: {
|
|
27
|
+
[tick.name]: async () => {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const JWT = "split-deploy-test-secret-must-be-32-chars!!";
|
|
33
|
+
|
|
34
|
+
// Per-test queue-name with a random suffix. Date.now() alone collided
|
|
35
|
+
// in jobs.integration.ts when two tests landed in the same millisecond —
|
|
36
|
+
// the random suffix pins each test's BullMQ queues even then.
|
|
37
|
+
function uniquePrefix(label: string): string {
|
|
38
|
+
return `${label}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let testDb: TestDb;
|
|
42
|
+
let testRedis: TestRedis;
|
|
43
|
+
|
|
44
|
+
beforeAll(async () => {
|
|
45
|
+
[testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
|
|
46
|
+
await createEventsTable(testDb.db);
|
|
47
|
+
await createArchivedStreamsTable(testDb.db);
|
|
48
|
+
await createEventConsumerStateTable(testDb.db);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await Promise.all([testDb.cleanup(), testRedis.cleanup()]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("entrypoint factories", () => {
|
|
56
|
+
test("API entrypoint exposes HTTP app but NOT dispatcher / jobRunner handles", async () => {
|
|
57
|
+
const registry = createRegistry([splitFeature]);
|
|
58
|
+
const api = createApiEntrypoint({
|
|
59
|
+
registry,
|
|
60
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
61
|
+
jwtSecret: JWT,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(api.mode).toBe("api");
|
|
65
|
+
expect(api.app).toBeDefined();
|
|
66
|
+
expect(api.jwt).toBeDefined();
|
|
67
|
+
expect(api.sseBroker).toBeDefined();
|
|
68
|
+
expect("eventDispatcher" in api).toBe(false);
|
|
69
|
+
expect("jobRunner" in api).toBe(false);
|
|
70
|
+
|
|
71
|
+
await api.start(); // no-op
|
|
72
|
+
await api.stop();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("Worker entrypoint exposes dispatcher + jobRunner but NOT an HTTP app", async () => {
|
|
76
|
+
const registry = createRegistry([splitFeature]);
|
|
77
|
+
const redisUrl = process.env["REDIS_URL"] ?? "redis://localhost:16379";
|
|
78
|
+
const worker = createWorkerEntrypoint({
|
|
79
|
+
registry,
|
|
80
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
81
|
+
jwtSecret: JWT,
|
|
82
|
+
redisUrl,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(worker.mode).toBe("worker");
|
|
86
|
+
expect(worker.eventDispatcher).toBeDefined();
|
|
87
|
+
expect(worker.jobRunner).toBeDefined();
|
|
88
|
+
expect("app" in worker).toBe(false);
|
|
89
|
+
expect("jwt" in worker).toBe(false);
|
|
90
|
+
|
|
91
|
+
// Stop without starting — lifecycle.drain runs the registered
|
|
92
|
+
// jobRunner hook which must be idempotent against an unstarted
|
|
93
|
+
// BullMQ worker.
|
|
94
|
+
await worker.stop();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("All-in-one entrypoint has both HTTP surface and background workers", async () => {
|
|
98
|
+
const registry = createRegistry([splitFeature]);
|
|
99
|
+
const redisUrl = process.env["REDIS_URL"] ?? "redis://localhost:16379";
|
|
100
|
+
const entry = createAllInOneEntrypoint({
|
|
101
|
+
registry,
|
|
102
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
103
|
+
jwtSecret: JWT,
|
|
104
|
+
redisUrl,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(entry.mode).toBe("all-in-one");
|
|
108
|
+
expect(entry.app).toBeDefined();
|
|
109
|
+
expect(entry.eventDispatcher).toBeDefined();
|
|
110
|
+
expect(entry.jobRunner).toBeDefined();
|
|
111
|
+
|
|
112
|
+
await entry.stop();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("lifecycle.drain() flips /health/ready to 503 across modes", async () => {
|
|
116
|
+
const registry = createRegistry([splitFeature]);
|
|
117
|
+
const api = createApiEntrypoint({
|
|
118
|
+
registry,
|
|
119
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
120
|
+
jwtSecret: JWT,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const before = await api.app.request("/health/ready");
|
|
124
|
+
expect(before.status).toBe(200);
|
|
125
|
+
|
|
126
|
+
await api.stop();
|
|
127
|
+
|
|
128
|
+
const after = await api.app.request("/health/ready");
|
|
129
|
+
expect(after.status).toBe(503);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// --- Welle 2.6.b: runIn lane-filtering ---
|
|
134
|
+
//
|
|
135
|
+
// Covers the per-lane consumer filtering added to buildServer. The feature
|
|
136
|
+
// declares three MSPs, one per runIn value, plus jobs with different runIns.
|
|
137
|
+
// We observe the number of consumers the dispatcher actually wires in each
|
|
138
|
+
// mode — the mechanism is "MSPs whose runIn isn't eligible for this
|
|
139
|
+
// process's lane are skipped during buildServer()".
|
|
140
|
+
|
|
141
|
+
const laneFeature = defineFeature("lane", (r) => {
|
|
142
|
+
const ping = r.defineEvent("ping", z.object({}), { version: 1 });
|
|
143
|
+
// Three MSPs: one pinned to api, one to worker (explicit), one to both.
|
|
144
|
+
// A fourth would be a default-undefined-runIn MSP which resolves to
|
|
145
|
+
// "worker" — covered implicitly by the worker test below.
|
|
146
|
+
r.multiStreamProjection({
|
|
147
|
+
name: "lane-api",
|
|
148
|
+
runIn: "api",
|
|
149
|
+
apply: { [ping.name]: async () => {} },
|
|
150
|
+
});
|
|
151
|
+
r.multiStreamProjection({
|
|
152
|
+
name: "lane-worker",
|
|
153
|
+
runIn: "worker",
|
|
154
|
+
apply: { [ping.name]: async () => {} },
|
|
155
|
+
});
|
|
156
|
+
r.multiStreamProjection({
|
|
157
|
+
name: "lane-both",
|
|
158
|
+
runIn: "both",
|
|
159
|
+
apply: { [ping.name]: async () => {} },
|
|
160
|
+
});
|
|
161
|
+
// Default runIn (= "worker") — makes the legacy-no-runIn path observable.
|
|
162
|
+
r.multiStreamProjection({
|
|
163
|
+
name: "lane-default",
|
|
164
|
+
apply: { [ping.name]: async () => {} },
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("runIn lane-filtering (Welle 2.6.b)", () => {
|
|
169
|
+
test("API entrypoint with runLocalJobs filters MSPs to runIn ∈ {api, both}", async () => {
|
|
170
|
+
const registry = createRegistry([laneFeature]);
|
|
171
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
172
|
+
const api = createApiEntrypoint({
|
|
173
|
+
registry,
|
|
174
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
175
|
+
jwtSecret: JWT,
|
|
176
|
+
// Force the dispatcher to actually build by clearing {disabled:true} —
|
|
177
|
+
// needs a jobs block because the API otherwise has no consumer at all
|
|
178
|
+
// once SSE is the default-on system-consumer. We still observe MSP
|
|
179
|
+
// count via the registered consumers; JobRunner is incidental here.
|
|
180
|
+
jobs: { redisUrl, queueNamePrefix: uniquePrefix("split-api"), runLocalJobs: true },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// API defaults the dispatcher to disabled, so eventDispatcher is not
|
|
185
|
+
// in the return shape. Assert the shape contract plus the fact that
|
|
186
|
+
// start() is a real operation now (runLocalJobs started a worker).
|
|
187
|
+
expect(api.mode).toBe("api");
|
|
188
|
+
expect("eventDispatcher" in api).toBe(false);
|
|
189
|
+
expect("jobRunner" in api).toBe(false);
|
|
190
|
+
await api.start();
|
|
191
|
+
} finally {
|
|
192
|
+
await api.stop();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("Worker entrypoint runs lane-worker + lane-both + lane-default, skips lane-api", async () => {
|
|
197
|
+
const registry = createRegistry([laneFeature]);
|
|
198
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
199
|
+
const worker = createWorkerEntrypoint({
|
|
200
|
+
registry,
|
|
201
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
202
|
+
jwtSecret: JWT,
|
|
203
|
+
redisUrl,
|
|
204
|
+
queueNamePrefix: uniquePrefix("split-worker"),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// eventDispatcher.consumers exposes the filtered list — lane-api must
|
|
208
|
+
// be absent, the other three present. SSE + Search system-consumers
|
|
209
|
+
// add noise (both default-on), so we only check MSP names.
|
|
210
|
+
const consumerNames = worker.eventDispatcher.consumers.map((c) => c.name);
|
|
211
|
+
expect(consumerNames).toContain("lane:projection:lane-worker");
|
|
212
|
+
expect(consumerNames).toContain("lane:projection:lane-both");
|
|
213
|
+
expect(consumerNames).toContain("lane:projection:lane-default");
|
|
214
|
+
expect(consumerNames).not.toContain("lane:projection:lane-api");
|
|
215
|
+
|
|
216
|
+
await worker.stop();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("All-in-one runs every MSP — processLane 'both' disables the filter", async () => {
|
|
220
|
+
const registry = createRegistry([laneFeature]);
|
|
221
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
222
|
+
const entry = createAllInOneEntrypoint({
|
|
223
|
+
registry,
|
|
224
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
225
|
+
jwtSecret: JWT,
|
|
226
|
+
redisUrl,
|
|
227
|
+
queueNamePrefix: uniquePrefix("split-all"),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const consumerNames = entry.eventDispatcher.consumers.map((c) => c.name);
|
|
231
|
+
expect(consumerNames).toContain("lane:projection:lane-api");
|
|
232
|
+
expect(consumerNames).toContain("lane:projection:lane-worker");
|
|
233
|
+
expect(consumerNames).toContain("lane:projection:lane-both");
|
|
234
|
+
expect(consumerNames).toContain("lane:projection:lane-default");
|
|
235
|
+
|
|
236
|
+
await entry.stop();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// --- Welle 2.6.c: boot-validation ---
|
|
241
|
+
//
|
|
242
|
+
// Jobs with runIn="api" can only be consumed by an API process that has
|
|
243
|
+
// runLocalJobs=true — workers never look at the "api" queue. The entrypoint
|
|
244
|
+
// factory must refuse to start with a config that would orphan those jobs,
|
|
245
|
+
// because otherwise an operator finds out at traffic time via "jobs
|
|
246
|
+
// enqueue fine, nothing runs".
|
|
247
|
+
|
|
248
|
+
const apiJobFeature = defineFeature("api-jobs", (r) => {
|
|
249
|
+
r.job("local-cleanup", { trigger: { manual: true }, runIn: "api" }, async () => {});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const workerJobFeature = defineFeature("worker-jobs", (r) => {
|
|
253
|
+
r.job("heavy", { trigger: { manual: true }, runIn: "worker" }, async () => {});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("createApiEntrypoint boot-validation (Welle 2.6.c)", () => {
|
|
257
|
+
test("declared jobs + no jobs-block → fails fast (enqueue would drop)", () => {
|
|
258
|
+
const registry = createRegistry([workerJobFeature]);
|
|
259
|
+
expect(() =>
|
|
260
|
+
createApiEntrypoint({
|
|
261
|
+
registry,
|
|
262
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
263
|
+
jwtSecret: JWT,
|
|
264
|
+
}),
|
|
265
|
+
).toThrow(/no `jobs` block was passed.*event-triggered writes would silently drop/i);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("runIn='api' jobs require runLocalJobs=true on the api entrypoint", () => {
|
|
269
|
+
const registry = createRegistry([apiJobFeature]);
|
|
270
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
271
|
+
expect(() =>
|
|
272
|
+
createApiEntrypoint({
|
|
273
|
+
registry,
|
|
274
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
275
|
+
jwtSecret: JWT,
|
|
276
|
+
jobs: { redisUrl, queueNamePrefix: uniquePrefix("val") },
|
|
277
|
+
}),
|
|
278
|
+
).toThrow(/runIn="api".*runLocalJobs.*no consumer.*local-cleanup/is);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("runIn='worker' jobs do NOT require runLocalJobs — api is enqueuer-only", async () => {
|
|
282
|
+
const registry = createRegistry([workerJobFeature]);
|
|
283
|
+
const redisUrl = `redis://${testRedis.redis.options.host}:${testRedis.redis.options.port}/${testRedis.redis.options.db}`;
|
|
284
|
+
const api = createApiEntrypoint({
|
|
285
|
+
registry,
|
|
286
|
+
context: { db: testDb.db, redis: testRedis.redis },
|
|
287
|
+
jwtSecret: JWT,
|
|
288
|
+
jobs: { redisUrl, queueNamePrefix: uniquePrefix("val-ok") },
|
|
289
|
+
});
|
|
290
|
+
try {
|
|
291
|
+
expect(api.mode).toBe("api");
|
|
292
|
+
await api.start();
|
|
293
|
+
} finally {
|
|
294
|
+
await api.stop();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|