@rikalabs/akter 0.1.0-alpha.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/CHANGELOG.md +15 -0
- package/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +116 -0
- package/dist/actor/codecs.d.ts +77 -0
- package/dist/actor/codecs.js +87 -0
- package/dist/actor/connections.d.ts +34 -0
- package/dist/actor/connections.js +145 -0
- package/dist/actor/definition.d.ts +893 -0
- package/dist/actor/definition.js +147 -0
- package/dist/actor/descriptor.d.ts +172 -0
- package/dist/actor/descriptor.js +367 -0
- package/dist/actor/handles.d.ts +77 -0
- package/dist/actor/handles.js +265 -0
- package/dist/actor/jobs.d.ts +10 -0
- package/dist/actor/jobs.js +137 -0
- package/dist/actor/layers.d.ts +41 -0
- package/dist/actor/layers.js +177 -0
- package/dist/actor/reads.d.ts +47 -0
- package/dist/actor/reads.js +224 -0
- package/dist/actor/served.d.ts +120 -0
- package/dist/actor/served.js +72 -0
- package/dist/actor/turns.d.ts +13 -0
- package/dist/actor/turns.js +332 -0
- package/dist/actor/workflows.d.ts +16 -0
- package/dist/actor/workflows.js +37 -0
- package/dist/client/calls.d.ts +47 -0
- package/dist/client/calls.js +425 -0
- package/dist/client/clock.d.ts +56 -0
- package/dist/client/clock.js +119 -0
- package/dist/client/fleet-page.d.ts +15 -0
- package/dist/client/fleet-page.js +13 -0
- package/dist/client/fleet.d.ts +29 -0
- package/dist/client/fleet.js +51 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +6 -0
- package/dist/client/make.d.ts +157 -0
- package/dist/client/make.js +221 -0
- package/dist/client/offline/queue.d.ts +118 -0
- package/dist/client/offline/queue.js +292 -0
- package/dist/client/offline/store.d.ts +74 -0
- package/dist/client/offline/store.js +78 -0
- package/dist/client/optimistic.d.ts +57 -0
- package/dist/client/optimistic.js +136 -0
- package/dist/client/report.d.ts +2 -0
- package/dist/client/report.js +8 -0
- package/dist/client/sessions/connection.d.ts +118 -0
- package/dist/client/sessions/connection.js +162 -0
- package/dist/client/sessions/feed.d.ts +42 -0
- package/dist/client/sessions/feed.js +43 -0
- package/dist/client/sessions/sse.d.ts +47 -0
- package/dist/client/sessions/sse.js +112 -0
- package/dist/client/sessions/stream.d.ts +28 -0
- package/dist/client/sessions/stream.js +35 -0
- package/dist/client/sessions/watch.d.ts +34 -0
- package/dist/client/sessions/watch.js +40 -0
- package/dist/client/transport.d.ts +37 -0
- package/dist/client/transport.js +66 -0
- package/dist/contexts/command.d.ts +132 -0
- package/dist/contexts/command.js +20 -0
- package/dist/contexts/connection.d.ts +68 -0
- package/dist/contexts/connection.js +0 -0
- package/dist/contexts/job.d.ts +49 -0
- package/dist/contexts/job.js +0 -0
- package/dist/contexts/workflow.d.ts +147 -0
- package/dist/contexts/workflow.js +22 -0
- package/dist/errors/actor.d.ts +200 -0
- package/dist/errors/actor.js +271 -0
- package/dist/errors/content.d.ts +19 -0
- package/dist/errors/content.js +14 -0
- package/dist/errors/database.d.ts +22 -0
- package/dist/errors/database.js +23 -0
- package/dist/errors/events.d.ts +17 -0
- package/dist/errors/events.js +14 -0
- package/dist/errors/subscription.d.ts +12 -0
- package/dist/errors/subscription.js +8 -0
- package/dist/errors/workflow.d.ts +28 -0
- package/dist/errors/workflow.js +17 -0
- package/dist/handles/actors.d.ts +17 -0
- package/dist/handles/actors.js +7 -0
- package/dist/handles/content.d.ts +59 -0
- package/dist/handles/content.js +36 -0
- package/dist/handles/intents.d.ts +164 -0
- package/dist/handles/intents.js +214 -0
- package/dist/handles/run.d.ts +31 -0
- package/dist/handles/run.js +21 -0
- package/dist/handles/workflow.d.ts +27 -0
- package/dist/handles/workflow.js +23 -0
- package/dist/identity/caller.d.ts +105 -0
- package/dist/identity/caller.js +68 -0
- package/dist/identity/child.d.ts +18 -0
- package/dist/identity/child.js +39 -0
- package/dist/identity/command.d.ts +19 -0
- package/dist/identity/command.js +21 -0
- package/dist/identity/connection.d.ts +24 -0
- package/dist/identity/connection.js +29 -0
- package/dist/identity/content.d.ts +18 -0
- package/dist/identity/content.js +12 -0
- package/dist/identity/digest.d.ts +4 -0
- package/dist/identity/digest.js +5 -0
- package/dist/identity/execution.d.ts +32 -0
- package/dist/identity/execution.js +63 -0
- package/dist/identity/mint.d.ts +51 -0
- package/dist/identity/mint.js +76 -0
- package/dist/identity/version.d.ts +2 -0
- package/dist/identity/version.js +3 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +97 -0
- package/dist/members/blob.d.ts +43 -0
- package/dist/members/blob.js +29 -0
- package/dist/members/command.d.ts +109 -0
- package/dist/members/command.js +61 -0
- package/dist/members/connection.d.ts +55 -0
- package/dist/members/connection.js +49 -0
- package/dist/members/event.d.ts +36 -0
- package/dist/members/event.js +24 -0
- package/dist/members/job.d.ts +157 -0
- package/dist/members/job.js +67 -0
- package/dist/members/payload.d.ts +93 -0
- package/dist/members/payload.js +145 -0
- package/dist/members/reducer.d.ts +64 -0
- package/dist/members/reducer.js +27 -0
- package/dist/members/stream.d.ts +36 -0
- package/dist/members/stream.js +14 -0
- package/dist/members/subscription.d.ts +182 -0
- package/dist/members/subscription.js +91 -0
- package/dist/members/workflow.d.ts +99 -0
- package/dist/members/workflow.js +2 -0
- package/dist/policies/access.d.ts +35 -0
- package/dist/policies/access.js +6 -0
- package/dist/policies/command.d.ts +127 -0
- package/dist/policies/command.js +45 -0
- package/dist/policies/job.d.ts +28 -0
- package/dist/policies/job.js +60 -0
- package/dist/policies/schedules.d.ts +33 -0
- package/dist/policies/schedules.js +131 -0
- package/dist/protocol/frames.d.ts +66 -0
- package/dist/protocol/frames.js +55 -0
- package/dist/protocol/inspection.d.ts +807 -0
- package/dist/protocol/inspection.js +243 -0
- package/dist/protocol/wire.d.ts +184 -0
- package/dist/protocol/wire.js +150 -0
- package/dist/runtime/actors.d.ts +155 -0
- package/dist/runtime/actors.js +4 -0
- package/dist/runtime/admission.d.ts +34 -0
- package/dist/runtime/admission.js +72 -0
- package/dist/runtime/adoption/backfill.d.ts +27 -0
- package/dist/runtime/adoption/backfill.js +144 -0
- package/dist/runtime/adoption/enforce.d.ts +55 -0
- package/dist/runtime/adoption/enforce.js +241 -0
- package/dist/runtime/adoption/observe.d.ts +43 -0
- package/dist/runtime/adoption/observe.js +139 -0
- package/dist/runtime/adoption/plan.d.ts +45 -0
- package/dist/runtime/adoption/plan.js +192 -0
- package/dist/runtime/adoption/startup.d.ts +32 -0
- package/dist/runtime/adoption/startup.js +76 -0
- package/dist/runtime/adoption/status.d.ts +23 -0
- package/dist/runtime/adoption/status.js +47 -0
- package/dist/runtime/adoption/target.d.ts +33 -0
- package/dist/runtime/adoption/target.js +49 -0
- package/dist/runtime/connections/holder.d.ts +221 -0
- package/dist/runtime/connections/holder.js +816 -0
- package/dist/runtime/connections/owner.d.ts +263 -0
- package/dist/runtime/connections/owner.js +896 -0
- package/dist/runtime/connections/protocol.d.ts +441 -0
- package/dist/runtime/connections/protocol.js +222 -0
- package/dist/runtime/connections/reads.d.ts +24 -0
- package/dist/runtime/connections/reads.js +19 -0
- package/dist/runtime/connections/streams.d.ts +62 -0
- package/dist/runtime/connections/streams.js +128 -0
- package/dist/runtime/connections/transport.d.ts +40 -0
- package/dist/runtime/connections/transport.js +68 -0
- package/dist/runtime/connections/watch.d.ts +52 -0
- package/dist/runtime/connections/watch.js +159 -0
- package/dist/runtime/content/grant.d.ts +34 -0
- package/dist/runtime/content/grant.js +58 -0
- package/dist/runtime/content/store.d.ts +63 -0
- package/dist/runtime/content/store.js +294 -0
- package/dist/runtime/cron/key.d.ts +2 -0
- package/dist/runtime/cron/key.js +2 -0
- package/dist/runtime/cron/schedule.d.ts +69 -0
- package/dist/runtime/cron/schedule.js +144 -0
- package/dist/runtime/database/bounded.d.ts +43 -0
- package/dist/runtime/database/bounded.js +108 -0
- package/dist/runtime/database/connection.d.ts +8 -0
- package/dist/runtime/database/connection.js +18 -0
- package/dist/runtime/database/coordination.d.ts +19 -0
- package/dist/runtime/database/coordination.js +72 -0
- package/dist/runtime/database/flock.d.ts +10 -0
- package/dist/runtime/database/flock.js +60 -0
- package/dist/runtime/database/gate.d.ts +26 -0
- package/dist/runtime/database/gate.js +54 -0
- package/dist/runtime/database/keepalive.d.ts +11 -0
- package/dist/runtime/database/keepalive.js +22 -0
- package/dist/runtime/database/migrations.d.ts +177 -0
- package/dist/runtime/database/migrations.js +1018 -0
- package/dist/runtime/database/neki/migrations.d.ts +31 -0
- package/dist/runtime/database/neki/migrations.js +276 -0
- package/dist/runtime/database/neki/session.d.ts +23 -0
- package/dist/runtime/database/neki/session.js +31 -0
- package/dist/runtime/database/pglite.d.ts +21 -0
- package/dist/runtime/database/pglite.js +74 -0
- package/dist/runtime/database/replica.d.ts +41 -0
- package/dist/runtime/database/replica.js +51 -0
- package/dist/runtime/database/shards.d.ts +21 -0
- package/dist/runtime/database/shards.js +37 -0
- package/dist/runtime/database/snapshot.d.ts +7 -0
- package/dist/runtime/database/snapshot.js +7 -0
- package/dist/runtime/database/tenancy.d.ts +69 -0
- package/dist/runtime/database/tenancy.js +145 -0
- package/dist/runtime/database/transient.d.ts +10 -0
- package/dist/runtime/database/transient.js +25 -0
- package/dist/runtime/drain.d.ts +132 -0
- package/dist/runtime/drain.js +109 -0
- package/dist/runtime/entity/mailbox.d.ts +86 -0
- package/dist/runtime/entity/mailbox.js +121 -0
- package/dist/runtime/entity/register.d.ts +424 -0
- package/dist/runtime/entity/register.js +669 -0
- package/dist/runtime/events/append.d.ts +192 -0
- package/dist/runtime/events/append.js +128 -0
- package/dist/runtime/events/cursor.d.ts +2 -0
- package/dist/runtime/events/cursor.js +5 -0
- package/dist/runtime/events/replay.d.ts +20 -0
- package/dist/runtime/events/replay.js +45 -0
- package/dist/runtime/feeds.d.ts +26 -0
- package/dist/runtime/feeds.js +100 -0
- package/dist/runtime/fleet/checks.d.ts +17 -0
- package/dist/runtime/fleet/checks.js +106 -0
- package/dist/runtime/fleet/maintainer.d.ts +38 -0
- package/dist/runtime/fleet/maintainer.js +383 -0
- package/dist/runtime/fleet/pgoutput.d.ts +41 -0
- package/dist/runtime/fleet/pgoutput.js +102 -0
- package/dist/runtime/fleet/setup.d.ts +36 -0
- package/dist/runtime/fleet/setup.js +58 -0
- package/dist/runtime/fleet/subscribe.d.ts +42 -0
- package/dist/runtime/fleet/subscribe.js +147 -0
- package/dist/runtime/index.d.ts +66 -0
- package/dist/runtime/index.js +37 -0
- package/dist/runtime/inspector/http.d.ts +19 -0
- package/dist/runtime/inspector/http.js +36 -0
- package/dist/runtime/inspector/layer.d.ts +46 -0
- package/dist/runtime/inspector/layer.js +180 -0
- package/dist/runtime/inspector/queries.d.ts +711 -0
- package/dist/runtime/inspector/queries.js +494 -0
- package/dist/runtime/jobs/attempt.d.ts +254 -0
- package/dist/runtime/jobs/attempt.js +413 -0
- package/dist/runtime/jobs/isolation.d.ts +20 -0
- package/dist/runtime/jobs/isolation.js +10 -0
- package/dist/runtime/jobs/progress.d.ts +95 -0
- package/dist/runtime/jobs/progress.js +144 -0
- package/dist/runtime/layer.d.ts +260 -0
- package/dist/runtime/layer.js +876 -0
- package/dist/runtime/members.d.ts +381 -0
- package/dist/runtime/members.js +8 -0
- package/dist/runtime/operators/audit.d.ts +64 -0
- package/dist/runtime/operators/audit.js +56 -0
- package/dist/runtime/operators/auth.d.ts +29 -0
- package/dist/runtime/operators/auth.js +42 -0
- package/dist/runtime/operators/export.d.ts +68 -0
- package/dist/runtime/operators/export.js +137 -0
- package/dist/runtime/operators/grants.d.ts +45 -0
- package/dist/runtime/operators/grants.js +40 -0
- package/dist/runtime/operators/repair.d.ts +206 -0
- package/dist/runtime/operators/repair.js +226 -0
- package/dist/runtime/operators/routes.d.ts +35 -0
- package/dist/runtime/operators/routes.js +190 -0
- package/dist/runtime/operators/seed.d.ts +123 -0
- package/dist/runtime/operators/seed.js +123 -0
- package/dist/runtime/payloads/versions.d.ts +65 -0
- package/dist/runtime/payloads/versions.js +240 -0
- package/dist/runtime/peering/authority.d.ts +42 -0
- package/dist/runtime/peering/authority.js +132 -0
- package/dist/runtime/peering/credentials.d.ts +21 -0
- package/dist/runtime/peering/credentials.js +13 -0
- package/dist/runtime/peering/transport.d.ts +51 -0
- package/dist/runtime/peering/transport.js +175 -0
- package/dist/runtime/queries.d.ts +18 -0
- package/dist/runtime/queries.js +169 -0
- package/dist/runtime/readiness.d.ts +36 -0
- package/dist/runtime/readiness.js +44 -0
- package/dist/runtime/registration.d.ts +58 -0
- package/dist/runtime/registration.js +186 -0
- package/dist/runtime/request.d.ts +158 -0
- package/dist/runtime/request.js +102 -0
- package/dist/runtime/retry.d.ts +8 -0
- package/dist/runtime/retry.js +11 -0
- package/dist/runtime/runner.d.ts +93 -0
- package/dist/runtime/runner.js +122 -0
- package/dist/runtime/storage/codec.d.ts +39 -0
- package/dist/runtime/storage/codec.js +41 -0
- package/dist/runtime/storage/generation.d.ts +69 -0
- package/dist/runtime/storage/generation.js +49 -0
- package/dist/runtime/storage/placements.d.ts +15 -0
- package/dist/runtime/storage/placements.js +38 -0
- package/dist/runtime/storage/platform.d.ts +6 -0
- package/dist/runtime/storage/platform.js +9 -0
- package/dist/runtime/storage/retention.d.ts +73 -0
- package/dist/runtime/storage/retention.js +158 -0
- package/dist/runtime/subscriptions/identity.d.ts +31 -0
- package/dist/runtime/subscriptions/identity.js +45 -0
- package/dist/runtime/subscriptions/relay.d.ts +175 -0
- package/dist/runtime/subscriptions/relay.js +763 -0
- package/dist/runtime/subscriptions/storage.d.ts +40 -0
- package/dist/runtime/subscriptions/storage.js +98 -0
- package/dist/runtime/sweeps.d.ts +39 -0
- package/dist/runtime/sweeps.js +54 -0
- package/dist/runtime/telemetry/defects.d.ts +57 -0
- package/dist/runtime/telemetry/defects.js +43 -0
- package/dist/runtime/telemetry/metrics.d.ts +52 -0
- package/dist/runtime/telemetry/metrics.js +138 -0
- package/dist/runtime/telemetry/routes.d.ts +19 -0
- package/dist/runtime/telemetry/routes.js +18 -0
- package/dist/runtime/telemetry/sampler.d.ts +26 -0
- package/dist/runtime/telemetry/sampler.js +101 -0
- package/dist/runtime/telemetry/spans.d.ts +38 -0
- package/dist/runtime/telemetry/spans.js +44 -0
- package/dist/runtime/telemetry/usage.d.ts +52 -0
- package/dist/runtime/telemetry/usage.js +30 -0
- package/dist/runtime/topology/admission.d.ts +13 -0
- package/dist/runtime/topology/admission.js +36 -0
- package/dist/runtime/topology/locks.d.ts +56 -0
- package/dist/runtime/topology/locks.js +81 -0
- package/dist/runtime/topology/messages.d.ts +15 -0
- package/dist/runtime/topology/messages.js +32 -0
- package/dist/runtime/turn/admission.d.ts +22 -0
- package/dist/runtime/turn/admission.js +41 -0
- package/dist/runtime/turn/blobs.d.ts +38 -0
- package/dist/runtime/turn/blobs.js +215 -0
- package/dist/runtime/turn/execute.d.ts +208 -0
- package/dist/runtime/turn/execute.js +972 -0
- package/dist/runtime/turn/group.d.ts +165 -0
- package/dist/runtime/turn/group.js +326 -0
- package/dist/runtime/turn/hooks.d.ts +44 -0
- package/dist/runtime/turn/hooks.js +28 -0
- package/dist/runtime/turn/outbox.d.ts +159 -0
- package/dist/runtime/turn/outbox.js +269 -0
- package/dist/runtime/turn/pipeline.d.ts +68 -0
- package/dist/runtime/turn/pipeline.js +83 -0
- package/dist/runtime/turn/receipt.d.ts +81 -0
- package/dist/runtime/turn/receipt.js +32 -0
- package/dist/runtime/turn/relay.d.ts +179 -0
- package/dist/runtime/turn/relay.js +685 -0
- package/dist/runtime/turn/rows.d.ts +43 -0
- package/dist/runtime/turn/rows.js +592 -0
- package/dist/runtime/workflows/compatibility.d.ts +125 -0
- package/dist/runtime/workflows/compatibility.js +285 -0
- package/dist/runtime/workflows/engine.d.ts +107 -0
- package/dist/runtime/workflows/engine.js +826 -0
- package/dist/runtime/workflows/manifest.d.ts +34 -0
- package/dist/runtime/workflows/manifest.js +39 -0
- package/dist/runtime/workflows/steps.d.ts +33 -0
- package/dist/runtime/workflows/steps.js +152 -0
- package/dist/serve/api.d.ts +99 -0
- package/dist/serve/api.js +403 -0
- package/dist/serve/assertion/binding.d.ts +41 -0
- package/dist/serve/assertion/binding.js +92 -0
- package/dist/serve/assertion/verify.d.ts +93 -0
- package/dist/serve/assertion/verify.js +189 -0
- package/dist/serve/auth.d.ts +200 -0
- package/dist/serve/auth.js +92 -0
- package/dist/serve/clock.d.ts +10 -0
- package/dist/serve/clock.js +25 -0
- package/dist/serve/jwt.d.ts +65 -0
- package/dist/serve/jwt.js +202 -0
- package/dist/serve/layer.d.ts +98 -0
- package/dist/serve/layer.js +809 -0
- package/dist/serve/mcp/endpoint.d.ts +39 -0
- package/dist/serve/mcp/endpoint.js +187 -0
- package/dist/serve/mcp/tools.d.ts +37 -0
- package/dist/serve/mcp/tools.js +125 -0
- package/dist/serve/origin.d.ts +6 -0
- package/dist/serve/origin.js +21 -0
- package/dist/serve/sessions/feed.d.ts +79 -0
- package/dist/serve/sessions/feed.js +115 -0
- package/dist/serve/sessions/socket.d.ts +56 -0
- package/dist/serve/sessions/socket.js +212 -0
- package/dist/serve/sessions/sse.d.ts +27 -0
- package/dist/serve/sessions/sse.js +26 -0
- package/dist/serve/sessions/stream.d.ts +9 -0
- package/dist/serve/sessions/stream.js +9 -0
- package/dist/serve/sessions/watch.d.ts +12 -0
- package/dist/serve/sessions/watch.js +11 -0
- package/dist/serve/wire.d.ts +16 -0
- package/dist/serve/wire.js +50 -0
- package/dist/state/blob.d.ts +71 -0
- package/dist/state/blob.js +0 -0
- package/dist/state/migration.d.ts +53 -0
- package/dist/state/migration.js +29 -0
- package/dist/tables/fleet.d.ts +170 -0
- package/dist/tables/fleet.js +214 -0
- package/dist/tables/owned.d.ts +250 -0
- package/dist/tables/owned.js +233 -0
- package/dist/testing/actor-test.d.ts +360 -0
- package/dist/testing/actor-test.js +337 -0
- package/dist/testing/cluster.d.ts +93 -0
- package/dist/testing/cluster.js +296 -0
- package/dist/testing/conformance/access.d.ts +5 -0
- package/dist/testing/conformance/access.js +531 -0
- package/dist/testing/conformance/admission.d.ts +11 -0
- package/dist/testing/conformance/admission.js +182 -0
- package/dist/testing/conformance/adoption.d.ts +2 -0
- package/dist/testing/conformance/adoption.js +1019 -0
- package/dist/testing/conformance/assertions.d.ts +202 -0
- package/dist/testing/conformance/assertions.js +849 -0
- package/dist/testing/conformance/batches.d.ts +41 -0
- package/dist/testing/conformance/batches.js +388 -0
- package/dist/testing/conformance/blobs.d.ts +14 -0
- package/dist/testing/conformance/blobs.js +405 -0
- package/dist/testing/conformance/capacity.d.ts +8 -0
- package/dist/testing/conformance/capacity.js +433 -0
- package/dist/testing/conformance/client.d.ts +5 -0
- package/dist/testing/conformance/client.js +719 -0
- package/dist/testing/conformance/cold-serve.d.ts +16 -0
- package/dist/testing/conformance/cold-serve.js +254 -0
- package/dist/testing/conformance/connections/actors.d.ts +1143 -0
- package/dist/testing/conformance/connections/actors.js +180 -0
- package/dist/testing/conformance/connections/harness.d.ts +1754 -0
- package/dist/testing/conformance/connections/harness.js +142 -0
- package/dist/testing/conformance/connections/holders.d.ts +4 -0
- package/dist/testing/conformance/connections/holders.js +165 -0
- package/dist/testing/conformance/connections/lifecycle.d.ts +4 -0
- package/dist/testing/conformance/connections/lifecycle.js +113 -0
- package/dist/testing/conformance/connections/limits.d.ts +4 -0
- package/dist/testing/conformance/connections/limits.js +127 -0
- package/dist/testing/conformance/connections/reauthorization.d.ts +4 -0
- package/dist/testing/conformance/connections/reauthorization.js +170 -0
- package/dist/testing/conformance/connections/resync.d.ts +4 -0
- package/dist/testing/conformance/connections/resync.js +360 -0
- package/dist/testing/conformance/connections/takeover.d.ts +4 -0
- package/dist/testing/conformance/connections/takeover.js +287 -0
- package/dist/testing/conformance/connections.d.ts +4 -0
- package/dist/testing/conformance/connections.js +15 -0
- package/dist/testing/conformance/content-blobs.d.ts +16 -0
- package/dist/testing/conformance/content-blobs.js +534 -0
- package/dist/testing/conformance/counter.d.ts +16 -0
- package/dist/testing/conformance/counter.js +813 -0
- package/dist/testing/conformance/cron.d.ts +5 -0
- package/dist/testing/conformance/cron.js +665 -0
- package/dist/testing/conformance/drain.d.ts +26 -0
- package/dist/testing/conformance/drain.js +540 -0
- package/dist/testing/conformance/effect-control.d.ts +38 -0
- package/dist/testing/conformance/effect-control.js +1059 -0
- package/dist/testing/conformance/effects.d.ts +23 -0
- package/dist/testing/conformance/effects.js +432 -0
- package/dist/testing/conformance/events.d.ts +28 -0
- package/dist/testing/conformance/events.js +335 -0
- package/dist/testing/conformance/export.d.ts +2 -0
- package/dist/testing/conformance/export.js +368 -0
- package/dist/testing/conformance/fleet.d.ts +444 -0
- package/dist/testing/conformance/fleet.js +1009 -0
- package/dist/testing/conformance/groups.d.ts +7 -0
- package/dist/testing/conformance/groups.js +353 -0
- package/dist/testing/conformance/heap.d.ts +3 -0
- package/dist/testing/conformance/heap.js +149 -0
- package/dist/testing/conformance/http.d.ts +2809 -0
- package/dist/testing/conformance/http.js +1234 -0
- package/dist/testing/conformance/inspection-views.d.ts +8 -0
- package/dist/testing/conformance/inspection-views.js +272 -0
- package/dist/testing/conformance/inspector.d.ts +7 -0
- package/dist/testing/conformance/inspector.js +650 -0
- package/dist/testing/conformance/mint.d.ts +58 -0
- package/dist/testing/conformance/mint.js +692 -0
- package/dist/testing/conformance/multi-runner.d.ts +8 -0
- package/dist/testing/conformance/multi-runner.js +403 -0
- package/dist/testing/conformance/neki/plan.d.ts +19 -0
- package/dist/testing/conformance/neki/plan.js +30 -0
- package/dist/testing/conformance/observability.d.ts +3 -0
- package/dist/testing/conformance/observability.js +343 -0
- package/dist/testing/conformance/offline.d.ts +4 -0
- package/dist/testing/conformance/offline.js +474 -0
- package/dist/testing/conformance/operator-harness.d.ts +57 -0
- package/dist/testing/conformance/operator-harness.js +78 -0
- package/dist/testing/conformance/operator.d.ts +3 -0
- package/dist/testing/conformance/operator.js +379 -0
- package/dist/testing/conformance/outbox-cross-shard.d.ts +14 -0
- package/dist/testing/conformance/outbox-cross-shard.js +160 -0
- package/dist/testing/conformance/outbox.d.ts +36 -0
- package/dist/testing/conformance/outbox.js +437 -0
- package/dist/testing/conformance/payload-migrations.d.ts +3 -0
- package/dist/testing/conformance/payload-migrations.js +612 -0
- package/dist/testing/conformance/pipeline.d.ts +3 -0
- package/dist/testing/conformance/pipeline.js +1040 -0
- package/dist/testing/conformance/placement.d.ts +22 -0
- package/dist/testing/conformance/placement.js +539 -0
- package/dist/testing/conformance/platform.d.ts +40 -0
- package/dist/testing/conformance/platform.js +101 -0
- package/dist/testing/conformance/progress.d.ts +39 -0
- package/dist/testing/conformance/progress.js +666 -0
- package/dist/testing/conformance/properties.d.ts +6 -0
- package/dist/testing/conformance/properties.js +295 -0
- package/dist/testing/conformance/protocols.d.ts +4 -0
- package/dist/testing/conformance/protocols.js +618 -0
- package/dist/testing/conformance/provider/advertise.d.ts +21 -0
- package/dist/testing/conformance/provider/advertise.js +44 -0
- package/dist/testing/conformance/read-your-writes.d.ts +20 -0
- package/dist/testing/conformance/read-your-writes.js +192 -0
- package/dist/testing/conformance/reducers.d.ts +8 -0
- package/dist/testing/conformance/reducers.js +258 -0
- package/dist/testing/conformance/relay.d.ts +42 -0
- package/dist/testing/conformance/relay.js +1115 -0
- package/dist/testing/conformance/restore.d.ts +43 -0
- package/dist/testing/conformance/restore.js +341 -0
- package/dist/testing/conformance/retention.d.ts +14 -0
- package/dist/testing/conformance/retention.js +463 -0
- package/dist/testing/conformance/rls.d.ts +24 -0
- package/dist/testing/conformance/rls.js +576 -0
- package/dist/testing/conformance/simulation.d.ts +31 -0
- package/dist/testing/conformance/simulation.js +167 -0
- package/dist/testing/conformance/single-shard.d.ts +2 -0
- package/dist/testing/conformance/single-shard.js +125 -0
- package/dist/testing/conformance/singleton.d.ts +3 -0
- package/dist/testing/conformance/singleton.js +293 -0
- package/dist/testing/conformance/statements.d.ts +60 -0
- package/dist/testing/conformance/statements.js +108 -0
- package/dist/testing/conformance/streams.d.ts +7 -0
- package/dist/testing/conformance/streams.js +280 -0
- package/dist/testing/conformance/subscriptions/access.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/access.js +98 -0
- package/dist/testing/conformance/subscriptions/actors.d.ts +4910 -0
- package/dist/testing/conformance/subscriptions/actors.js +372 -0
- package/dist/testing/conformance/subscriptions/cluster.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/cluster.js +89 -0
- package/dist/testing/conformance/subscriptions/delivery.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/delivery.js +253 -0
- package/dist/testing/conformance/subscriptions/deploys.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/deploys.js +162 -0
- package/dist/testing/conformance/subscriptions/epochs.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/epochs.js +169 -0
- package/dist/testing/conformance/subscriptions/failures.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/failures.js +108 -0
- package/dist/testing/conformance/subscriptions/feeds.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/feeds.js +218 -0
- package/dist/testing/conformance/subscriptions/harness.d.ts +124 -0
- package/dist/testing/conformance/subscriptions/harness.js +143 -0
- package/dist/testing/conformance/subscriptions/operator.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/operator.js +128 -0
- package/dist/testing/conformance/subscriptions/retention.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/retention.js +288 -0
- package/dist/testing/conformance/subscriptions/routing.d.ts +4 -0
- package/dist/testing/conformance/subscriptions/routing.js +179 -0
- package/dist/testing/conformance/subscriptions.d.ts +4 -0
- package/dist/testing/conformance/subscriptions.js +19 -0
- package/dist/testing/conformance/tables.d.ts +125 -0
- package/dist/testing/conformance/tables.js +664 -0
- package/dist/testing/conformance/transports/actors.d.ts +1907 -0
- package/dist/testing/conformance/transports/actors.js +163 -0
- package/dist/testing/conformance/transports/client.d.ts +3 -0
- package/dist/testing/conformance/transports/client.js +237 -0
- package/dist/testing/conformance/transports/feeds.d.ts +3 -0
- package/dist/testing/conformance/transports/feeds.js +232 -0
- package/dist/testing/conformance/transports/harness.d.ts +93 -0
- package/dist/testing/conformance/transports/harness.js +84 -0
- package/dist/testing/conformance/transports/streams.d.ts +3 -0
- package/dist/testing/conformance/transports/streams.js +96 -0
- package/dist/testing/conformance/transports/websocket.d.ts +3 -0
- package/dist/testing/conformance/transports/websocket.js +384 -0
- package/dist/testing/conformance/transports/wire.d.ts +141 -0
- package/dist/testing/conformance/transports/wire.js +204 -0
- package/dist/testing/conformance/transports.d.ts +3 -0
- package/dist/testing/conformance/transports.js +11 -0
- package/dist/testing/conformance/watch.d.ts +59 -0
- package/dist/testing/conformance/watch.js +879 -0
- package/dist/testing/conformance/workflow-engine.d.ts +97 -0
- package/dist/testing/conformance/workflow-engine.js +253 -0
- package/dist/testing/conformance/workflow-versions.d.ts +3 -0
- package/dist/testing/conformance/workflow-versions.js +545 -0
- package/dist/testing/conformance/workflows/access.d.ts +4 -0
- package/dist/testing/conformance/workflows/access.js +147 -0
- package/dist/testing/conformance/workflows/actors.d.ts +2317 -0
- package/dist/testing/conformance/workflows/actors.js +305 -0
- package/dist/testing/conformance/workflows/basics.d.ts +4 -0
- package/dist/testing/conformance/workflows/basics.js +144 -0
- package/dist/testing/conformance/workflows/engine.d.ts +4 -0
- package/dist/testing/conformance/workflows/engine.js +80 -0
- package/dist/testing/conformance/workflows/guards.d.ts +4 -0
- package/dist/testing/conformance/workflows/guards.js +172 -0
- package/dist/testing/conformance/workflows/harness.d.ts +14 -0
- package/dist/testing/conformance/workflows/harness.js +44 -0
- package/dist/testing/conformance/workflows/recovery.d.ts +4 -0
- package/dist/testing/conformance/workflows/recovery.js +181 -0
- package/dist/testing/conformance/workflows/starts.d.ts +4 -0
- package/dist/testing/conformance/workflows/starts.js +57 -0
- package/dist/testing/conformance/workflows/suspension.d.ts +4 -0
- package/dist/testing/conformance/workflows/suspension.js +106 -0
- package/dist/testing/conformance/workflows/waits.d.ts +4 -0
- package/dist/testing/conformance/workflows/waits.js +284 -0
- package/dist/testing/conformance/workflows.d.ts +4 -0
- package/dist/testing/conformance/workflows.js +19 -0
- package/dist/testing/conformance.d.ts +330 -0
- package/dist/testing/conformance.js +364 -0
- package/dist/testing/database.d.ts +31 -0
- package/dist/testing/database.js +70 -0
- package/dist/testing/foundation.d.ts +41 -0
- package/dist/testing/foundation.js +517 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/property.d.ts +40 -0
- package/dist/testing/property.js +95 -0
- package/dist/testing/simulate-cluster.d.ts +112 -0
- package/dist/testing/simulate-cluster.js +257 -0
- package/dist/testing/simulate.d.ts +90 -0
- package/dist/testing/simulate.js +127 -0
- package/package.json +1 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
import { Cause, Context, Crypto, Data, Effect, Exit, Fiber, Latch, Option, Schema, Scope, } from "effect";
|
|
2
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
3
|
+
import { Activity, Workflow as EffectWorkflow, WorkflowEngine } from "effect/workflow";
|
|
4
|
+
import { CallPhase, CurrentCallPhase, CurrentWorkflow, RecordedExit, StoredResult, } from "../../contexts/workflow.js";
|
|
5
|
+
import { ActivityOutcomeUnknown } from "../../errors/workflow.js";
|
|
6
|
+
import {} from "../members.js";
|
|
7
|
+
import { Outcome, Request } from "../request.js";
|
|
8
|
+
import { Due, emptyOutbox } from "../../handles/intents.js";
|
|
9
|
+
import { ExecutionIdOutput, INTERRUPT, RESUME, START, StartPayload, ExecutionTarget, } from "../../handles/workflow.js";
|
|
10
|
+
import { Caller, principal, System } from "../../identity/caller.js";
|
|
11
|
+
import { decodeExecutionId, encodeExecutionId } from "../../identity/execution.js";
|
|
12
|
+
import { bucketOf, OutboxRuntime, writeOutbox } from "../turn/outbox.js";
|
|
13
|
+
import { databaseTime } from "../turn/admission.js";
|
|
14
|
+
import { TurnHooks } from "../turn/hooks.js";
|
|
15
|
+
import { compress, decompress } from "../storage/codec.js";
|
|
16
|
+
import { actorRow, fence } from "../storage/generation.js";
|
|
17
|
+
import { decodeStoredManifest, markerProblem, recordedStepProblem, startVerdict, } from "./compatibility.js";
|
|
18
|
+
import { manifestOf, toJson } from "./manifest.js";
|
|
19
|
+
/** A running activity re-arms its execution's timer this far ahead, so a lost runner's work resumes. */
|
|
20
|
+
export const RECOVERY_MS = 30_000;
|
|
21
|
+
/** The outbox timer key of an execution's recovery timer. */
|
|
22
|
+
export const timerKey = (executionId) => `wf:${executionId}`;
|
|
23
|
+
const CallerJson = Schema.fromJsonString(Caller);
|
|
24
|
+
const encodeCaller = Schema.encodeEffect(CallerJson);
|
|
25
|
+
const decodeCaller = Schema.decodeEffect(CallerJson);
|
|
26
|
+
const encodeExecutionOutput = Schema.encodeEffect(ExecutionIdOutput);
|
|
27
|
+
const encodeTarget = Schema.encodeEffect(ExecutionTarget);
|
|
28
|
+
const decodeTarget = Schema.decodeEffect(ExecutionTarget);
|
|
29
|
+
const decodeStart = Schema.decodeEffect(StartPayload);
|
|
30
|
+
/** Dies a run whose activation no longer holds the owner's generation; nothing it wrote commits. */
|
|
31
|
+
class StaleRun extends Data.TaggedError("StaleRun") {
|
|
32
|
+
}
|
|
33
|
+
/** Ends a replay that reached a step it must wait for; its rows and timer are committed. */
|
|
34
|
+
class Suspend extends Data.TaggedError("Suspend") {
|
|
35
|
+
}
|
|
36
|
+
const isSignal = (signal) => (cause) => cause.reasons.some((reason) => Cause.isDieReason(reason) && reason.defect instanceof signal);
|
|
37
|
+
const suspended = isSignal(Suspend);
|
|
38
|
+
const stale = isSignal(StaleRun);
|
|
39
|
+
const success = (value) => ({
|
|
40
|
+
outcome: Outcome.cases.Success.make({ value }),
|
|
41
|
+
state: [],
|
|
42
|
+
complete: false,
|
|
43
|
+
events: [],
|
|
44
|
+
outbox: emptyOutbox,
|
|
45
|
+
});
|
|
46
|
+
/** Writes the execution's one keyed resume timer, replacing any pending one. */
|
|
47
|
+
const armTimer = Effect.fnUntraced(function* (routingKey, ref, executionId, dueAt, onBehalfOf) {
|
|
48
|
+
const key = timerKey(executionId);
|
|
49
|
+
return yield* writeOutbox(routingKey, ref, {
|
|
50
|
+
intents: [
|
|
51
|
+
{
|
|
52
|
+
target: ref,
|
|
53
|
+
command: RESUME,
|
|
54
|
+
payload: yield* encodeTarget({ executionId }).pipe(Effect.orDie),
|
|
55
|
+
caller: System.make({
|
|
56
|
+
source: "workflow",
|
|
57
|
+
ref,
|
|
58
|
+
onBehalfOf: Option.getOrUndefined(onBehalfOf),
|
|
59
|
+
}),
|
|
60
|
+
due: dueAt === undefined ? undefined : Due.cases.At.make({ epochMillis: dueAt }),
|
|
61
|
+
key,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
replaced: [key],
|
|
65
|
+
jobs: [],
|
|
66
|
+
subscriptions: [],
|
|
67
|
+
cancelledJobs: [],
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
const deleteTimer = (sql, routingKey, ref, executionId) => sql `DELETE FROM actor_outbox WHERE ${actorRow({ sql, actor: { key: routingKey, ref } })} AND timer_key = ${timerKey(executionId)}`;
|
|
71
|
+
/**
|
|
72
|
+
* Inserts an execution with its version markers inside the starting owner
|
|
73
|
+
* turn. A repeated start attaches: nothing is rewritten.
|
|
74
|
+
*
|
|
75
|
+
* An owner-staged start's cursor sits before the staging turn's events, so a wait
|
|
76
|
+
* sees them and every later owner event, including ones committed before the
|
|
77
|
+
* start is delivered. The run starts at once on this activation and the recovery
|
|
78
|
+
* timer resumes it if this runner dies first. The start manifest is restored if
|
|
79
|
+
* retention pruned it while a runner of an older deployment still starts
|
|
80
|
+
* executions under it.
|
|
81
|
+
*/
|
|
82
|
+
const insertExecution = Effect.fnUntraced(function* (options) {
|
|
83
|
+
const sql = yield* SqlClient.SqlClient;
|
|
84
|
+
const { routingKey, ref, workflow, executionId } = options;
|
|
85
|
+
const now = yield* databaseTime;
|
|
86
|
+
const manifest = yield* manifestOf(ref.actor, workflow.member);
|
|
87
|
+
const inserted = yield* sql `
|
|
88
|
+
WITH x AS (INSERT INTO actor_workflow_executions (routing_key, execution_id, bucket, tenant_id, actor_type, actor_id,
|
|
89
|
+
workflow, workflow_key, manifest_hash, payload, caller, event_cursor, status, started_at_ms)
|
|
90
|
+
SELECT ${routingKey}, ${executionId}, ${bucketOf(routingKey)}, ${ref.tenant}, ${ref.actor}, ${ref.id},
|
|
91
|
+
${workflow.member.tag}, ${options.key}, ${manifest.hash}, ${compress(options.payload)},
|
|
92
|
+
${yield* encodeCaller(options.caller).pipe(Effect.orDie)},
|
|
93
|
+
COALESCE(${options.after}::bigint, g.event_sequence),
|
|
94
|
+
'running', ${now}
|
|
95
|
+
FROM actor_generations g WHERE ${actorRow({ sql, actor: { key: routingKey, ref } })}
|
|
96
|
+
ON CONFLICT DO NOTHING RETURNING 1),
|
|
97
|
+
m AS (INSERT INTO actor_workflow_manifests (actor_type, workflow, manifest_hash, manifest, accepted_at_ms)
|
|
98
|
+
SELECT ${ref.actor}, ${workflow.member.tag}, ${manifest.hash}, ${toJson(manifest.manifest)}::jsonb, 0
|
|
99
|
+
WHERE EXISTS (SELECT 1 FROM x)
|
|
100
|
+
ON CONFLICT DO NOTHING)
|
|
101
|
+
SELECT 1 FROM x`;
|
|
102
|
+
if (inserted.length === 0)
|
|
103
|
+
return false;
|
|
104
|
+
const markers = Object.entries(workflow.member.versions);
|
|
105
|
+
if (markers.length > 0)
|
|
106
|
+
yield* sql `INSERT INTO actor_workflow_step ${sql.insert(markers.map(([name, range]) => ({
|
|
107
|
+
routing_key: routingKey,
|
|
108
|
+
execution_id: executionId,
|
|
109
|
+
tenant_id: ref.tenant,
|
|
110
|
+
actor_type: ref.actor,
|
|
111
|
+
actor_id: ref.id,
|
|
112
|
+
step: name,
|
|
113
|
+
attempt: 0,
|
|
114
|
+
kind: "version",
|
|
115
|
+
version: range.current,
|
|
116
|
+
started_at_ms: now,
|
|
117
|
+
})))}`;
|
|
118
|
+
yield* armTimer(routingKey, ref, executionId, now + RECOVERY_MS, principal(options.caller));
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* The reserved commands a workflow member adds to its owner: the member's own
|
|
123
|
+
* start, a start staged as an intent, the relay's resume, and interrupt.
|
|
124
|
+
*
|
|
125
|
+
* Interrupting a finished or unknown execution changes nothing.
|
|
126
|
+
*/
|
|
127
|
+
export const workflowCommands = ({ registration, routingKeyOf, services, }) => {
|
|
128
|
+
const commands = new Map();
|
|
129
|
+
const start = (request, workflow, payload, key, after) => Effect.gen(function* () {
|
|
130
|
+
const executionId = yield* encodeExecutionId({
|
|
131
|
+
tenant: request.ref.tenant,
|
|
132
|
+
actor: request.ref.actor,
|
|
133
|
+
id: request.ref.id,
|
|
134
|
+
workflow: workflow.member.tag,
|
|
135
|
+
key,
|
|
136
|
+
}).pipe(Effect.orDie);
|
|
137
|
+
yield* insertExecution({
|
|
138
|
+
routingKey: routingKeyOf(request.ref),
|
|
139
|
+
ref: request.ref,
|
|
140
|
+
workflow,
|
|
141
|
+
executionId,
|
|
142
|
+
key,
|
|
143
|
+
payload,
|
|
144
|
+
caller: request.caller,
|
|
145
|
+
after,
|
|
146
|
+
});
|
|
147
|
+
return success(yield* encodeExecutionOutput({ value: executionId }).pipe(Effect.orDie));
|
|
148
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideContext(services));
|
|
149
|
+
for (const workflow of registration.workflows.values())
|
|
150
|
+
commands.set(workflow.member.tag, {
|
|
151
|
+
internal: false,
|
|
152
|
+
handler: false,
|
|
153
|
+
run: (request) => Effect.gen(function* () {
|
|
154
|
+
const key = yield* workflow.key(request.payload, request.commandId);
|
|
155
|
+
return yield* start(request, workflow, request.payload, key, null);
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
if (registration.workflows.size === 0)
|
|
159
|
+
return commands;
|
|
160
|
+
commands.set(START, {
|
|
161
|
+
internal: true,
|
|
162
|
+
handler: false,
|
|
163
|
+
run: (request) => Effect.gen(function* () {
|
|
164
|
+
const staged = yield* decodeStart(request.payload).pipe(Effect.orDie);
|
|
165
|
+
const workflow = registration.workflows.get(staged.workflow);
|
|
166
|
+
if (workflow === undefined)
|
|
167
|
+
return yield* Effect.die(new Error(`Unregistered workflow ${staged.workflow}`));
|
|
168
|
+
return yield* start(request, workflow, staged.input, staged.key, staged.after);
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
commands.set(RESUME, {
|
|
172
|
+
internal: true,
|
|
173
|
+
handler: false,
|
|
174
|
+
run: (request) => decodeTarget(request.payload).pipe(Effect.orDie, Effect.map(() => success('{"value":null}'))),
|
|
175
|
+
});
|
|
176
|
+
commands.set(INTERRUPT, {
|
|
177
|
+
internal: false,
|
|
178
|
+
handler: false,
|
|
179
|
+
run: (request) => Effect.gen(function* () {
|
|
180
|
+
const sql = yield* SqlClient.SqlClient;
|
|
181
|
+
const { executionId } = yield* decodeTarget(request.payload).pipe(Effect.orDie);
|
|
182
|
+
const execution = yield* decodeExecutionId(executionId).pipe(Effect.orDie);
|
|
183
|
+
const routingKey = routingKeyOf(request.ref);
|
|
184
|
+
if (execution.tenant !== request.ref.tenant ||
|
|
185
|
+
execution.actor !== request.ref.actor ||
|
|
186
|
+
execution.id !== request.ref.id)
|
|
187
|
+
return yield* Effect.die(new Error("Execution id names another owner"));
|
|
188
|
+
const [row] = yield* sql `
|
|
189
|
+
UPDATE actor_workflow_executions SET interrupt = true
|
|
190
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId} AND status <> 'finished'
|
|
191
|
+
RETURNING caller`;
|
|
192
|
+
if (row !== undefined) {
|
|
193
|
+
const caller = yield* decodeCaller(row.caller).pipe(Effect.orDie);
|
|
194
|
+
yield* armTimer(routingKey, request.ref, executionId, undefined, principal(caller));
|
|
195
|
+
}
|
|
196
|
+
return success('{"value":null}');
|
|
197
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideContext(services)),
|
|
198
|
+
});
|
|
199
|
+
return commands;
|
|
200
|
+
};
|
|
201
|
+
/** The execution a successful workflow command names, if it names one. */
|
|
202
|
+
export const kickedExecution = ({ request, outcome, }) => Effect.gen(function* () {
|
|
203
|
+
if (!Outcome.guards.Success(outcome))
|
|
204
|
+
return undefined;
|
|
205
|
+
if (request.command === RESUME || request.command === INTERRUPT)
|
|
206
|
+
return {
|
|
207
|
+
executionId: (yield* decodeTarget(request.payload)).executionId,
|
|
208
|
+
interrupt: request.command === INTERRUPT,
|
|
209
|
+
};
|
|
210
|
+
const started = yield* Schema.decodeEffect(ExecutionIdOutput)(outcome.value);
|
|
211
|
+
return { executionId: started.value, interrupt: false };
|
|
212
|
+
}).pipe(Effect.orDie);
|
|
213
|
+
const RecordedJson = Schema.fromJsonString(RecordedExit);
|
|
214
|
+
const ResultJson = Schema.fromJsonString(StoredResult);
|
|
215
|
+
const decodeRecorded = (bytes) => Schema.decodeEffect(RecordedJson)(decompress(bytes)).pipe(Effect.orDie);
|
|
216
|
+
const encodeRecorded = (exit) => compress(JSON.stringify(exit));
|
|
217
|
+
const encodeResult = (result) => compress(JSON.stringify(result));
|
|
218
|
+
/** A step row's key in a run's replay map; only activities have attempts past 1. */
|
|
219
|
+
const slot = (step, attempt) => `${attempt}/${step}`;
|
|
220
|
+
const callIdentity = (executionId, step, attempt, ordinal) => JSON.stringify([executionId, step, attempt, ordinal]);
|
|
221
|
+
/** Decodes a finished execution's stored result. */
|
|
222
|
+
export const decodeResult = (bytes) => Schema.decodeEffect(ResultJson)(decompress(bytes)).pipe(Effect.orDie);
|
|
223
|
+
/** Stable 128 bits as a version-4 UUID string, so a derived id passes command-id validation. */
|
|
224
|
+
const derivedUuid = (bytes) => {
|
|
225
|
+
const hex = Array.from(bytes.subarray(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
226
|
+
const variant = ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
|
|
227
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Runs one activation's workflow executions. Postgres holds every step; the
|
|
231
|
+
* live fibers here only save a replay. Each write is a short transaction that
|
|
232
|
+
* first checks the activation still holds the owner's generation.
|
|
233
|
+
*
|
|
234
|
+
* Compatibility: a runner leaves an execution for a compatible one when it lacks
|
|
235
|
+
* the workflow, its markers exclude the execution's, it lacks a recorded step or a
|
|
236
|
+
* step of the start manifest, or it would decode a recorded result or the payload
|
|
237
|
+
* differently. Once this runner's result schemas apply to the steps still to
|
|
238
|
+
* settle, its manifest becomes the execution's start manifest; a `newer` manifest
|
|
239
|
+
* is one accepted after this runner's own, as when a newer deployment starts
|
|
240
|
+
* executions while this runner still serves.
|
|
241
|
+
*
|
|
242
|
+
* Replay: an interrupted execution replays to its first unsettled step and stops
|
|
243
|
+
* there, so the compensation finalizers of completed steps run before the
|
|
244
|
+
* interrupt is recorded. A replay of a parked execution counts as running until it parks or
|
|
245
|
+
* finishes, so inspection never reports a working run as suspended. A wake that
|
|
246
|
+
* arrived during a pass replays the run once more unless the pass left the
|
|
247
|
+
* execution for another runner or lost its generation, and a pass the engine
|
|
248
|
+
* stopped to replay always replays. A parking step waits, on the pass's idle
|
|
249
|
+
* signal, until every other step of the pass has parked or finished: a parked branch would
|
|
250
|
+
* otherwise interrupt concurrent branches mid-step, losing a sibling's clock, wait
|
|
251
|
+
* or activity work and possibly replaying forever. A race branch parked on a clock
|
|
252
|
+
* or wait that can now settle stops the run so its replay settles it, and a
|
|
253
|
+
* running sibling activity reruns under the same attempt. The relay consumed the
|
|
254
|
+
* recovery timer, so a still-running activity needs another.
|
|
255
|
+
*
|
|
256
|
+
* Steps: only the caller and tenant are inherited from the activation, because
|
|
257
|
+
* steps run inside the body's fiber whose caller and tenant are the execution's
|
|
258
|
+
* recorded ones. Effect's workflow instance names a workflow, and compensation
|
|
259
|
+
* never reads more of it. Rows are keyed by step and attempt, so each
|
|
260
|
+
* `Activity.retry` attempt is its own row with its own exit. The pending row is
|
|
261
|
+
* committed before an activity runs, so a rerun after a crash reuses its attempt
|
|
262
|
+
* and derived ids. A clock's due time is recorded once, so a replay never moves it.
|
|
263
|
+
* Step writes run inside the body's fiber, where a failed statement is a defect
|
|
264
|
+
* and the timer resumes. A wait's `where` runs outside any lock, and the fenced
|
|
265
|
+
* conditional settle makes a racing timeout or second run settle once. Holding the
|
|
266
|
+
* generation lock, a suspend finds any event committed after a wait's scan, so its
|
|
267
|
+
* wake survives.
|
|
268
|
+
*
|
|
269
|
+
* Compensation: `Workflow.withCompensation` and `Workflow.addFinalizer` register
|
|
270
|
+
* on the run's instance scope. Only a run that records a result closes it, and a
|
|
271
|
+
* replay registers them again, so they run once per execution.
|
|
272
|
+
*/
|
|
273
|
+
export const activationEngine = (options) => Effect.gen(function* () {
|
|
274
|
+
const { registration, ref, routingKey, cache, scope } = options;
|
|
275
|
+
const sql = yield* SqlClient.SqlClient;
|
|
276
|
+
const crypto = yield* Crypto.Crypto;
|
|
277
|
+
const { retryWindowMs } = yield* OutboxRuntime;
|
|
278
|
+
const services = Context.pick(SqlClient.SqlClient, Crypto.Crypto)(yield* Effect.context());
|
|
279
|
+
const actor = { key: routingKey, ref };
|
|
280
|
+
const ownerRow = actorRow({ sql, actor });
|
|
281
|
+
const instanceWorkflows = new Map();
|
|
282
|
+
const instanceWorkflow = (tag) => {
|
|
283
|
+
let found = instanceWorkflows.get(tag);
|
|
284
|
+
if (found === undefined) {
|
|
285
|
+
found = EffectWorkflow.make(tag, { payload: {}, idempotencyKey: () => tag });
|
|
286
|
+
instanceWorkflows.set(tag, found);
|
|
287
|
+
}
|
|
288
|
+
return found;
|
|
289
|
+
};
|
|
290
|
+
const live = new Map();
|
|
291
|
+
const fenced = (effect) => sql.withTransaction(Effect.gen(function* () {
|
|
292
|
+
if (!(yield* fence({ actor, cache, lock: "UPDATE" })))
|
|
293
|
+
return yield* Effect.die(new StaleRun());
|
|
294
|
+
return yield* effect;
|
|
295
|
+
}));
|
|
296
|
+
const now = databaseTime;
|
|
297
|
+
const startVerdicts = new Map();
|
|
298
|
+
/**
|
|
299
|
+
* The verdict on the manifest an execution started under, cached per hash
|
|
300
|
+
* for this activation. Unlike the startup check, it knows whether that
|
|
301
|
+
* manifest was accepted after this runner's own.
|
|
302
|
+
*/
|
|
303
|
+
const startOf = (declared, workflow, hash) => Effect.gen(function* () {
|
|
304
|
+
const known = hash === declared.hash ? undefined : startVerdicts.get(hash);
|
|
305
|
+
if (hash === declared.hash || known !== undefined)
|
|
306
|
+
return known ?? startVerdict({ declared, hash, start: undefined, newer: false });
|
|
307
|
+
const [row] = yield* sql `
|
|
308
|
+
SELECT m.manifest::text AS manifest, m.accepted_at_ms > COALESCE((SELECT o.accepted_at_ms
|
|
309
|
+
FROM actor_workflow_manifests o WHERE o.actor_type = m.actor_type AND o.workflow = m.workflow
|
|
310
|
+
AND o.manifest_hash = ${declared.hash}), -1) AS newer
|
|
311
|
+
FROM actor_workflow_manifests m
|
|
312
|
+
WHERE m.actor_type = ${ref.actor} AND m.workflow = ${workflow.member.tag}
|
|
313
|
+
AND m.manifest_hash = ${hash}`;
|
|
314
|
+
const verdict = startVerdict({
|
|
315
|
+
declared,
|
|
316
|
+
hash,
|
|
317
|
+
start: row === undefined
|
|
318
|
+
? undefined
|
|
319
|
+
: yield* decodeStoredManifest(row.manifest).pipe(Effect.orDie),
|
|
320
|
+
newer: row?.newer === true,
|
|
321
|
+
});
|
|
322
|
+
startVerdicts.set(hash, verdict);
|
|
323
|
+
return verdict;
|
|
324
|
+
});
|
|
325
|
+
/** An execution's recorded steps by slot and its version markers. */
|
|
326
|
+
const readSteps = (executionId) => Effect.gen(function* () {
|
|
327
|
+
const rows = yield* sql `
|
|
328
|
+
SELECT step, attempt, kind, exit, due_at_ms::text AS due_at_ms, wait_after::text AS wait_after,
|
|
329
|
+
scanned::text AS scanned, version, started_at_ms::text AS started_at_ms
|
|
330
|
+
FROM actor_workflow_step
|
|
331
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
332
|
+
ORDER BY step, attempt`;
|
|
333
|
+
const steps = new Map();
|
|
334
|
+
const markers = new Map();
|
|
335
|
+
for (const row of rows)
|
|
336
|
+
if (row.kind === "version")
|
|
337
|
+
markers.set(row.step, row.version);
|
|
338
|
+
else
|
|
339
|
+
steps.set(slot(row.step, row.attempt), row);
|
|
340
|
+
return { steps, markers };
|
|
341
|
+
});
|
|
342
|
+
/**
|
|
343
|
+
* Why this runner cannot replay an execution, empty when it can, and the
|
|
344
|
+
* steps whose result schemas differ from its start manifest's. Every
|
|
345
|
+
* recorded step, settled or pending, has to be registered as the same kind.
|
|
346
|
+
*/
|
|
347
|
+
const compatibility = (workflow, execution, recorded) => Effect.gen(function* () {
|
|
348
|
+
const declared = yield* manifestOf(ref.actor, workflow.member);
|
|
349
|
+
const start = yield* startOf(declared, workflow, execution.manifest_hash);
|
|
350
|
+
const { versions } = workflow.member;
|
|
351
|
+
const problems = [
|
|
352
|
+
...start.problems,
|
|
353
|
+
...[...new Set([...Object.keys(versions), ...recorded.markers.keys()])].flatMap((name) => markerProblem({ versions, name, recorded: recorded.markers.get(name) })),
|
|
354
|
+
...[...recorded.steps.values()].flatMap((row) => recordedStepProblem({
|
|
355
|
+
declared,
|
|
356
|
+
step: row.step,
|
|
357
|
+
kind: row.kind,
|
|
358
|
+
settled: row.exit !== null,
|
|
359
|
+
changed: start.changed,
|
|
360
|
+
})),
|
|
361
|
+
];
|
|
362
|
+
return { problems, changed: start.changed, declared };
|
|
363
|
+
});
|
|
364
|
+
/**
|
|
365
|
+
* Moves an execution started under an older manifest whose steps changed
|
|
366
|
+
* onto this runner's manifest, recording that manifest if it is new.
|
|
367
|
+
*/
|
|
368
|
+
const adoptManifest = (declared, workflow, execution, executionId, changed) => execution.manifest_hash === declared.hash || changed.length === 0
|
|
369
|
+
? Effect.void
|
|
370
|
+
: fenced(sql `
|
|
371
|
+
WITH m AS (INSERT INTO actor_workflow_manifests
|
|
372
|
+
(actor_type, workflow, manifest_hash, manifest, accepted_at_ms)
|
|
373
|
+
VALUES (${ref.actor}, ${workflow.member.tag}, ${declared.hash}, ${toJson(declared.manifest)}::jsonb, 0)
|
|
374
|
+
ON CONFLICT DO NOTHING)
|
|
375
|
+
UPDATE actor_workflow_executions SET manifest_hash = ${declared.hash}
|
|
376
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
377
|
+
AND manifest_hash = ${execution.manifest_hash}`).pipe(Effect.asVoid);
|
|
378
|
+
/** Records an execution's result, then drops its steps and its timer. */
|
|
379
|
+
const finish = (executionId, result) => fenced(Effect.gen(function* () {
|
|
380
|
+
const at = yield* now;
|
|
381
|
+
const done = yield* sql `
|
|
382
|
+
UPDATE actor_workflow_executions SET status = 'finished', result = ${encodeResult(result)},
|
|
383
|
+
finished_at_ms = ${at}
|
|
384
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId} AND status <> 'finished'
|
|
385
|
+
RETURNING 1`;
|
|
386
|
+
if (done.length === 0)
|
|
387
|
+
return;
|
|
388
|
+
yield* sql `DELETE FROM actor_workflow_step
|
|
389
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}`;
|
|
390
|
+
yield* deleteTimer(sql, routingKey, ref, executionId);
|
|
391
|
+
})).pipe(Effect.as("finished"));
|
|
392
|
+
/**
|
|
393
|
+
* The step primitives one run of an execution records through: each step
|
|
394
|
+
* replays its recorded exit, or records a new one in a fenced write.
|
|
395
|
+
*/
|
|
396
|
+
const executionSteps = ({ executionId, workflow, steps, pass, interrupting, eventCursor, onBehalfOf, }) => {
|
|
397
|
+
const registered = (step) => step.workflow === workflow.member.tag && workflow.steps.get(step.name)?.kind === step.kind
|
|
398
|
+
? Effect.void
|
|
399
|
+
: Effect.die(new Error(`Unregistered workflow step ${step.name}`));
|
|
400
|
+
const recordedOf = (step, attempt = 1) => {
|
|
401
|
+
const row = steps.get(slot(step.name, attempt));
|
|
402
|
+
if (row !== undefined && row.kind !== step.kind)
|
|
403
|
+
return Effect.die(new Error(`Step ${step.name} was recorded as a ${row.kind}`));
|
|
404
|
+
return Effect.succeed(row);
|
|
405
|
+
};
|
|
406
|
+
const insertStep = (step, fields, at, attempt = 1) => sql `INSERT INTO actor_workflow_step ${sql.insert({
|
|
407
|
+
routing_key: routingKey,
|
|
408
|
+
execution_id: executionId,
|
|
409
|
+
tenant_id: ref.tenant,
|
|
410
|
+
actor_type: ref.actor,
|
|
411
|
+
actor_id: ref.id,
|
|
412
|
+
step: step.name,
|
|
413
|
+
attempt,
|
|
414
|
+
kind: step.kind,
|
|
415
|
+
started_at_ms: at,
|
|
416
|
+
...fields,
|
|
417
|
+
})}`;
|
|
418
|
+
const settle = (step, exit, at, extra) => sql `
|
|
419
|
+
UPDATE actor_workflow_step SET exit = ${encodeRecorded(exit)}, settled_at_ms = ${at},
|
|
420
|
+
matched = ${extra?.matched ?? null}
|
|
421
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId} AND step = ${step.name}
|
|
422
|
+
AND attempt = ${extra?.attempt ?? 1} AND exit IS NULL
|
|
423
|
+
RETURNING exit`;
|
|
424
|
+
const readExit = (step, attempt = 1) => sql `
|
|
425
|
+
SELECT exit FROM actor_workflow_step WHERE routing_key = ${routingKey}
|
|
426
|
+
AND execution_id = ${executionId} AND step = ${step.name} AND attempt = ${attempt}`.pipe(Effect.map((found) => found[0]?.exit ?? null));
|
|
427
|
+
const remember = (step, row, attempt = 1) => steps.set(slot(step.name, attempt), {
|
|
428
|
+
step: step.name,
|
|
429
|
+
attempt,
|
|
430
|
+
kind: step.kind,
|
|
431
|
+
exit: null,
|
|
432
|
+
due_at_ms: null,
|
|
433
|
+
wait_after: null,
|
|
434
|
+
scanned: null,
|
|
435
|
+
version: null,
|
|
436
|
+
started_at_ms: "0",
|
|
437
|
+
...steps.get(slot(step.name, attempt)),
|
|
438
|
+
...row,
|
|
439
|
+
});
|
|
440
|
+
const shift = (delta) => Effect.sync(() => {
|
|
441
|
+
pass.working += delta;
|
|
442
|
+
if (pass.working === 0)
|
|
443
|
+
pass.idle.openUnsafe();
|
|
444
|
+
else
|
|
445
|
+
pass.idle.closeUnsafe();
|
|
446
|
+
});
|
|
447
|
+
const working = (effect) => Effect.acquireUseRelease(shift(1), () => effect, () => shift(-1));
|
|
448
|
+
/**
|
|
449
|
+
* Waits until no other step of the pass works. A step that just ended gets
|
|
450
|
+
* one scheduling turn to start its branch's next step before the pass
|
|
451
|
+
* counts as quiet.
|
|
452
|
+
*/
|
|
453
|
+
const quiet = Effect.gen(function* () {
|
|
454
|
+
for (;;) {
|
|
455
|
+
yield* pass.idle.await;
|
|
456
|
+
yield* Effect.yieldNow;
|
|
457
|
+
if (pass.working === 0)
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
const suspend = Effect.acquireUseRelease(shift(-1), () => quiet, () => shift(1)).pipe(Effect.andThen(Effect.die(new Suspend())));
|
|
462
|
+
const guarded = (effect) => effect.pipe(Effect.orDie, Effect.provideContext(services));
|
|
463
|
+
return {
|
|
464
|
+
activity: (step, run) => Effect.gen(function* () {
|
|
465
|
+
yield* registered(step);
|
|
466
|
+
const attempt = yield* Activity.CurrentAttempt;
|
|
467
|
+
let row = yield* recordedOf(step, attempt);
|
|
468
|
+
if (row?.exit !== null && row?.exit !== undefined)
|
|
469
|
+
return yield* decodeRecorded(row.exit);
|
|
470
|
+
if (interrupting)
|
|
471
|
+
return yield* Effect.interrupt;
|
|
472
|
+
if (row === undefined) {
|
|
473
|
+
const at = yield* now;
|
|
474
|
+
yield* fenced(Effect.gen(function* () {
|
|
475
|
+
yield* insertStep(step, {}, at, attempt);
|
|
476
|
+
yield* armTimer(routingKey, ref, executionId, at + RECOVERY_MS, onBehalfOf);
|
|
477
|
+
}));
|
|
478
|
+
remember(step, { started_at_ms: String(at) }, attempt);
|
|
479
|
+
row = steps.get(slot(step.name, attempt));
|
|
480
|
+
}
|
|
481
|
+
const issuedAt = Number(row.started_at_ms);
|
|
482
|
+
const expiresAt = issuedAt + retryWindowMs;
|
|
483
|
+
let ordinal = 0;
|
|
484
|
+
const nextCommandId = Effect.gen(function* () {
|
|
485
|
+
ordinal += 1;
|
|
486
|
+
if ((yield* now) + options.deliveryMs >= expiresAt)
|
|
487
|
+
return yield* Effect.die(ActivityOutcomeUnknown.make({ executionId, step: step.name }));
|
|
488
|
+
const digest = yield* crypto
|
|
489
|
+
.digest("SHA-256", new TextEncoder().encode(callIdentity(executionId, step.name, row.attempt, ordinal)))
|
|
490
|
+
.pipe(Effect.orDie);
|
|
491
|
+
return `v1.${issuedAt}.${expiresAt}.${derivedUuid(digest)}`;
|
|
492
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideContext(services));
|
|
493
|
+
const exit = yield* run.pipe(Effect.provideService(CurrentCallPhase, CallPhase.Activity({ nextCommandId })));
|
|
494
|
+
const at = yield* now;
|
|
495
|
+
const settled = yield* fenced(settle(step, exit, at, { attempt }));
|
|
496
|
+
const recorded = settled[0]?.exit ?? (yield* readExit(step, attempt));
|
|
497
|
+
if (recorded === null)
|
|
498
|
+
return yield* Effect.die(new Error("Activity exit missing"));
|
|
499
|
+
remember(step, { exit: recorded }, attempt);
|
|
500
|
+
return yield* decodeRecorded(recorded);
|
|
501
|
+
}).pipe(working, guarded),
|
|
502
|
+
sleep: (step, millis) => Effect.gen(function* () {
|
|
503
|
+
yield* registered(step);
|
|
504
|
+
const row = yield* recordedOf(step);
|
|
505
|
+
if (row?.exit !== null && row?.exit !== undefined)
|
|
506
|
+
return;
|
|
507
|
+
if (interrupting)
|
|
508
|
+
return yield* Effect.interrupt;
|
|
509
|
+
const at = yield* now;
|
|
510
|
+
let dueAt;
|
|
511
|
+
if (row === undefined) {
|
|
512
|
+
dueAt = at + millis;
|
|
513
|
+
yield* fenced(insertStep(step, { due_at_ms: dueAt }, at));
|
|
514
|
+
remember(step, { due_at_ms: String(dueAt), started_at_ms: String(at) });
|
|
515
|
+
}
|
|
516
|
+
else
|
|
517
|
+
dueAt = Number(row.due_at_ms);
|
|
518
|
+
if (at < dueAt) {
|
|
519
|
+
pass.parked.add(step.name);
|
|
520
|
+
return yield* suspend;
|
|
521
|
+
}
|
|
522
|
+
yield* fenced(settle(step, RecordedExit.cases.Success.make({ value: null }), at));
|
|
523
|
+
remember(step, {
|
|
524
|
+
exit: encodeRecorded(RecordedExit.cases.Success.make({ value: null })),
|
|
525
|
+
});
|
|
526
|
+
}).pipe(working, guarded),
|
|
527
|
+
wait: (step, event, matches, timeoutMs) => Effect.gen(function* () {
|
|
528
|
+
yield* registered(step);
|
|
529
|
+
let row = yield* recordedOf(step);
|
|
530
|
+
const settledValue = (exit) => decodeRecorded(exit).pipe(Effect.flatMap((recorded) => Schema.decodeUnknownEffect(Schema.NullOr(Schema.String))(RecordedExit.guards.Success(recorded) ? recorded.value : null).pipe(Effect.orDie)), Effect.map(Option.fromNullOr));
|
|
531
|
+
if (row?.exit !== null && row?.exit !== undefined)
|
|
532
|
+
return yield* settledValue(row.exit);
|
|
533
|
+
if (interrupting)
|
|
534
|
+
return yield* Effect.interrupt;
|
|
535
|
+
if (row === undefined) {
|
|
536
|
+
const at = yield* now;
|
|
537
|
+
const dueAt = timeoutMs === undefined ? null : at + timeoutMs;
|
|
538
|
+
const after = eventCursor.value;
|
|
539
|
+
yield* fenced(insertStep(step, { wait_event: event, wait_after: after, scanned: after, due_at_ms: dueAt }, at));
|
|
540
|
+
remember(step, {
|
|
541
|
+
wait_after: String(after),
|
|
542
|
+
scanned: String(after),
|
|
543
|
+
due_at_ms: dueAt === null ? null : String(dueAt),
|
|
544
|
+
started_at_ms: String(at),
|
|
545
|
+
});
|
|
546
|
+
row = steps.get(slot(step.name, 1));
|
|
547
|
+
}
|
|
548
|
+
let scanned = BigInt(row.scanned);
|
|
549
|
+
for (;;) {
|
|
550
|
+
const page = yield* sql `
|
|
551
|
+
SELECT sequence::text AS sequence, value, payload_version FROM actor_events
|
|
552
|
+
WHERE ${ownerRow} AND event = ${event} AND sequence > ${scanned}
|
|
553
|
+
ORDER BY sequence LIMIT 256`;
|
|
554
|
+
for (const found of page) {
|
|
555
|
+
const matched = yield* matches(decompress(found.value), found.payload_version);
|
|
556
|
+
if (Option.isNone(matched))
|
|
557
|
+
continue;
|
|
558
|
+
const value = matched.value;
|
|
559
|
+
const at = yield* now;
|
|
560
|
+
const sequence = BigInt(found.sequence);
|
|
561
|
+
const exit = RecordedExit.cases.Success.make({ value });
|
|
562
|
+
const settled = yield* fenced(Effect.gen(function* () {
|
|
563
|
+
const done = yield* settle(step, exit, at, { matched: sequence });
|
|
564
|
+
if (done.length > 0)
|
|
565
|
+
yield* sql `UPDATE actor_workflow_executions SET event_cursor = GREATEST(event_cursor, ${sequence})
|
|
566
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}`;
|
|
567
|
+
return done;
|
|
568
|
+
}));
|
|
569
|
+
if (settled.length > 0 && sequence > eventCursor.value)
|
|
570
|
+
eventCursor.value = sequence;
|
|
571
|
+
const recorded = settled[0]?.exit ?? (yield* readExit(step));
|
|
572
|
+
remember(step, { exit: recorded });
|
|
573
|
+
return yield* settledValue(recorded);
|
|
574
|
+
}
|
|
575
|
+
if (page.length < 256) {
|
|
576
|
+
if (page.length > 0)
|
|
577
|
+
scanned = BigInt(page[page.length - 1].sequence);
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
scanned = BigInt(page[page.length - 1].sequence);
|
|
581
|
+
}
|
|
582
|
+
const at = yield* now;
|
|
583
|
+
if (row.due_at_ms !== null && at >= Number(row.due_at_ms)) {
|
|
584
|
+
const settled = yield* fenced(settle(step, RecordedExit.cases.Success.make({ value: null }), at));
|
|
585
|
+
const recorded = settled[0]?.exit ?? (yield* readExit(step));
|
|
586
|
+
remember(step, { exit: recorded });
|
|
587
|
+
return yield* settledValue(recorded);
|
|
588
|
+
}
|
|
589
|
+
if (scanned > BigInt(row.scanned)) {
|
|
590
|
+
yield* fenced(sql `UPDATE actor_workflow_step SET scanned = ${scanned}
|
|
591
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
592
|
+
AND step = ${step.name} AND attempt = 1 AND exit IS NULL AND scanned < ${scanned}`);
|
|
593
|
+
remember(step, { scanned: String(scanned) });
|
|
594
|
+
}
|
|
595
|
+
pass.parked.add(step.name);
|
|
596
|
+
return yield* suspend;
|
|
597
|
+
}).pipe(working, guarded),
|
|
598
|
+
race: (step, run) => guarded(Effect.gen(function* () {
|
|
599
|
+
yield* registered(step);
|
|
600
|
+
const row = yield* recordedOf(step);
|
|
601
|
+
if (row?.exit !== null && row?.exit !== undefined)
|
|
602
|
+
return yield* decodeRecorded(row.exit);
|
|
603
|
+
if (interrupting)
|
|
604
|
+
return yield* Effect.interrupt;
|
|
605
|
+
const exit = yield* run;
|
|
606
|
+
const at = yield* now;
|
|
607
|
+
yield* fenced(insertStep(step, { exit: encodeRecorded(exit), settled_at_ms: at }, at).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die)));
|
|
608
|
+
remember(step, { exit: encodeRecorded(exit) });
|
|
609
|
+
return exit;
|
|
610
|
+
})),
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
/**
|
|
614
|
+
* Marks a run that suspended as suspended, and re-arms its timer: at once
|
|
615
|
+
* when an event it waits on arrived after its scan, at its earliest due
|
|
616
|
+
* step otherwise, or not at all when nothing is due.
|
|
617
|
+
*/
|
|
618
|
+
const park = (executionId, onBehalfOf) => Effect.gen(function* () {
|
|
619
|
+
yield* (yield* TurnHooks).at("beforeWorkflowSuspend", Request.make({
|
|
620
|
+
ref,
|
|
621
|
+
caller: System.make({ source: "workflow", ref }),
|
|
622
|
+
command: RESUME,
|
|
623
|
+
commandId: "",
|
|
624
|
+
payload: executionId,
|
|
625
|
+
}));
|
|
626
|
+
yield* fenced(Effect.gen(function* () {
|
|
627
|
+
const [due] = yield* sql `
|
|
628
|
+
SELECT min(due_at_ms)::text AS due FROM actor_workflow_step
|
|
629
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
630
|
+
AND exit IS NULL AND due_at_ms IS NOT NULL`;
|
|
631
|
+
yield* sql `UPDATE actor_workflow_executions SET status = 'suspended'
|
|
632
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId} AND status = 'running'`;
|
|
633
|
+
const [unseen] = yield* sql `
|
|
634
|
+
SELECT EXISTS (
|
|
635
|
+
SELECT 1 FROM actor_workflow_step s
|
|
636
|
+
JOIN actor_events e ON e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
|
|
637
|
+
AND e.actor_type = s.actor_type AND e.actor_id = s.actor_id
|
|
638
|
+
AND e.event = s.wait_event AND e.sequence > s.scanned
|
|
639
|
+
WHERE s.routing_key = ${routingKey} AND s.execution_id = ${executionId}
|
|
640
|
+
AND s.kind = 'wait' AND s.exit IS NULL
|
|
641
|
+
) AS found`;
|
|
642
|
+
if (unseen?.found === true)
|
|
643
|
+
yield* armTimer(routingKey, ref, executionId, undefined, onBehalfOf);
|
|
644
|
+
else if (due?.due === null || due === undefined)
|
|
645
|
+
yield* deleteTimer(sql, routingKey, ref, executionId);
|
|
646
|
+
else
|
|
647
|
+
yield* armTimer(routingKey, ref, executionId, Number(due.due), onBehalfOf);
|
|
648
|
+
}));
|
|
649
|
+
});
|
|
650
|
+
/**
|
|
651
|
+
* Runs one pass of an execution: replays it against this runner's workflow,
|
|
652
|
+
* then records its result, parks it, or leaves it for a compatible runner.
|
|
653
|
+
*/
|
|
654
|
+
const runOnce = (executionId, pass) => Effect.gen(function* () {
|
|
655
|
+
const [execution] = yield* sql `
|
|
656
|
+
SELECT workflow, workflow_key, payload, caller, event_cursor::text AS event_cursor, status, interrupt,
|
|
657
|
+
manifest_hash
|
|
658
|
+
FROM actor_workflow_executions
|
|
659
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}`;
|
|
660
|
+
if (execution === undefined || execution.status === "finished")
|
|
661
|
+
return "finished";
|
|
662
|
+
const caller = yield* decodeCaller(execution.caller).pipe(Effect.orDie);
|
|
663
|
+
const onBehalfOf = principal(caller);
|
|
664
|
+
const interrupting = execution.interrupt;
|
|
665
|
+
const workflow = registration.workflows.get(execution.workflow);
|
|
666
|
+
const recorded = yield* readSteps(executionId);
|
|
667
|
+
const verdict = workflow === undefined ? undefined : yield* compatibility(workflow, execution, recorded);
|
|
668
|
+
if (workflow === undefined || verdict === undefined || verdict.problems.length > 0) {
|
|
669
|
+
yield* Effect.logWarning("Workflow execution incompatible with this runner; suspended").pipe(Effect.annotateLogs({
|
|
670
|
+
executionId,
|
|
671
|
+
problems: verdict?.problems ?? ["workflow not registered"],
|
|
672
|
+
}));
|
|
673
|
+
yield* fenced(armTimer(routingKey, ref, executionId, (yield* now) + RECOVERY_MS, onBehalfOf));
|
|
674
|
+
return "abandoned";
|
|
675
|
+
}
|
|
676
|
+
yield* adoptManifest(verdict.declared, workflow, execution, executionId, verdict.changed);
|
|
677
|
+
if (execution.status === "suspended")
|
|
678
|
+
yield* fenced(sql `UPDATE actor_workflow_executions SET status = 'running'
|
|
679
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
680
|
+
AND status = 'suspended'`);
|
|
681
|
+
const engine = executionSteps({
|
|
682
|
+
executionId,
|
|
683
|
+
workflow,
|
|
684
|
+
steps: recorded.steps,
|
|
685
|
+
pass,
|
|
686
|
+
interrupting,
|
|
687
|
+
eventCursor: { value: BigInt(execution.event_cursor) },
|
|
688
|
+
onBehalfOf,
|
|
689
|
+
});
|
|
690
|
+
const instance = WorkflowEngine.WorkflowInstance.initial(instanceWorkflow(workflow.member.tag), executionId);
|
|
691
|
+
const exit = yield* workflow
|
|
692
|
+
.run(decompress(execution.payload), {
|
|
693
|
+
id: ref.id,
|
|
694
|
+
ref,
|
|
695
|
+
principal: onBehalfOf,
|
|
696
|
+
executionId,
|
|
697
|
+
key: execution.workflow_key,
|
|
698
|
+
version: (name) => Effect.succeed(recorded.markers.get(name) ?? 0),
|
|
699
|
+
})
|
|
700
|
+
.pipe(Effect.provideService(CurrentWorkflow, engine), Effect.provideService(CurrentCallPhase, CallPhase.Body()), Effect.provideService(WorkflowEngine.WorkflowInstance, instance), Effect.scoped);
|
|
701
|
+
const interrupted = Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause);
|
|
702
|
+
if (Exit.isFailure(exit)) {
|
|
703
|
+
if (stale(exit.cause) || (interrupted && !interrupting))
|
|
704
|
+
return "abandoned";
|
|
705
|
+
if (suspended(exit.cause)) {
|
|
706
|
+
yield* park(executionId, onBehalfOf);
|
|
707
|
+
return "suspended";
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (interrupted) {
|
|
711
|
+
yield* Scope.close(instance.scope, Exit.interrupt());
|
|
712
|
+
return yield* finish(executionId, StoredResult.cases.Interrupt.make({}));
|
|
713
|
+
}
|
|
714
|
+
yield* Scope.close(instance.scope, exit);
|
|
715
|
+
return yield* finish(executionId, yield* workflow.encodeExit(exit));
|
|
716
|
+
}).pipe(Effect.catchCause((cause) => stale(cause) || Cause.hasInterruptsOnly(cause)
|
|
717
|
+
? Effect.succeed("abandoned")
|
|
718
|
+
: Effect.logWarning("Workflow run failed; its timer resumes it", cause).pipe(Effect.as("abandoned"))), Effect.annotateLogs({ actor: ref.actor, tenant: ref.tenant, id: ref.id, executionId }));
|
|
719
|
+
/**
|
|
720
|
+
* Replays the run pass by pass until a pass ends with nothing asking for
|
|
721
|
+
* another. The decision to stop and the run's removal happen in one step, so
|
|
722
|
+
* a kick either reaches this run or starts a new one.
|
|
723
|
+
*/
|
|
724
|
+
const loop = (executionId, run) => Effect.gen(function* () {
|
|
725
|
+
for (;;) {
|
|
726
|
+
const pass = {
|
|
727
|
+
body: undefined,
|
|
728
|
+
parked: new Set(),
|
|
729
|
+
working: 0,
|
|
730
|
+
idle: Latch.makeUnsafe(true),
|
|
731
|
+
};
|
|
732
|
+
run.pass = pass;
|
|
733
|
+
const body = yield* Effect.forkChild(runOnce(executionId, pass));
|
|
734
|
+
pass.body = body;
|
|
735
|
+
const outcome = yield* Fiber.join(body).pipe(Effect.exit);
|
|
736
|
+
const again = yield* Effect.sync(() => {
|
|
737
|
+
const left = Exit.isSuccess(outcome) && outcome.value === "abandoned";
|
|
738
|
+
const replay = run.next === "preempt" || (run.next === "wake" && !left);
|
|
739
|
+
run.pass = undefined;
|
|
740
|
+
run.next = "end";
|
|
741
|
+
if (!replay)
|
|
742
|
+
live.delete(executionId);
|
|
743
|
+
return replay;
|
|
744
|
+
});
|
|
745
|
+
if (!again)
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
749
|
+
if (live.get(executionId) === run)
|
|
750
|
+
live.delete(executionId);
|
|
751
|
+
})));
|
|
752
|
+
const settleable = (executionId, parked) => parked.size === 0
|
|
753
|
+
? Effect.succeed(false)
|
|
754
|
+
: Effect.gen(function* () {
|
|
755
|
+
const at = yield* now;
|
|
756
|
+
const [found] = yield* sql `
|
|
757
|
+
SELECT EXISTS (
|
|
758
|
+
SELECT 1 FROM actor_workflow_step s
|
|
759
|
+
WHERE s.routing_key = ${routingKey} AND s.execution_id = ${executionId}
|
|
760
|
+
AND s.step IN ${sql.in([...parked])} AND s.exit IS NULL
|
|
761
|
+
AND (s.due_at_ms <= ${at}
|
|
762
|
+
OR (s.kind = 'wait' AND EXISTS (
|
|
763
|
+
SELECT 1 FROM actor_events e
|
|
764
|
+
WHERE e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
|
|
765
|
+
AND e.actor_type = s.actor_type AND e.actor_id = s.actor_id
|
|
766
|
+
AND e.event = s.wait_event AND e.sequence > s.scanned)))
|
|
767
|
+
) AS found`;
|
|
768
|
+
return found?.found === true;
|
|
769
|
+
}).pipe(Effect.orElseSucceed(() => false));
|
|
770
|
+
/**
|
|
771
|
+
* Runs or replays an execution after a committed workflow command. A live
|
|
772
|
+
* run replays once more after it suspends; an interrupt stops it first.
|
|
773
|
+
* A pass with working steps gets its recovery timer back, since the relay
|
|
774
|
+
* consumed it; that write holds the generation row like `finish`, so it
|
|
775
|
+
* never re-arms an execution a replay already finished.
|
|
776
|
+
*/
|
|
777
|
+
const kick = (executionId, interrupt) => Effect.gen(function* () {
|
|
778
|
+
const current = live.get(executionId);
|
|
779
|
+
if (current === undefined) {
|
|
780
|
+
const run = { pass: undefined, next: "end" };
|
|
781
|
+
live.set(executionId, run);
|
|
782
|
+
yield* loop(executionId, run).pipe(Effect.forkIn(scope));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const pass = current.pass;
|
|
786
|
+
if (current.next === "end")
|
|
787
|
+
current.next = "wake";
|
|
788
|
+
if (pass?.body === undefined)
|
|
789
|
+
return;
|
|
790
|
+
if (interrupt || (yield* settleable(executionId, pass.parked))) {
|
|
791
|
+
if (current.pass !== pass)
|
|
792
|
+
return;
|
|
793
|
+
current.next = "preempt";
|
|
794
|
+
yield* Fiber.interrupt(pass.body).pipe(Effect.forkIn(scope));
|
|
795
|
+
}
|
|
796
|
+
if (pass.working > 0)
|
|
797
|
+
yield* fenced(Effect.gen(function* () {
|
|
798
|
+
const [row] = yield* sql `SELECT caller FROM actor_workflow_executions
|
|
799
|
+
WHERE routing_key = ${routingKey} AND execution_id = ${executionId}
|
|
800
|
+
AND status <> 'finished'`;
|
|
801
|
+
if (row === undefined)
|
|
802
|
+
return;
|
|
803
|
+
const caller = yield* decodeCaller(row.caller).pipe(Effect.orDie);
|
|
804
|
+
yield* armTimer(routingKey, ref, executionId, (yield* now) + RECOVERY_MS, principal(caller));
|
|
805
|
+
})).pipe(Effect.ignoreCause);
|
|
806
|
+
}).pipe(Effect.provideContext(services));
|
|
807
|
+
return { kick, live: () => live.size };
|
|
808
|
+
});
|
|
809
|
+
/**
|
|
810
|
+
* Re-arms the resume timer of every execution with a pending wait for one of
|
|
811
|
+
* `tags`, inside the emitting turn. The turn holds the owner's generation
|
|
812
|
+
* lock, which a wait's registration also takes, so no event is missed.
|
|
813
|
+
*/
|
|
814
|
+
export const notifyWaits = Effect.fnUntraced(function* (routingKey, ref, tags) {
|
|
815
|
+
const sql = yield* SqlClient.SqlClient;
|
|
816
|
+
const waiting = yield* sql `
|
|
817
|
+
SELECT DISTINCT s.execution_id, x.caller FROM actor_workflow_step s
|
|
818
|
+
JOIN actor_workflow_executions x ON x.routing_key = s.routing_key AND x.execution_id = s.execution_id
|
|
819
|
+
WHERE s.routing_key = ${routingKey} AND s.tenant_id = ${ref.tenant} AND s.actor_type = ${ref.actor}
|
|
820
|
+
AND s.actor_id = ${ref.id} AND s.kind = 'wait' AND s.exit IS NULL AND s.wait_event IN ${sql.in(tags)}`;
|
|
821
|
+
for (const row of waiting) {
|
|
822
|
+
const caller = yield* decodeCaller(row.caller).pipe(Effect.orDie);
|
|
823
|
+
yield* armTimer(routingKey, ref, row.execution_id, undefined, principal(caller));
|
|
824
|
+
}
|
|
825
|
+
return waiting.length > 0;
|
|
826
|
+
});
|