@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,816 @@
|
|
|
1
|
+
import { Cause, Clock, Crypto, Effect, Exit, Fiber, Option, Predicate, Queue, Random, Schedule, Schema, Scope, Stream, } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { ActorError, ActorUnavailable, SessionEnded, Unauthorized } from "../../errors/actor.js";
|
|
4
|
+
import { connectionSecret } from "../../identity/connection.js";
|
|
5
|
+
import { FrameworkClock } from "../turn/admission.js";
|
|
6
|
+
import { shardClients } from "../database/shards.js";
|
|
7
|
+
import { ClientMessage, HolderItem, isWatchMember, watchedQuery, } from "./protocol.js";
|
|
8
|
+
/** The largest encoded inbound frame or open params a holder accepts. */
|
|
9
|
+
export const MAX_INBOUND_BYTES = 65_536;
|
|
10
|
+
/** Open connections one holder keeps at once. */
|
|
11
|
+
export const MAX_HELD_CONNECTIONS = 50_000;
|
|
12
|
+
/** Bytes buffered across one holder's connections, both directions. */
|
|
13
|
+
export const MAX_HELD_BYTES = 268_435_456;
|
|
14
|
+
/** Outbound frames one connection may have waiting for its client. */
|
|
15
|
+
export const MAX_OUTBOUND_FRAMES = 1_024;
|
|
16
|
+
/** Outbound bytes one connection may have waiting for its client. */
|
|
17
|
+
export const MAX_OUTBOUND_BYTES = 1_048_576;
|
|
18
|
+
/** How long a client has to answer `Resync` with `ResyncDone`. */
|
|
19
|
+
export const RESYNC_DEADLINE_MS = 30_000;
|
|
20
|
+
/** A third owner loss within this window ends the connection instead of resyncing again. */
|
|
21
|
+
export const RESYNC_WINDOW_MS = 300_000;
|
|
22
|
+
/** Inbound frames a connection may have in flight before its transport stops reading. */
|
|
23
|
+
export const MAX_INFLIGHT_FRAMES = 32;
|
|
24
|
+
const TICK = "100 millis";
|
|
25
|
+
const OWNER_CHECK_MS = 1_000;
|
|
26
|
+
const LIVENESS_MS = 10_000;
|
|
27
|
+
const MAX_INBOUND_FRAMES = 1_024;
|
|
28
|
+
const COMMAND_SKEW_MS = 1_000;
|
|
29
|
+
const utf8 = new TextEncoder();
|
|
30
|
+
/** The declared failure an `open` handler returned, still encoded. */
|
|
31
|
+
export class OpenRejected extends Schema.TaggedError()("OpenRejected", {
|
|
32
|
+
value: Schema.String,
|
|
33
|
+
}) {
|
|
34
|
+
}
|
|
35
|
+
const actorKey = (ref) => `${ref.tenant}\u0000${ref.actor}\u0000${ref.id}`;
|
|
36
|
+
const ended = (cause, resync, retryAfterMs) => ActorError.make({
|
|
37
|
+
reason: SessionEnded.make({
|
|
38
|
+
cause,
|
|
39
|
+
resync,
|
|
40
|
+
retryAfterMs,
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* The socket side of connections on one runner: it holds each connection's
|
|
45
|
+
* buffers, caller, and authorization clock, orders inbound frames, applies the
|
|
46
|
+
* owner's ordered messages, and resyncs its connections in place when their
|
|
47
|
+
* owner dies. Actor activations never hold a socket.
|
|
48
|
+
*
|
|
49
|
+
* Authorization: nothing reaches a client or an owner past the connection's
|
|
50
|
+
* authorization bound. A session past its credential's expiry reports that;
|
|
51
|
+
* otherwise its last check is too old. A credential's expiry caps the session and
|
|
52
|
+
* its buffered frames go with it. An answer that arrives past the bound never
|
|
53
|
+
* extends it, and a credential that expired while authorization ran opens nothing.
|
|
54
|
+
* A holder that cannot confirm its rows for a whole bound stops serving them; a
|
|
55
|
+
* connection opened since the checks began failing counts from its open. A
|
|
56
|
+
* revoked client receives nothing more, including frames already queued.
|
|
57
|
+
*
|
|
58
|
+
* Buffering: a member frame evicts buffered progress oldest first before the
|
|
59
|
+
* connection counts as a slow consumer. A newer progress frame that would not fit
|
|
60
|
+
* is dropped and the waiting one stays. A progress marker delivers the newest
|
|
61
|
+
* frame of its job, or nothing if it was discarded.
|
|
62
|
+
*
|
|
63
|
+
* Owner ordering: an owner acknowledges each message before sending the next, so
|
|
64
|
+
* the first message a record sees follows everything its connections could have
|
|
65
|
+
* missed. Redelivered messages were already applied, and late messages from a
|
|
66
|
+
* dead or superseded generation are dropped. A gap makes every connection replay
|
|
67
|
+
* from its cursor. A newer generation over an unsealed older one means the older
|
|
68
|
+
* owner died, and every connection resyncs in place; a loss during a resync
|
|
69
|
+
* restarts it from the same cursor and keeps the frames it deferred. An older
|
|
70
|
+
* owner's progress never reaches a client after a newer owner's messages, and a
|
|
71
|
+
* ping answered after a newer owner was observed says nothing about that owner.
|
|
72
|
+
* Live frames wait until a resync's replay is acknowledged, and inbound frames are
|
|
73
|
+
* delivered one at a time after any pending resync. Acknowledgments before the new
|
|
74
|
+
* owner answered, or before the member's replay finished, are ignored. Until the
|
|
75
|
+
* new owner answers, the resync deadline bounds the takeover; then the client's
|
|
76
|
+
* acknowledgment does.
|
|
77
|
+
*
|
|
78
|
+
* Opening and closing: a connection registers before the owner commits so frames
|
|
79
|
+
* flushed after that commit find it. The owner may still commit an open the holder
|
|
80
|
+
* gave up on; such a late row is closed again. A lost owner's opening frames and
|
|
81
|
+
* unflushed broadcasts may be gone. An open handler that closed the connection
|
|
82
|
+
* leaves it ended with `ServerClosed`. On close the owner deletes the row, and the
|
|
83
|
+
* holder deletes its own if the owner cannot. Rows an unreachable owner dropped
|
|
84
|
+
* end their connections so clients reconnect. A graceful shutdown ends every
|
|
85
|
+
* connection with a resync hint and deletes its rows. A feed connection is
|
|
86
|
+
* authorized as each event tag it reads, all of which must pass.
|
|
87
|
+
*/
|
|
88
|
+
export const connectionHolder = Effect.fnUntraced(function* (options) {
|
|
89
|
+
const sql = yield* SqlClient.SqlClient;
|
|
90
|
+
const shards = yield* shardClients;
|
|
91
|
+
const crypto = yield* Crypto.Crypto;
|
|
92
|
+
const clock = yield* FrameworkClock;
|
|
93
|
+
const scope = yield* Effect.scope;
|
|
94
|
+
const actors = new Map();
|
|
95
|
+
const held = new Map();
|
|
96
|
+
let heldBytes = 0;
|
|
97
|
+
let lastLiveness = 0;
|
|
98
|
+
let livenessFailedSince = undefined;
|
|
99
|
+
let checkingLiveness = false;
|
|
100
|
+
const now = Effect.map(Clock.currentTimeMillis, (millis) => millis + clock.offsetMillis());
|
|
101
|
+
const address = (connection) => {
|
|
102
|
+
const transport = options.transport();
|
|
103
|
+
return {
|
|
104
|
+
ref: connection.ref,
|
|
105
|
+
connectionId: connection.id,
|
|
106
|
+
holder: transport.holder,
|
|
107
|
+
holderEpoch: transport.epoch,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const actorOf = (ref) => {
|
|
111
|
+
const key = actorKey(ref);
|
|
112
|
+
const found = actors.get(key);
|
|
113
|
+
if (found !== undefined)
|
|
114
|
+
return found;
|
|
115
|
+
const created = {
|
|
116
|
+
generation: 0n,
|
|
117
|
+
seq: 0,
|
|
118
|
+
sealed: true,
|
|
119
|
+
through: "0",
|
|
120
|
+
owner: "",
|
|
121
|
+
ownerEpoch: "",
|
|
122
|
+
lastCheck: 0,
|
|
123
|
+
fresh: true,
|
|
124
|
+
connections: new Map(),
|
|
125
|
+
};
|
|
126
|
+
actors.set(key, created);
|
|
127
|
+
return created;
|
|
128
|
+
};
|
|
129
|
+
const deleteRow = (connection) => sql `DELETE FROM actor_connections WHERE routing_key = ${connection.type.routingKey(connection.ref)}
|
|
130
|
+
AND tenant_id = ${connection.ref.tenant} AND actor_type = ${connection.ref.actor}
|
|
131
|
+
AND actor_id = ${connection.ref.id} AND connection_id = ${connection.id}
|
|
132
|
+
AND holder_epoch = ${options.transport().epoch}`.pipe(Effect.ignore);
|
|
133
|
+
const release = (connection) => {
|
|
134
|
+
heldBytes -= connection.outBytes + connection.inBytes + (connection.resync?.deferredBytes ?? 0);
|
|
135
|
+
if (connection.resync !== undefined)
|
|
136
|
+
connection.resync.deferredBytes = 0;
|
|
137
|
+
connection.outBytes = 0;
|
|
138
|
+
connection.inBytes = 0;
|
|
139
|
+
connection.inbound.length = 0;
|
|
140
|
+
connection.progress.clear();
|
|
141
|
+
held.delete(connection.id);
|
|
142
|
+
const actor = actors.get(connection.key);
|
|
143
|
+
actor?.connections.delete(connection.id);
|
|
144
|
+
if (actor !== undefined && actor.connections.size === 0)
|
|
145
|
+
actors.delete(connection.key);
|
|
146
|
+
};
|
|
147
|
+
const end = (connection, error, deleteOwnRow) => Effect.gen(function* () {
|
|
148
|
+
if (connection.ended)
|
|
149
|
+
return;
|
|
150
|
+
connection.ended = true;
|
|
151
|
+
connection.open = false;
|
|
152
|
+
release(connection);
|
|
153
|
+
if (Predicate.isTagged(error.reason, "Unauthorized"))
|
|
154
|
+
yield* Effect.ignore(Queue.clear(connection.outbound));
|
|
155
|
+
yield* Queue.fail(connection.outbound, error);
|
|
156
|
+
yield* Queue.offer(connection.wake, undefined);
|
|
157
|
+
yield* Queue.offer(connection.drained, undefined);
|
|
158
|
+
if (deleteOwnRow)
|
|
159
|
+
yield* deleteRow(connection);
|
|
160
|
+
});
|
|
161
|
+
const discardProgress = (connection, jobId) => {
|
|
162
|
+
const pending = connection.progress.get(jobId);
|
|
163
|
+
if (pending === undefined)
|
|
164
|
+
return;
|
|
165
|
+
connection.progress.delete(jobId);
|
|
166
|
+
connection.outFrames -= 1;
|
|
167
|
+
connection.outBytes -= pending.bytes;
|
|
168
|
+
heldBytes -= pending.bytes;
|
|
169
|
+
};
|
|
170
|
+
const fits = (connection, bytes) => connection.outFrames + 1 <= MAX_OUTBOUND_FRAMES &&
|
|
171
|
+
connection.outBytes + bytes <= MAX_OUTBOUND_BYTES &&
|
|
172
|
+
heldBytes + bytes <= MAX_HELD_BYTES;
|
|
173
|
+
/**
|
|
174
|
+
* Buffers a progress frame. A newer frame of the same job replaces the
|
|
175
|
+
* waiting one in place; one that does not fit is dropped. Progress never
|
|
176
|
+
* ends a session.
|
|
177
|
+
*/
|
|
178
|
+
const pushProgress = (connection, message) => Effect.gen(function* () {
|
|
179
|
+
if (connection.ended || connection.resync !== undefined)
|
|
180
|
+
return;
|
|
181
|
+
const bytes = utf8.encode(message.frame).byteLength;
|
|
182
|
+
const waiting = connection.progress.get(message.jobId);
|
|
183
|
+
if (waiting !== undefined) {
|
|
184
|
+
if (connection.outBytes - waiting.bytes + bytes > MAX_OUTBOUND_BYTES ||
|
|
185
|
+
heldBytes - waiting.bytes + bytes > MAX_HELD_BYTES)
|
|
186
|
+
return;
|
|
187
|
+
connection.outBytes += bytes - waiting.bytes;
|
|
188
|
+
heldBytes += bytes - waiting.bytes;
|
|
189
|
+
waiting.message = message;
|
|
190
|
+
waiting.bytes = bytes;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!fits(connection, bytes))
|
|
194
|
+
return;
|
|
195
|
+
connection.progress.set(message.jobId, { message, bytes });
|
|
196
|
+
connection.outFrames += 1;
|
|
197
|
+
connection.outBytes += bytes;
|
|
198
|
+
heldBytes += bytes;
|
|
199
|
+
yield* Queue.offer(connection.outbound, message);
|
|
200
|
+
});
|
|
201
|
+
const push = (connection, message, control) => Effect.gen(function* () {
|
|
202
|
+
if (connection.ended)
|
|
203
|
+
return;
|
|
204
|
+
const bytes = ClientMessage.guards.Frame(message) ? utf8.encode(message.frame).byteLength : 0;
|
|
205
|
+
if (!control)
|
|
206
|
+
for (const jobId of connection.progress.keys()) {
|
|
207
|
+
if (fits(connection, bytes))
|
|
208
|
+
break;
|
|
209
|
+
discardProgress(connection, jobId);
|
|
210
|
+
}
|
|
211
|
+
if (!control &&
|
|
212
|
+
(connection.outFrames + 1 > MAX_OUTBOUND_FRAMES ||
|
|
213
|
+
connection.outBytes + bytes > MAX_OUTBOUND_BYTES ||
|
|
214
|
+
heldBytes + bytes > MAX_HELD_BYTES))
|
|
215
|
+
return yield* end(connection, ended("SlowConsumer", true), true);
|
|
216
|
+
connection.outFrames += 1;
|
|
217
|
+
connection.outBytes += bytes;
|
|
218
|
+
heldBytes += bytes;
|
|
219
|
+
yield* Queue.offer(connection.outbound, message);
|
|
220
|
+
});
|
|
221
|
+
const takenProgress = (connection, marker) => Effect.sync(() => {
|
|
222
|
+
const pending = connection.progress.get(marker.jobId);
|
|
223
|
+
if (connection.ended || pending === undefined)
|
|
224
|
+
return Option.none();
|
|
225
|
+
discardProgress(connection, marker.jobId);
|
|
226
|
+
return Option.some(pending.message);
|
|
227
|
+
});
|
|
228
|
+
const taken = (connection, message) => Effect.sync(() => {
|
|
229
|
+
if (connection.ended)
|
|
230
|
+
return;
|
|
231
|
+
const bytes = ClientMessage.guards.Frame(message) ? utf8.encode(message.frame).byteLength : 0;
|
|
232
|
+
connection.outFrames -= 1;
|
|
233
|
+
connection.outBytes -= bytes;
|
|
234
|
+
heldBytes -= bytes;
|
|
235
|
+
});
|
|
236
|
+
const ownerLost = (actor) => Effect.gen(function* () {
|
|
237
|
+
if (actor.sealed)
|
|
238
|
+
return;
|
|
239
|
+
actor.sealed = true;
|
|
240
|
+
const at = yield* now;
|
|
241
|
+
for (const connection of actor.connections.values()) {
|
|
242
|
+
if (connection.open)
|
|
243
|
+
yield* resync(actor, connection, at);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
const resync = (actor, connection, at, from) => Effect.gen(function* () {
|
|
247
|
+
connection.resyncs = connection.resyncs.filter((time) => at - time < RESYNC_WINDOW_MS);
|
|
248
|
+
connection.resyncs.push(at);
|
|
249
|
+
if (connection.resyncs.length >= 3) {
|
|
250
|
+
const retryAfterMs = yield* Random.nextIntBetween(1_000, 5_000);
|
|
251
|
+
yield* end(connection, ended("OwnerLost", true, retryAfterMs), true);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const previous = connection.resync;
|
|
255
|
+
const after = previous !== undefined
|
|
256
|
+
? previous.after
|
|
257
|
+
: (from ?? actor.through) === "0"
|
|
258
|
+
? undefined
|
|
259
|
+
: (from ?? actor.through);
|
|
260
|
+
connection.resync = {
|
|
261
|
+
after,
|
|
262
|
+
replayed: false,
|
|
263
|
+
sent: false,
|
|
264
|
+
answered: false,
|
|
265
|
+
deadline: at + connection.type.takeoverMs,
|
|
266
|
+
deferred: previous?.deferred ?? [],
|
|
267
|
+
deferredBytes: previous?.deferredBytes ?? 0,
|
|
268
|
+
replayedEvents: previous?.replayedEvents ?? new Set(),
|
|
269
|
+
};
|
|
270
|
+
yield* push(connection, ClientMessage.cases.Resync.make({
|
|
271
|
+
after,
|
|
272
|
+
reason: "OwnerLost",
|
|
273
|
+
deadlineMs: RESYNC_DEADLINE_MS,
|
|
274
|
+
}), true);
|
|
275
|
+
yield* Queue.offer(connection.wake, undefined);
|
|
276
|
+
});
|
|
277
|
+
const observe = (actor, owner) => Effect.gen(function* () {
|
|
278
|
+
const generation = BigInt(owner.generation);
|
|
279
|
+
if (generation < actor.generation)
|
|
280
|
+
return false;
|
|
281
|
+
if (generation > actor.generation) {
|
|
282
|
+
for (const connection of actor.connections.values())
|
|
283
|
+
for (const jobId of connection.progress.keys())
|
|
284
|
+
discardProgress(connection, jobId);
|
|
285
|
+
if (actor.generation > 0n)
|
|
286
|
+
yield* ownerLost(actor);
|
|
287
|
+
actor.generation = generation;
|
|
288
|
+
actor.seq = 0;
|
|
289
|
+
actor.sealed = false;
|
|
290
|
+
}
|
|
291
|
+
actor.owner = owner.owner;
|
|
292
|
+
actor.ownerEpoch = owner.ownerEpoch;
|
|
293
|
+
return true;
|
|
294
|
+
});
|
|
295
|
+
const deliver = (message) => Effect.gen(function* () {
|
|
296
|
+
const actor = actors.get(actorKey(message.ref));
|
|
297
|
+
const unknown = [];
|
|
298
|
+
if (actor === undefined) {
|
|
299
|
+
for (const item of message.items)
|
|
300
|
+
if (HolderItem.guards.Frame(item))
|
|
301
|
+
unknown.push(...item.to);
|
|
302
|
+
return { wrongEpoch: false, unknown };
|
|
303
|
+
}
|
|
304
|
+
if (!(yield* observe(actor, message)))
|
|
305
|
+
return { wrongEpoch: false, unknown };
|
|
306
|
+
if (actor.fresh) {
|
|
307
|
+
actor.fresh = false;
|
|
308
|
+
actor.seq = message.seq - 1;
|
|
309
|
+
}
|
|
310
|
+
if (message.seq <= actor.seq)
|
|
311
|
+
return { wrongEpoch: false, unknown };
|
|
312
|
+
if (message.seq !== actor.seq + 1) {
|
|
313
|
+
for (const connection of actor.connections.values())
|
|
314
|
+
yield* end(connection, ended("SlowConsumer", true), true);
|
|
315
|
+
actor.seq = message.seq;
|
|
316
|
+
return { wrongEpoch: false, unknown };
|
|
317
|
+
}
|
|
318
|
+
actor.seq = message.seq;
|
|
319
|
+
const at = yield* now;
|
|
320
|
+
for (const item of message.items)
|
|
321
|
+
yield* HolderItem.match(item, {
|
|
322
|
+
Frame: (frame) => Effect.forEach(frame.to, (id) => {
|
|
323
|
+
const connection = actor.connections.get(id);
|
|
324
|
+
if (connection === undefined || connection.member !== frame.member) {
|
|
325
|
+
unknown.push(id);
|
|
326
|
+
return Effect.void;
|
|
327
|
+
}
|
|
328
|
+
if (at >= authorizedUntil(connection))
|
|
329
|
+
return end(connection, lapsed(connection, at), true);
|
|
330
|
+
const out = ClientMessage.cases.Frame.make({
|
|
331
|
+
frame: frame.frame,
|
|
332
|
+
cursor: frame.stamp ? message.through : undefined,
|
|
333
|
+
event: frame.stamp ? frame.event : undefined,
|
|
334
|
+
});
|
|
335
|
+
const pending = connection.resync;
|
|
336
|
+
if (pending !== undefined && frame.replay !== true) {
|
|
337
|
+
const bytes = utf8.encode(out.frame).byteLength;
|
|
338
|
+
if (pending.deferred.length + connection.outFrames >= MAX_OUTBOUND_FRAMES ||
|
|
339
|
+
pending.deferredBytes + connection.outBytes + bytes > MAX_OUTBOUND_BYTES ||
|
|
340
|
+
heldBytes + bytes > MAX_HELD_BYTES)
|
|
341
|
+
return end(connection, ended("SlowConsumer", true), true);
|
|
342
|
+
pending.deferred.push(out);
|
|
343
|
+
pending.deferredBytes += bytes;
|
|
344
|
+
heldBytes += bytes;
|
|
345
|
+
return Effect.void;
|
|
346
|
+
}
|
|
347
|
+
if (pending !== undefined && frame.event !== undefined)
|
|
348
|
+
pending.replayedEvents.add(frame.event);
|
|
349
|
+
return push(connection, out, false);
|
|
350
|
+
}, { discard: true }),
|
|
351
|
+
Flushed: (flushed) => Effect.sync(() => {
|
|
352
|
+
actor.through = flushed.through;
|
|
353
|
+
}),
|
|
354
|
+
End: (item) => {
|
|
355
|
+
const connection = actor.connections.get(item.connectionId);
|
|
356
|
+
return connection === undefined
|
|
357
|
+
? Effect.void
|
|
358
|
+
: end(connection, ActorError.make({ reason: item.ended }), false);
|
|
359
|
+
},
|
|
360
|
+
Seal: () => Effect.sync(() => {
|
|
361
|
+
actor.sealed = true;
|
|
362
|
+
}),
|
|
363
|
+
Progress: (item) => Effect.forEach(item.to, (id) => {
|
|
364
|
+
const connection = actor.connections.get(id);
|
|
365
|
+
if (connection === undefined || connection.member !== item.member) {
|
|
366
|
+
unknown.push(id);
|
|
367
|
+
return Effect.void;
|
|
368
|
+
}
|
|
369
|
+
if (at >= authorizedUntil(connection))
|
|
370
|
+
return end(connection, lapsed(connection, at), true);
|
|
371
|
+
return pushProgress(connection, ClientMessage.cases.Progress.make({
|
|
372
|
+
job: item.job,
|
|
373
|
+
jobId: item.jobId,
|
|
374
|
+
attempt: item.attempt,
|
|
375
|
+
seq: item.seq,
|
|
376
|
+
frame: item.frame,
|
|
377
|
+
}));
|
|
378
|
+
}, { discard: true }),
|
|
379
|
+
ProgressEnd: (item) => Effect.sync(() => {
|
|
380
|
+
for (const connection of actor.connections.values())
|
|
381
|
+
discardProgress(connection, item.jobId);
|
|
382
|
+
}),
|
|
383
|
+
});
|
|
384
|
+
return { wrongEpoch: false, unknown };
|
|
385
|
+
});
|
|
386
|
+
const retried = (connection, effect, refused = () => false) => effect.pipe(Effect.retry({
|
|
387
|
+
while: (error) => error.isRetryable && !connection.ended && !refused(error),
|
|
388
|
+
schedule: Schedule.min([
|
|
389
|
+
Schedule.exponential("10 millis", 2),
|
|
390
|
+
Schedule.spaced("250 millis"),
|
|
391
|
+
]),
|
|
392
|
+
}));
|
|
393
|
+
const retrying = (connection, effect, deadlineMs) => retried(connection, effect).pipe(Effect.timeoutOrElse({
|
|
394
|
+
duration: deadlineMs,
|
|
395
|
+
orElse: () => Effect.fail(ended("ActorUnavailable", true)),
|
|
396
|
+
}));
|
|
397
|
+
const authorizedUntil = (connection) => Math.min(connection.lastAuthorized + connection.type.reauthorizeMs, connection.expiresAt ?? Number.POSITIVE_INFINITY);
|
|
398
|
+
const unauthorized = ActorError.make({
|
|
399
|
+
reason: Unauthorized.make({ code: "reauthorization_unavailable" }),
|
|
400
|
+
});
|
|
401
|
+
const credentialExpired = ActorError.make({ reason: Unauthorized.make({ code: "expired" }) });
|
|
402
|
+
const lapsed = (connection, at) => connection.expiresAt !== undefined && at >= connection.expiresAt
|
|
403
|
+
? credentialExpired
|
|
404
|
+
: unauthorized;
|
|
405
|
+
const expired = (connection) => Effect.gen(function* () {
|
|
406
|
+
const at = yield* now;
|
|
407
|
+
if (at < authorizedUntil(connection))
|
|
408
|
+
return false;
|
|
409
|
+
yield* end(connection, lapsed(connection, at), true);
|
|
410
|
+
return true;
|
|
411
|
+
});
|
|
412
|
+
const commandsOf = (connection, seq, issuedAt) => ({
|
|
413
|
+
secret: connection.secret,
|
|
414
|
+
seq,
|
|
415
|
+
issuedAt,
|
|
416
|
+
expiresAt: issuedAt + connection.type.retryWindowMs,
|
|
417
|
+
});
|
|
418
|
+
const inboundLoop = (connection) => Effect.gen(function* () {
|
|
419
|
+
while (!connection.ended) {
|
|
420
|
+
const pending = connection.resync;
|
|
421
|
+
if (pending !== undefined && !pending.sent) {
|
|
422
|
+
if (yield* expired(connection))
|
|
423
|
+
return;
|
|
424
|
+
pending.sent = true;
|
|
425
|
+
const answer = yield* retrying(connection, connection.type.channel.resync({
|
|
426
|
+
...address(connection),
|
|
427
|
+
after: pending.after,
|
|
428
|
+
authorizedUntil: authorizedUntil(connection),
|
|
429
|
+
}), Math.max(0, Math.min(authorizedUntil(connection), pending.deadline) - (yield* now))).pipe(Effect.exit);
|
|
430
|
+
if (connection.ended)
|
|
431
|
+
return;
|
|
432
|
+
if (yield* expired(connection))
|
|
433
|
+
return;
|
|
434
|
+
if (Exit.isFailure(answer)) {
|
|
435
|
+
yield* end(connection, ended("OwnerLost", true), true);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (Predicate.isTagged(answer.value, "Closed")) {
|
|
439
|
+
yield* end(connection, ActorError.make({ reason: answer.value.ended }), true);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
yield* observe(actorOf(connection.ref), answer.value);
|
|
443
|
+
pending.answered = true;
|
|
444
|
+
pending.deadline = (yield* now) + RESYNC_DEADLINE_MS;
|
|
445
|
+
if (connection.type.hasResync(connection.member)) {
|
|
446
|
+
pending.replayed = true;
|
|
447
|
+
yield* push(connection, ClientMessage.cases.ResyncReplayed.make({}), true);
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (pending !== undefined || connection.inbound.length === 0) {
|
|
452
|
+
yield* Queue.take(connection.wake);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (yield* expired(connection))
|
|
456
|
+
return;
|
|
457
|
+
const { frame, issuedAt } = connection.inbound.shift();
|
|
458
|
+
const bytes = utf8.encode(frame).byteLength;
|
|
459
|
+
connection.inBytes -= bytes;
|
|
460
|
+
heldBytes -= bytes;
|
|
461
|
+
yield* Queue.offer(connection.drained, undefined);
|
|
462
|
+
connection.nextSeq += 1;
|
|
463
|
+
const seq = connection.nextSeq;
|
|
464
|
+
const answer = yield* retrying(connection, connection.type.channel.frame({
|
|
465
|
+
...address(connection),
|
|
466
|
+
seq,
|
|
467
|
+
frame,
|
|
468
|
+
authorizedUntil: authorizedUntil(connection),
|
|
469
|
+
commands: commandsOf(connection, seq, issuedAt),
|
|
470
|
+
}), Math.min(connection.type.deliveryMs, authorizedUntil(connection) - (yield* now))).pipe(Effect.exit);
|
|
471
|
+
if (connection.ended)
|
|
472
|
+
return;
|
|
473
|
+
if (yield* expired(connection))
|
|
474
|
+
return;
|
|
475
|
+
if (Exit.isFailure(answer)) {
|
|
476
|
+
const failure = Cause.findErrorOption(answer.cause);
|
|
477
|
+
yield* end(connection, Option.isSome(failure) ? failure.value : ended("ActorUnavailable", true), true);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (Predicate.isTagged(answer.value, "Closed")) {
|
|
481
|
+
yield* end(connection, ActorError.make({ reason: answer.value.ended }), false);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
yield* observe(actorOf(connection.ref), answer.value);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
const check = (session, feed, kind) => isWatchMember(session.member)
|
|
488
|
+
? options.authorize({
|
|
489
|
+
caller: session.caller,
|
|
490
|
+
ref: session.ref,
|
|
491
|
+
command: watchedQuery(session.member),
|
|
492
|
+
kind: kind === "first" ? "watch" : "reauthorize",
|
|
493
|
+
of: kind === "first" ? undefined : "watch",
|
|
494
|
+
})
|
|
495
|
+
: feed === undefined
|
|
496
|
+
? options.authorize({
|
|
497
|
+
caller: session.caller,
|
|
498
|
+
ref: session.ref,
|
|
499
|
+
command: session.member,
|
|
500
|
+
kind: kind === "first" ? "open" : "reauthorize",
|
|
501
|
+
of: kind === "first" ? undefined : "open",
|
|
502
|
+
})
|
|
503
|
+
: Effect.forEach(feed, (tag) => options.authorize({
|
|
504
|
+
caller: session.caller,
|
|
505
|
+
ref: session.ref,
|
|
506
|
+
command: tag,
|
|
507
|
+
kind: kind === "first" ? "feed" : "reauthorize",
|
|
508
|
+
of: kind === "first" ? undefined : "feed",
|
|
509
|
+
})).pipe(Effect.map((answers) => answers.every(Boolean)));
|
|
510
|
+
const reauthorize = (connection, at) => Effect.gen(function* () {
|
|
511
|
+
connection.checking = true;
|
|
512
|
+
const allowed = yield* check(connection, connection.feed, "reauthorize").pipe(Effect.exit);
|
|
513
|
+
connection.checking = false;
|
|
514
|
+
if (connection.ended)
|
|
515
|
+
return;
|
|
516
|
+
const answered = yield* now;
|
|
517
|
+
if (Exit.isSuccess(allowed) && allowed.value && answered >= authorizedUntil(connection))
|
|
518
|
+
yield* end(connection, lapsed(connection, answered), true);
|
|
519
|
+
else if (Exit.isSuccess(allowed) && allowed.value)
|
|
520
|
+
connection.lastAuthorized = at;
|
|
521
|
+
else if (Exit.isSuccess(allowed))
|
|
522
|
+
yield* end(connection, ActorError.make({ reason: Unauthorized.make({ code: "access_denied" }) }), true);
|
|
523
|
+
});
|
|
524
|
+
const liveness = (at) => Effect.gen(function* () {
|
|
525
|
+
const transport = options.transport();
|
|
526
|
+
const checked = [...held.values()].filter((connection) => connection.open);
|
|
527
|
+
const rows = (yield* Effect.forEach(shards, ({ sql, range }) => sql `
|
|
528
|
+
SELECT c.connection_id
|
|
529
|
+
FROM generate_series(${range.first}::int, ${range.last}::int) AS b(bucket)
|
|
530
|
+
CROSS JOIN LATERAL (
|
|
531
|
+
SELECT connection_id FROM actor_connections
|
|
532
|
+
WHERE actor_connections.bucket = b.bucket AND holder = ${transport.holder}
|
|
533
|
+
AND holder_epoch = ${transport.epoch}
|
|
534
|
+
) c`)).flat();
|
|
535
|
+
lastLiveness = at;
|
|
536
|
+
livenessFailedSince = undefined;
|
|
537
|
+
const present = new Set(rows.map((row) => row.connection_id));
|
|
538
|
+
for (const connection of checked)
|
|
539
|
+
if (!present.has(connection.id))
|
|
540
|
+
yield* end(connection, ended("ServerClosed", true), false);
|
|
541
|
+
}).pipe(Effect.catchCause((cause) => Effect.andThen(Effect.sync(() => (livenessFailedSince ??= at)), Effect.logWarning("Holder liveness check failed", cause))));
|
|
542
|
+
const tick = Effect.gen(function* () {
|
|
543
|
+
const at = yield* now;
|
|
544
|
+
for (const connection of held.values()) {
|
|
545
|
+
if (!connection.open)
|
|
546
|
+
continue;
|
|
547
|
+
const every = connection.type.reauthorizeMs;
|
|
548
|
+
if (livenessFailedSince !== undefined &&
|
|
549
|
+
at - Math.max(livenessFailedSince, connection.openedAt) >= every) {
|
|
550
|
+
yield* end(connection, ended("ActorUnavailable", true), false);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (connection.expiresAt !== undefined && at >= connection.expiresAt) {
|
|
554
|
+
yield* end(connection, credentialExpired, true);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (at >= connection.lastAuthorized + every) {
|
|
558
|
+
yield* end(connection, unauthorized, true);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (!connection.checking &&
|
|
562
|
+
at >= connection.lastAuthorized + every - Math.min(10_000, every / 2))
|
|
563
|
+
yield* reauthorize(connection, at).pipe(Effect.forkIn(scope));
|
|
564
|
+
if (connection.resync !== undefined && at >= connection.resync.deadline)
|
|
565
|
+
yield* end(connection, ended("OwnerLost", true), true);
|
|
566
|
+
}
|
|
567
|
+
const transport = options.transport();
|
|
568
|
+
const every = Math.min(LIVENESS_MS, ...[...held.values()].map((connection) => connection.type.reauthorizeMs));
|
|
569
|
+
if (!checkingLiveness && held.size > 0 && at - lastLiveness >= every) {
|
|
570
|
+
checkingLiveness = true;
|
|
571
|
+
yield* liveness(at).pipe(Effect.ensuring(Effect.sync(() => (checkingLiveness = false))), Effect.forkIn(scope));
|
|
572
|
+
}
|
|
573
|
+
for (const actor of actors.values()) {
|
|
574
|
+
if (actor.sealed || actor.owner === "" || at - actor.lastCheck < OWNER_CHECK_MS)
|
|
575
|
+
continue;
|
|
576
|
+
actor.lastCheck = at;
|
|
577
|
+
if (actor.owner === transport.holder && actor.ownerEpoch === transport.epoch)
|
|
578
|
+
continue;
|
|
579
|
+
const { owner, ownerEpoch, generation } = actor;
|
|
580
|
+
yield* transport.ping(owner, ownerEpoch).pipe(Effect.flatMap((alive) => alive ||
|
|
581
|
+
actor.generation !== generation ||
|
|
582
|
+
actor.owner !== owner ||
|
|
583
|
+
actor.ownerEpoch !== ownerEpoch
|
|
584
|
+
? Effect.void
|
|
585
|
+
: ownerLost(actor)), Effect.forkIn(scope));
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
yield* tick.pipe(Effect.repeat(Schedule.spaced(TICK)), Effect.forkIn(scope));
|
|
589
|
+
/**
|
|
590
|
+
* Refuses an open the holder can't take, before anything is held: an
|
|
591
|
+
* unregistered actor or member, oversized params, a full holder, an expired
|
|
592
|
+
* credential, or a caller `authorize` denies. Expiry is checked again after
|
|
593
|
+
* `authorize`, which may have outlasted the credential. Answers the actor type.
|
|
594
|
+
*/
|
|
595
|
+
const admit = (request) => Effect.gen(function* () {
|
|
596
|
+
const type = options.actorType(request.ref.actor);
|
|
597
|
+
if (type === undefined)
|
|
598
|
+
return yield* ActorError.make({
|
|
599
|
+
reason: ActorUnavailable.make({ cause: new Error("Actor not registered") }),
|
|
600
|
+
});
|
|
601
|
+
if (!type.hasMember(request.member))
|
|
602
|
+
return yield* ActorError.make({
|
|
603
|
+
reason: ActorUnavailable.make({ cause: new Error("Connection not declared") }),
|
|
604
|
+
});
|
|
605
|
+
if (utf8.encode(request.params).byteLength > MAX_INBOUND_BYTES)
|
|
606
|
+
return yield* Effect.die(new Error("Connection params exceed 64 KiB"));
|
|
607
|
+
if (held.size >= MAX_HELD_CONNECTIONS)
|
|
608
|
+
return yield* ActorError.make({
|
|
609
|
+
reason: ActorUnavailable.make({ cause: new Error("Holder is at its connection limit") }),
|
|
610
|
+
});
|
|
611
|
+
if (request.expiresAt !== undefined && (yield* now) >= request.expiresAt)
|
|
612
|
+
return yield* credentialExpired;
|
|
613
|
+
const allowed = yield* check(request, request.feed, "first");
|
|
614
|
+
if (!allowed)
|
|
615
|
+
return yield* ActorError.make({ reason: Unauthorized.make({ code: "access_denied" }) });
|
|
616
|
+
if (request.expiresAt !== undefined && (yield* now) >= request.expiresAt)
|
|
617
|
+
return yield* credentialExpired;
|
|
618
|
+
return type;
|
|
619
|
+
});
|
|
620
|
+
/** A new, not yet open connection, held on this holder and its actor. */
|
|
621
|
+
const hold = (request, type) => Effect.gen(function* () {
|
|
622
|
+
const connection = {
|
|
623
|
+
id: yield* crypto.randomUUIDv7.pipe(Effect.orDie),
|
|
624
|
+
ref: request.ref,
|
|
625
|
+
key: actorKey(request.ref),
|
|
626
|
+
member: request.member,
|
|
627
|
+
feed: request.feed,
|
|
628
|
+
caller: request.caller,
|
|
629
|
+
type,
|
|
630
|
+
outbound: yield* Queue.unbounded(),
|
|
631
|
+
secret: connectionSecret(yield* crypto.randomBytes(32).pipe(Effect.orDie)),
|
|
632
|
+
inbound: [],
|
|
633
|
+
wake: yield* Queue.sliding(1),
|
|
634
|
+
drained: yield* Queue.sliding(1),
|
|
635
|
+
expiresAt: request.expiresAt,
|
|
636
|
+
open: false,
|
|
637
|
+
ended: false,
|
|
638
|
+
outFrames: 0,
|
|
639
|
+
outBytes: 0,
|
|
640
|
+
inBytes: 0,
|
|
641
|
+
nextSeq: 0,
|
|
642
|
+
lastAuthorized: yield* now,
|
|
643
|
+
openedAt: 0,
|
|
644
|
+
checking: false,
|
|
645
|
+
resync: undefined,
|
|
646
|
+
resyncs: [],
|
|
647
|
+
loop: undefined,
|
|
648
|
+
progress: new Map(),
|
|
649
|
+
};
|
|
650
|
+
const actor = actorOf(request.ref);
|
|
651
|
+
actor.connections.set(connection.id, connection);
|
|
652
|
+
held.set(connection.id, connection);
|
|
653
|
+
return { actor, connection };
|
|
654
|
+
});
|
|
655
|
+
/**
|
|
656
|
+
* Asks the owner to open the connection, within the delivery timeout. A
|
|
657
|
+
* caller that stops waiting abandons the open: the connection is released
|
|
658
|
+
* and, should the owner still answer `Opened`, closed there again.
|
|
659
|
+
*/
|
|
660
|
+
const openAtOwner = (request, connection) => Effect.gen(function* () {
|
|
661
|
+
const { type } = connection;
|
|
662
|
+
const issuedAt = (yield* now) - COMMAND_SKEW_MS;
|
|
663
|
+
const openCall = type.channel.open({
|
|
664
|
+
...address(connection),
|
|
665
|
+
member: request.member,
|
|
666
|
+
caller: request.caller,
|
|
667
|
+
params: request.params,
|
|
668
|
+
commands: {
|
|
669
|
+
secret: connection.secret,
|
|
670
|
+
seq: 0,
|
|
671
|
+
issuedAt,
|
|
672
|
+
expiresAt: issuedAt + type.retryWindowMs,
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
const attempt = yield* retried(connection, openCall, (error) => isWatchMember(request.member) && Predicate.isTagged(error.reason, "RunnerAtCapacity")).pipe(Effect.forkIn(scope));
|
|
676
|
+
const abandon = Effect.gen(function* () {
|
|
677
|
+
connection.ended = true;
|
|
678
|
+
release(connection);
|
|
679
|
+
yield* deleteRow(connection);
|
|
680
|
+
yield* Fiber.await(attempt).pipe(Effect.flatMap((exit) => Exit.isSuccess(exit) && Predicate.isTagged(exit.value, "Opened")
|
|
681
|
+
? retrying(connection, type.channel.close({
|
|
682
|
+
...address(connection),
|
|
683
|
+
cause: SessionEnded.make({ cause: "ActorUnavailable", resync: true }),
|
|
684
|
+
}), type.deliveryMs).pipe(Effect.catch(() => deleteRow(connection)))
|
|
685
|
+
: Effect.void), Effect.forkIn(scope));
|
|
686
|
+
});
|
|
687
|
+
const answer = yield* Fiber.join(attempt).pipe(Effect.timeoutOrElse({
|
|
688
|
+
duration: type.deliveryMs,
|
|
689
|
+
orElse: () => Effect.fail(ended("ActorUnavailable", true)),
|
|
690
|
+
}), Effect.onError(() => abandon));
|
|
691
|
+
if (Predicate.isTagged(answer, "Failed")) {
|
|
692
|
+
release(connection);
|
|
693
|
+
return yield* OpenRejected.make({ value: answer.value });
|
|
694
|
+
}
|
|
695
|
+
return answer;
|
|
696
|
+
});
|
|
697
|
+
/**
|
|
698
|
+
* Marks an opened connection live under the owner that answered: its
|
|
699
|
+
* inbound loop starts, and a connection the owner recovered resyncs from
|
|
700
|
+
* its baseline first.
|
|
701
|
+
*/
|
|
702
|
+
const start = (actor, connection, answer) => Effect.gen(function* () {
|
|
703
|
+
yield* observe(actor, answer);
|
|
704
|
+
if (BigInt(answer.baseline) > BigInt(actor.through))
|
|
705
|
+
actor.through = answer.baseline;
|
|
706
|
+
if (connection.ended)
|
|
707
|
+
return;
|
|
708
|
+
connection.openedAt = yield* now;
|
|
709
|
+
connection.open = true;
|
|
710
|
+
if (answer.recovered === true)
|
|
711
|
+
yield* resync(actor, connection, yield* now, answer.baseline);
|
|
712
|
+
connection.loop = yield* inboundLoop(connection).pipe(Effect.forkIn(scope));
|
|
713
|
+
});
|
|
714
|
+
/** The client's messages: frames as they are taken, and progress still current. */
|
|
715
|
+
const clientMessages = (connection) => Stream.fromQueue(connection.outbound).pipe(Stream.mapEffect((message) => ClientMessage.guards.Progress(message)
|
|
716
|
+
? takenProgress(connection, message)
|
|
717
|
+
: Effect.as(taken(connection, message), Option.some(message))), Stream.filter(Option.isSome), Stream.map((message) => message.value));
|
|
718
|
+
/** Queues one client frame for the inbound loop; a client over the held limits is slow. */
|
|
719
|
+
const receive = (connection, frame) => Effect.gen(function* () {
|
|
720
|
+
if (connection.ended)
|
|
721
|
+
return yield* ended("ClientClosed", false);
|
|
722
|
+
const bytes = utf8.encode(frame).byteLength;
|
|
723
|
+
if (bytes > MAX_INBOUND_BYTES)
|
|
724
|
+
return yield* Effect.die(new Error("Connection frame exceeds 64 KiB"));
|
|
725
|
+
if (heldBytes + bytes > MAX_HELD_BYTES || connection.inbound.length >= MAX_INBOUND_FRAMES)
|
|
726
|
+
return yield* end(connection, ended("SlowConsumer", true), true);
|
|
727
|
+
connection.inbound.push({ frame, issuedAt: (yield* now) - COMMAND_SKEW_MS });
|
|
728
|
+
connection.inBytes += bytes;
|
|
729
|
+
heldBytes += bytes;
|
|
730
|
+
yield* Queue.offer(connection.wake, undefined);
|
|
731
|
+
});
|
|
732
|
+
/**
|
|
733
|
+
* Ends a resync once the owner answered it and, for a member with a resync
|
|
734
|
+
* handler, replayed it: the messages deferred meanwhile go out, less the
|
|
735
|
+
* events the replay already carried.
|
|
736
|
+
*/
|
|
737
|
+
const finishResync = (connection) => Effect.gen(function* () {
|
|
738
|
+
const pending = connection.resync;
|
|
739
|
+
if (pending === undefined ||
|
|
740
|
+
!pending.answered ||
|
|
741
|
+
(connection.type.hasResync(connection.member) && !pending.replayed))
|
|
742
|
+
return;
|
|
743
|
+
connection.resync = undefined;
|
|
744
|
+
heldBytes -= pending.deferredBytes;
|
|
745
|
+
pending.deferredBytes = 0;
|
|
746
|
+
for (const message of pending.deferred)
|
|
747
|
+
if (!ClientMessage.guards.Frame(message) ||
|
|
748
|
+
message.event === undefined ||
|
|
749
|
+
!pending.replayedEvents.has(message.event))
|
|
750
|
+
yield* push(connection, message, false);
|
|
751
|
+
yield* Queue.offer(connection.wake, undefined);
|
|
752
|
+
});
|
|
753
|
+
/**
|
|
754
|
+
* Renews a connection's authorization; any refusal ends the session. The
|
|
755
|
+
* answer must come back before the current authorization lapses, and within
|
|
756
|
+
* the new credential's expiry.
|
|
757
|
+
*/
|
|
758
|
+
const reauthenticate = (connection, expiresAt) => Effect.gen(function* () {
|
|
759
|
+
const fail = (error) => Effect.andThen(end(connection, error, true), Effect.fail(error));
|
|
760
|
+
if (connection.ended)
|
|
761
|
+
return yield* ended("ClientClosed", false);
|
|
762
|
+
const at = yield* now;
|
|
763
|
+
if (expiresAt !== undefined && at >= expiresAt)
|
|
764
|
+
return yield* fail(credentialExpired);
|
|
765
|
+
const allowed = yield* check(connection, connection.feed, "reauthorize");
|
|
766
|
+
if (connection.ended)
|
|
767
|
+
return yield* ended("ClientClosed", false);
|
|
768
|
+
const answered = yield* now;
|
|
769
|
+
if (answered >= authorizedUntil(connection))
|
|
770
|
+
return yield* fail(lapsed(connection, answered));
|
|
771
|
+
if (!allowed)
|
|
772
|
+
return yield* fail(ActorError.make({ reason: Unauthorized.make({ code: "access_denied" }) }));
|
|
773
|
+
if (expiresAt !== undefined && answered >= expiresAt)
|
|
774
|
+
return yield* fail(credentialExpired);
|
|
775
|
+
connection.lastAuthorized = at;
|
|
776
|
+
connection.expiresAt = expiresAt;
|
|
777
|
+
});
|
|
778
|
+
/** Ends a connection its client closed, and closes it at the owner. */
|
|
779
|
+
const closeByClient = (connection) => Effect.gen(function* () {
|
|
780
|
+
if (connection.ended)
|
|
781
|
+
return;
|
|
782
|
+
const cause = SessionEnded.make({ cause: "ClientClosed", resync: false });
|
|
783
|
+
yield* end(connection, ActorError.make({ reason: cause }), false);
|
|
784
|
+
yield* retrying(connection, connection.type.channel.close({ ...address(connection), cause }), connection.type.deliveryMs).pipe(Effect.catch(() => deleteRow(connection)));
|
|
785
|
+
});
|
|
786
|
+
const open = Effect.fnUntraced(function* (request) {
|
|
787
|
+
const type = yield* admit(request);
|
|
788
|
+
const { actor, connection } = yield* hold(request, type);
|
|
789
|
+
const answer = yield* openAtOwner(request, connection);
|
|
790
|
+
yield* start(actor, connection, answer);
|
|
791
|
+
return {
|
|
792
|
+
connectionId: connection.id,
|
|
793
|
+
cursor: answer.baseline,
|
|
794
|
+
messages: clientMessages(connection),
|
|
795
|
+
send: (frame) => receive(connection, frame),
|
|
796
|
+
resyncDone: finishResync(connection),
|
|
797
|
+
authorized: Effect.map(now, (at) => !connection.ended && at < authorizedUntil(connection)),
|
|
798
|
+
writable: Effect.gen(function* () {
|
|
799
|
+
while (!connection.ended && connection.inbound.length >= MAX_INFLIGHT_FRAMES)
|
|
800
|
+
yield* Queue.take(connection.drained);
|
|
801
|
+
}),
|
|
802
|
+
reauthenticate: (expiresAt) => reauthenticate(connection, expiresAt),
|
|
803
|
+
close: closeByClient(connection),
|
|
804
|
+
};
|
|
805
|
+
});
|
|
806
|
+
yield* Effect.addFinalizer(() => Effect.forEach([...held.values()], (connection) => end(connection, ended("HolderShutdown", true), true), {
|
|
807
|
+
discard: true,
|
|
808
|
+
}));
|
|
809
|
+
return {
|
|
810
|
+
open,
|
|
811
|
+
deliver,
|
|
812
|
+
size: () => held.size,
|
|
813
|
+
/** The clock authorization and credential expiry are measured on, in epoch milliseconds. */
|
|
814
|
+
now,
|
|
815
|
+
};
|
|
816
|
+
});
|