@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,896 @@
|
|
|
1
|
+
import { Cause, Clock, Deferred, Effect, Exit, Option, Queue, Schema, Semaphore, Stream, } from "effect";
|
|
2
|
+
import { inTenant } from "../database/tenancy.js";
|
|
3
|
+
import { Entity } from "effect/cluster";
|
|
4
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
5
|
+
import { ActorError, ActorUnavailable, NotCreated, RunnerAtCapacity, SessionEnded, } from "../../errors/actor.js";
|
|
6
|
+
import { ConnectionPhase, } from "../members.js";
|
|
7
|
+
import { Caller, principal } from "../../identity/caller.js";
|
|
8
|
+
import { replayEvents } from "../events/replay.js";
|
|
9
|
+
import { compress, decompress } from "../storage/codec.js";
|
|
10
|
+
import { FrameworkClock } from "../turn/admission.js";
|
|
11
|
+
import { actorRow, emptyActivationCache, forget as forgetGeneration, heldGeneration, } from "../storage/generation.js";
|
|
12
|
+
import { Committed, FEED_MEMBER, FeedFrame, HolderItem, isWatchMember, watchedQuery, watchMember, } from "./protocol.js";
|
|
13
|
+
import { streamSubscriptions } from "./streams.js";
|
|
14
|
+
import { HolderUnreachable } from "./transport.js";
|
|
15
|
+
import { WatchTap } from "./watch.js";
|
|
16
|
+
/** Encoded bytes one connection's session may hold. */
|
|
17
|
+
export const MAX_SESSION_BYTES = 16_384;
|
|
18
|
+
/** Open connections one actor may have per connection member. */
|
|
19
|
+
const MAX_MEMBER_CONNECTIONS = 10_000;
|
|
20
|
+
/** Events one `read.follow` page reads at most. */
|
|
21
|
+
const FOLLOW_PAGE = 1_000;
|
|
22
|
+
/** Progress frames one actor accepts per second, with a burst of as many. */
|
|
23
|
+
const PROGRESS_PER_SECOND = 20;
|
|
24
|
+
/** How long a job check's answer stands before a later frame reads the row again. */
|
|
25
|
+
const JOB_CHECK_MS = 5_000;
|
|
26
|
+
/** How long after a failed job check the next one waits. */
|
|
27
|
+
const JOB_CHECK_RETRY_MS = 1_000;
|
|
28
|
+
/** Clock skew allowed between the executor's lease deadline and the owner's clock. */
|
|
29
|
+
const PROGRESS_SKEW_MS = 1_000;
|
|
30
|
+
/** Progress entries one `read.progress` subscription buffers; the oldest goes first. */
|
|
31
|
+
const STREAM_PROGRESS_BUFFER = 16;
|
|
32
|
+
/** How long a progress record stays after its last frame, check, or close. */
|
|
33
|
+
const PROGRESS_RECORD_MS = 60_000;
|
|
34
|
+
const closedJob = (at) => ({
|
|
35
|
+
open: false,
|
|
36
|
+
checkedAt: at,
|
|
37
|
+
touchedAt: at,
|
|
38
|
+
attempt: 0,
|
|
39
|
+
seq: 0,
|
|
40
|
+
principal: Option.none(),
|
|
41
|
+
payload: "",
|
|
42
|
+
holders: new Set(),
|
|
43
|
+
});
|
|
44
|
+
const samePrincipal = (a, b) => Option.isSome(a) && Option.isSome(b) && a.value.subject === b.value.subject;
|
|
45
|
+
const utf8 = new TextEncoder();
|
|
46
|
+
/** A session is stored inside its codec's `{"value":…}` envelope, which the limit does not count. */
|
|
47
|
+
const SESSION_ENVELOPE_BYTES = utf8.encode(JSON.stringify({ value: null })).byteLength - utf8.encode("null").byteLength;
|
|
48
|
+
const encodeCaller = Schema.encodeEffect(Schema.fromJsonString(Caller));
|
|
49
|
+
const decodeCaller = Schema.decodeEffect(Schema.fromJsonString(Caller));
|
|
50
|
+
const emptyResult = {
|
|
51
|
+
session: undefined,
|
|
52
|
+
changed: false,
|
|
53
|
+
sends: [],
|
|
54
|
+
broadcasts: [],
|
|
55
|
+
close: false,
|
|
56
|
+
};
|
|
57
|
+
const ended = (cause, resync) => SessionEnded.make({ cause, resync });
|
|
58
|
+
/** Answers a database failure as `ActorUnavailable`, so the caller retries. */
|
|
59
|
+
const sqlUnavailable = (cause) => Effect.fail(ActorError.make({ reason: ActorUnavailable.make({ cause }) }));
|
|
60
|
+
const unavailable = (message) => ActorError.make({ reason: ActorUnavailable.make({ cause: new Error(message) }) });
|
|
61
|
+
/**
|
|
62
|
+
* The owner side of connections for one actor type: the activation registry
|
|
63
|
+
* shared with commands, the fenced `actor_connections` writes, and ordered,
|
|
64
|
+
* post-commit delivery of frames to every holder.
|
|
65
|
+
*
|
|
66
|
+
* Ordering and safety: every holder learns this owner before any of its
|
|
67
|
+
* broadcasts, so it can resync if the owner dies, and a holder that cannot be
|
|
68
|
+
* reached is excluded, its rows dropped, so it ends those connections itself.
|
|
69
|
+
* Dropping a generation's state means whatever runs next acquires a new one,
|
|
70
|
+
* reloads the rows, and numbers each holder's messages from 1 again, which a
|
|
71
|
+
* holder requires of every new generation; a clean end tells holders not to
|
|
72
|
+
* resync. A flush that read an older head than a concurrent one never moves the
|
|
73
|
+
* cursor back. Ends run on their own fibers so one slow handler never holds up the
|
|
74
|
+
* rest. A handler defect closes only its connection.
|
|
75
|
+
*
|
|
76
|
+
* Connections: feeds are framework connections, so an actor type with feeds loads
|
|
77
|
+
* its rows like one with members, and a feed has no handler, so its open only
|
|
78
|
+
* inserts the row and fixes its baseline and its close only deletes the row. A
|
|
79
|
+
* feed's holder rereads events itself once the new owner answers, and a feed
|
|
80
|
+
* serves the current shape even under a `writeVersion`. A connection's lock lives
|
|
81
|
+
* only as long as its row. A retried open cannot know whether its opening frames
|
|
82
|
+
* reached the holder. The owner runs frames only from the connection's stored
|
|
83
|
+
* holder, and a frame or subscription that reaches it past its authorization bound
|
|
84
|
+
* never runs.
|
|
85
|
+
*
|
|
86
|
+
* Subscriptions: followers wait on `advanced`, and a new head wakes every one. A
|
|
87
|
+
* replay reads pages from where the previous stopped up to the head, which only
|
|
88
|
+
* moves after a commit, so nothing is skipped or read twice. The subscription
|
|
89
|
+
* limit is checked and taken in one step so concurrent subscriptions cannot all
|
|
90
|
+
* pass it. A hook that hangs is retried at the next tick while the bound still
|
|
91
|
+
* ends the session, and an answer past the bound never extends it. A stream
|
|
92
|
+
* subscriber's live progress is a sliding buffer, so a slow reader loses the
|
|
93
|
+
* oldest.
|
|
94
|
+
*
|
|
95
|
+
* Progress frames: a route or settle that committed while the job check read
|
|
96
|
+
* keeps the job closed. A row that has not counted an attempt yet does not
|
|
97
|
+
* prove a frame open. Every frame of a job shares one activation record so its
|
|
98
|
+
* order and holders stay whole, and a record decides after a permit wait, never a
|
|
99
|
+
* copy taken before it. A record nothing touched for a while goes, and a late
|
|
100
|
+
* frame runs the job check again.
|
|
101
|
+
*/
|
|
102
|
+
export const activationOwner = ({ registration, transport, authorize, clock, role, }) => {
|
|
103
|
+
const activations = new Map();
|
|
104
|
+
const hasConnections = registration.connections.size > 0 ||
|
|
105
|
+
registration.feeds.size > 0 ||
|
|
106
|
+
registration.watches.size > 0;
|
|
107
|
+
const hasStreams = registration.streams.size > 0;
|
|
108
|
+
const wanted = new Set([
|
|
109
|
+
...[...registration.connections.values()].flatMap((member) => [
|
|
110
|
+
...(member.progress?.jobs ?? []),
|
|
111
|
+
]),
|
|
112
|
+
...[...registration.streams.values()].flatMap((member) => [...member.progress]),
|
|
113
|
+
]);
|
|
114
|
+
const hasProgress = wanted.size > 0;
|
|
115
|
+
const now = Effect.map(Clock.currentTimeMillis, (millis) => millis + clock.offsetMillis());
|
|
116
|
+
const advance = (activation, head) => {
|
|
117
|
+
if (BigInt(head) <= BigInt(activation.head))
|
|
118
|
+
return;
|
|
119
|
+
activation.head = head;
|
|
120
|
+
const previous = activation.advanced;
|
|
121
|
+
activation.advanced = Deferred.makeUnsafe();
|
|
122
|
+
Deferred.doneUnsafe(previous, Exit.void);
|
|
123
|
+
};
|
|
124
|
+
const encodeFeedFrame = Schema.encodeEffect(Schema.fromJsonString(FeedFrame));
|
|
125
|
+
const encodeCommitted = Schema.encodeEffect(Schema.fromJsonString(Committed));
|
|
126
|
+
/**
|
|
127
|
+
* The frame every watch of the actor receives after a commit, one broadcast
|
|
128
|
+
* per watchable query so each reaches its own connections. A commit that
|
|
129
|
+
* wrote nothing a query could have read sends none.
|
|
130
|
+
*/
|
|
131
|
+
const watchBroadcasts = (activation, writes, version) => Effect.gen(function* () {
|
|
132
|
+
const nothing = !writes.state &&
|
|
133
|
+
writes.events.length === 0 &&
|
|
134
|
+
writes.tables.length === 0 &&
|
|
135
|
+
writes.blobs.length === 0;
|
|
136
|
+
if (nothing || registration.watches.size === 0 || (yield* WatchTap).dropsCommitted())
|
|
137
|
+
return [];
|
|
138
|
+
if (![...(activation.rows?.values() ?? [])].some((row) => isWatchMember(row.member)))
|
|
139
|
+
return [];
|
|
140
|
+
const frame = yield* encodeCommitted({ version, writes }).pipe(Effect.orDie);
|
|
141
|
+
return [...registration.watches].map((query) => ({
|
|
142
|
+
member: watchMember(query),
|
|
143
|
+
frame,
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
/** A committed turn's feed events, broadcast to every open feed of the actor. */
|
|
147
|
+
const feedBroadcasts = (committed) => Effect.forEach(committed.events.flatMap((event, index) => registration.feeds.has(event.tag)
|
|
148
|
+
? [{ event, cursor: String(BigInt(committed.after) + BigInt(index) + 1n) }]
|
|
149
|
+
: []), ({ event, cursor }) => registration.upcastEvent(event.tag, event.version, event.value).pipe(Effect.flatMap((value) => encodeFeedFrame({
|
|
150
|
+
tag: event.tag,
|
|
151
|
+
value,
|
|
152
|
+
commandId: committed.commandId,
|
|
153
|
+
timestampMs: committed.emittedAtMs,
|
|
154
|
+
}).pipe(Effect.orDie)), Effect.map((frame) => ({ member: FEED_MEMBER, frame, event: cursor }))));
|
|
155
|
+
const enter = (entityId, ref, key) => Effect.acquireRelease(Effect.sync(() => {
|
|
156
|
+
const found = activations.get(entityId);
|
|
157
|
+
if (found !== undefined) {
|
|
158
|
+
found.presence += 1;
|
|
159
|
+
return found;
|
|
160
|
+
}
|
|
161
|
+
const created = {
|
|
162
|
+
ref,
|
|
163
|
+
key,
|
|
164
|
+
cache: emptyActivationCache(),
|
|
165
|
+
presence: 1,
|
|
166
|
+
rows: undefined,
|
|
167
|
+
head: "0",
|
|
168
|
+
advanced: Deferred.makeUnsafe(),
|
|
169
|
+
streams: new Set(),
|
|
170
|
+
progress: new Map(),
|
|
171
|
+
checking: new Set(),
|
|
172
|
+
checkFailed: new Map(),
|
|
173
|
+
listeners: new Set(),
|
|
174
|
+
progressTokens: PROGRESS_PER_SECOND,
|
|
175
|
+
progressRefilledAt: 0,
|
|
176
|
+
progressSweptAt: 0,
|
|
177
|
+
through: "0",
|
|
178
|
+
channels: new Map(),
|
|
179
|
+
flush: Semaphore.makeUnsafe(1),
|
|
180
|
+
loading: Semaphore.makeUnsafe(1),
|
|
181
|
+
locks: new Map(),
|
|
182
|
+
opened: new Set(),
|
|
183
|
+
keptAwake: undefined,
|
|
184
|
+
acquiring: Semaphore.makeUnsafe(1),
|
|
185
|
+
};
|
|
186
|
+
activations.set(entityId, created);
|
|
187
|
+
return created;
|
|
188
|
+
}), (activation) => Effect.gen(function* () {
|
|
189
|
+
activation.presence -= 1;
|
|
190
|
+
if (activation.presence > 0)
|
|
191
|
+
return;
|
|
192
|
+
activations.delete(entityId);
|
|
193
|
+
yield* seal(activation);
|
|
194
|
+
}));
|
|
195
|
+
const channelOf = (activation, holder, epoch) => {
|
|
196
|
+
const name = `${holder}|${epoch}`;
|
|
197
|
+
const found = activation.channels.get(name);
|
|
198
|
+
if (found !== undefined)
|
|
199
|
+
return found;
|
|
200
|
+
const created = { holder, epoch, seq: 0 };
|
|
201
|
+
activation.channels.set(name, created);
|
|
202
|
+
return created;
|
|
203
|
+
};
|
|
204
|
+
const dropRows = (activation, ids) => Effect.gen(function* () {
|
|
205
|
+
if (ids.length === 0)
|
|
206
|
+
return;
|
|
207
|
+
const sql = yield* SqlClient.SqlClient;
|
|
208
|
+
const actor = actorRow({ sql, actor: activation });
|
|
209
|
+
yield* sql `DELETE FROM actor_connections WHERE ${actor} AND connection_id IN ${sql.in(ids)}`;
|
|
210
|
+
for (const id of ids)
|
|
211
|
+
activation.rows?.delete(id);
|
|
212
|
+
const holder = activation.keptAwake;
|
|
213
|
+
if (holder !== undefined && (activation.rows?.size ?? 0) === 0) {
|
|
214
|
+
activation.keptAwake = undefined;
|
|
215
|
+
yield* Entity.keepAlive(false).pipe(Effect.provideContext(holder));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const send = (activation, channel, items) => Effect.gen(function* () {
|
|
219
|
+
channel.seq += 1;
|
|
220
|
+
const message = {
|
|
221
|
+
epoch: channel.epoch,
|
|
222
|
+
owner: transport.holder,
|
|
223
|
+
ownerEpoch: transport.epoch,
|
|
224
|
+
ref: activation.ref,
|
|
225
|
+
generation: activation.cache.generation ?? "0",
|
|
226
|
+
seq: channel.seq,
|
|
227
|
+
through: activation.through,
|
|
228
|
+
items,
|
|
229
|
+
};
|
|
230
|
+
const held = [...(activation.rows?.values() ?? [])].flatMap((row) => row.holder === channel.holder && row.holderEpoch === channel.epoch
|
|
231
|
+
? [row.connectionId]
|
|
232
|
+
: []);
|
|
233
|
+
const answer = yield* transport
|
|
234
|
+
.deliver(channel.holder, channel.epoch, message)
|
|
235
|
+
.pipe(Effect.exit);
|
|
236
|
+
if (Exit.isFailure(answer)) {
|
|
237
|
+
if (Option.isSome(Cause.findErrorOption(answer.cause)))
|
|
238
|
+
return yield* dropRows(activation, held);
|
|
239
|
+
return yield* Effect.die(HolderUnreachable.make({ message: "Holder delivery failed" }));
|
|
240
|
+
}
|
|
241
|
+
if (answer.value.wrongEpoch)
|
|
242
|
+
return yield* dropRows(activation, held);
|
|
243
|
+
const mine = new Set(held);
|
|
244
|
+
yield* dropRows(activation, answer.value.unknown.filter((id) => mine.has(id)));
|
|
245
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, () => Effect.void));
|
|
246
|
+
const seal = (activation) => activation.flush
|
|
247
|
+
.withPermit(Effect.forEach(new Set([
|
|
248
|
+
...activation.channels.values(),
|
|
249
|
+
...[...(activation.rows?.values() ?? [])].map((row) => channelOf(activation, row.holder, row.holderEpoch)),
|
|
250
|
+
]), (channel) => send(activation, channel, [HolderItem.cases.Seal.make({})]), { discard: true }))
|
|
251
|
+
.pipe(Effect.timeout("2 seconds"), Effect.ignore);
|
|
252
|
+
const forget = (activation) => {
|
|
253
|
+
forgetGeneration(activation.cache);
|
|
254
|
+
activation.rows = undefined;
|
|
255
|
+
activation.channels.clear();
|
|
256
|
+
};
|
|
257
|
+
/** Fences this activation's generation and loads committed state, as a command turn would. */
|
|
258
|
+
const acquire = (activation) => activation.cache.generation !== undefined && activation.cache.state !== undefined
|
|
259
|
+
? Effect.void
|
|
260
|
+
: activation.acquiring.withPermit(acquireOnce(activation));
|
|
261
|
+
const acquireOnce = (activation) => Effect.gen(function* () {
|
|
262
|
+
if (activation.cache.generation !== undefined && activation.cache.state !== undefined)
|
|
263
|
+
return;
|
|
264
|
+
const sql = yield* SqlClient.SqlClient;
|
|
265
|
+
const actor = actorRow({ sql, actor: activation });
|
|
266
|
+
const { ref, key } = activation;
|
|
267
|
+
const acquired = yield* sql.withTransaction(Effect.gen(function* () {
|
|
268
|
+
yield* sql `INSERT INTO actor_generations (routing_key, tenant_id, actor_type, actor_id)
|
|
269
|
+
VALUES (${key}, ${ref.tenant}, ${ref.actor}, ${ref.id}) ON CONFLICT DO NOTHING`;
|
|
270
|
+
const [row] = activation.cache.generation === undefined
|
|
271
|
+
? yield* sql `
|
|
272
|
+
UPDATE actor_generations SET generation = generation + 1 WHERE ${actor}
|
|
273
|
+
RETURNING generation::text AS generation, event_sequence::text AS head`
|
|
274
|
+
: yield* sql `
|
|
275
|
+
SELECT generation::text AS generation, event_sequence::text AS head
|
|
276
|
+
FROM actor_generations WHERE ${actor}`;
|
|
277
|
+
if (activation.cache.generation !== undefined &&
|
|
278
|
+
activation.cache.generation !== row.generation) {
|
|
279
|
+
forget(activation);
|
|
280
|
+
return yield* unavailable("Stale actor generation");
|
|
281
|
+
}
|
|
282
|
+
const state = yield* sql `
|
|
283
|
+
SELECT key, value FROM actor_state WHERE ${actor}`;
|
|
284
|
+
return { row: row, state };
|
|
285
|
+
}));
|
|
286
|
+
activation.cache.generation = acquired.row.generation;
|
|
287
|
+
activation.cache.state = new Map(acquired.state.map(({ key, value }) => [key, decompress(value)]));
|
|
288
|
+
advance(activation, acquired.row.head);
|
|
289
|
+
activation.through = acquired.row.head;
|
|
290
|
+
});
|
|
291
|
+
/** Loads the actor's connection rows once per activation, excluding holders that are gone. */
|
|
292
|
+
const load = (activation) => activation.rows !== undefined || !hasConnections
|
|
293
|
+
? Effect.void
|
|
294
|
+
: activation.loading.withPermit(loadOnce(activation));
|
|
295
|
+
const loadOnce = (activation) => Effect.gen(function* () {
|
|
296
|
+
if (activation.rows !== undefined)
|
|
297
|
+
return;
|
|
298
|
+
const sql = yield* SqlClient.SqlClient;
|
|
299
|
+
const actor = actorRow({ sql, actor: activation });
|
|
300
|
+
const stored = yield* sql `SELECT connection_id, member, holder, holder_epoch, caller, session, frame_seq::text AS frame_seq,
|
|
301
|
+
opened_through::text AS opened_through
|
|
302
|
+
FROM actor_connections WHERE ${actor}`;
|
|
303
|
+
const rows = new Map();
|
|
304
|
+
for (const row of stored)
|
|
305
|
+
rows.set(row.connection_id, {
|
|
306
|
+
connectionId: row.connection_id,
|
|
307
|
+
member: row.member,
|
|
308
|
+
holder: row.holder,
|
|
309
|
+
holderEpoch: row.holder_epoch,
|
|
310
|
+
caller: yield* decodeCaller(row.caller).pipe(Effect.orDie),
|
|
311
|
+
baseline: row.opened_through,
|
|
312
|
+
session: row.session === null ? undefined : decompress(row.session),
|
|
313
|
+
frameSeq: Number(row.frame_seq),
|
|
314
|
+
});
|
|
315
|
+
activation.rows = rows;
|
|
316
|
+
yield* setKeepAwake(activation);
|
|
317
|
+
yield* activation.flush.withPermit(Effect.forEach(new Set([...rows.values()].map((row) => `${row.holder}|${row.holderEpoch}`)), (name) => {
|
|
318
|
+
const [holder, epoch] = name.split("|");
|
|
319
|
+
return send(activation, channelOf(activation, holder, epoch), []);
|
|
320
|
+
}, { discard: true }));
|
|
321
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die));
|
|
322
|
+
/** Readies an activation that is about to run a turn: fenced, with its connection rows. */
|
|
323
|
+
const prepare = (activation) => hasConnections
|
|
324
|
+
? Effect.andThen(acquire(activation), load(activation)).pipe(Effect.orDie)
|
|
325
|
+
: Effect.void;
|
|
326
|
+
const list = (activation) => (member) => Effect.sync(() => [...(activation.rows?.values() ?? [])]
|
|
327
|
+
.filter((row) => row.member === member && row.buffered === undefined)
|
|
328
|
+
.slice(0, 1_000)
|
|
329
|
+
.map((row) => ({
|
|
330
|
+
connectionId: row.connectionId,
|
|
331
|
+
caller: row.caller,
|
|
332
|
+
session: row.session,
|
|
333
|
+
})));
|
|
334
|
+
/** Sends committed frames to their holders in order and advances the flushed-through cursor. */
|
|
335
|
+
const flush = (activation, broadcasts, head, own) => activation.flush.withPermit(Effect.gen(function* () {
|
|
336
|
+
if (activation.rows === undefined || (broadcasts.length === 0 && own === undefined)) {
|
|
337
|
+
advance(activation, head);
|
|
338
|
+
if (BigInt(head) > BigInt(activation.through))
|
|
339
|
+
activation.through = head;
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const perChannel = new Map();
|
|
343
|
+
const add = (row, item) => {
|
|
344
|
+
const channel = channelOf(activation, row.holder, row.holderEpoch);
|
|
345
|
+
const items = perChannel.get(channel) ?? [];
|
|
346
|
+
const last = items.at(-1);
|
|
347
|
+
if (last?._tag === "Frame" &&
|
|
348
|
+
last.frame === item.frame &&
|
|
349
|
+
last.member === item.member &&
|
|
350
|
+
last.event === item.event &&
|
|
351
|
+
!last.to.includes(item.to[0]))
|
|
352
|
+
perChannel.set(channel, [
|
|
353
|
+
...items.slice(0, -1),
|
|
354
|
+
{ ...last, to: [...last.to, ...item.to] },
|
|
355
|
+
]);
|
|
356
|
+
else
|
|
357
|
+
perChannel.set(channel, [...items, item]);
|
|
358
|
+
};
|
|
359
|
+
const self = own === undefined ? undefined : activation.rows.get(own.connectionId);
|
|
360
|
+
if (own !== undefined && self !== undefined) {
|
|
361
|
+
const stamp = registration.connections.get(own.member)?.stampCursor ?? true;
|
|
362
|
+
for (const frame of own.frames)
|
|
363
|
+
add(self, HolderItem.cases.Frame.make({
|
|
364
|
+
member: own.member,
|
|
365
|
+
to: [own.connectionId],
|
|
366
|
+
frame: frame.frame,
|
|
367
|
+
event: frame.event,
|
|
368
|
+
stamp,
|
|
369
|
+
replay: own.replay === true,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
for (const broadcast of broadcasts) {
|
|
373
|
+
const stamp = registration.connections.get(broadcast.member)?.stampCursor ?? true;
|
|
374
|
+
const to = broadcast.to === undefined ? undefined : new Set(broadcast.to);
|
|
375
|
+
const except = new Set(broadcast.except ?? []);
|
|
376
|
+
for (const row of activation.rows.values())
|
|
377
|
+
if (row.member === broadcast.member &&
|
|
378
|
+
(to === undefined || to.has(row.connectionId)) &&
|
|
379
|
+
!except.has(row.connectionId))
|
|
380
|
+
if (row.buffered !== undefined)
|
|
381
|
+
row.buffered.push({ frame: broadcast.frame, event: broadcast.event });
|
|
382
|
+
else
|
|
383
|
+
add(row, HolderItem.cases.Frame.make({
|
|
384
|
+
member: row.member,
|
|
385
|
+
to: [row.connectionId],
|
|
386
|
+
frame: broadcast.frame,
|
|
387
|
+
event: broadcast.event,
|
|
388
|
+
stamp,
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
const advanced = BigInt(head) > BigInt(activation.through);
|
|
392
|
+
for (const [channel, items] of perChannel)
|
|
393
|
+
yield* send(activation, channel, advanced ? [...items, HolderItem.cases.Flushed.make({ through: head })] : items);
|
|
394
|
+
advance(activation, head);
|
|
395
|
+
if (advanced)
|
|
396
|
+
activation.through = head;
|
|
397
|
+
}));
|
|
398
|
+
const lockOf = (activation, connectionId) => {
|
|
399
|
+
const found = activation.locks.get(connectionId);
|
|
400
|
+
if (found !== undefined)
|
|
401
|
+
return found;
|
|
402
|
+
const created = Semaphore.makeUnsafe(1);
|
|
403
|
+
activation.locks.set(connectionId, created);
|
|
404
|
+
return created;
|
|
405
|
+
};
|
|
406
|
+
const withLock = (activation, connectionId, effect) => lockOf(activation, connectionId)
|
|
407
|
+
.withPermit(effect)
|
|
408
|
+
.pipe(Effect.ensuring(Effect.sync(() => {
|
|
409
|
+
if (activation.rows?.has(connectionId) !== true)
|
|
410
|
+
activation.locks.delete(connectionId);
|
|
411
|
+
})));
|
|
412
|
+
const events = (activation, sql) => (tag, after, limit) => replayEvents(activation.ref, activation.key, [tag], after, BigInt(activation.head), limit).pipe(inTenant({ sql, role, tenant: activation.ref.tenant }), Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideService(SqlClient.SqlClient, sql));
|
|
413
|
+
const run = (activation, row, phase, commands) => Effect.gen(function* () {
|
|
414
|
+
const connection = registration.connections.get(row.member);
|
|
415
|
+
const sql = yield* SqlClient.SqlClient;
|
|
416
|
+
return yield* connection.run({
|
|
417
|
+
ref: activation.ref,
|
|
418
|
+
connectionId: row.connectionId,
|
|
419
|
+
member: row.member,
|
|
420
|
+
caller: row.caller,
|
|
421
|
+
resumed: !ConnectionPhase.guards.Open(phase) && !activation.opened.has(row.connectionId),
|
|
422
|
+
cursor: activation.through,
|
|
423
|
+
state: [...(activation.cache.state ?? new Map())],
|
|
424
|
+
session: row.session,
|
|
425
|
+
connections: list(activation),
|
|
426
|
+
events: events(activation, sql),
|
|
427
|
+
commands,
|
|
428
|
+
}, phase);
|
|
429
|
+
});
|
|
430
|
+
const checkSession = (result) => result.session !== undefined &&
|
|
431
|
+
utf8.encode(result.session).byteLength - SESSION_ENVELOPE_BYTES > MAX_SESSION_BYTES
|
|
432
|
+
? Effect.die(new Error("Connection session exceeds 16 KiB"))
|
|
433
|
+
: Effect.void;
|
|
434
|
+
const setKeepAwake = (activation) => Effect.gen(function* () {
|
|
435
|
+
if (registration.policy.connections !== "keepAwake")
|
|
436
|
+
return;
|
|
437
|
+
const open = (activation.rows?.size ?? 0) > 0;
|
|
438
|
+
if (open === (activation.keptAwake !== undefined))
|
|
439
|
+
return;
|
|
440
|
+
if (open) {
|
|
441
|
+
activation.keptAwake = yield* Effect.context();
|
|
442
|
+
yield* Entity.keepAlive(true);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const holder = activation.keptAwake;
|
|
446
|
+
activation.keptAwake = undefined;
|
|
447
|
+
yield* Entity.keepAlive(false).pipe(Effect.provideContext(holder));
|
|
448
|
+
});
|
|
449
|
+
const closeOnDefect = (activation, connectionId) => (cause) => Effect.gen(function* () {
|
|
450
|
+
yield* Effect.logError("Connection handler defect", Cause.die(cause));
|
|
451
|
+
yield* dropRows(activation, [connectionId]);
|
|
452
|
+
return { _tag: "Closed", ended: ended("Defect", false) };
|
|
453
|
+
});
|
|
454
|
+
const identity = (activation) => ({
|
|
455
|
+
generation: activation.cache.generation,
|
|
456
|
+
owner: transport.holder,
|
|
457
|
+
ownerEpoch: transport.epoch,
|
|
458
|
+
});
|
|
459
|
+
const open = (activation, request) => withLock(activation, request.connectionId, Effect.gen(function* () {
|
|
460
|
+
const feed = request.member === FEED_MEMBER;
|
|
461
|
+
const watch = isWatchMember(request.member);
|
|
462
|
+
if (feed
|
|
463
|
+
? registration.feeds.size === 0
|
|
464
|
+
: watch
|
|
465
|
+
? !registration.watches.has(watchedQuery(request.member))
|
|
466
|
+
: !registration.connections.has(request.member))
|
|
467
|
+
return yield* Effect.die(new Error(`Unregistered connection ${request.member}`));
|
|
468
|
+
yield* acquire(activation);
|
|
469
|
+
yield* load(activation);
|
|
470
|
+
const existing = activation.rows.get(request.connectionId);
|
|
471
|
+
if (existing !== undefined && existing.holderEpoch === request.holderEpoch)
|
|
472
|
+
return {
|
|
473
|
+
_tag: "Opened",
|
|
474
|
+
...identity(activation),
|
|
475
|
+
baseline: existing.baseline,
|
|
476
|
+
recovered: true,
|
|
477
|
+
};
|
|
478
|
+
const sql = yield* SqlClient.SqlClient;
|
|
479
|
+
const actor = actorRow({ sql, actor: activation });
|
|
480
|
+
if (registration.policy.createdBy !== undefined) {
|
|
481
|
+
const [created] = yield* sql `
|
|
482
|
+
SELECT created FROM actor_generations WHERE ${actor}`;
|
|
483
|
+
if (created?.created !== true)
|
|
484
|
+
return yield* ActorError.make({ reason: NotCreated.make({}) });
|
|
485
|
+
}
|
|
486
|
+
if (watch &&
|
|
487
|
+
[...activation.rows.values()].filter((row) => isWatchMember(row.member)).length >=
|
|
488
|
+
registration.policy.watch.maxPerActor)
|
|
489
|
+
return yield* ActorError.make({ reason: RunnerAtCapacity.make({}) });
|
|
490
|
+
if ([...activation.rows.values()].filter((row) => row.member === request.member).length >=
|
|
491
|
+
MAX_MEMBER_CONNECTIONS)
|
|
492
|
+
return yield* feed
|
|
493
|
+
? ActorError.make({ reason: RunnerAtCapacity.make({}) })
|
|
494
|
+
: unavailable("Actor is at its connection limit for this member");
|
|
495
|
+
const baseline = activation.through;
|
|
496
|
+
const row = {
|
|
497
|
+
connectionId: request.connectionId,
|
|
498
|
+
member: request.member,
|
|
499
|
+
holder: request.holder,
|
|
500
|
+
holderEpoch: request.holderEpoch,
|
|
501
|
+
caller: request.caller,
|
|
502
|
+
baseline,
|
|
503
|
+
session: undefined,
|
|
504
|
+
};
|
|
505
|
+
activation.rows.set(request.connectionId, { ...row, frameSeq: 0, buffered: [] });
|
|
506
|
+
const result = yield* (feed || watch
|
|
507
|
+
? Effect.succeed(emptyResult)
|
|
508
|
+
: run(activation, row, ConnectionPhase.cases.Open.make({ params: request.params }), request.commands)).pipe(Effect.catchDefect((cause) => Effect.gen(function* () {
|
|
509
|
+
yield* Effect.logError("Connection open defect", Cause.die(cause));
|
|
510
|
+
return yield* ActorError.make({ reason: ended("Defect", false) });
|
|
511
|
+
})), Effect.catchIf((error) => "failure" in error, (error) => Effect.succeed({ failure: error.failure })));
|
|
512
|
+
if ("failure" in result)
|
|
513
|
+
return { _tag: "Failed", value: result.failure };
|
|
514
|
+
yield* checkSession(result).pipe(Effect.catchDefect(() => ActorError.make({ reason: ended("Defect", false) })));
|
|
515
|
+
const caller = yield* encodeCaller(request.caller).pipe(Effect.orDie);
|
|
516
|
+
const inserted = yield* sql `
|
|
517
|
+
INSERT INTO actor_connections (routing_key, connection_id, bucket, tenant_id, actor_type, actor_id,
|
|
518
|
+
member, holder, holder_epoch, caller, session, opened_at_ms, opened_through)
|
|
519
|
+
SELECT routing_key, ${request.connectionId}, (routing_key >> 56)::integer, tenant_id, actor_type, actor_id,
|
|
520
|
+
${request.member}, ${request.holder}, ${request.holderEpoch}, ${caller},
|
|
521
|
+
${result.session === undefined ? null : compress(result.session)},
|
|
522
|
+
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint, ${baseline}::bigint
|
|
523
|
+
FROM (${heldGeneration({ sql, actor: activation, generation: activation.cache.generation, lock: "SHARE" })}) g
|
|
524
|
+
RETURNING connection_id`;
|
|
525
|
+
if (inserted.length === 0) {
|
|
526
|
+
forget(activation);
|
|
527
|
+
return yield* unavailable("Stale actor generation");
|
|
528
|
+
}
|
|
529
|
+
const buffered = activation.rows.get(request.connectionId)?.buffered ?? [];
|
|
530
|
+
activation.rows.set(request.connectionId, { ...row, session: result.session, frameSeq: 0 });
|
|
531
|
+
activation.opened.add(request.connectionId);
|
|
532
|
+
yield* setKeepAwake(activation);
|
|
533
|
+
yield* flush(activation, result.broadcasts, activation.head, {
|
|
534
|
+
connectionId: request.connectionId,
|
|
535
|
+
member: request.member,
|
|
536
|
+
frames: [...result.sends, ...buffered],
|
|
537
|
+
});
|
|
538
|
+
if (result.close)
|
|
539
|
+
yield* closeRow(activation, request.connectionId, ended("ServerClosed", false));
|
|
540
|
+
return { _tag: "Opened", ...identity(activation), baseline };
|
|
541
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
542
|
+
if (activation.rows?.get(request.connectionId)?.buffered !== undefined)
|
|
543
|
+
activation.rows.delete(request.connectionId);
|
|
544
|
+
})), Effect.catchIf(SqlError.isSqlError, sqlUnavailable)));
|
|
545
|
+
const closeRow = (activation, connectionId, cause) => Effect.gen(function* () {
|
|
546
|
+
const row = activation.rows?.get(connectionId);
|
|
547
|
+
if (row === undefined)
|
|
548
|
+
return;
|
|
549
|
+
yield* activation.flush.withPermit(Effect.gen(function* () {
|
|
550
|
+
yield* dropRows(activation, [connectionId]);
|
|
551
|
+
yield* send(activation, channelOf(activation, row.holder, row.holderEpoch), [
|
|
552
|
+
HolderItem.cases.End.make({ connectionId, ended: cause }),
|
|
553
|
+
]);
|
|
554
|
+
}));
|
|
555
|
+
yield* setKeepAwake(activation);
|
|
556
|
+
});
|
|
557
|
+
/**
|
|
558
|
+
* Runs `body` under the connection's lock with the activation fenced and
|
|
559
|
+
* its rows loaded, on the connection's row when the requesting holder still
|
|
560
|
+
* owns it. A database failure answers `ActorUnavailable`, so the holder retries.
|
|
561
|
+
*/
|
|
562
|
+
const onOwnedRow = (activation, request, body) => withLock(activation, request.connectionId, Effect.gen(function* () {
|
|
563
|
+
yield* acquire(activation);
|
|
564
|
+
yield* load(activation);
|
|
565
|
+
const row = activation.rows?.get(request.connectionId);
|
|
566
|
+
return yield* body(row !== undefined &&
|
|
567
|
+
row.holder === request.holder &&
|
|
568
|
+
row.holderEpoch === request.holderEpoch
|
|
569
|
+
? row
|
|
570
|
+
: undefined);
|
|
571
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, sqlUnavailable)));
|
|
572
|
+
/** Whether a request's authorization has lapsed on the framework clock. */
|
|
573
|
+
const lapsed = (authorizedUntil) => Effect.gen(function* () {
|
|
574
|
+
const clock = yield* FrameworkClock;
|
|
575
|
+
return (yield* Clock.currentTimeMillis) + clock.offsetMillis() >= authorizedUntil;
|
|
576
|
+
});
|
|
577
|
+
/** Drops a connection the server ended and answers it `ServerClosed`. */
|
|
578
|
+
const serverClosed = (activation, connectionId) => Effect.gen(function* () {
|
|
579
|
+
yield* dropRows(activation, [connectionId]);
|
|
580
|
+
yield* setKeepAwake(activation);
|
|
581
|
+
return { _tag: "Closed", ended: ended("ServerClosed", false) };
|
|
582
|
+
});
|
|
583
|
+
const frame = (activation, request) => onOwnedRow(activation, request, (row) => Effect.gen(function* () {
|
|
584
|
+
if (row === undefined)
|
|
585
|
+
return { _tag: "Closed", ended: ended("ServerClosed", true) };
|
|
586
|
+
if (request.seq <= row.frameSeq)
|
|
587
|
+
return { _tag: "Acked", ...identity(activation) };
|
|
588
|
+
if (yield* lapsed(request.authorizedUntil))
|
|
589
|
+
return yield* serverClosed(activation, request.connectionId);
|
|
590
|
+
return yield* Effect.gen(function* () {
|
|
591
|
+
const result = yield* run(activation, row, ConnectionPhase.cases.Frame.make({ frame: request.frame }), request.commands).pipe(Effect.catch(() => Effect.die(new Error("A frame handler cannot fail"))));
|
|
592
|
+
yield* checkSession(result);
|
|
593
|
+
if (result.changed) {
|
|
594
|
+
const sql = yield* SqlClient.SqlClient;
|
|
595
|
+
const written = yield* sql `
|
|
596
|
+
UPDATE actor_connections c SET session = ${result.session === undefined ? null : compress(result.session)},
|
|
597
|
+
frame_seq = ${request.seq}
|
|
598
|
+
FROM (${heldGeneration({ sql, actor: activation, generation: activation.cache.generation, lock: "SHARE" })}) g
|
|
599
|
+
WHERE c.routing_key = g.routing_key AND c.tenant_id = g.tenant_id
|
|
600
|
+
AND c.actor_type = g.actor_type AND c.actor_id = g.actor_id
|
|
601
|
+
AND c.connection_id = ${request.connectionId} AND c.frame_seq < ${request.seq}
|
|
602
|
+
RETURNING c.connection_id`;
|
|
603
|
+
if (written.length === 0) {
|
|
604
|
+
forget(activation);
|
|
605
|
+
return yield* unavailable("Stale actor generation");
|
|
606
|
+
}
|
|
607
|
+
row.session = result.session;
|
|
608
|
+
}
|
|
609
|
+
row.frameSeq = request.seq;
|
|
610
|
+
yield* flush(activation, result.broadcasts, activation.head, {
|
|
611
|
+
connectionId: request.connectionId,
|
|
612
|
+
member: row.member,
|
|
613
|
+
frames: result.sends,
|
|
614
|
+
});
|
|
615
|
+
if (result.close)
|
|
616
|
+
return yield* serverClosed(activation, request.connectionId);
|
|
617
|
+
return { _tag: "Acked", ...identity(activation) };
|
|
618
|
+
}).pipe(Effect.catchDefect(closeOnDefect(activation, request.connectionId)));
|
|
619
|
+
}));
|
|
620
|
+
const close = (activation, request) => onOwnedRow(activation, request, (row) => Effect.gen(function* () {
|
|
621
|
+
if (row === undefined)
|
|
622
|
+
return;
|
|
623
|
+
const result = row.member === FEED_MEMBER || isWatchMember(row.member)
|
|
624
|
+
? undefined
|
|
625
|
+
: yield* run(activation, row, ConnectionPhase.cases.Close.make({ reason: request.cause.cause })).pipe(Effect.catchDefect((cause) => Effect.as(Effect.logError("Connection close defect", Cause.die(cause)), undefined)), Effect.orElseSucceed(() => undefined));
|
|
626
|
+
yield* dropRows(activation, [request.connectionId]);
|
|
627
|
+
activation.opened.delete(request.connectionId);
|
|
628
|
+
activation.locks.delete(request.connectionId);
|
|
629
|
+
yield* setKeepAwake(activation);
|
|
630
|
+
if (result !== undefined)
|
|
631
|
+
yield* flush(activation, result.broadcasts, activation.head);
|
|
632
|
+
}));
|
|
633
|
+
const resync = (activation, request) => onOwnedRow(activation, request, (row) => Effect.gen(function* () {
|
|
634
|
+
if (row === undefined)
|
|
635
|
+
return { _tag: "Closed", ended: ended("OwnerLost", true) };
|
|
636
|
+
if (yield* lapsed(request.authorizedUntil))
|
|
637
|
+
return yield* serverClosed(activation, request.connectionId);
|
|
638
|
+
if (row.member === FEED_MEMBER ||
|
|
639
|
+
isWatchMember(row.member) ||
|
|
640
|
+
!registration.connections.get(row.member).hasResync)
|
|
641
|
+
return { _tag: "Replayed", ...identity(activation) };
|
|
642
|
+
return yield* Effect.gen(function* () {
|
|
643
|
+
const result = yield* run(activation, row, ConnectionPhase.cases.Resync.make({ after: request.after })).pipe(Effect.catch(() => Effect.die(new Error("A resync handler cannot fail"))));
|
|
644
|
+
yield* flush(activation, result.broadcasts, activation.head, {
|
|
645
|
+
connectionId: request.connectionId,
|
|
646
|
+
member: row.member,
|
|
647
|
+
frames: result.sends,
|
|
648
|
+
replay: true,
|
|
649
|
+
});
|
|
650
|
+
if (result.close)
|
|
651
|
+
return yield* serverClosed(activation, request.connectionId);
|
|
652
|
+
return { _tag: "Replayed", ...identity(activation) };
|
|
653
|
+
}).pipe(Effect.catchDefect(closeOnDefect(activation, request.connectionId)));
|
|
654
|
+
}));
|
|
655
|
+
const follow = (activation, sql) => (tag, after) => Stream.paginate({ cursor: after, first: true }, ({ cursor, first }) => Effect.gen(function* () {
|
|
656
|
+
const signal = activation.advanced;
|
|
657
|
+
const head = activation.head;
|
|
658
|
+
if (!first && BigInt(head) <= BigInt(cursor ?? "0")) {
|
|
659
|
+
yield* Deferred.await(signal);
|
|
660
|
+
return [[], Option.some({ cursor, first: false })];
|
|
661
|
+
}
|
|
662
|
+
const page = yield* replayEvents(activation.ref, activation.key, [tag], cursor, BigInt(head), FOLLOW_PAGE).pipe(inTenant({ sql, role, tenant: activation.ref.tenant }), Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideService(SqlClient.SqlClient, sql));
|
|
663
|
+
const next = page.length === FOLLOW_PAGE ? page.at(-1).cursor : head;
|
|
664
|
+
return [page, Option.some({ cursor: next, first: false })];
|
|
665
|
+
}));
|
|
666
|
+
const streams = streamSubscriptions({
|
|
667
|
+
registration,
|
|
668
|
+
activations,
|
|
669
|
+
transport,
|
|
670
|
+
authorize,
|
|
671
|
+
now,
|
|
672
|
+
prepare: Effect.fnUntraced(function* (activation) {
|
|
673
|
+
yield* acquire(activation).pipe(Effect.catchIf(SqlError.isSqlError, sqlUnavailable));
|
|
674
|
+
const sql = yield* SqlClient.SqlClient;
|
|
675
|
+
return {
|
|
676
|
+
ref: activation.ref,
|
|
677
|
+
cursor: activation.head,
|
|
678
|
+
state: [...(activation.cache.state ?? new Map())],
|
|
679
|
+
events: events(activation, sql),
|
|
680
|
+
follow: follow(activation, sql),
|
|
681
|
+
progress: (tag, jobId) => progressFeed(activation, tag, jobId),
|
|
682
|
+
};
|
|
683
|
+
}),
|
|
684
|
+
});
|
|
685
|
+
const progressFeed = (activation, tag, jobId) => Stream.unwrap(Effect.gen(function* () {
|
|
686
|
+
const queue = yield* Queue.sliding(STREAM_PROGRESS_BUFFER);
|
|
687
|
+
const listener = { tag, jobId, queue };
|
|
688
|
+
yield* Effect.acquireRelease(Effect.sync(() => activation.listeners.add(listener)), () => Effect.sync(() => activation.listeners.delete(listener)));
|
|
689
|
+
return Stream.fromQueue(queue);
|
|
690
|
+
}));
|
|
691
|
+
const takeProgressToken = (activation, at) => {
|
|
692
|
+
activation.progressTokens = Math.min(PROGRESS_PER_SECOND, activation.progressTokens +
|
|
693
|
+
((at - activation.progressRefilledAt) * PROGRESS_PER_SECOND) / 1000);
|
|
694
|
+
activation.progressRefilledAt = at;
|
|
695
|
+
if (activation.progressTokens < 1)
|
|
696
|
+
return false;
|
|
697
|
+
activation.progressTokens -= 1;
|
|
698
|
+
return true;
|
|
699
|
+
};
|
|
700
|
+
/**
|
|
701
|
+
* Whether the job is still open: one indexed read of its outbox row per
|
|
702
|
+
* job id per `JOB_CHECK_MS`. A row that is gone or no longer a job
|
|
703
|
+
* closes it for this activation.
|
|
704
|
+
*/
|
|
705
|
+
const checkJob = (activation, message, at) => Effect.gen(function* () {
|
|
706
|
+
const current = activation.progress.get(message.jobId);
|
|
707
|
+
if (current !== undefined && (!current.open || at - current.checkedAt < JOB_CHECK_MS))
|
|
708
|
+
return current;
|
|
709
|
+
const failed = activation.checkFailed.get(message.jobId);
|
|
710
|
+
if (failed !== undefined && at - failed < JOB_CHECK_RETRY_MS)
|
|
711
|
+
return undefined;
|
|
712
|
+
const sql = yield* SqlClient.SqlClient;
|
|
713
|
+
const actor = actorRow({ sql, actor: activation });
|
|
714
|
+
activation.checking.add(message.jobId);
|
|
715
|
+
const read = yield* sql `SELECT kind, attempts, cancelled_at_ms IS NOT NULL AS cancelled, caller, payload
|
|
716
|
+
FROM actor_outbox
|
|
717
|
+
WHERE ${actor} AND intent_id = ${message.jobId}`.pipe(Effect.ensuring(Effect.sync(() => activation.checking.delete(message.jobId))), Effect.exit);
|
|
718
|
+
if (Exit.isFailure(read)) {
|
|
719
|
+
activation.checkFailed.set(message.jobId, at);
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
activation.checkFailed.delete(message.jobId);
|
|
723
|
+
const closedMeanwhile = activation.progress.get(message.jobId);
|
|
724
|
+
if (closedMeanwhile !== undefined && !closedMeanwhile.open)
|
|
725
|
+
return closedMeanwhile;
|
|
726
|
+
const [row] = read.value;
|
|
727
|
+
if (row === undefined || row.kind !== "job" || row.cancelled) {
|
|
728
|
+
const closed = closedJob(at);
|
|
729
|
+
activation.progress.set(message.jobId, closed);
|
|
730
|
+
return closed;
|
|
731
|
+
}
|
|
732
|
+
if (row.attempts < message.attempt)
|
|
733
|
+
return undefined;
|
|
734
|
+
const stored = activation.progress.get(message.jobId);
|
|
735
|
+
if (stored !== undefined && stored.open) {
|
|
736
|
+
stored.checkedAt = at;
|
|
737
|
+
stored.touchedAt = at;
|
|
738
|
+
return stored;
|
|
739
|
+
}
|
|
740
|
+
const checked = {
|
|
741
|
+
open: true,
|
|
742
|
+
checkedAt: at,
|
|
743
|
+
touchedAt: at,
|
|
744
|
+
attempt: 0,
|
|
745
|
+
seq: 0,
|
|
746
|
+
principal: principal(yield* decodeCaller(row.caller).pipe(Effect.orDie)),
|
|
747
|
+
payload: row.payload,
|
|
748
|
+
holders: new Set(),
|
|
749
|
+
};
|
|
750
|
+
activation.progress.set(message.jobId, checked);
|
|
751
|
+
return checked;
|
|
752
|
+
});
|
|
753
|
+
const after = (message, job) => message.attempt > job.attempt || (message.attempt === job.attempt && message.seq > job.seq);
|
|
754
|
+
/**
|
|
755
|
+
* Admits one executor progress frame and forwards it through this
|
|
756
|
+
* activation's ordered channels to the member connections that want it,
|
|
757
|
+
* and to stream subscribers. Every refusal drops the frame; nothing here
|
|
758
|
+
* fails, writes, or closes a session.
|
|
759
|
+
*/
|
|
760
|
+
const progress = (activation, message) => Effect.gen(function* () {
|
|
761
|
+
const { ref } = activation;
|
|
762
|
+
if (message.ref.tenant !== ref.tenant ||
|
|
763
|
+
message.ref.actor !== ref.actor ||
|
|
764
|
+
message.ref.id !== ref.id ||
|
|
765
|
+
!wanted.has(message.job))
|
|
766
|
+
return;
|
|
767
|
+
const at = yield* now;
|
|
768
|
+
if (at - activation.progressSweptAt >= PROGRESS_RECORD_MS) {
|
|
769
|
+
activation.progressSweptAt = at;
|
|
770
|
+
for (const [jobId, record] of activation.progress)
|
|
771
|
+
if (at - record.touchedAt >= PROGRESS_RECORD_MS)
|
|
772
|
+
activation.progress.delete(jobId);
|
|
773
|
+
}
|
|
774
|
+
if (at > message.leaseUntil + PROGRESS_SKEW_MS)
|
|
775
|
+
return;
|
|
776
|
+
const known = activation.progress.get(message.jobId);
|
|
777
|
+
if (known !== undefined && (!known.open || !after(message, known)))
|
|
778
|
+
return;
|
|
779
|
+
if (!takeProgressToken(activation, at))
|
|
780
|
+
return;
|
|
781
|
+
yield* acquire(activation);
|
|
782
|
+
yield* load(activation);
|
|
783
|
+
const job = yield* checkJob(activation, message, at);
|
|
784
|
+
if (job === undefined)
|
|
785
|
+
return;
|
|
786
|
+
yield* activation.flush.withPermit(Effect.gen(function* () {
|
|
787
|
+
if (activation.progress.get(message.jobId) !== job)
|
|
788
|
+
return;
|
|
789
|
+
if (!job.open || !after(message, job))
|
|
790
|
+
return;
|
|
791
|
+
job.attempt = message.attempt;
|
|
792
|
+
job.seq = message.seq;
|
|
793
|
+
job.touchedAt = at;
|
|
794
|
+
const perChannel = new Map();
|
|
795
|
+
for (const row of activation.rows?.values() ?? []) {
|
|
796
|
+
const wants = registration.connections.get(row.member)?.progress;
|
|
797
|
+
if (wants === undefined ||
|
|
798
|
+
!wants.jobs.has(message.job) ||
|
|
799
|
+
row.buffered !== undefined ||
|
|
800
|
+
(wants.to === "principal" && !samePrincipal(principal(row.caller), job.principal)))
|
|
801
|
+
continue;
|
|
802
|
+
const channel = channelOf(activation, row.holder, row.holderEpoch);
|
|
803
|
+
const members = perChannel.get(channel) ?? new Map();
|
|
804
|
+
members.set(row.member, [...(members.get(row.member) ?? []), row.connectionId]);
|
|
805
|
+
perChannel.set(channel, members);
|
|
806
|
+
}
|
|
807
|
+
for (const [channel, members] of perChannel) {
|
|
808
|
+
job.holders.add(`${channel.holder}|${channel.epoch}`);
|
|
809
|
+
yield* send(activation, channel, [...members].map(([member, to]) => HolderItem.cases.Progress.make({
|
|
810
|
+
member,
|
|
811
|
+
to,
|
|
812
|
+
job: message.job,
|
|
813
|
+
jobId: message.jobId,
|
|
814
|
+
attempt: message.attempt,
|
|
815
|
+
seq: message.seq,
|
|
816
|
+
frame: message.frame,
|
|
817
|
+
})));
|
|
818
|
+
}
|
|
819
|
+
for (const listener of activation.listeners)
|
|
820
|
+
if (listener.tag === message.job &&
|
|
821
|
+
(listener.jobId === undefined || listener.jobId === message.jobId))
|
|
822
|
+
Queue.offerUnsafe(listener.queue, {
|
|
823
|
+
jobId: message.jobId,
|
|
824
|
+
job: job.payload,
|
|
825
|
+
attempt: message.attempt,
|
|
826
|
+
seq: message.seq,
|
|
827
|
+
frame: message.frame,
|
|
828
|
+
});
|
|
829
|
+
}));
|
|
830
|
+
}).pipe(Effect.catchCause((cause) => Effect.logDebug("Progress frame dropped", cause)));
|
|
831
|
+
/**
|
|
832
|
+
* Closes a job's progress on this activation, once its route turn (whose
|
|
833
|
+
* command id is the job id) commits or its settle is reported: holders
|
|
834
|
+
* that received its progress discard what they still buffer, before the
|
|
835
|
+
* route's own broadcasts.
|
|
836
|
+
*/
|
|
837
|
+
const closeProgress = (activation, jobId) => Effect.suspend(() => {
|
|
838
|
+
const job = activation.progress.get(jobId);
|
|
839
|
+
if (job === undefined && !activation.checking.has(jobId))
|
|
840
|
+
return Effect.void;
|
|
841
|
+
return activation.flush.withPermit(Effect.gen(function* () {
|
|
842
|
+
const current = activation.progress.get(jobId);
|
|
843
|
+
activation.progress.set(jobId, closedJob(yield* now));
|
|
844
|
+
if (current === undefined || !current.open)
|
|
845
|
+
return;
|
|
846
|
+
current.open = false;
|
|
847
|
+
for (const name of current.holders) {
|
|
848
|
+
const channel = activation.channels.get(name);
|
|
849
|
+
if (channel !== undefined)
|
|
850
|
+
yield* send(activation, channel, [HolderItem.cases.ProgressEnd.make({ jobId })]);
|
|
851
|
+
}
|
|
852
|
+
}));
|
|
853
|
+
});
|
|
854
|
+
const progressClosed = (activation, message) => {
|
|
855
|
+
const job = activation.progress.get(message.jobId);
|
|
856
|
+
return job !== undefined && message.attempt < job.attempt
|
|
857
|
+
? Effect.void
|
|
858
|
+
: closeProgress(activation, message.jobId);
|
|
859
|
+
};
|
|
860
|
+
const hibernate = (entityId) => Effect.gen(function* () {
|
|
861
|
+
const activation = activations.get(entityId);
|
|
862
|
+
if (activation === undefined)
|
|
863
|
+
return;
|
|
864
|
+
yield* streams.end(activation);
|
|
865
|
+
yield* seal(activation);
|
|
866
|
+
forget(activation);
|
|
867
|
+
activation.opened.clear();
|
|
868
|
+
activation.progress.clear();
|
|
869
|
+
activation.checkFailed.clear();
|
|
870
|
+
activation.head = "0";
|
|
871
|
+
activation.through = "0";
|
|
872
|
+
});
|
|
873
|
+
return {
|
|
874
|
+
activations,
|
|
875
|
+
hasConnections,
|
|
876
|
+
hasStreams,
|
|
877
|
+
hasProgress,
|
|
878
|
+
progress,
|
|
879
|
+
closeProgress,
|
|
880
|
+
progressClosed,
|
|
881
|
+
feedBroadcasts,
|
|
882
|
+
watchBroadcasts,
|
|
883
|
+
hibernate,
|
|
884
|
+
subscribe: streams.subscribe,
|
|
885
|
+
endStreams: streams.end,
|
|
886
|
+
watchStreams: streams.watch,
|
|
887
|
+
enter,
|
|
888
|
+
prepare,
|
|
889
|
+
list,
|
|
890
|
+
flush,
|
|
891
|
+
open,
|
|
892
|
+
frame,
|
|
893
|
+
close,
|
|
894
|
+
resync,
|
|
895
|
+
};
|
|
896
|
+
};
|