@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 +9 -38
- package/package.json +21 -2
- package/src/__tests__/anonymous-access.integration.test.ts +185 -0
- package/src/__tests__/consumer-cli.integration.test.ts +172 -0
- package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
- package/src/__tests__/full-stack.integration.test.ts +1 -1
- package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
- package/src/__tests__/schema-cli.integration.test.ts +50 -0
- package/src/__tests__/store-table.integration.test.ts +93 -0
- package/src/api/__tests__/api.test.ts +373 -0
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +101 -11
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +200 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
- package/src/api/__tests__/pat-scope.test.ts +36 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
- package/src/api/__tests__/request-id-middleware.test.ts +51 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/api-constants.ts +11 -0
- package/src/api/auth-middleware.ts +248 -40
- package/src/api/auth-routes.ts +576 -95
- package/src/api/index.ts +13 -4
- package/src/api/jwt.ts +170 -11
- package/src/api/pat-scope.ts +14 -0
- package/src/api/pii-leak-guard.ts +47 -0
- package/src/api/request-context.ts +3 -0
- package/src/api/request-id-middleware.ts +2 -0
- package/src/api/routes.ts +169 -2
- package/src/api/server.ts +112 -16
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/__tests__/PATTERN.md +0 -1
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/__tests__/write-brand.test.ts +21 -0
- package/src/bun-db/connection.ts +3 -3
- package/src/bun-db/index.ts +2 -0
- package/src/bun-db/query.ts +105 -32
- package/src/consumer-cli.ts +134 -0
- package/src/crypto/__tests__/blind-index.test.ts +130 -0
- package/src/crypto/__tests__/event-pii.test.ts +161 -0
- package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
- package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
- package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
- package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
- package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
- package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
- package/src/crypto/blind-index.ts +118 -0
- package/src/crypto/event-pii.ts +70 -0
- package/src/crypto/in-memory-kms-adapter.ts +50 -0
- package/src/crypto/index.ts +67 -0
- package/src/crypto/kms-adapter.ts +2 -0
- package/src/crypto/pg-kms-adapter.ts +295 -0
- package/src/crypto/pii-field-encryption.ts +248 -0
- package/src/crypto/request-kms-cache.ts +38 -0
- package/src/crypto/subject-resolver.ts +91 -0
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
- package/src/db/__tests__/blind-index.integration.test.ts +248 -0
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/collect-table-metas.test.ts +10 -10
- package/src/db/__tests__/config-seed.integration.test.ts +13 -5
- package/src/db/__tests__/dialect-instant.test.ts +1 -4
- package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
- package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
- package/src/db/__tests__/feature-table-sources.test.ts +34 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
- package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-generator.test.ts +21 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
- package/src/db/__tests__/rebuild-marker.test.ts +13 -3
- package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
- package/src/db/__tests__/schema-inspection.test.ts +7 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
- package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
- package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/apply-entity-event.ts +18 -14
- package/src/db/blind-index-cleanup.ts +55 -0
- package/src/db/bun-provider.ts +2 -2
- package/src/db/collect-table-metas.ts +6 -7
- package/src/db/config-seed.ts +9 -9
- package/src/db/connection.ts +7 -12
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +20 -26
- package/src/db/entity-field-encryption.ts +81 -24
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +63 -90
- package/src/db/event-store-executor-context.ts +404 -0
- package/src/db/event-store-executor-read.ts +275 -0
- package/src/db/event-store-executor-write.ts +644 -0
- package/src/db/event-store-executor.ts +28 -1155
- package/src/db/feature-table-sources.ts +6 -5
- package/src/db/index.ts +20 -3
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-generator.ts +39 -6
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +9 -1
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/backfill-pii.ts +277 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/queries/event-store.ts +126 -19
- package/src/db/queries/projection-rebuild.ts +22 -8
- package/src/db/queries/shadow-swap.ts +183 -0
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/query-api.ts +1 -0
- package/src/db/query.ts +1 -0
- package/src/db/rebuild-marker.ts +18 -2
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +257 -0
- package/src/db/schema-inspection.ts +1 -1
- package/src/db/table-builder.ts +102 -71
- package/src/db/tenant-db.ts +15 -53
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
- package/src/engine/__tests__/boot-validator.test.ts +219 -15
- package/src/engine/__tests__/build-app-schema.test.ts +82 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
- package/src/engine/__tests__/build-target.test.ts +3 -11
- package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
- package/src/engine/__tests__/config-helpers.test.ts +16 -0
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +163 -1
- package/src/engine/__tests__/entity-handlers.test.ts +80 -0
- package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
- package/src/engine/__tests__/factories-time.test.ts +2 -66
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
- package/src/engine/__tests__/feature-manifest.test.ts +31 -1
- package/src/engine/__tests__/field-access.test.ts +23 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/membership-roles.test.ts +4 -10
- package/src/engine/__tests__/nav.test.ts +86 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
- package/src/engine/__tests__/post-query-hook.test.ts +7 -7
- package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
- package/src/engine/__tests__/registry.test.ts +144 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/screen.test.ts +147 -1
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
- package/src/engine/__tests__/store-table.test.ts +229 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
- package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
- package/src/engine/boot-validator/action-wiring.ts +149 -0
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/config-deps.ts +40 -4
- package/src/engine/boot-validator/entity-handler.ts +20 -21
- package/src/engine/boot-validator/entity-list-screens.ts +88 -0
- package/src/engine/boot-validator/gdpr-storage.ts +0 -20
- package/src/engine/boot-validator/i18n-keys.ts +58 -4
- package/src/engine/boot-validator/index.ts +33 -12
- package/src/engine/boot-validator/nav.ts +125 -0
- package/src/engine/boot-validator/pii-retention.ts +95 -10
- package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
- package/src/engine/boot-validator/workspaces.ts +68 -0
- package/src/engine/build-app-schema.ts +16 -0
- package/src/engine/build-config-feature-schema.ts +44 -7
- package/src/engine/codemod/pipeline-codemod.ts +5 -5
- package/src/engine/config-helpers.ts +15 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/define-feature.ts +95 -947
- package/src/engine/define-handler.ts +28 -94
- package/src/engine/define-workflow.ts +1 -1
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/entity-handlers.ts +76 -11
- package/src/engine/extensions/tenant-data.ts +19 -0
- package/src/engine/extensions/user-data.ts +29 -2
- package/src/engine/factories.ts +8 -49
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
- package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
- package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
- package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
- package/src/engine/feature-ast/extractors/events.ts +322 -0
- package/src/engine/feature-ast/extractors/handlers.ts +234 -0
- package/src/engine/feature-ast/extractors/hooks.ts +243 -0
- package/src/engine/feature-ast/extractors/index.ts +34 -31
- package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
- package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
- package/src/engine/feature-ast/extractors/round1.ts +3 -3
- package/src/engine/feature-ast/extractors/round5.ts +3 -3
- package/src/engine/feature-ast/extractors/round6.ts +2 -36
- package/src/engine/feature-ast/extractors/shared.ts +64 -6
- package/src/engine/feature-ast/index.ts +2 -4
- package/src/engine/feature-ast/parse.ts +130 -23
- package/src/engine/feature-ast/patch.ts +39 -50
- package/src/engine/feature-ast/patcher.ts +37 -38
- package/src/engine/feature-ast/patterns.ts +50 -62
- package/src/engine/feature-ast/render.ts +67 -44
- package/src/engine/feature-builder-state.ts +168 -0
- package/src/engine/feature-config-events-jobs.ts +403 -0
- package/src/engine/feature-entity-handlers.ts +208 -0
- package/src/engine/feature-manifest.ts +2 -1
- package/src/engine/feature-ui-extensions.ts +500 -0
- package/src/engine/field-access.ts +13 -2
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +16 -0
- package/src/engine/index.ts +23 -7
- package/src/engine/membership-roles.ts +13 -0
- package/src/engine/object-form.ts +27 -0
- package/src/engine/ownership.ts +26 -79
- package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
- package/src/engine/pattern-library/library.ts +44 -1152
- package/src/engine/pattern-library/mixed-schemas.ts +450 -0
- package/src/engine/pattern-library/opaque-schemas.ts +124 -0
- package/src/engine/pattern-library/shared-fields.ts +75 -0
- package/src/engine/pattern-library/static-schemas.ts +456 -0
- package/src/engine/pipeline.ts +6 -11
- package/src/engine/registry-facade.ts +362 -0
- package/src/engine/registry-ingest.ts +478 -0
- package/src/engine/registry-state.ts +388 -0
- package/src/engine/registry-validate.ts +642 -0
- package/src/engine/registry.ts +78 -1658
- package/src/engine/run-pipeline.ts +1 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/soft-delete-cleanup.ts +6 -2
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -482
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -972
- package/src/engine/types/fields.ts +2 -675
- package/src/engine/types/handlers.ts +2 -774
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -54
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +86 -40
- package/src/engine/types/nav.ts +2 -63
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -138
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -574
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -132
- package/src/engine/types/workspace.ts +2 -49
- package/src/engine/validate-projection-allowlist.ts +6 -6
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
- package/src/entrypoint/index.ts +49 -6
- package/src/errors/classes.ts +22 -1
- package/src/errors/field-issue.ts +0 -3
- package/src/errors/index.ts +1 -1
- package/src/errors/write-error-info.ts +10 -22
- package/src/es-ops/README.md +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
- package/src/es-ops/context.ts +4 -2
- package/src/es-ops/types.ts +8 -1
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
- package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
- package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
- package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
- package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +64 -78
- package/src/event-store/events-schema.ts +11 -13
- package/src/event-store/index.ts +18 -3
- package/src/event-store/rebuild-dead-letter.ts +111 -0
- package/src/event-store/snapshot.ts +63 -40
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/file-ref-entity.test.ts +8 -0
- package/src/files/__tests__/files.integration.test.ts +24 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/provider-resolver.test.ts +70 -0
- package/src/files/__tests__/write-stream.test.ts +13 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/file-ref-entity.ts +2 -2
- package/src/files/file-routes.ts +13 -0
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +27 -8
- package/src/files/provider-resolver.ts +31 -20
- package/src/files/types.ts +13 -55
- package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
- package/src/i18n/required-surface-keys.ts +120 -1
- package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
- package/src/jobs/job-runner.ts +82 -12
- package/src/logging/types.ts +1 -7
- package/src/migrations/pending-rebuilds.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +4 -1
- package/src/observability/__tests__/recording-tracer.test.ts +6 -4
- package/src/observability/index.ts +2 -0
- package/src/observability/noop-provider.ts +0 -9
- package/src/observability/recording-tracer.ts +0 -12
- package/src/observability/standard-metrics.ts +64 -2
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -64
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
- package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
- package/src/pipeline/dispatch-batch.ts +187 -0
- package/src/pipeline/dispatch-query.ts +165 -0
- package/src/pipeline/dispatch-shared.ts +826 -0
- package/src/pipeline/dispatch-stream.ts +90 -0
- package/src/pipeline/dispatch-write.ts +451 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +44 -1372
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +30 -2
- package/src/pipeline/event-dispatcher-admin.ts +293 -0
- package/src/pipeline/event-dispatcher-delivery.ts +305 -0
- package/src/pipeline/event-dispatcher.ts +85 -542
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/msp-rebuild.ts +42 -3
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/projection-rebuild.ts +105 -3
- package/src/pipeline/system-hooks.ts +144 -1
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +16 -32
- package/src/schema-cli.ts +76 -16
- package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
- package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
- package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
- package/src/search/index.ts +6 -0
- package/src/search/meilisearch-adapter.ts +13 -12
- package/src/search/reindex-entity.ts +177 -0
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/contains-secret.test.ts +34 -0
- package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/dek-cache.ts +26 -5
- package/src/secrets/envelope-cipher.ts +53 -0
- package/src/secrets/envelope.ts +5 -3
- package/src/secrets/index.ts +13 -1
- package/src/secrets/stored-envelope.ts +46 -0
- package/src/secrets/types.ts +2 -162
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +4 -3
- package/src/stack/redis.ts +8 -0
- package/src/stack/request-helper.ts +38 -2
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +249 -131
- package/src/testing/__tests__/late-bound.test.ts +32 -0
- package/src/testing/__tests__/wait-for.test.ts +59 -0
- package/src/testing/boot-validator-fixture.ts +121 -0
- package/src/testing/e2e-generator.ts +8 -0
- package/src/testing/file-provider-contract.ts +104 -0
- package/src/testing/handler-context.ts +3 -1
- package/src/testing/index.ts +5 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/mutable-master-key-provider.ts +29 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/__tests__/tz-dateline.test.ts +8 -10
- package/src/time/geo-tz.ts +1 -32
- package/src/time/index.ts +1 -0
- package/src/time/legacy-date.ts +18 -0
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +44 -85
- package/src/ui-types/app-schema.ts +12 -0
- package/src/ui-types/index.ts +20 -7
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/__tests__/raw-table.integration.test.ts +0 -116
- package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
- package/src/db/__tests__/encryption.test.ts +0 -39
- package/src/db/encryption.ts +0 -39
- package/src/db/row-helpers.ts +0 -4
- package/src/engine/__tests__/raw-table.test.ts +0 -150
- package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
- package/src/engine/feature-ast/extractors/round4.ts +0 -1366
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Issue #1350 — bounded auto-revival of a dead consumer.
|
|
2
|
+
//
|
|
3
|
+
// acquireConsumerState no longer parks a dead consumer forever: once its
|
|
4
|
+
// last write is older than rearmCooldownMs it gets reset to idle and
|
|
5
|
+
// retried, up to maxRearmCount times. A poison event (permanent failure)
|
|
6
|
+
// still ends up permanently dead once the budget is exhausted — the
|
|
7
|
+
// mechanism is a bounded number of extra chances, not unlimited retry.
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
10
|
+
import { createEventStoreExecutor } from "../../db/event-store-executor";
|
|
11
|
+
import { asRawClient } from "../../db/query";
|
|
12
|
+
import { createTenantDb, type TenantDb } from "../../db/tenant-db";
|
|
13
|
+
import { defineFeature } from "../../engine";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_SENSITIVE_CONFIG,
|
|
16
|
+
type MetricEvent,
|
|
17
|
+
type ObservabilityProvider,
|
|
18
|
+
RecordingMeter,
|
|
19
|
+
RecordingTracer,
|
|
20
|
+
} from "../../observability";
|
|
21
|
+
import { getConsumerState } from "../../pipeline";
|
|
22
|
+
import {
|
|
23
|
+
resetEventStore,
|
|
24
|
+
setupTestStack,
|
|
25
|
+
type TestStack,
|
|
26
|
+
TestUsers,
|
|
27
|
+
unsafeCreateEntityTable,
|
|
28
|
+
} from "../../stack";
|
|
29
|
+
import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
|
|
30
|
+
import { withoutAmbientTemporal } from "../../testing/without-ambient-temporal";
|
|
31
|
+
import { SHARED_INSTANCE_SENTINEL } from "../event-consumer-state";
|
|
32
|
+
|
|
33
|
+
const executor = createEventStoreExecutor(sharedWidgetTable, sharedWidgetEntity, {
|
|
34
|
+
entityName: "widget",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let poisonNames = new Set<string>();
|
|
38
|
+
let observed: Array<{ name: string }> = [];
|
|
39
|
+
|
|
40
|
+
const rearmFeature = defineFeature("rearmtest", (r) => {
|
|
41
|
+
r.entity("widget", sharedWidgetEntity);
|
|
42
|
+
|
|
43
|
+
r.multiStreamProjection({
|
|
44
|
+
name: "observer",
|
|
45
|
+
apply: {
|
|
46
|
+
"widget.created": async (event) => {
|
|
47
|
+
const name = event.payload["name"] as string;
|
|
48
|
+
if (poisonNames.has(name)) {
|
|
49
|
+
throw new Error(`poisoned: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
observed.push({ name });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const admin = TestUsers.admin;
|
|
58
|
+
const qn = "rearmtest:projection:observer";
|
|
59
|
+
let stack: TestStack;
|
|
60
|
+
let tdb: TenantDb;
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
stack = await setupTestStack({
|
|
64
|
+
features: [rearmFeature],
|
|
65
|
+
systemHooks: [],
|
|
66
|
+
});
|
|
67
|
+
await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
|
|
68
|
+
tdb = createTenantDb(stack.db, admin.tenantId);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(async () => {
|
|
72
|
+
poisonNames = new Set();
|
|
73
|
+
observed = [];
|
|
74
|
+
await resetEventStore(stack, ["read_widgets"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
async function appendWidget(name: string): Promise<void> {
|
|
78
|
+
await executor.create({ name }, admin, tdb);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function driveUntilDead(): Promise<void> {
|
|
82
|
+
for (let i = 0; i < 10; i++) {
|
|
83
|
+
await stack.eventDispatcher?.runOnce();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Simulates cooldown elapsed without waiting real wall-clock time.
|
|
88
|
+
async function backdateUpdatedAt(minutesAgo: number): Promise<void> {
|
|
89
|
+
await asRawClient(stack.db).unsafe(
|
|
90
|
+
`UPDATE "kumiko_event_consumers" SET "updated_at" = now() - make_interval(mins => $1)
|
|
91
|
+
WHERE "name" = $2 AND "instance_id" = $3`,
|
|
92
|
+
[minutesAgo, qn, SHARED_INSTANCE_SENTINEL],
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
describe("issue #1350 — bounded dead-consumer re-arm", () => {
|
|
97
|
+
test("stays dead within the cooldown window", async () => {
|
|
98
|
+
poisonNames.add("poison");
|
|
99
|
+
await appendWidget("poison");
|
|
100
|
+
await driveUntilDead();
|
|
101
|
+
|
|
102
|
+
const dead = await getConsumerState(stack.db, qn);
|
|
103
|
+
expect(dead?.status).toBe("dead");
|
|
104
|
+
expect(dead?.rearmCount).toBe(0);
|
|
105
|
+
|
|
106
|
+
// No backdate — cooldown (5min default) hasn't elapsed.
|
|
107
|
+
await stack.eventDispatcher?.runOnce();
|
|
108
|
+
const stillDead = await getConsumerState(stack.db, qn);
|
|
109
|
+
expect(stillDead?.status).toBe("dead");
|
|
110
|
+
expect(stillDead?.rearmCount).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("auto-revives once cooldown elapses, and re-dies on the same poison event", async () => {
|
|
114
|
+
poisonNames.add("poison");
|
|
115
|
+
await appendWidget("poison");
|
|
116
|
+
await driveUntilDead();
|
|
117
|
+
|
|
118
|
+
await backdateUpdatedAt(10);
|
|
119
|
+
await stack.eventDispatcher?.runOnce();
|
|
120
|
+
|
|
121
|
+
const revived = await getConsumerState(stack.db, qn);
|
|
122
|
+
expect(revived?.status).toBe("idle");
|
|
123
|
+
expect(revived?.rearmCount).toBe(1);
|
|
124
|
+
// Same pass that revives it also retries the pending poisoned event —
|
|
125
|
+
// acquireConsumerState's reset (attempts=0) is followed immediately by
|
|
126
|
+
// one failed delivery attempt within this runOnce() call.
|
|
127
|
+
expect(revived?.attempts).toBe(1);
|
|
128
|
+
|
|
129
|
+
// Handler still throws on "poison" — climbs back to dead.
|
|
130
|
+
await driveUntilDead();
|
|
131
|
+
const deadAgain = await getConsumerState(stack.db, qn);
|
|
132
|
+
expect(deadAgain?.status).toBe("dead");
|
|
133
|
+
expect(deadAgain?.rearmCount).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("a re-arm that leads to a real delivery resets rearmCount to 0", async () => {
|
|
137
|
+
poisonNames.add("poison");
|
|
138
|
+
await appendWidget("poison");
|
|
139
|
+
await driveUntilDead();
|
|
140
|
+
|
|
141
|
+
// Cause is gone by the time the cooldown elapses — the retried event
|
|
142
|
+
// now succeeds instead of re-poisoning the consumer.
|
|
143
|
+
poisonNames.delete("poison");
|
|
144
|
+
await backdateUpdatedAt(10);
|
|
145
|
+
await stack.eventDispatcher?.runOnce();
|
|
146
|
+
|
|
147
|
+
const recovered = await getConsumerState(stack.db, qn);
|
|
148
|
+
expect(recovered?.status).toBe("idle");
|
|
149
|
+
expect(recovered?.rearmCount).toBe(0);
|
|
150
|
+
expect(observed.map((o) => o.name)).toEqual(["poison"]);
|
|
151
|
+
|
|
152
|
+
// A later, unrelated poison event gets its own fresh maxRearmCount
|
|
153
|
+
// budget instead of inheriting the spent counter from the resolved one.
|
|
154
|
+
poisonNames.add("later-poison");
|
|
155
|
+
await appendWidget("later-poison");
|
|
156
|
+
await driveUntilDead();
|
|
157
|
+
const deadAgain = await getConsumerState(stack.db, qn);
|
|
158
|
+
expect(deadAgain?.status).toBe("dead");
|
|
159
|
+
expect(deadAgain?.rearmCount).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("kumiko-framework#1525: cooldown-elapsed re-arm check works without relying on globalThis.Temporal", async () => {
|
|
163
|
+
poisonNames.add("poison");
|
|
164
|
+
await appendWidget("poison");
|
|
165
|
+
await driveUntilDead();
|
|
166
|
+
await backdateUpdatedAt(10);
|
|
167
|
+
|
|
168
|
+
// ensureTemporalPolyfill re-installs if the ambient global was torn down,
|
|
169
|
+
// so runOnce stays safe if a nested path calls it during this window.
|
|
170
|
+
await withoutAmbientTemporal(async () => {
|
|
171
|
+
await stack.eventDispatcher?.runOnce();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const revived = await getConsumerState(stack.db, qn);
|
|
175
|
+
expect(revived?.status).toBe("idle");
|
|
176
|
+
expect(revived?.rearmCount).toBe(1);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("permanently dead after maxRearmCount cycles, even once cooldown elapses again", async () => {
|
|
180
|
+
poisonNames.add("poison");
|
|
181
|
+
await appendWidget("poison");
|
|
182
|
+
await driveUntilDead();
|
|
183
|
+
|
|
184
|
+
// Cycles 1..3: each backdate+drive re-arms once, then re-dies on the
|
|
185
|
+
// same poison event. Default maxRearmCount is 3.
|
|
186
|
+
for (let cycle = 1; cycle <= 3; cycle++) {
|
|
187
|
+
await backdateUpdatedAt(10);
|
|
188
|
+
await stack.eventDispatcher?.runOnce();
|
|
189
|
+
const revived = await getConsumerState(stack.db, qn);
|
|
190
|
+
expect(revived?.rearmCount).toBe(cycle);
|
|
191
|
+
await driveUntilDead();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const exhausted = await getConsumerState(stack.db, qn);
|
|
195
|
+
expect(exhausted?.status).toBe("dead");
|
|
196
|
+
expect(exhausted?.rearmCount).toBe(3);
|
|
197
|
+
|
|
198
|
+
// Cooldown elapsed again, but the budget is spent — stays dead.
|
|
199
|
+
await backdateUpdatedAt(10);
|
|
200
|
+
await stack.eventDispatcher?.runOnce();
|
|
201
|
+
const stillExhausted = await getConsumerState(stack.db, qn);
|
|
202
|
+
expect(stillExhausted?.status).toBe("dead");
|
|
203
|
+
expect(stillExhausted?.rearmCount).toBe(3);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("kumiko_event_consumer_rearm_exhausted_total fires once, not per still-dead pass (#1362)", async () => {
|
|
207
|
+
// Dedicated stack with a RecordingMeter — module-level `stack` above is
|
|
208
|
+
// shared across every test in this file and has no metric capture.
|
|
209
|
+
const metricEvents: MetricEvent[] = [];
|
|
210
|
+
const meter = new RecordingMeter((e) => metricEvents.push(e));
|
|
211
|
+
const tracer = new RecordingTracer({
|
|
212
|
+
sensitiveConfig: DEFAULT_SENSITIVE_CONFIG,
|
|
213
|
+
onSpanEnd: () => {},
|
|
214
|
+
});
|
|
215
|
+
const recordingProvider: ObservabilityProvider = {
|
|
216
|
+
name: "recording",
|
|
217
|
+
meter,
|
|
218
|
+
tracer,
|
|
219
|
+
shutdown: async () => {},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const recStack = await setupTestStack({
|
|
223
|
+
features: [rearmFeature],
|
|
224
|
+
systemHooks: [],
|
|
225
|
+
observability: recordingProvider,
|
|
226
|
+
});
|
|
227
|
+
try {
|
|
228
|
+
await unsafeCreateEntityTable(recStack.db, sharedWidgetEntity, "widget");
|
|
229
|
+
const recTdb = createTenantDb(recStack.db, admin.tenantId);
|
|
230
|
+
|
|
231
|
+
poisonNames.add("poison-metric");
|
|
232
|
+
await executor.create({ name: "poison-metric" }, admin, recTdb);
|
|
233
|
+
for (let i = 0; i < 10; i++) {
|
|
234
|
+
await recStack.eventDispatcher?.runOnce();
|
|
235
|
+
}
|
|
236
|
+
// The 10th pass is the one whose failed delivery flips status to
|
|
237
|
+
// "dead" (persisted for the NEXT pass to see) — acquireConsumerState's
|
|
238
|
+
// dead-branch (where the exhausted-metric fires) only runs starting
|
|
239
|
+
// from the pass AFTER that transition, same as the "stays dead within
|
|
240
|
+
// cooldown window" test above needing one extra runOnce().
|
|
241
|
+
await recStack.eventDispatcher?.runOnce();
|
|
242
|
+
|
|
243
|
+
const exhaustedEvents = () =>
|
|
244
|
+
metricEvents.filter(
|
|
245
|
+
(e) =>
|
|
246
|
+
e.type === "counter.inc" && e.name === "kumiko_event_consumer_rearm_exhausted_total",
|
|
247
|
+
);
|
|
248
|
+
expect(exhaustedEvents().length).toBe(1);
|
|
249
|
+
|
|
250
|
+
// Several more passes while it's still dead — must stay at 1, not
|
|
251
|
+
// re-fire on every pass that observes "still dead" (log/metric spam
|
|
252
|
+
// the finding flagged).
|
|
253
|
+
for (let i = 0; i < 5; i++) {
|
|
254
|
+
await recStack.eventDispatcher?.runOnce();
|
|
255
|
+
}
|
|
256
|
+
expect(exhaustedEvents().length).toBe(1);
|
|
257
|
+
expect(exhaustedEvents()[0]?.labels?.["consumer"]).toBe(qn);
|
|
258
|
+
} finally {
|
|
259
|
+
await resetEventStore(recStack, ["read_widgets"]);
|
|
260
|
+
await recStack.cleanup();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -159,10 +159,20 @@ describe("event-dispatcher — happy path", () => {
|
|
|
159
159
|
await stack.eventDispatcher?.runOnce();
|
|
160
160
|
expect(captureA).toHaveLength(1);
|
|
161
161
|
|
|
162
|
+
const stateBefore = await getConsumerState(stack.db, qnA);
|
|
163
|
+
|
|
162
164
|
const second = await stack.eventDispatcher?.runOnce();
|
|
163
165
|
expect(second?.byConsumer[qnA]).toEqual({ processed: 0, failed: 0 });
|
|
164
166
|
// Still only one — consumer correctly saw "nothing new past my cursor".
|
|
165
167
|
expect(captureA).toHaveLength(1);
|
|
168
|
+
|
|
169
|
+
// No pending events means no write at all — an idle pass must not touch
|
|
170
|
+
// updated_at/status, otherwise every empty poll tick burns a WAL record.
|
|
171
|
+
const stateAfter = await getConsumerState(stack.db, qnA);
|
|
172
|
+
expect(stateAfter?.updatedAt.epochMilliseconds).toEqual(
|
|
173
|
+
stateBefore?.updatedAt.epochMilliseconds,
|
|
174
|
+
);
|
|
175
|
+
expect(stateAfter?.status).toBe(stateBefore?.status);
|
|
166
176
|
});
|
|
167
177
|
|
|
168
178
|
test("cursor advances only past successfully-consumed events", async () => {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// createJobTriggerEventConsumer — proves r.job's trigger.on can fire on an
|
|
2
|
+
// r.defineEvent QN appended by a multiStreamProjection's unsafeAppendEvent
|
|
3
|
+
// (kumiko-framework#1505). Mirrors document-ingest-foundation's actual
|
|
4
|
+
// request-ingest MSP (upload → fileRef.created → an owned defineEvent),
|
|
5
|
+
// the motivating case for this fix — fileRef.created itself never reaches
|
|
6
|
+
// jobRunner.handleEvent because the upload route appends it via the raw
|
|
7
|
+
// event-store executor, not a write-handler dispatch (see #1505).
|
|
8
|
+
//
|
|
9
|
+
// Not covered here: a job triggered on a write/query-handler QN still
|
|
10
|
+
// firing exactly once (unaffected by the new consumer). The full suite
|
|
11
|
+
// stays green (e.g. the lane-routing sample), but that's not a positive
|
|
12
|
+
// test of the partition guard — no stored event's `type` is ever a
|
|
13
|
+
// handler QN in practice (entity events are "entity.verb"; custom
|
|
14
|
+
// write-handlers like lane-routing's don't append to the store at all),
|
|
15
|
+
// so `getWriteHandler`/`getQueryHandler` in the new consumer's handler is
|
|
16
|
+
// defense-in-depth for an input shape the framework doesn't currently
|
|
17
|
+
// produce, not something exercised end-to-end by any test today.
|
|
18
|
+
|
|
19
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { entityEventName } from "../../db";
|
|
22
|
+
import { defineFeature } from "../../engine";
|
|
23
|
+
import { createInMemoryFileProvider, type InMemoryFileProvider } from "../../files";
|
|
24
|
+
import { setupTestStack, type TestStack, TestUsers } from "../../stack";
|
|
25
|
+
import { waitFor } from "../../testing";
|
|
26
|
+
|
|
27
|
+
const ITEM_REQUESTED_EVENT_QN = "job-trigger-fixture:event:item-requested";
|
|
28
|
+
const FILE_REF_CREATED = entityEventName("fileRef", "created");
|
|
29
|
+
|
|
30
|
+
const processedItems: Array<{ readonly fileRefId: string }> = [];
|
|
31
|
+
|
|
32
|
+
const jobTriggerFixtureFeature = defineFeature("job-trigger-fixture", (r) => {
|
|
33
|
+
r.defineEvent("item-requested", z.object({ fileRefId: z.string().min(1) }));
|
|
34
|
+
|
|
35
|
+
// Mirrors document-ingest-foundation's request-ingest MSP exactly: reacts
|
|
36
|
+
// to fileRef.created, appends a NEW event via unsafeAppendEvent — no
|
|
37
|
+
// write-handler behind the appended event itself.
|
|
38
|
+
r.multiStreamProjection({
|
|
39
|
+
name: "request-item",
|
|
40
|
+
apply: {
|
|
41
|
+
[FILE_REF_CREATED]: async (event, _tx, ctx) => {
|
|
42
|
+
await ctx.unsafeAppendEvent({
|
|
43
|
+
aggregateId: event.aggregateId,
|
|
44
|
+
aggregateType: "job-trigger-fixture-request",
|
|
45
|
+
type: ITEM_REQUESTED_EVENT_QN,
|
|
46
|
+
payload: { fileRefId: event.aggregateId },
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Under test: only reachable via createJobTriggerEventConsumer, since
|
|
53
|
+
// ITEM_REQUESTED_EVENT_QN is an r.defineEvent QN, not a handler QN.
|
|
54
|
+
r.job(
|
|
55
|
+
"process-item",
|
|
56
|
+
{ trigger: { on: ITEM_REQUESTED_EVENT_QN }, runIn: "worker" },
|
|
57
|
+
async (payload) => {
|
|
58
|
+
processedItems.push({ fileRefId: payload["fileRefId"] as string });
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let stack: TestStack;
|
|
64
|
+
let provider: InMemoryFileProvider;
|
|
65
|
+
|
|
66
|
+
beforeAll(async () => {
|
|
67
|
+
provider = createInMemoryFileProvider();
|
|
68
|
+
stack = await setupTestStack({
|
|
69
|
+
features: [jobTriggerFixtureFeature],
|
|
70
|
+
files: { storageProvider: provider },
|
|
71
|
+
jobs: { consumerLane: "worker" },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await stack.cleanup();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
processedItems.length = 0;
|
|
81
|
+
provider.clear();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("job-trigger event consumer", () => {
|
|
85
|
+
test("a job triggers on an r.defineEvent QN appended by an MSP's unsafeAppendEvent", async () => {
|
|
86
|
+
const token = await stack.jwt.sign(TestUsers.admin);
|
|
87
|
+
const formData = new FormData();
|
|
88
|
+
formData.append("file", new File([Buffer.from("hello")], "note.txt", { type: "text/plain" }));
|
|
89
|
+
const res = await stack.app.request("/api/files", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
92
|
+
body: formData,
|
|
93
|
+
});
|
|
94
|
+
expect(res.status).toBe(201);
|
|
95
|
+
|
|
96
|
+
await waitFor(async () => {
|
|
97
|
+
// Drives both the MSP (appends item-requested off fileRef.created)
|
|
98
|
+
// and the new job-trigger consumer (reacts to it) — may need more
|
|
99
|
+
// than one pass since the MSP's append happens mid-drain.
|
|
100
|
+
await stack.eventDispatcher?.runOnce();
|
|
101
|
+
expect(processedItems).toHaveLength(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(processedItems[0]?.fileRefId).toBeTruthy();
|
|
105
|
+
});
|
|
106
|
+
});
|