@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,763 @@
|
|
|
1
|
+
import { DateTime, Effect, Match, Result, Schema } from "effect";
|
|
2
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
3
|
+
import {} from "../database/shards.js";
|
|
4
|
+
import { SubscriptionFailure } from "../../errors/subscription.js";
|
|
5
|
+
import { Outcome, Request } from "../request.js";
|
|
6
|
+
import {} from "../members.js";
|
|
7
|
+
import { ActorRef, System } from "../../identity/caller.js";
|
|
8
|
+
import { decompress, routingKey } from "../storage/codec.js";
|
|
9
|
+
import { databaseTime, FrameworkClock } from "../turn/admission.js";
|
|
10
|
+
import { TurnHooks } from "../turn/hooks.js";
|
|
11
|
+
import { ControlPayload, StringsJson, textArray } from "../turn/outbox.js";
|
|
12
|
+
import { candidates, outboxNow } from "../turn/relay.js";
|
|
13
|
+
import { changeRows, rowColumns } from "./storage.js";
|
|
14
|
+
import { deliveryCommandId } from "./identity.js";
|
|
15
|
+
import { count, Metrics } from "../telemetry/metrics.js";
|
|
16
|
+
import { SpanNames } from "../telemetry/spans.js";
|
|
17
|
+
/** How long a row whose declaration was removed stays due before cleanup deletes it. */
|
|
18
|
+
const REMOVED_AFTER_MS = 86_400_000;
|
|
19
|
+
/** Source-side subscription rows one expansion statement marks at most. */
|
|
20
|
+
const EXPANSION_PAGE = 1000;
|
|
21
|
+
/** A claimed feed row to expand, or control row to register at its source. */
|
|
22
|
+
const OutboxWork = Schema.Struct({
|
|
23
|
+
kind: Schema.Literals(["feed", "control"]),
|
|
24
|
+
routing_key: Schema.String,
|
|
25
|
+
intent_id: Schema.String,
|
|
26
|
+
attempts: Schema.Int,
|
|
27
|
+
tenant_id: Schema.String,
|
|
28
|
+
actor_type: Schema.String,
|
|
29
|
+
actor_id: Schema.String,
|
|
30
|
+
target_type: Schema.String,
|
|
31
|
+
target_id: Schema.String,
|
|
32
|
+
command: Schema.String,
|
|
33
|
+
payload: Schema.String,
|
|
34
|
+
scheduled_at_ms: Schema.String,
|
|
35
|
+
claimed_until: Schema.String,
|
|
36
|
+
});
|
|
37
|
+
/** A claimed subscription row to deliver. */
|
|
38
|
+
const SubscriptionRow = Schema.Struct({
|
|
39
|
+
kind: Schema.Literal("subscription"),
|
|
40
|
+
routing_key: Schema.String,
|
|
41
|
+
tenant_id: Schema.String,
|
|
42
|
+
source_type: Schema.String,
|
|
43
|
+
source_id: Schema.String,
|
|
44
|
+
subscriber_type: Schema.String,
|
|
45
|
+
subscription: Schema.String,
|
|
46
|
+
subscriber_id: Schema.String,
|
|
47
|
+
events: Schema.Array(Schema.String),
|
|
48
|
+
epoch: Schema.String,
|
|
49
|
+
delivered: Schema.String,
|
|
50
|
+
attempts: Schema.Int,
|
|
51
|
+
gap_through: Schema.NullOr(Schema.String),
|
|
52
|
+
claimed_until: Schema.String,
|
|
53
|
+
});
|
|
54
|
+
/** A unit of relay work, as a claim returns it in its `work` column: an outbox row to expand or a subscription row to deliver. */
|
|
55
|
+
const SubscriptionWork = Schema.fromJsonString(Schema.Union([OutboxWork, SubscriptionRow]));
|
|
56
|
+
const JsonText = Schema.fromJsonString(Schema.Unknown);
|
|
57
|
+
/**
|
|
58
|
+
* A delivery as the relay encodes it for any subscriber's handler: the event
|
|
59
|
+
* stays the source's stored JSON, which the handler's own schema decodes.
|
|
60
|
+
*/
|
|
61
|
+
const WireEvent = Schema.TaggedStruct("Event", {
|
|
62
|
+
subscription: Schema.String,
|
|
63
|
+
source: ActorRef,
|
|
64
|
+
cursor: Schema.String,
|
|
65
|
+
event: Schema.Unknown,
|
|
66
|
+
commandId: Schema.String,
|
|
67
|
+
timestamp: Schema.String,
|
|
68
|
+
});
|
|
69
|
+
const WireRejected = Schema.TaggedStruct("Rejected", {
|
|
70
|
+
subscription: Schema.String,
|
|
71
|
+
source: ActorRef,
|
|
72
|
+
reason: Schema.Literal("UnknownCursor"),
|
|
73
|
+
cursor: Schema.String,
|
|
74
|
+
});
|
|
75
|
+
const WireGap = Schema.TaggedStruct("RetentionGap", {
|
|
76
|
+
subscription: Schema.String,
|
|
77
|
+
source: ActorRef,
|
|
78
|
+
after: Schema.String,
|
|
79
|
+
resumeAfter: Schema.String,
|
|
80
|
+
});
|
|
81
|
+
const WireDelivery = Schema.Union([WireEvent, WireGap, WireRejected]);
|
|
82
|
+
const encodePayload = Schema.encodeEffect(Schema.fromJsonString(Schema.Struct({ value: WireDelivery })));
|
|
83
|
+
const decodeStrings = Schema.decodeEffect(StringsJson);
|
|
84
|
+
const decodeJson = Schema.decodeEffect(JsonText);
|
|
85
|
+
const decodeWork = Schema.decodeEffect(SubscriptionWork);
|
|
86
|
+
const backoff = (attempts, settings) => Math.max(settings.claimLeaseMs(), Math.min(1000 * 2 ** Math.max(attempts - 1, 0), settings.maxBackoffMs));
|
|
87
|
+
/** A request-shaped value naming the work at a fault point, for `TurnHooks`. */
|
|
88
|
+
const hookRequest = (ref, command, commandId) => Request.make({
|
|
89
|
+
ref,
|
|
90
|
+
caller: System.make({ source: "subscription" }),
|
|
91
|
+
command,
|
|
92
|
+
commandId,
|
|
93
|
+
payload: "",
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* The relay's subscription work on one runner: expanding source feeds,
|
|
97
|
+
* registering dynamic subscriptions at their sources, and delivering due
|
|
98
|
+
* subscription rows of the subscriber types this runner registers. A runner
|
|
99
|
+
* that registers none claims none of it.
|
|
100
|
+
*
|
|
101
|
+
* Delivery: rows this runner can deliver that an expansion makes due are leased at
|
|
102
|
+
* once, up to its free delivery slots, and delivered without a claim pass; the
|
|
103
|
+
* rest become due for any runner. Updates bound their rows by the page's key
|
|
104
|
+
* range, read from the key index, rather than joining the page, so a planner on
|
|
105
|
+
* stale row counts cannot rescan the page for every stored row. A renewal never
|
|
106
|
+
* shortens a claim, so it cannot undo a lease a test clock moved. Shutdown
|
|
107
|
+
* releases a claim at once; the receipt and cursor make redelivery safe.
|
|
108
|
+
*
|
|
109
|
+
* Subscribing: a source that has never been created can be subscribed to. Only a
|
|
110
|
+
* refused subscribe leaves a tombstone at its own epoch. A subscription is due at
|
|
111
|
+
* once when history after its start already matches, so one to a quiet source
|
|
112
|
+
* still delivers it. Widening only adds this declaration's classes to the row's
|
|
113
|
+
* list. Every write that changes a row's `active` or `events` goes through
|
|
114
|
+
* `changeRows`, which moves the tag summary in the same transaction.
|
|
115
|
+
*
|
|
116
|
+
* Gaps: pruning removes a prefix, so history after `delivered` is missing when the
|
|
117
|
+
* oldest retained event is past its successor or nothing is retained. An
|
|
118
|
+
* operator's skip plants the position it skips through as the gap's end. The
|
|
119
|
+
* detection time fixes the gap's id and its range is kept, so a redelivery reports
|
|
120
|
+
* the same gap even after pruning advances. An id route needs the pruned event to
|
|
121
|
+
* name a subscriber. A short batch scanned every matching event through the head
|
|
122
|
+
* it read, and after a gap older than the pruning it stops at the gap to find the
|
|
123
|
+
* next one.
|
|
124
|
+
*
|
|
125
|
+
* Delivery outcomes: the subscriber reads the current shape, and a value its chain
|
|
126
|
+
* cannot read backs the row off at this event rather than skipping it. A failure
|
|
127
|
+
* backs the row off, and a stale or unsubscribed acknowledgement removes this
|
|
128
|
+
* epoch's row. The marked position is read before the settle in its own snapshot:
|
|
129
|
+
* a commit after it raised `marked` through its expansion, which the settle reads
|
|
130
|
+
* from the row.
|
|
131
|
+
*/
|
|
132
|
+
export const subscriptionRelay = Effect.fnUntraced(function* (options) {
|
|
133
|
+
const sql = yield* SqlClient.SqlClient;
|
|
134
|
+
const hooks = yield* TurnHooks;
|
|
135
|
+
const clock = yield* FrameworkClock;
|
|
136
|
+
const { settings } = options;
|
|
137
|
+
const now = () => outboxNow({ sql, offsetMillis: clock.offsetMillis() });
|
|
138
|
+
const sourceWhere = (alias, key, source) => sql `${sql(alias)}.routing_key = ${key} AND ${sql(alias)}.tenant_id = ${source.tenant}
|
|
139
|
+
AND ${sql(alias)}.source_type = ${source.actor} AND ${sql(alias)}.source_id = ${source.id}`;
|
|
140
|
+
const eventsOf = (alias, key, source) => sql `${sql(alias)}.routing_key = ${key} AND ${sql(alias)}.tenant_id = ${source.tenant}
|
|
141
|
+
AND ${sql(alias)}.actor_type = ${source.actor} AND ${sql(alias)}.actor_id = ${source.id}`;
|
|
142
|
+
/** When a claim of the row aliased `alias` ends: one lease, or its backoff once that is longer. */
|
|
143
|
+
const leaseEnd = (alias) => sql `${now()} + greatest(${settings.claimLeaseMs()}::bigint,
|
|
144
|
+
least(1000 * power(2, least(${sql(alias)}.attempts, 31)), ${settings.maxBackoffMs}::bigint))::bigint`;
|
|
145
|
+
const outboxClaim = (kind, now, limit, range) => {
|
|
146
|
+
const probe = 4 * limit;
|
|
147
|
+
const found = sql.literal(`${kind}_candidates`);
|
|
148
|
+
const locked = sql.literal(`${kind}_locked`);
|
|
149
|
+
const claimed = sql.literal(`${kind}_claimed`);
|
|
150
|
+
return sql `${found} AS (
|
|
151
|
+
${candidates({ sql, kind, now, limit: probe, range })}
|
|
152
|
+
ORDER BY o.due_at_ms LIMIT ${probe}
|
|
153
|
+
),
|
|
154
|
+
${locked} AS (
|
|
155
|
+
SELECT o.routing_key, o.intent_id FROM actor_outbox o
|
|
156
|
+
JOIN ${found} USING (routing_key, intent_id)
|
|
157
|
+
WHERE o.kind = ${kind} AND o.due_at_ms <= ${now}
|
|
158
|
+
ORDER BY o.due_at_ms LIMIT ${limit}
|
|
159
|
+
FOR UPDATE OF o SKIP LOCKED
|
|
160
|
+
),
|
|
161
|
+
${claimed} AS (
|
|
162
|
+
UPDATE actor_outbox o SET attempts = o.attempts + 1, due_at_ms = ${leaseEnd("o")}
|
|
163
|
+
FROM ${locked} c
|
|
164
|
+
WHERE o.routing_key = c.routing_key AND o.intent_id = c.intent_id
|
|
165
|
+
RETURNING jsonb_build_object('kind', o.kind, 'routing_key', o.routing_key::text,
|
|
166
|
+
'intent_id', o.intent_id, 'attempts', o.attempts, 'tenant_id', o.tenant_id,
|
|
167
|
+
'actor_type', o.actor_type, 'actor_id', o.actor_id, 'target_type', o.target_type,
|
|
168
|
+
'target_id', o.target_id, 'command', o.command, 'payload', o.payload,
|
|
169
|
+
'scheduled_at_ms', o.scheduled_at_ms::text,
|
|
170
|
+
'claimed_until', o.due_at_ms::text)::text AS work
|
|
171
|
+
)`;
|
|
172
|
+
};
|
|
173
|
+
const subscribed = (local) => sql `subscribed (subscriber_type, subscription, known) AS (VALUES ${sql.csv(local.map(({ subscriberType, subscription }) => sql `(${subscriberType}::text, ${subscription.tag}::text, ${textArray({
|
|
174
|
+
sql,
|
|
175
|
+
values: [...subscription.events, ...subscription.retired],
|
|
176
|
+
})})`))})`;
|
|
177
|
+
const claimedRow = sql `jsonb_build_object('kind', 'subscription', 'routing_key', s.routing_key::text,
|
|
178
|
+
'tenant_id', s.tenant_id, 'source_type', s.source_type, 'source_id', s.source_id,
|
|
179
|
+
'subscriber_type', s.subscriber_type, 'subscription', s.subscription,
|
|
180
|
+
'subscriber_id', s.subscriber_id, 'events', to_jsonb(s.events),
|
|
181
|
+
'epoch', s.epoch::text, 'delivered', s.delivered::text, 'attempts', s.attempts,
|
|
182
|
+
'gap_through', s.gap_through::text, 'claimed_until', s.due_at_ms::text)`;
|
|
183
|
+
/**
|
|
184
|
+
* Claims due feed and control rows and due subscription rows, each up to
|
|
185
|
+
* its free slots, as parts of the relay's claim statement. Subscription
|
|
186
|
+
* rows are probed per bucket and subscriber type through
|
|
187
|
+
* `actor_subscriptions_due`, so caught-up rows and other types' rows are
|
|
188
|
+
* never read, and only rows whose event tags this runner's declaration
|
|
189
|
+
* knows are taken: a runner never settles past a class it can't decode.
|
|
190
|
+
*/
|
|
191
|
+
const claim = (slots, range) => {
|
|
192
|
+
const local = options.local();
|
|
193
|
+
if (local.length === 0)
|
|
194
|
+
return undefined;
|
|
195
|
+
const parts = [];
|
|
196
|
+
const results = [];
|
|
197
|
+
for (const kind of ["feed", "control"])
|
|
198
|
+
if (slots[kind] > 0) {
|
|
199
|
+
parts.push(outboxClaim(kind, now(), slots[kind], range));
|
|
200
|
+
results.push(sql `SELECT work FROM ${sql.literal(`${kind}_claimed`)}`);
|
|
201
|
+
}
|
|
202
|
+
if (slots.subscription > 0) {
|
|
203
|
+
const probe = 4 * slots.subscription;
|
|
204
|
+
parts.push(sql `${subscribed(local)},
|
|
205
|
+
subscription_candidates AS (
|
|
206
|
+
SELECT s.* FROM generate_series(${range.first}::int, ${range.last}::int) AS b(bucket)
|
|
207
|
+
CROSS JOIN (SELECT DISTINCT subscriber_type FROM subscribed) AS t
|
|
208
|
+
CROSS JOIN LATERAL (
|
|
209
|
+
SELECT routing_key, tenant_id, source_type, source_id, subscriber_type, subscription,
|
|
210
|
+
subscriber_id, due_at_ms
|
|
211
|
+
FROM actor_subscriptions
|
|
212
|
+
WHERE actor_subscriptions.bucket = b.bucket
|
|
213
|
+
AND actor_subscriptions.subscriber_type = t.subscriber_type
|
|
214
|
+
AND actor_subscriptions.due_at_ms <= ${now()}
|
|
215
|
+
AND EXISTS (SELECT 1 FROM subscribed m WHERE m.subscriber_type = actor_subscriptions.subscriber_type
|
|
216
|
+
AND m.subscription = actor_subscriptions.subscription
|
|
217
|
+
AND m.known @> actor_subscriptions.events)
|
|
218
|
+
ORDER BY actor_subscriptions.due_at_ms LIMIT ${probe}
|
|
219
|
+
) s
|
|
220
|
+
ORDER BY s.due_at_ms LIMIT ${probe}
|
|
221
|
+
),
|
|
222
|
+
subscription_locked AS (
|
|
223
|
+
SELECT s.routing_key, s.tenant_id, s.source_type, s.source_id, s.subscriber_type,
|
|
224
|
+
s.subscription, s.subscriber_id
|
|
225
|
+
FROM actor_subscriptions s
|
|
226
|
+
JOIN subscription_candidates c USING (routing_key, tenant_id, source_type, source_id,
|
|
227
|
+
subscriber_type, subscription, subscriber_id)
|
|
228
|
+
WHERE s.due_at_ms <= ${now()} AND s.active
|
|
229
|
+
ORDER BY s.due_at_ms LIMIT ${slots.subscription}
|
|
230
|
+
FOR UPDATE OF s SKIP LOCKED
|
|
231
|
+
),
|
|
232
|
+
subscription_claimed AS (
|
|
233
|
+
UPDATE actor_subscriptions s SET attempts = s.attempts + 1, due_at_ms = ${leaseEnd("s")}
|
|
234
|
+
FROM subscription_locked c
|
|
235
|
+
WHERE s.routing_key = c.routing_key AND s.tenant_id = c.tenant_id
|
|
236
|
+
AND s.source_type = c.source_type AND s.source_id = c.source_id
|
|
237
|
+
AND s.subscriber_type = c.subscriber_type AND s.subscription = c.subscription
|
|
238
|
+
AND s.subscriber_id = c.subscriber_id
|
|
239
|
+
RETURNING ${claimedRow}::text AS work
|
|
240
|
+
)`);
|
|
241
|
+
results.push(sql `SELECT work FROM subscription_claimed`);
|
|
242
|
+
}
|
|
243
|
+
return results.length === 0 ? undefined : { parts, results };
|
|
244
|
+
};
|
|
245
|
+
const decode = (work) => Effect.orDie(decodeWork(work));
|
|
246
|
+
const placementKey = Effect.fnUntraced(function* (ref) {
|
|
247
|
+
const placement = yield* options.placementOf(ref.actor);
|
|
248
|
+
if (placement === undefined)
|
|
249
|
+
return yield* SubscriptionFailure.make({
|
|
250
|
+
message: `Source type ${ref.actor} has no recorded placement`,
|
|
251
|
+
});
|
|
252
|
+
return routingKey({ ref, placement });
|
|
253
|
+
});
|
|
254
|
+
const rowKey = (alias, row, key, source) => sql `${sourceWhere(alias, key, source)} AND ${sql(alias)}.subscriber_type = ${row.subscriber_type}
|
|
255
|
+
AND ${sql(alias)}.subscription = ${row.subscription} AND ${sql(alias)}.subscriber_id = ${row.subscriber_id}`;
|
|
256
|
+
/** Deletes the rows `old_rows` locked, returning each as no longer active. */
|
|
257
|
+
const deleteLocked = sql `DELETE FROM actor_subscriptions s USING old_rows o
|
|
258
|
+
WHERE s.routing_key = o.routing_key AND s.tenant_id = o.tenant_id
|
|
259
|
+
AND s.source_type = o.source_type AND s.source_id = o.source_id
|
|
260
|
+
AND s.subscriber_type = o.subscriber_type AND s.subscription = o.subscription
|
|
261
|
+
AND s.subscriber_id = o.subscriber_id
|
|
262
|
+
RETURNING s.routing_key, s.tenant_id, s.source_type, s.source_id, s.subscriber_type,
|
|
263
|
+
s.subscription, s.subscriber_id, false AS active, s.events`;
|
|
264
|
+
/**
|
|
265
|
+
* Deletes a dynamic row the subscriber acknowledged as stale or
|
|
266
|
+
* unsubscribed, but only at the delivery's epoch, so an old acknowledgement
|
|
267
|
+
* never removes a newer subscription.
|
|
268
|
+
*/
|
|
269
|
+
const deleteRow = (row, key, source) => changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
|
|
270
|
+
WHERE ${rowKey("s", row, key, source)} AND s.epoch = ${row.epoch} FOR UPDATE`, deleteLocked);
|
|
271
|
+
/**
|
|
272
|
+
* Expands one source's feed: a claimed or backing-off row gets `marked`
|
|
273
|
+
* raised to the source's head, and a caught-up row with a matching event
|
|
274
|
+
* after its `delivered` becomes due. A row already due and unclaimed is left
|
|
275
|
+
* alone, so a burst of commits beside many waiting rows writes nothing. A claimed or backing-off row keeps its due
|
|
276
|
+
* time, so a commit can't cut a retry short, and `marked` alone keeps it
|
|
277
|
+
* due after its settle. The feed row goes only if no commit reset its
|
|
278
|
+
* `attempts` meanwhile; otherwise it stays due and is expanded again.
|
|
279
|
+
*/
|
|
280
|
+
const expand = Effect.fnUntraced(function* (row, handoff) {
|
|
281
|
+
const key = BigInt(row.routing_key);
|
|
282
|
+
const source = { tenant: row.tenant_id, actor: row.actor_type, id: row.actor_id };
|
|
283
|
+
yield* hooks.at("afterClaim", hookRequest(source, "$feed", row.intent_id));
|
|
284
|
+
const local = options.local();
|
|
285
|
+
let after = ["", "", ""];
|
|
286
|
+
for (;;) {
|
|
287
|
+
const free = local.length === 0 ? 0 : Math.max(0, yield* handoff.free);
|
|
288
|
+
const leasable = free === 0
|
|
289
|
+
? sql `leasable AS MATERIALIZED (
|
|
290
|
+
SELECT NULL::text AS subscriber_type, NULL::text AS subscription,
|
|
291
|
+
NULL::text AS subscriber_id WHERE false)`
|
|
292
|
+
: sql `${subscribed(local)},
|
|
293
|
+
leasable AS MATERIALIZED (
|
|
294
|
+
SELECT p.subscriber_type, p.subscription, p.subscriber_id
|
|
295
|
+
FROM page p JOIN subscribed m ON m.subscriber_type = p.subscriber_type
|
|
296
|
+
AND m.subscription = p.subscription AND m.known @> p.events, head
|
|
297
|
+
WHERE p.due_at_ms IS NULL AND p.marked < head.h
|
|
298
|
+
-- OFFSET 0 keeps this a per-row probe bounded by the row's
|
|
299
|
+
-- cursor in the index, rather than a join that reads the
|
|
300
|
+
-- source's whole event log for every row of the page.
|
|
301
|
+
AND EXISTS (
|
|
302
|
+
SELECT 1 FROM actor_events e
|
|
303
|
+
WHERE ${eventsOf("e", key, source)}
|
|
304
|
+
AND e.sequence > p.delivered AND e.sequence <= head.h
|
|
305
|
+
AND e.event = ANY(p.events) OFFSET 0)
|
|
306
|
+
LIMIT ${free})`;
|
|
307
|
+
const inPage = sql `(s.subscriber_type, s.subscription, s.subscriber_id) > (${after[0]}, ${after[1]}, ${after[2]})
|
|
308
|
+
AND (s.subscriber_type, s.subscription, s.subscriber_id)
|
|
309
|
+
<= (SELECT subscriber_type, subscription, subscriber_id FROM last)`;
|
|
310
|
+
const [page] = yield* sql `
|
|
311
|
+
WITH head AS (
|
|
312
|
+
SELECT event_sequence AS h FROM actor_generations g
|
|
313
|
+
WHERE ${eventsOf("g", key, source)}),
|
|
314
|
+
page AS MATERIALIZED (
|
|
315
|
+
SELECT s.subscriber_type, s.subscription, s.subscriber_id, s.due_at_ms, s.marked,
|
|
316
|
+
s.delivered, s.events
|
|
317
|
+
FROM actor_subscriptions s
|
|
318
|
+
WHERE ${sourceWhere("s", key, source)} AND s.active
|
|
319
|
+
AND (s.subscriber_type, s.subscription, s.subscriber_id) > (${after[0]}, ${after[1]}, ${after[2]})
|
|
320
|
+
ORDER BY s.subscriber_type, s.subscription, s.subscriber_id
|
|
321
|
+
LIMIT ${EXPANSION_PAGE}),
|
|
322
|
+
last AS (
|
|
323
|
+
SELECT subscriber_type, subscription, subscriber_id FROM page
|
|
324
|
+
ORDER BY subscriber_type DESC, subscription DESC, subscriber_id DESC LIMIT 1),
|
|
325
|
+
${leasable},
|
|
326
|
+
-- Leased only while still unclaimed in the version this updates, so a
|
|
327
|
+
-- row another runner claimed meanwhile is left to the marking below.
|
|
328
|
+
leased AS (
|
|
329
|
+
UPDATE actor_subscriptions s SET marked = greatest(s.marked, head.h),
|
|
330
|
+
due_at_ms = ${leaseEnd("s")}, attempts = s.attempts + 1
|
|
331
|
+
FROM leasable l, head
|
|
332
|
+
WHERE ${sourceWhere("s", key, source)} AND ${inPage}
|
|
333
|
+
AND s.subscriber_type = l.subscriber_type AND s.subscription = l.subscription
|
|
334
|
+
AND s.subscriber_id = l.subscriber_id AND s.due_at_ms IS NULL
|
|
335
|
+
RETURNING s.subscriber_type, s.subscription, s.subscriber_id, ${claimedRow}::text AS work),
|
|
336
|
+
marked AS (
|
|
337
|
+
UPDATE actor_subscriptions s SET marked = greatest(s.marked, head.h),
|
|
338
|
+
due_at_ms = coalesce(s.due_at_ms, ${now()})
|
|
339
|
+
FROM head
|
|
340
|
+
WHERE NOT EXISTS (SELECT 1 FROM leased x WHERE x.subscriber_type = s.subscriber_type
|
|
341
|
+
AND x.subscription = s.subscription AND x.subscriber_id = s.subscriber_id)
|
|
342
|
+
AND ${sourceWhere("s", key, source)} AND s.active
|
|
343
|
+
AND ${inPage} AND s.marked < head.h
|
|
344
|
+
-- A row due and unclaimed since its last settle reads through the
|
|
345
|
+
-- head when it is claimed, so it needs no write.
|
|
346
|
+
AND NOT (s.due_at_ms IS NOT NULL AND s.due_at_ms <= ${now()} AND s.attempts = 0)
|
|
347
|
+
AND (s.due_at_ms IS NOT NULL OR EXISTS (
|
|
348
|
+
SELECT 1 FROM actor_events e
|
|
349
|
+
WHERE ${eventsOf("e", key, source)}
|
|
350
|
+
AND e.sequence > s.delivered AND e.sequence <= head.h AND e.event = ANY(s.events)))
|
|
351
|
+
RETURNING 1)
|
|
352
|
+
SELECT (SELECT count(*) FROM page)::int AS rows,
|
|
353
|
+
(SELECT json_agg(work)::text FROM leased) AS leased,
|
|
354
|
+
(SELECT json_build_array(subscriber_type, subscription, subscriber_id)::text FROM last) AS last`;
|
|
355
|
+
if (page.leased !== null)
|
|
356
|
+
for (const work of yield* decodeStrings(page.leased))
|
|
357
|
+
yield* handoff.start(yield* decode(work));
|
|
358
|
+
if (page.rows < EXPANSION_PAGE || page.last === null)
|
|
359
|
+
break;
|
|
360
|
+
after = yield* decodeStrings(page.last);
|
|
361
|
+
}
|
|
362
|
+
yield* hooks.at("afterExpand", hookRequest(source, "$feed", row.intent_id));
|
|
363
|
+
yield* sql `DELETE FROM actor_outbox WHERE routing_key = ${key} AND intent_id = ${row.intent_id}
|
|
364
|
+
AND kind = 'feed' AND attempts = ${row.attempts}`;
|
|
365
|
+
yield* options.wake;
|
|
366
|
+
});
|
|
367
|
+
/** Delivers one subscription delivery from `source` and answers how it settled. */
|
|
368
|
+
const deliverOne = Effect.fnUntraced(function* (subscriber, handler, source, delivery, issuedAt, value) {
|
|
369
|
+
const envelope = { ...delivery, sourceType: source.actor, sourceId: source.id };
|
|
370
|
+
const commandId = yield* deliveryCommandId({
|
|
371
|
+
subscriber,
|
|
372
|
+
envelope,
|
|
373
|
+
issuedAt,
|
|
374
|
+
retryWindowMs: settings.retryWindowMs,
|
|
375
|
+
});
|
|
376
|
+
const request = Request.make({
|
|
377
|
+
ref: subscriber,
|
|
378
|
+
caller: System.make({ source: "subscription", ref: source }),
|
|
379
|
+
command: handler,
|
|
380
|
+
commandId,
|
|
381
|
+
payload: yield* encodePayload({ value }),
|
|
382
|
+
delivery: envelope,
|
|
383
|
+
});
|
|
384
|
+
return yield* options.deliver(request).pipe(Effect.result);
|
|
385
|
+
});
|
|
386
|
+
const failure = (outcome) => {
|
|
387
|
+
if (Result.isFailure(outcome))
|
|
388
|
+
return outcome.failure.reason._tag;
|
|
389
|
+
return Outcome.guards.Defect(outcome.success) ? String(outcome.success.cause) : undefined;
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* Registers one dynamic subscription change at its source with a framework
|
|
393
|
+
* statement, never a turn, so the source isn't activated. Holding the
|
|
394
|
+
* source's generation row `FOR SHARE` orders it against the emit path's
|
|
395
|
+
* `FOR UPDATE`, and a row changes only for a strictly newer epoch, so a
|
|
396
|
+
* rerun after a crash, or an older change delivered late, changes nothing.
|
|
397
|
+
* A cursor past the source's head is refused: any older row becomes a
|
|
398
|
+
* tombstone at this epoch and the subscriber receives `Rejected`.
|
|
399
|
+
*/
|
|
400
|
+
const register = Effect.fnUntraced(function* (row) {
|
|
401
|
+
const change = yield* Schema.decodeEffect(ControlPayload)(row.payload);
|
|
402
|
+
const subscriber = { tenant: row.tenant_id, actor: row.actor_type, id: row.actor_id };
|
|
403
|
+
const source = { tenant: row.tenant_id, actor: row.target_type, id: row.target_id };
|
|
404
|
+
const epoch = BigInt(change.epoch);
|
|
405
|
+
yield* hooks.at("afterClaim", hookRequest(source, "$control", row.intent_id));
|
|
406
|
+
const key = yield* placementKey(source);
|
|
407
|
+
const pk = sourceWhere("s", key, source);
|
|
408
|
+
const target = sql `${pk} AND s.subscriber_type = ${subscriber.actor}
|
|
409
|
+
AND s.subscription = ${row.command} AND s.subscriber_id = ${subscriber.id}`;
|
|
410
|
+
const rejected = yield* sql.withTransaction(Effect.gen(function* () {
|
|
411
|
+
yield* sql `INSERT INTO actor_generations (routing_key, tenant_id, actor_type, actor_id)
|
|
412
|
+
VALUES (${key}, ${source.tenant}, ${source.actor}, ${source.id}) ON CONFLICT DO NOTHING`;
|
|
413
|
+
const [generation] = yield* sql `
|
|
414
|
+
SELECT event_sequence::text AS head FROM actor_generations g
|
|
415
|
+
WHERE ${eventsOf("g", key, source)} FOR SHARE`;
|
|
416
|
+
const head = BigInt(generation.head);
|
|
417
|
+
const [existing] = yield* sql `
|
|
418
|
+
SELECT s.epoch::text AS epoch, s.active FROM actor_subscriptions s
|
|
419
|
+
WHERE ${target} FOR UPDATE`;
|
|
420
|
+
const refused = change.op === "subscribe" &&
|
|
421
|
+
change.start !== "now" &&
|
|
422
|
+
change.start !== "start" &&
|
|
423
|
+
BigInt(change.start) > head;
|
|
424
|
+
if (existing !== undefined && BigInt(existing.epoch) >= epoch)
|
|
425
|
+
return refused && BigInt(existing.epoch) === epoch && !existing.active;
|
|
426
|
+
const old = sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
|
|
427
|
+
WHERE ${target} FOR UPDATE`;
|
|
428
|
+
if (change.op === "remove" || refused) {
|
|
429
|
+
yield* changeRows(old, sql `INSERT INTO actor_subscriptions AS s (routing_key, tenant_id, source_type, source_id,
|
|
430
|
+
subscriber_type, subscription, subscriber_id, events, epoch, active, delivered, bucket)
|
|
431
|
+
VALUES (${key}, ${source.tenant}, ${source.actor}, ${source.id}, ${subscriber.actor},
|
|
432
|
+
${row.command}, ${subscriber.id}, ${textArray({ sql, values: change.events })}, ${epoch}, false,
|
|
433
|
+
${head}, ${Number(key >> 56n)})
|
|
434
|
+
ON CONFLICT (routing_key, tenant_id, source_type, source_id, subscriber_type, subscription, subscriber_id)
|
|
435
|
+
DO UPDATE SET epoch = EXCLUDED.epoch, active = false, due_at_ms = NULL, attempts = 0,
|
|
436
|
+
last_error = NULL, gap_at_ms = NULL, gap_through = NULL
|
|
437
|
+
RETURNING ${rowColumns({ sql, alias: "s" })}`);
|
|
438
|
+
return refused;
|
|
439
|
+
}
|
|
440
|
+
const delivered = Match.value(change.start).pipe(Match.when("now", () => head), Match.when("start", () => 0n), Match.orElse((cursor) => BigInt(cursor)));
|
|
441
|
+
yield* changeRows(old, sql `INSERT INTO actor_subscriptions AS s (routing_key, tenant_id, source_type, source_id,
|
|
442
|
+
subscriber_type, subscription, subscriber_id, events, epoch, active, delivered, bucket,
|
|
443
|
+
due_at_ms)
|
|
444
|
+
SELECT ${key}, ${source.tenant}, ${source.actor}, ${source.id}, ${subscriber.actor},
|
|
445
|
+
${row.command}, ${subscriber.id}, x.events, ${epoch}, true, ${delivered},
|
|
446
|
+
${Number(key >> 56n)},
|
|
447
|
+
CASE WHEN EXISTS (SELECT 1 FROM actor_events e
|
|
448
|
+
WHERE ${eventsOf("e", key, source)}
|
|
449
|
+
AND e.sequence > ${delivered} AND e.sequence <= ${head} AND e.event = ANY(x.events))
|
|
450
|
+
THEN ${now()} END
|
|
451
|
+
FROM (SELECT ${textArray({ sql, values: change.events })} AS events) AS x
|
|
452
|
+
ON CONFLICT (routing_key, tenant_id, source_type, source_id, subscriber_type, subscription, subscriber_id)
|
|
453
|
+
DO UPDATE SET events = EXCLUDED.events, epoch = EXCLUDED.epoch, active = true,
|
|
454
|
+
delivered = EXCLUDED.delivered, marked = 0, due_at_ms = EXCLUDED.due_at_ms, attempts = 0,
|
|
455
|
+
last_error = NULL, gap_at_ms = NULL, gap_through = NULL
|
|
456
|
+
RETURNING ${rowColumns({ sql, alias: "s" })}`);
|
|
457
|
+
return false;
|
|
458
|
+
}));
|
|
459
|
+
const claim = sql `routing_key = ${BigInt(row.routing_key)} AND intent_id = ${row.intent_id}
|
|
460
|
+
AND kind = 'control' AND due_at_ms = ${BigInt(row.claimed_until)}`;
|
|
461
|
+
if (rejected) {
|
|
462
|
+
const outcome = yield* deliverOne(subscriber, yield* handlerOf(subscriber.actor, row.command), source, {
|
|
463
|
+
subscription: row.command,
|
|
464
|
+
epoch: change.epoch,
|
|
465
|
+
kind: "rejected",
|
|
466
|
+
position: change.start,
|
|
467
|
+
}, Number(row.scheduled_at_ms), WireRejected.make({
|
|
468
|
+
subscription: row.command,
|
|
469
|
+
source,
|
|
470
|
+
reason: "UnknownCursor",
|
|
471
|
+
cursor: change.start,
|
|
472
|
+
}));
|
|
473
|
+
const failed = failure(outcome);
|
|
474
|
+
if (failed !== undefined) {
|
|
475
|
+
yield* sql `UPDATE actor_outbox SET last_error = ${failed},
|
|
476
|
+
due_at_ms = ${(yield* databaseTime) + backoff(row.attempts, settings)}
|
|
477
|
+
WHERE ${claim}`;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
yield* hooks.at("beforeOutboxDelete", hookRequest(source, "$control", row.intent_id));
|
|
482
|
+
yield* sql `DELETE FROM actor_outbox WHERE ${claim}`;
|
|
483
|
+
yield* options.wake;
|
|
484
|
+
});
|
|
485
|
+
const handlerOf = Effect.fnUntraced(function* (subscriberType, tag) {
|
|
486
|
+
const found = options
|
|
487
|
+
.local()
|
|
488
|
+
.find((local) => local.subscriberType === subscriberType && local.subscription.tag === tag);
|
|
489
|
+
if (found === undefined)
|
|
490
|
+
return yield* SubscriptionFailure.make({
|
|
491
|
+
message: `Subscription ${subscriberType}.${tag} is not declared on this runner`,
|
|
492
|
+
});
|
|
493
|
+
return found.subscription.handler;
|
|
494
|
+
});
|
|
495
|
+
/**
|
|
496
|
+
* Delivers one claimed subscription row: up to `batch` matching events
|
|
497
|
+
* after `delivered`, one command at a time in cursor order, each waiting
|
|
498
|
+
* for the previous one's outcome, renewing the claim after each. It then
|
|
499
|
+
* settles `delivered` to the position it scanned through, fenced on the
|
|
500
|
+
* claim and the epoch. A failed delivery settles the progress before it and
|
|
501
|
+
* backs the row off; later events wait behind it.
|
|
502
|
+
*/
|
|
503
|
+
const deliverRow = Effect.fnUntraced(function* (row, claim) {
|
|
504
|
+
const local = options
|
|
505
|
+
.local()
|
|
506
|
+
.find(({ subscriberType, subscription }) => subscriberType === row.subscriber_type && subscription.tag === row.subscription);
|
|
507
|
+
const source = { tenant: row.tenant_id, actor: row.source_type, id: row.source_id };
|
|
508
|
+
const key = BigInt(row.routing_key);
|
|
509
|
+
const pk = sql `${sourceWhere("s", key, source)}
|
|
510
|
+
AND s.subscriber_type = ${row.subscriber_type} AND s.subscription = ${row.subscription}
|
|
511
|
+
AND s.subscriber_id = ${row.subscriber_id}`;
|
|
512
|
+
const held = () => sql `${pk} AND s.epoch = ${row.epoch} AND s.due_at_ms = ${claim.lease}`;
|
|
513
|
+
const name = `${row.subscriber_type}.${row.subscription}`;
|
|
514
|
+
const lostClaim = (step) => Effect.logWarning(`Subscription ${step} lost its claim`).pipe(Effect.annotateLogs({ subscription: name }));
|
|
515
|
+
const at = (point) => hooks.at(point, hookRequest(source, row.subscription, row.subscriber_id));
|
|
516
|
+
yield* at("afterClaim");
|
|
517
|
+
const backOff = (progress, cause) => Effect.gen(function* () {
|
|
518
|
+
yield* Effect.logWarning("Subscription delivery failed; retrying with backoff").pipe(Effect.annotateLogs({
|
|
519
|
+
subscription: name,
|
|
520
|
+
source: `${source.actor}/${source.id}`,
|
|
521
|
+
tenant: source.tenant,
|
|
522
|
+
cause,
|
|
523
|
+
}));
|
|
524
|
+
yield* sql `UPDATE actor_subscriptions s SET delivered = greatest(s.delivered, ${progress}),
|
|
525
|
+
last_error = ${cause},
|
|
526
|
+
due_at_ms = ${(yield* databaseTime) + backoff(row.attempts, settings)}
|
|
527
|
+
WHERE ${held()}`;
|
|
528
|
+
yield* count(Metrics.relayRetried, { kind: "subscription" }, 1);
|
|
529
|
+
});
|
|
530
|
+
if (local === undefined)
|
|
531
|
+
return yield* backOff(BigInt(row.delivered), "Undeclared subscription");
|
|
532
|
+
const { subscription } = local;
|
|
533
|
+
const batch = yield* sql `SELECT (SELECT event_sequence::text FROM actor_generations g
|
|
534
|
+
WHERE ${eventsOf("g", key, source)}) AS head,
|
|
535
|
+
(SELECT min(o.sequence)::text FROM actor_events o
|
|
536
|
+
WHERE ${eventsOf("o", key, source)}) AS oldest,
|
|
537
|
+
e.sequence::text AS sequence, e.event, e.command_id, e.value, e.payload_version,
|
|
538
|
+
e.emitted_at_ms::text AS emitted_at_ms
|
|
539
|
+
FROM (VALUES (1)) AS one (x)
|
|
540
|
+
LEFT JOIN LATERAL (
|
|
541
|
+
SELECT sequence, event, command_id, value, payload_version, emitted_at_ms FROM actor_events e
|
|
542
|
+
WHERE ${eventsOf("e", key, source)}
|
|
543
|
+
AND e.sequence > ${BigInt(row.delivered)}
|
|
544
|
+
AND e.event = ANY(${textArray({ sql, values: subscription.events })})
|
|
545
|
+
ORDER BY e.sequence LIMIT ${settings.batch}) e ON true`;
|
|
546
|
+
const head = BigInt(batch[0].head);
|
|
547
|
+
const oldest = batch[0].oldest;
|
|
548
|
+
const events = batch.filter((event) => event.sequence !== null);
|
|
549
|
+
let progress = BigInt(row.delivered);
|
|
550
|
+
let continuous = true;
|
|
551
|
+
let uncountedGaps = 0;
|
|
552
|
+
const settleOutcome = Effect.fnUntraced(function* (outcome, position) {
|
|
553
|
+
const failed = failure(outcome);
|
|
554
|
+
if (failed !== undefined) {
|
|
555
|
+
yield* backOff(progress, failed);
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
const settled = outcome.success;
|
|
559
|
+
if (Outcome.guards.Acknowledged(settled) &&
|
|
560
|
+
(settled.reason === "Stale" || settled.reason === "Unsubscribed")) {
|
|
561
|
+
yield* deleteRow(row, key, source);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (Outcome.guards.Acknowledged(settled) && settled.reason === "NotCreated")
|
|
565
|
+
yield* Effect.logInfo("Subscription event skipped: the subscriber is not created").pipe(Effect.annotateLogs({
|
|
566
|
+
subscription: name,
|
|
567
|
+
cursor: String(position),
|
|
568
|
+
}));
|
|
569
|
+
progress = position;
|
|
570
|
+
return yield* renew;
|
|
571
|
+
});
|
|
572
|
+
const renew = Effect.gen(function* () {
|
|
573
|
+
const renewed = yield* sql `UPDATE actor_subscriptions s
|
|
574
|
+
SET due_at_ms = greatest(s.due_at_ms, ${(yield* databaseTime) + settings.claimLeaseMs()})
|
|
575
|
+
WHERE ${held()} RETURNING s.due_at_ms::text AS due_at_ms`;
|
|
576
|
+
if (renewed.length === 0)
|
|
577
|
+
return yield* lostClaim("delivery").pipe(Effect.as(false));
|
|
578
|
+
claim.lease = BigInt(renewed[0].due_at_ms);
|
|
579
|
+
return true;
|
|
580
|
+
});
|
|
581
|
+
const planted = row.gap_through === null ? undefined : BigInt(row.gap_through);
|
|
582
|
+
if (progress < head &&
|
|
583
|
+
((planted !== undefined && planted > progress) ||
|
|
584
|
+
oldest === null ||
|
|
585
|
+
BigInt(oldest) > progress + 1n)) {
|
|
586
|
+
const resumeAfter = planted !== undefined && planted > progress
|
|
587
|
+
? planted
|
|
588
|
+
: oldest === null
|
|
589
|
+
? head
|
|
590
|
+
: BigInt(oldest) - 1n;
|
|
591
|
+
const [gap] = yield* sql `UPDATE actor_subscriptions s
|
|
592
|
+
SET gap_at_ms = coalesce(s.gap_at_ms, ${yield* databaseTime}),
|
|
593
|
+
gap_through = coalesce(s.gap_through, ${resumeAfter})
|
|
594
|
+
WHERE ${held()}
|
|
595
|
+
RETURNING s.gap_at_ms::text AS at, s.gap_through::text AS through`;
|
|
596
|
+
if (gap === undefined)
|
|
597
|
+
return yield* lostClaim("delivery");
|
|
598
|
+
const through = BigInt(gap.through);
|
|
599
|
+
continuous = through === resumeAfter;
|
|
600
|
+
if (subscription.routed === "id") {
|
|
601
|
+
yield* Effect.logWarning("Subscription gap without a recipient").pipe(Effect.annotateLogs({
|
|
602
|
+
subscription: name,
|
|
603
|
+
source: `${source.actor}/${source.id}`,
|
|
604
|
+
after: String(progress),
|
|
605
|
+
resumeAfter: String(through),
|
|
606
|
+
}));
|
|
607
|
+
uncountedGaps = 1;
|
|
608
|
+
progress = through;
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const outcome = yield* deliverOne({
|
|
612
|
+
tenant: source.tenant,
|
|
613
|
+
actor: row.subscriber_type,
|
|
614
|
+
id: subscription.routed === "singleton" ? "singleton" : row.subscriber_id,
|
|
615
|
+
}, subscription.handler, source, {
|
|
616
|
+
subscription: row.subscription,
|
|
617
|
+
epoch: row.epoch,
|
|
618
|
+
kind: "gap",
|
|
619
|
+
position: String(through),
|
|
620
|
+
}, Number(gap.at), WireGap.make({
|
|
621
|
+
subscription: row.subscription,
|
|
622
|
+
source,
|
|
623
|
+
after: String(progress),
|
|
624
|
+
resumeAfter: String(through),
|
|
625
|
+
}));
|
|
626
|
+
if (!(yield* settleOutcome(outcome, through)))
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
for (const event of continuous ? events.filter((e) => BigInt(e.sequence) > progress) : []) {
|
|
631
|
+
const upcast = yield* subscription
|
|
632
|
+
.upcast(event.event, event.payload_version, decompress(event.value))
|
|
633
|
+
.pipe(Effect.result);
|
|
634
|
+
if (Result.isFailure(upcast))
|
|
635
|
+
return yield* backOff(progress, `Event does not decode: ${upcast.failure.message}`);
|
|
636
|
+
const value = upcast.success;
|
|
637
|
+
const subscriberId = subscription.routed === undefined
|
|
638
|
+
? Result.succeed(row.subscriber_id)
|
|
639
|
+
: yield* subscription.route(event.event, value, source).pipe(Effect.result);
|
|
640
|
+
if (Result.isFailure(subscriberId))
|
|
641
|
+
return yield* backOff(progress, `Route failed: ${String(subscriberId.failure)}`);
|
|
642
|
+
if (subscriberId.success.length === 0)
|
|
643
|
+
return yield* backOff(progress, "Route failed: route returned an empty id");
|
|
644
|
+
const subscriber = {
|
|
645
|
+
tenant: source.tenant,
|
|
646
|
+
actor: row.subscriber_type,
|
|
647
|
+
id: subscriberId.success,
|
|
648
|
+
};
|
|
649
|
+
const outcome = yield* deliverOne(subscriber, subscription.handler, source, {
|
|
650
|
+
subscription: row.subscription,
|
|
651
|
+
epoch: row.epoch,
|
|
652
|
+
kind: "event",
|
|
653
|
+
position: event.sequence,
|
|
654
|
+
}, Number(event.emitted_at_ms), WireEvent.make({
|
|
655
|
+
subscription: row.subscription,
|
|
656
|
+
source,
|
|
657
|
+
cursor: event.sequence,
|
|
658
|
+
event: yield* decodeJson(value),
|
|
659
|
+
commandId: event.command_id,
|
|
660
|
+
timestamp: DateTime.formatIso(DateTime.makeUnsafe(Number(event.emitted_at_ms))),
|
|
661
|
+
}));
|
|
662
|
+
if (!(yield* settleOutcome(outcome, BigInt(event.sequence))))
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const delivered = continuous && events.length < settings.batch ? (head > progress ? head : progress) : progress;
|
|
666
|
+
yield* at("beforeSettle");
|
|
667
|
+
const [pending] = yield* sql `SELECT EXISTS (
|
|
668
|
+
SELECT 1 FROM actor_events e
|
|
669
|
+
WHERE ${eventsOf("e", key, source)}
|
|
670
|
+
AND e.sequence > ${delivered} AND e.event = ANY(${textArray({ sql, values: subscription.events })})
|
|
671
|
+
) AS due`;
|
|
672
|
+
yield* at("afterSettleSnapshot");
|
|
673
|
+
const declared = textArray({ sql, values: subscription.events });
|
|
674
|
+
const settled = yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s WHERE ${held()} FOR UPDATE`, sql `UPDATE actor_subscriptions s SET delivered = ${delivered}, attempts = 0, last_error = NULL,
|
|
675
|
+
gaps = s.gaps + ${uncountedGaps}, gap_at_ms = NULL, gap_through = NULL,
|
|
676
|
+
due_at_ms = CASE WHEN ${pending.due} OR s.marked > ${delivered} THEN ${now()} END,
|
|
677
|
+
events = CASE WHEN s.events @> ${declared} THEN s.events
|
|
678
|
+
ELSE ARRAY(SELECT DISTINCT x FROM unnest(s.events || ${declared}) AS u(x) ORDER BY x) END
|
|
679
|
+
WHERE ${held()}
|
|
680
|
+
RETURNING ${rowColumns({ sql, alias: "s" })}`);
|
|
681
|
+
if (settled === 0)
|
|
682
|
+
return yield* lostClaim("settle");
|
|
683
|
+
yield* count(Metrics.relayDelivered, { kind: "subscription" }, 1);
|
|
684
|
+
yield* count(Metrics.undeliverableGaps, { subscriber_type: row.subscriber_type, subscription: row.subscription }, uncountedGaps);
|
|
685
|
+
});
|
|
686
|
+
const release = (work, claim) => Effect.gen(function* () {
|
|
687
|
+
const at = yield* databaseTime;
|
|
688
|
+
if (work.kind !== "subscription")
|
|
689
|
+
return yield* sql `UPDATE actor_outbox SET due_at_ms = ${at}
|
|
690
|
+
WHERE routing_key = ${BigInt(work.routing_key)} AND intent_id = ${work.intent_id}
|
|
691
|
+
AND kind = ${work.kind} AND due_at_ms = ${claim.lease}`;
|
|
692
|
+
return yield* sql `UPDATE actor_subscriptions SET due_at_ms = ${at}
|
|
693
|
+
WHERE routing_key = ${BigInt(work.routing_key)} AND tenant_id = ${work.tenant_id}
|
|
694
|
+
AND source_type = ${work.source_type} AND source_id = ${work.source_id}
|
|
695
|
+
AND subscriber_type = ${work.subscriber_type} AND subscription = ${work.subscription}
|
|
696
|
+
AND subscriber_id = ${work.subscriber_id} AND epoch = ${work.epoch}
|
|
697
|
+
AND due_at_ms = ${claim.lease}`;
|
|
698
|
+
}).pipe(Effect.ignore);
|
|
699
|
+
/**
|
|
700
|
+
* Widens the rows of one dynamic subscription to its declaration's event
|
|
701
|
+
* classes, 1,000 rows per transaction, with their tag summary. It keeps every cursor and epoch, never
|
|
702
|
+
* narrows a row, and is idempotent, so every runner of a rolling deploy may
|
|
703
|
+
* run it. A widened row with an event of an added class after its
|
|
704
|
+
* `delivered` becomes due. Rows claimed right now are skipped; their settle
|
|
705
|
+
* widens them.
|
|
706
|
+
*/
|
|
707
|
+
const widen = Effect.fnUntraced(function* (subscriberType, declared) {
|
|
708
|
+
const tags = textArray({ sql, values: declared.events });
|
|
709
|
+
for (;;) {
|
|
710
|
+
const widened = yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
|
|
711
|
+
WHERE s.subscriber_type = ${subscriberType} AND s.subscription = ${declared.tag}
|
|
712
|
+
AND s.active AND NOT s.events @> ${tags}
|
|
713
|
+
LIMIT ${EXPANSION_PAGE}
|
|
714
|
+
FOR UPDATE SKIP LOCKED`, sql `UPDATE actor_subscriptions s
|
|
715
|
+
SET events = ARRAY(SELECT DISTINCT u.x FROM unnest(s.events || ${tags}) AS u(x) ORDER BY u.x),
|
|
716
|
+
due_at_ms = CASE WHEN s.due_at_ms IS NULL AND EXISTS (
|
|
717
|
+
SELECT 1 FROM actor_events e
|
|
718
|
+
WHERE e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
|
|
719
|
+
AND e.actor_type = s.source_type AND e.actor_id = s.source_id
|
|
720
|
+
AND e.sequence > s.delivered AND e.event = ANY(${tags})
|
|
721
|
+
AND NOT e.event = ANY(o.events))
|
|
722
|
+
THEN ${now()} ELSE s.due_at_ms END
|
|
723
|
+
FROM old_rows o
|
|
724
|
+
WHERE s.routing_key = o.routing_key AND s.tenant_id = o.tenant_id
|
|
725
|
+
AND s.source_type = o.source_type AND s.source_id = o.source_id
|
|
726
|
+
AND s.subscriber_type = o.subscriber_type AND s.subscription = o.subscription
|
|
727
|
+
AND s.subscriber_id = o.subscriber_id
|
|
728
|
+
RETURNING ${rowColumns({ sql, alias: "s" })}`);
|
|
729
|
+
if (widened < EXPANSION_PAGE)
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
/**
|
|
734
|
+
* Deletes rows of `subscriberType` whose subscription this runner doesn't
|
|
735
|
+
* declare once they have been due for a day. Such a row is never claimed
|
|
736
|
+
* here, so a rolling deploy's newer runners get a day to take it first.
|
|
737
|
+
*/
|
|
738
|
+
const cleanupRemoved = Effect.fnUntraced(function* (subscriberType, declared) {
|
|
739
|
+
const cutoff = (yield* databaseTime) - REMOVED_AFTER_MS;
|
|
740
|
+
return yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
|
|
741
|
+
WHERE s.subscriber_type = ${subscriberType}
|
|
742
|
+
AND NOT s.subscription = ANY(${textArray({ sql, values: declared })})
|
|
743
|
+
AND s.due_at_ms < ${cutoff}
|
|
744
|
+
FOR UPDATE`, deleteLocked);
|
|
745
|
+
});
|
|
746
|
+
const run = (work, handoff) => {
|
|
747
|
+
const claim = { lease: BigInt(work.claimed_until) };
|
|
748
|
+
const running = work.kind === "subscription"
|
|
749
|
+
? deliverRow(work, claim).pipe(Effect.withSpan(SpanNames.relaySubscription, {
|
|
750
|
+
kind: "producer",
|
|
751
|
+
attributes: {
|
|
752
|
+
"actor.type": work.subscriber_type,
|
|
753
|
+
"actor.tenant": work.tenant_id,
|
|
754
|
+
"subscription.name": work.subscription,
|
|
755
|
+
"subscription.source_type": work.source_type,
|
|
756
|
+
"relay.attempt": work.attempts,
|
|
757
|
+
},
|
|
758
|
+
}, { captureStackTrace: false }))
|
|
759
|
+
: Match.value(work.kind).pipe(Match.when("feed", () => expand(work, handoff)), Match.orElse(() => register(work)));
|
|
760
|
+
return running.pipe(Effect.onInterrupt(() => release(work, claim)));
|
|
761
|
+
};
|
|
762
|
+
return { claim, decode, run, widen, cleanupRemoved };
|
|
763
|
+
});
|