@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,192 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import type { EmittedEvent } from "../members.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The statement that appends a turn's events inside its transaction, and the
|
|
6
|
+
* stamp it records once it replies. The caller already holds the actor's
|
|
7
|
+
* generation row lock, so reserving the next sequence numbers there gives one
|
|
8
|
+
* gap-free order per actor even when activations race; the counter lives on
|
|
9
|
+
* the generation row so pruning never lets a sequence be reused. The
|
|
10
|
+
* reservation and the insert share a statement, so nothing waits on the
|
|
11
|
+
* reserved numbers.
|
|
12
|
+
*
|
|
13
|
+
* The same statement probes the actor's subscription tag summary by key and,
|
|
14
|
+
* when some subscription follows an emitted class, upserts the actor's one
|
|
15
|
+
* feed row, due now. So the turn costs the same with no subscribers or ten
|
|
16
|
+
* thousand, and the relay fans the events out after commit; the stamp's `fed`
|
|
17
|
+
* says the relay should wake. The upsert resets `attempts`, so a relay
|
|
18
|
+
* expanding the feed while this commits keeps it due. Routed subscriptions
|
|
19
|
+
* registered here that name an emitted class get their missing source-side
|
|
20
|
+
* rows first, starting at this turn's first event. Those rows are widened,
|
|
21
|
+
* never narrowed, so an older runner cannot shrink one back, and their tag
|
|
22
|
+
* summary moves in the same statement by the tags the upsert added. The
|
|
23
|
+
* statement locks the routed rows it reads and builds each upserted row from
|
|
24
|
+
* the locked version, so the lock precedes the write whatever the plan: a
|
|
25
|
+
* settle that widened a row, or a cleanup that deleted one, after this
|
|
26
|
+
* statement's snapshot is read as committed, never counted twice or lost.
|
|
27
|
+
*/
|
|
28
|
+
export declare const eventsStatement: (request: {
|
|
29
|
+
readonly ref: {
|
|
30
|
+
readonly tenant: string;
|
|
31
|
+
readonly actor: string;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
};
|
|
34
|
+
readonly caller: {
|
|
35
|
+
readonly _tag: "Anonymous";
|
|
36
|
+
} | {
|
|
37
|
+
readonly _tag: "System";
|
|
38
|
+
readonly source: "actor" | "cron" | "job" | "process" | "subscription" | "timer" | "workflow";
|
|
39
|
+
readonly ref?: {
|
|
40
|
+
readonly tenant: string;
|
|
41
|
+
readonly actor: string;
|
|
42
|
+
readonly id: string;
|
|
43
|
+
} | undefined;
|
|
44
|
+
readonly onBehalfOf?: {
|
|
45
|
+
readonly subject: string;
|
|
46
|
+
} | undefined;
|
|
47
|
+
readonly mint?: {
|
|
48
|
+
readonly commandId: string;
|
|
49
|
+
readonly ordinal: number;
|
|
50
|
+
} | undefined;
|
|
51
|
+
} | {
|
|
52
|
+
readonly _tag: "User";
|
|
53
|
+
readonly subject: string;
|
|
54
|
+
};
|
|
55
|
+
readonly command: string;
|
|
56
|
+
readonly commandId: string;
|
|
57
|
+
readonly payload: string;
|
|
58
|
+
readonly external?: boolean | undefined;
|
|
59
|
+
readonly clockOffset?: number | undefined;
|
|
60
|
+
readonly redelivered?: boolean | undefined;
|
|
61
|
+
readonly intent?: {
|
|
62
|
+
readonly tenant: string;
|
|
63
|
+
readonly actor: string;
|
|
64
|
+
readonly id: string;
|
|
65
|
+
} | undefined;
|
|
66
|
+
readonly delivery?: {
|
|
67
|
+
readonly subscription: string;
|
|
68
|
+
readonly sourceType: string;
|
|
69
|
+
readonly sourceId: string;
|
|
70
|
+
readonly epoch: string;
|
|
71
|
+
readonly kind: "event" | "gap" | "rejected";
|
|
72
|
+
readonly position: string;
|
|
73
|
+
} | undefined;
|
|
74
|
+
readonly queuedAtMs?: number | undefined;
|
|
75
|
+
readonly usageToken?: string | undefined;
|
|
76
|
+
}, routingKey: bigint, events: readonly EmittedEvent[]) => Effect.Effect<{
|
|
77
|
+
statement: Effect.Effect<void, import("effect/sql/SqlError").SqlError, never>;
|
|
78
|
+
stamp: {
|
|
79
|
+
emittedAtMs: number;
|
|
80
|
+
fed: boolean;
|
|
81
|
+
};
|
|
82
|
+
}, never, SqlClient.SqlClient>;
|
|
83
|
+
/**
|
|
84
|
+
* Re-arms the timers of this actor's workflows that wait for one of the
|
|
85
|
+
* emitted classes; the result says whether the relay should wake.
|
|
86
|
+
*/
|
|
87
|
+
export declare const notifyEvents: (request: {
|
|
88
|
+
readonly ref: {
|
|
89
|
+
readonly tenant: string;
|
|
90
|
+
readonly actor: string;
|
|
91
|
+
readonly id: string;
|
|
92
|
+
};
|
|
93
|
+
readonly caller: {
|
|
94
|
+
readonly _tag: "Anonymous";
|
|
95
|
+
} | {
|
|
96
|
+
readonly _tag: "System";
|
|
97
|
+
readonly source: "actor" | "cron" | "job" | "process" | "subscription" | "timer" | "workflow";
|
|
98
|
+
readonly ref?: {
|
|
99
|
+
readonly tenant: string;
|
|
100
|
+
readonly actor: string;
|
|
101
|
+
readonly id: string;
|
|
102
|
+
} | undefined;
|
|
103
|
+
readonly onBehalfOf?: {
|
|
104
|
+
readonly subject: string;
|
|
105
|
+
} | undefined;
|
|
106
|
+
readonly mint?: {
|
|
107
|
+
readonly commandId: string;
|
|
108
|
+
readonly ordinal: number;
|
|
109
|
+
} | undefined;
|
|
110
|
+
} | {
|
|
111
|
+
readonly _tag: "User";
|
|
112
|
+
readonly subject: string;
|
|
113
|
+
};
|
|
114
|
+
readonly command: string;
|
|
115
|
+
readonly commandId: string;
|
|
116
|
+
readonly payload: string;
|
|
117
|
+
readonly external?: boolean | undefined;
|
|
118
|
+
readonly clockOffset?: number | undefined;
|
|
119
|
+
readonly redelivered?: boolean | undefined;
|
|
120
|
+
readonly intent?: {
|
|
121
|
+
readonly tenant: string;
|
|
122
|
+
readonly actor: string;
|
|
123
|
+
readonly id: string;
|
|
124
|
+
} | undefined;
|
|
125
|
+
readonly delivery?: {
|
|
126
|
+
readonly subscription: string;
|
|
127
|
+
readonly sourceType: string;
|
|
128
|
+
readonly sourceId: string;
|
|
129
|
+
readonly epoch: string;
|
|
130
|
+
readonly kind: "event" | "gap" | "rejected";
|
|
131
|
+
readonly position: string;
|
|
132
|
+
} | undefined;
|
|
133
|
+
readonly queuedAtMs?: number | undefined;
|
|
134
|
+
readonly usageToken?: string | undefined;
|
|
135
|
+
}, routingKey: bigint, events: readonly EmittedEvent[], waited: ReadonlySet<string>) => Effect.Effect<boolean, import("effect/sql/SqlError").SqlError, import("effect/Crypto").Crypto | SqlClient.SqlClient>;
|
|
136
|
+
/**
|
|
137
|
+
* Appends a turn's events and notifies their waiting workflows, one statement
|
|
138
|
+
* at a time; the result says whether the relay should wake, and when the
|
|
139
|
+
* events were stamped.
|
|
140
|
+
*/
|
|
141
|
+
export declare const appendEvents: (request: {
|
|
142
|
+
readonly ref: {
|
|
143
|
+
readonly tenant: string;
|
|
144
|
+
readonly actor: string;
|
|
145
|
+
readonly id: string;
|
|
146
|
+
};
|
|
147
|
+
readonly caller: {
|
|
148
|
+
readonly _tag: "Anonymous";
|
|
149
|
+
} | {
|
|
150
|
+
readonly _tag: "System";
|
|
151
|
+
readonly source: "actor" | "cron" | "job" | "process" | "subscription" | "timer" | "workflow";
|
|
152
|
+
readonly ref?: {
|
|
153
|
+
readonly tenant: string;
|
|
154
|
+
readonly actor: string;
|
|
155
|
+
readonly id: string;
|
|
156
|
+
} | undefined;
|
|
157
|
+
readonly onBehalfOf?: {
|
|
158
|
+
readonly subject: string;
|
|
159
|
+
} | undefined;
|
|
160
|
+
readonly mint?: {
|
|
161
|
+
readonly commandId: string;
|
|
162
|
+
readonly ordinal: number;
|
|
163
|
+
} | undefined;
|
|
164
|
+
} | {
|
|
165
|
+
readonly _tag: "User";
|
|
166
|
+
readonly subject: string;
|
|
167
|
+
};
|
|
168
|
+
readonly command: string;
|
|
169
|
+
readonly commandId: string;
|
|
170
|
+
readonly payload: string;
|
|
171
|
+
readonly external?: boolean | undefined;
|
|
172
|
+
readonly clockOffset?: number | undefined;
|
|
173
|
+
readonly redelivered?: boolean | undefined;
|
|
174
|
+
readonly intent?: {
|
|
175
|
+
readonly tenant: string;
|
|
176
|
+
readonly actor: string;
|
|
177
|
+
readonly id: string;
|
|
178
|
+
} | undefined;
|
|
179
|
+
readonly delivery?: {
|
|
180
|
+
readonly subscription: string;
|
|
181
|
+
readonly sourceType: string;
|
|
182
|
+
readonly sourceId: string;
|
|
183
|
+
readonly epoch: string;
|
|
184
|
+
readonly kind: "event" | "gap" | "rejected";
|
|
185
|
+
readonly position: string;
|
|
186
|
+
} | undefined;
|
|
187
|
+
readonly queuedAtMs?: number | undefined;
|
|
188
|
+
readonly usageToken?: string | undefined;
|
|
189
|
+
}, routingKey: bigint, events: readonly EmittedEvent[], waited?: ReadonlySet<string> | undefined) => Effect.Effect<{
|
|
190
|
+
notified: boolean;
|
|
191
|
+
emittedAtMs: number;
|
|
192
|
+
}, import("effect/sql/SqlError").SqlError, import("effect/Crypto").Crypto | SqlClient.SqlClient>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { System } from "../../identity/caller.js";
|
|
4
|
+
import { FrameworkClock } from "../turn/admission.js";
|
|
5
|
+
import { bucketOf, CallerJson, OutboxRuntime, textArray } from "../turn/outbox.js";
|
|
6
|
+
import { compress } from "../storage/codec.js";
|
|
7
|
+
import { notifyWaits } from "../workflows/engine.js";
|
|
8
|
+
import { summarize } from "../subscriptions/storage.js";
|
|
9
|
+
/** The outbox key of an actor's feed row, which tells the relay to expand its subscriptions. */
|
|
10
|
+
const FEED_KEY = "$feed";
|
|
11
|
+
/**
|
|
12
|
+
* The statement that appends a turn's events inside its transaction, and the
|
|
13
|
+
* stamp it records once it replies. The caller already holds the actor's
|
|
14
|
+
* generation row lock, so reserving the next sequence numbers there gives one
|
|
15
|
+
* gap-free order per actor even when activations race; the counter lives on
|
|
16
|
+
* the generation row so pruning never lets a sequence be reused. The
|
|
17
|
+
* reservation and the insert share a statement, so nothing waits on the
|
|
18
|
+
* reserved numbers.
|
|
19
|
+
*
|
|
20
|
+
* The same statement probes the actor's subscription tag summary by key and,
|
|
21
|
+
* when some subscription follows an emitted class, upserts the actor's one
|
|
22
|
+
* feed row, due now. So the turn costs the same with no subscribers or ten
|
|
23
|
+
* thousand, and the relay fans the events out after commit; the stamp's `fed`
|
|
24
|
+
* says the relay should wake. The upsert resets `attempts`, so a relay
|
|
25
|
+
* expanding the feed while this commits keeps it due. Routed subscriptions
|
|
26
|
+
* registered here that name an emitted class get their missing source-side
|
|
27
|
+
* rows first, starting at this turn's first event. Those rows are widened,
|
|
28
|
+
* never narrowed, so an older runner cannot shrink one back, and their tag
|
|
29
|
+
* summary moves in the same statement by the tags the upsert added. The
|
|
30
|
+
* statement locks the routed rows it reads and builds each upserted row from
|
|
31
|
+
* the locked version, so the lock precedes the write whatever the plan: a
|
|
32
|
+
* settle that widened a row, or a cleanup that deleted one, after this
|
|
33
|
+
* statement's snapshot is read as committed, never counted twice or lost.
|
|
34
|
+
*/
|
|
35
|
+
export const eventsStatement = Effect.fnUntraced(function* (request, routingKey, events) {
|
|
36
|
+
const sql = yield* SqlClient.SqlClient;
|
|
37
|
+
const clock = yield* FrameworkClock;
|
|
38
|
+
const outbox = yield* OutboxRuntime;
|
|
39
|
+
const { tenant, actor, id } = request.ref;
|
|
40
|
+
const stamp = { emittedAtMs: 0, fed: false };
|
|
41
|
+
const emitted = [...new Set(events.map((event) => event.tag))];
|
|
42
|
+
const routed = outbox
|
|
43
|
+
.routed(actor)
|
|
44
|
+
.filter((sub) => sub.events.some((tag) => emitted.includes(tag)));
|
|
45
|
+
const values = sql.csv(events.map((event, index) => sql `(${index + 1}::bigint, ${event.tag}::text, ${compress(event.value)}::bytea, ${event.version}::int)`));
|
|
46
|
+
const source = (alias) => sql `${sql(alias)}.routing_key = ${routingKey} AND ${sql(alias)}.tenant_id = ${tenant}
|
|
47
|
+
AND ${sql(alias)}.source_type = ${actor} AND ${sql(alias)}.source_id = ${id}`;
|
|
48
|
+
const caller = yield* Schema.encodeEffect(CallerJson)(System.make({ source: "actor", ref: request.ref })).pipe(Effect.orDie);
|
|
49
|
+
const routedRows = routed.length === 0
|
|
50
|
+
? sql.literal("")
|
|
51
|
+
: sql `, routed_want (subscriber_type, subscription, events) AS (
|
|
52
|
+
VALUES ${sql.csv(routed.map((sub) => sql `(${sub.subscriberType}::text, ${sub.tag}::text, ${textArray({ sql, values: sub.events })})`))}),
|
|
53
|
+
routed_old AS MATERIALIZED (
|
|
54
|
+
SELECT s.subscriber_type, s.subscription, s.events FROM actor_subscriptions s
|
|
55
|
+
JOIN routed_want w USING (subscriber_type, subscription)
|
|
56
|
+
WHERE ${source("s")} AND s.subscriber_id = ''
|
|
57
|
+
FOR UPDATE OF s),
|
|
58
|
+
routed_new AS (
|
|
59
|
+
INSERT INTO actor_subscriptions (routing_key, tenant_id, source_type, source_id,
|
|
60
|
+
subscriber_type, subscription, subscriber_id, events, epoch, active, delivered, bucket)
|
|
61
|
+
SELECT ${routingKey}, ${tenant}, ${actor}, ${id}, w.subscriber_type, w.subscription, '',
|
|
62
|
+
CASE WHEN o.events IS NULL THEN w.events
|
|
63
|
+
ELSE ARRAY(SELECT DISTINCT e FROM unnest(o.events || w.events) AS u(e) ORDER BY e) END,
|
|
64
|
+
0, true, reserved.base, ${bucketOf(routingKey)}
|
|
65
|
+
FROM routed_want w LEFT JOIN routed_old o USING (subscriber_type, subscription), reserved
|
|
66
|
+
ON CONFLICT (routing_key, tenant_id, source_type, source_id, subscriber_type, subscription, subscriber_id)
|
|
67
|
+
DO UPDATE SET events = EXCLUDED.events
|
|
68
|
+
WHERE NOT actor_subscriptions.events @> EXCLUDED.events
|
|
69
|
+
RETURNING subscriber_type, subscription, events),
|
|
70
|
+
routed_changed AS (
|
|
71
|
+
SELECT ${routingKey}::bigint AS routing_key, ${tenant}::text AS tenant_id,
|
|
72
|
+
${actor}::text AS source_type, ${id}::text AS source_id,
|
|
73
|
+
coalesce(o.events, '{}'::text[]) AS was, n.events AS now
|
|
74
|
+
FROM routed_new n LEFT JOIN routed_old o USING (subscriber_type, subscription)),
|
|
75
|
+
${summarize({ sql, changed: "routed_changed" })}`;
|
|
76
|
+
const statement = Effect.map(sql `WITH reserved AS (
|
|
77
|
+
UPDATE actor_generations SET event_sequence = event_sequence + ${events.length}
|
|
78
|
+
WHERE routing_key = ${routingKey} AND tenant_id = ${tenant} AND actor_type = ${actor} AND actor_id = ${id}
|
|
79
|
+
RETURNING event_sequence - ${events.length} AS base,
|
|
80
|
+
floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint + ${clock.offsetMillis()} AS now
|
|
81
|
+
),
|
|
82
|
+
appended AS (
|
|
83
|
+
INSERT INTO actor_events (routing_key, tenant_id, actor_type, actor_id, sequence, event, command_id, value, emitted_at_ms, payload_version)
|
|
84
|
+
SELECT ${routingKey}, ${tenant}, ${actor}, ${id}, reserved.base + staged.ordinal, staged.event,
|
|
85
|
+
${request.commandId}, staged.value, reserved.now, staged.version
|
|
86
|
+
FROM reserved, (VALUES ${values}) AS staged (ordinal, event, value, version)
|
|
87
|
+
RETURNING 1)
|
|
88
|
+
${routedRows},
|
|
89
|
+
feed AS (
|
|
90
|
+
INSERT INTO actor_outbox (routing_key, intent_id, kind, bucket, due_at_ms, scheduled_at_ms,
|
|
91
|
+
tenant_id, actor_type, actor_id, timer_key, target_type, target_id, command, payload, caller)
|
|
92
|
+
SELECT ${routingKey}, gen_random_uuid()::text, 'feed', ${bucketOf(routingKey)}, reserved.now,
|
|
93
|
+
reserved.now, ${tenant}, ${actor}, ${id}, ${FEED_KEY}, ${actor}, ${id}, ${FEED_KEY}, '', ${caller}
|
|
94
|
+
FROM reserved
|
|
95
|
+
WHERE EXISTS (SELECT 1 FROM actor_subscription_tags t
|
|
96
|
+
WHERE ${source("t")} AND t.event IN ${sql.in(emitted)})
|
|
97
|
+
${routed.length === 0 ? sql.literal("") : sql.literal("OR EXISTS (SELECT 1 FROM routed_new)")}
|
|
98
|
+
ON CONFLICT (routing_key, tenant_id, actor_type, actor_id, timer_key) WHERE timer_key IS NOT NULL
|
|
99
|
+
DO UPDATE SET due_at_ms = least(actor_outbox.due_at_ms, EXCLUDED.due_at_ms), attempts = 0
|
|
100
|
+
RETURNING 1)
|
|
101
|
+
SELECT reserved.now::text AS emitted_at_ms, (SELECT count(*) FROM feed)::int AS fed
|
|
102
|
+
FROM reserved`, (rows) => {
|
|
103
|
+
stamp.emittedAtMs = Number(rows[0].emitted_at_ms);
|
|
104
|
+
stamp.fed = rows[0].fed > 0;
|
|
105
|
+
});
|
|
106
|
+
return { statement, stamp };
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Re-arms the timers of this actor's workflows that wait for one of the
|
|
110
|
+
* emitted classes; the result says whether the relay should wake.
|
|
111
|
+
*/
|
|
112
|
+
export const notifyEvents = Effect.fnUntraced(function* (request, routingKey, events, waited) {
|
|
113
|
+
const tags = [...new Set(events.map((event) => event.tag))].filter((tag) => waited.has(tag));
|
|
114
|
+
return tags.length === 0 ? false : yield* notifyWaits(routingKey, request.ref, tags);
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Appends a turn's events and notifies their waiting workflows, one statement
|
|
118
|
+
* at a time; the result says whether the relay should wake, and when the
|
|
119
|
+
* events were stamped.
|
|
120
|
+
*/
|
|
121
|
+
export const appendEvents = Effect.fnUntraced(function* (request, routingKey, events, waited = new Set()) {
|
|
122
|
+
if (events.length === 0)
|
|
123
|
+
return { notified: false, emittedAtMs: 0 };
|
|
124
|
+
const { statement, stamp } = yield* eventsStatement(request, routingKey, events);
|
|
125
|
+
yield* statement;
|
|
126
|
+
const notified = yield* notifyEvents(request, routingKey, events, waited);
|
|
127
|
+
return { notified: notified || stamp.fed, emittedAtMs: stamp.emittedAtMs };
|
|
128
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** A cursor is an event sequence: a non-negative 64-bit integer in canonical decimal. */
|
|
2
|
+
const CURSOR = /^(0|[1-9][0-9]{0,18})$/;
|
|
3
|
+
const MAX_SEQUENCE = 2n ** 63n - 1n;
|
|
4
|
+
/** True for a canonical cursor a stream could have issued. */
|
|
5
|
+
export const isCursor = (cursor) => CURSOR.test(cursor) && BigInt(cursor) <= MAX_SEQUENCE;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { RetentionGap, UnknownCursor } from "../../errors/events.ts";
|
|
4
|
+
import type { StoredEvent } from "../members.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Replays committed events of the given tags after an exclusive cursor, up to `head`,
|
|
7
|
+
* the last sequence committed when the query read state. Every event up to
|
|
8
|
+
* `head` had committed by then, so a later read sees each of them unless it
|
|
9
|
+
* was pruned; the oldest retained event and the matching rows come from one
|
|
10
|
+
* statement, and because pruning only removes a prefix, an oldest event past
|
|
11
|
+
* the cursor's successor means history is missing. A page stops after
|
|
12
|
+
* `limit` matching events.
|
|
13
|
+
*/
|
|
14
|
+
export declare const replayEvents: (ref: {
|
|
15
|
+
readonly tenant: string;
|
|
16
|
+
readonly actor: string;
|
|
17
|
+
readonly id: string;
|
|
18
|
+
}, routingKey: bigint, tags: readonly string[], after: string | undefined, head: bigint, limit: number) => Effect.Effect<(StoredEvent & {
|
|
19
|
+
readonly tag: string;
|
|
20
|
+
})[], RetentionGap | import("effect/sql/SqlError").SqlError | UnknownCursor, SqlClient.SqlClient>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { RetentionGap, UnknownCursor } from "../../errors/events.js";
|
|
4
|
+
import { decompress } from "../storage/codec.js";
|
|
5
|
+
import { isCursor } from "./cursor.js";
|
|
6
|
+
/**
|
|
7
|
+
* Replays committed events of the given tags after an exclusive cursor, up to `head`,
|
|
8
|
+
* the last sequence committed when the query read state. Every event up to
|
|
9
|
+
* `head` had committed by then, so a later read sees each of them unless it
|
|
10
|
+
* was pruned; the oldest retained event and the matching rows come from one
|
|
11
|
+
* statement, and because pruning only removes a prefix, an oldest event past
|
|
12
|
+
* the cursor's successor means history is missing. A page stops after
|
|
13
|
+
* `limit` matching events.
|
|
14
|
+
*/
|
|
15
|
+
export const replayEvents = Effect.fnUntraced(function* (ref, routingKey, tags, after, head, limit) {
|
|
16
|
+
const cursor = after ?? "0";
|
|
17
|
+
if (!isCursor(cursor) || BigInt(cursor) > head)
|
|
18
|
+
return yield* UnknownCursor.make({ cursor });
|
|
19
|
+
const position = BigInt(cursor);
|
|
20
|
+
const sql = yield* SqlClient.SqlClient;
|
|
21
|
+
const owner = (alias) => sql `${sql(alias)}.routing_key = ${routingKey} AND ${sql(alias)}.tenant_id = ${ref.tenant}
|
|
22
|
+
AND ${sql(alias)}.actor_type = ${ref.actor} AND ${sql(alias)}.actor_id = ${ref.id}`;
|
|
23
|
+
const rows = yield* sql `
|
|
24
|
+
SELECT (SELECT min(o.sequence)::text FROM actor_events o WHERE ${owner("o")}) AS oldest,
|
|
25
|
+
e.sequence::text AS sequence, e.event, e.command_id, e.value, e.payload_version,
|
|
26
|
+
e.emitted_at_ms::text AS emitted_at_ms
|
|
27
|
+
FROM (VALUES (1)) AS one (x)
|
|
28
|
+
LEFT JOIN actor_events e ON ${owner("e")} AND e.sequence > ${position}
|
|
29
|
+
AND e.sequence <= ${head} AND e.event IN ${sql.in(tags)}
|
|
30
|
+
ORDER BY e.sequence
|
|
31
|
+
LIMIT ${limit}`;
|
|
32
|
+
const oldest = rows[0]?.oldest;
|
|
33
|
+
if (position < head && (oldest == null || BigInt(oldest) > position + 1n))
|
|
34
|
+
return yield* RetentionGap.make({ cursor });
|
|
35
|
+
return rows
|
|
36
|
+
.filter((row) => row.sequence !== null)
|
|
37
|
+
.map((row) => ({
|
|
38
|
+
cursor: row.sequence,
|
|
39
|
+
tag: row.event,
|
|
40
|
+
commandId: row.command_id,
|
|
41
|
+
value: decompress(row.value),
|
|
42
|
+
version: row.payload_version,
|
|
43
|
+
timestampMs: Number(row.emitted_at_ms),
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Context, Crypto, Effect } from "effect";
|
|
2
|
+
import { Sharding } from "effect/cluster";
|
|
3
|
+
import { SqlClient } from "effect/sql";
|
|
4
|
+
import { ActorError } from "../errors/actor.ts";
|
|
5
|
+
import type { InternalActors } from "./actors.ts";
|
|
6
|
+
import type { Registration } from "./members.ts";
|
|
7
|
+
import type { Request } from "./request.ts";
|
|
8
|
+
import type { ActorRef } from "../identity/caller.ts";
|
|
9
|
+
import type { Transport } from "./connections/transport.ts";
|
|
10
|
+
import type { FrameworkClock } from "./turn/admission.ts";
|
|
11
|
+
/**
|
|
12
|
+
* The runtime's feeds to one subscriber: a page of an actor's committed
|
|
13
|
+
* events after a cursor, upcast to their current version, and a stream
|
|
14
|
+
* member's live items from the actor's connection entity. A stream whose
|
|
15
|
+
* owner lives on another runner is pinged and ends with `SessionEnded` once
|
|
16
|
+
* that owner is gone.
|
|
17
|
+
*/
|
|
18
|
+
export declare const eventFeeds: ({ registrations, services, allow, sharding, entityId, frameworkClock, transport, }: {
|
|
19
|
+
readonly registrations: ReadonlyMap<string, Registration>;
|
|
20
|
+
readonly services: Context.Context<SqlClient.SqlClient | Crypto.Crypto | Sharding.Sharding>;
|
|
21
|
+
readonly allow: (request: Request, kind?: "command" | "query" | "stream") => Effect.Effect<void, ActorError>;
|
|
22
|
+
readonly sharding: Sharding.Sharding["Service"];
|
|
23
|
+
readonly entityId: (ref: ActorRef) => Effect.Effect<string>;
|
|
24
|
+
readonly frameworkClock: (typeof FrameworkClock)["Service"];
|
|
25
|
+
readonly transport: Transport;
|
|
26
|
+
}) => Pick<InternalActors["Service"], "readFeed" | "subscribe">;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Cause, Clock, Context, Crypto, Deferred, Effect, Option, Schedule, Schema, Stream, } from "effect";
|
|
2
|
+
import { Sharding } from "effect/cluster";
|
|
3
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
4
|
+
import { ActorError, ActorUnavailable, NotCreated, SessionEnded } from "../errors/actor.js";
|
|
5
|
+
import { StreamFailed, StreamItem } from "./connections/protocol.js";
|
|
6
|
+
import { connectionEntity } from "./entity/register.js";
|
|
7
|
+
import { withTenant } from "./database/tenancy.js";
|
|
8
|
+
import { replayEvents } from "./events/replay.js";
|
|
9
|
+
import { routingKey } from "./storage/codec.js";
|
|
10
|
+
/** How often a subscriber checks that a stream's owner on another runner is alive. */
|
|
11
|
+
const OWNER_CHECK_INTERVAL = "1 second";
|
|
12
|
+
const activationEnded = () => ActorError.make({ reason: SessionEnded.make({ cause: "ActivationEnded", resync: false }) });
|
|
13
|
+
/**
|
|
14
|
+
* The runtime's feeds to one subscriber: a page of an actor's committed
|
|
15
|
+
* events after a cursor, upcast to their current version, and a stream
|
|
16
|
+
* member's live items from the actor's connection entity. A stream whose
|
|
17
|
+
* owner lives on another runner is pinged and ends with `SessionEnded` once
|
|
18
|
+
* that owner is gone.
|
|
19
|
+
*/
|
|
20
|
+
export const eventFeeds = ({ registrations, services, allow, sharding, entityId, frameworkClock, transport, }) => ({
|
|
21
|
+
readFeed: Effect.fnUntraced(function* (ref, tags, after, limit) {
|
|
22
|
+
const registration = registrations.get(ref.actor);
|
|
23
|
+
if (registration === undefined)
|
|
24
|
+
return yield* ActorError.make({
|
|
25
|
+
reason: ActorUnavailable.make({ cause: new Error("Actor not registered") }),
|
|
26
|
+
});
|
|
27
|
+
const key = routingKey({ ref, placement: registration.placement });
|
|
28
|
+
const sql = yield* SqlClient.SqlClient;
|
|
29
|
+
const events = yield* Effect.gen(function* () {
|
|
30
|
+
const [row] = yield* sql `
|
|
31
|
+
SELECT event_sequence::text AS head FROM actor_generations
|
|
32
|
+
WHERE routing_key = ${key} AND tenant_id = ${ref.tenant}
|
|
33
|
+
AND actor_type = ${ref.actor} AND actor_id = ${ref.id}`;
|
|
34
|
+
if (row === undefined)
|
|
35
|
+
return yield* ActorError.make({ reason: NotCreated.make({}) });
|
|
36
|
+
return yield* replayEvents(ref, key, tags, after, BigInt(row.head), limit);
|
|
37
|
+
}).pipe(withTenant(ref.tenant));
|
|
38
|
+
return yield* Effect.forEach(events, (event) => registration.upcastEvent(event.tag, event.version, event.value).pipe(Effect.map((value) => ({
|
|
39
|
+
cursor: event.cursor,
|
|
40
|
+
tag: event.tag,
|
|
41
|
+
commandId: event.commandId,
|
|
42
|
+
value,
|
|
43
|
+
timestampMs: event.timestampMs,
|
|
44
|
+
}))));
|
|
45
|
+
}, Effect.provideContext(services), Effect.catchIf(SqlError.isSqlError, (cause) => Effect.fail(ActorError.make({ reason: ActorUnavailable.make({ cause }) })))),
|
|
46
|
+
subscribe: (request) => Stream.unwrap(Effect.gen(function* () {
|
|
47
|
+
const registration = registrations.get(request.ref.actor);
|
|
48
|
+
if (registration === undefined || !registration.streams.has(request.command))
|
|
49
|
+
return yield* ActorError.make({
|
|
50
|
+
reason: ActorUnavailable.make({ cause: new Error("Stream not registered") }),
|
|
51
|
+
});
|
|
52
|
+
yield* allow(request, "stream");
|
|
53
|
+
const client = (yield* sharding.makeClient(connectionEntity(request.ref.actor)))(yield* entityId(request.ref));
|
|
54
|
+
const authorizedUntil = (yield* Clock.currentTimeMillis) +
|
|
55
|
+
frameworkClock.offsetMillis() +
|
|
56
|
+
registration.policy.reauthorizeMs;
|
|
57
|
+
const started = yield* Deferred.make();
|
|
58
|
+
const lost = yield* Deferred.make();
|
|
59
|
+
let owner;
|
|
60
|
+
yield* Deferred.await(started).pipe(Effect.flatMap(({ owner, ownerEpoch }) => owner === transport.holder
|
|
61
|
+
? Effect.never
|
|
62
|
+
: transport.ping(owner, ownerEpoch).pipe(Effect.repeat({
|
|
63
|
+
schedule: Schedule.spaced(OWNER_CHECK_INTERVAL),
|
|
64
|
+
while: (alive) => alive,
|
|
65
|
+
}), Effect.andThen(Deferred.fail(lost, activationEnded())))), Effect.forkScoped);
|
|
66
|
+
let finished = false;
|
|
67
|
+
return client
|
|
68
|
+
.Subscribe({
|
|
69
|
+
member: request.command,
|
|
70
|
+
caller: request.caller,
|
|
71
|
+
input: request.payload,
|
|
72
|
+
authorizedUntil,
|
|
73
|
+
})
|
|
74
|
+
.pipe(Stream.tap((item) => Effect.gen(function* () {
|
|
75
|
+
if (StreamItem.guards.Done(item))
|
|
76
|
+
finished = true;
|
|
77
|
+
if (!StreamItem.guards.Started(item))
|
|
78
|
+
return;
|
|
79
|
+
if (owner !== undefined)
|
|
80
|
+
return yield* activationEnded();
|
|
81
|
+
owner = item;
|
|
82
|
+
yield* Deferred.succeed(started, item);
|
|
83
|
+
})), Stream.takeWhile((item) => !StreamItem.guards.Done(item)), Stream.filter(StreamItem.guards.Element), Stream.map((item) => item.value), Stream.concat(Stream.fromEffect(Effect.suspend(() => (finished ? Effect.void : activationEnded()))).pipe(Stream.drain)), Stream.interruptWhen(Deferred.await(lost)), Stream.catchCause((cause) => {
|
|
84
|
+
const failure = Cause.findErrorOption(cause);
|
|
85
|
+
if (Option.isSome(failure)) {
|
|
86
|
+
if (Schema.is(ActorError)(failure.value))
|
|
87
|
+
return Stream.fail(failure.value);
|
|
88
|
+
if (Schema.is(StreamFailed)(failure.value))
|
|
89
|
+
return Stream.fail({ failure: failure.value.value });
|
|
90
|
+
}
|
|
91
|
+
if (Cause.hasInterruptsOnly(cause))
|
|
92
|
+
return Stream.fromEffect(Effect.interrupt);
|
|
93
|
+
return Stream.fail(owner === undefined
|
|
94
|
+
? ActorError.make({
|
|
95
|
+
reason: ActorUnavailable.make({ cause: Cause.squash(cause) }),
|
|
96
|
+
})
|
|
97
|
+
: activationEnded());
|
|
98
|
+
}));
|
|
99
|
+
}).pipe(Effect.provideContext(services))),
|
|
100
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import type { AnyFleetView } from "../../tables/fleet.ts";
|
|
4
|
+
import { type ResolvedView } from "./maintainer.ts";
|
|
5
|
+
/** Resolves each view's source and derived table schemas on this database. */
|
|
6
|
+
export declare const resolveViews: (views: readonly AnyFleetView[]) => Effect.Effect<ResolvedView[], import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
7
|
+
/**
|
|
8
|
+
* Refuses to start fleet views on a database that cannot maintain them, each
|
|
9
|
+
* time naming the fix: `wal_level` below logical, a login without
|
|
10
|
+
* `REPLICATION`, a source outside the publication or without full replica
|
|
11
|
+
* identity, a missing or lost slot, a source whose actor type is not placed
|
|
12
|
+
* by tenant, a source without an index leading with
|
|
13
|
+
* `(routing_key, tenant_id, …group columns)`, a missing derived table, and a
|
|
14
|
+
* derived table the tenant role owns, which would let a turn bypass its
|
|
15
|
+
* policy. Then records each view, marking one whose definition changed stale.
|
|
16
|
+
*/
|
|
17
|
+
export declare const checkFleet: (views: readonly AnyFleetView[], tenantRole: string | undefined) => Effect.Effect<ResolvedView[], import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Clock, Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { FLEET_PUBLICATION, FLEET_SLOT } from "./maintainer.js";
|
|
4
|
+
const refuse = (message) => Effect.die(new Error(`Fleet views cannot start: ${message}`));
|
|
5
|
+
const currentSchema = Effect.gen(function* () {
|
|
6
|
+
const sql = yield* SqlClient.SqlClient;
|
|
7
|
+
return (yield* sql `SELECT current_schema() AS schema`)[0].schema;
|
|
8
|
+
});
|
|
9
|
+
/** Resolves each view's source and derived table schemas on this database. */
|
|
10
|
+
export const resolveViews = Effect.fnUntraced(function* (views) {
|
|
11
|
+
const schema = yield* currentSchema;
|
|
12
|
+
return views.map((view) => ({
|
|
13
|
+
view,
|
|
14
|
+
sourceSchema: view.source.schema ?? schema,
|
|
15
|
+
derivedSchema: view.source.schema ?? schema,
|
|
16
|
+
}));
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Refuses to start fleet views on a database that cannot maintain them, each
|
|
20
|
+
* time naming the fix: `wal_level` below logical, a login without
|
|
21
|
+
* `REPLICATION`, a source outside the publication or without full replica
|
|
22
|
+
* identity, a missing or lost slot, a source whose actor type is not placed
|
|
23
|
+
* by tenant, a source without an index leading with
|
|
24
|
+
* `(routing_key, tenant_id, …group columns)`, a missing derived table, and a
|
|
25
|
+
* derived table the tenant role owns, which would let a turn bypass its
|
|
26
|
+
* policy. Then records each view, marking one whose definition changed stale.
|
|
27
|
+
*/
|
|
28
|
+
export const checkFleet = Effect.fnUntraced(function* (views, tenantRole) {
|
|
29
|
+
const sql = yield* SqlClient.SqlClient;
|
|
30
|
+
const names = views.map(({ name }) => name);
|
|
31
|
+
if (new Set(names).size !== names.length)
|
|
32
|
+
return yield* refuse(`a view name is registered twice (${names.join(", ")})`);
|
|
33
|
+
const [level] = yield* sql `SELECT current_setting('wal_level') AS wal_level`;
|
|
34
|
+
if (level?.wal_level !== "logical")
|
|
35
|
+
return yield* refuse(`wal_level is ${level?.wal_level}; set wal_level = logical (ALTER SYSTEM SET wal_level = logical) and restart Postgres`);
|
|
36
|
+
const [login] = yield* sql `
|
|
37
|
+
SELECT rolname AS name, rolreplication OR rolsuper AS replication FROM pg_roles
|
|
38
|
+
WHERE rolname = session_user`;
|
|
39
|
+
if (login?.replication !== true)
|
|
40
|
+
return yield* refuse(`login ${login?.name} lacks REPLICATION, which the slot functions need; run ALTER ROLE ${login?.name} REPLICATION`);
|
|
41
|
+
const resolved = yield* resolveViews(views);
|
|
42
|
+
for (const { view, sourceSchema, derivedSchema } of resolved) {
|
|
43
|
+
const source = `${sourceSchema}.${view.source.table}`;
|
|
44
|
+
if (view.source.owner === undefined)
|
|
45
|
+
return yield* refuse(`${view.name} reads ${source}, which no Actor.make lists in tables`);
|
|
46
|
+
if (view.source.placement !== "tenant")
|
|
47
|
+
return yield* refuse(`${view.name} reads ${source} of actor ${view.source.owner}, which is not placed by tenant; a fleet view needs placement: "tenant"`);
|
|
48
|
+
const [table] = yield* sql `
|
|
49
|
+
SELECT c.relreplident AS identity,
|
|
50
|
+
EXISTS (SELECT 1 FROM pg_publication_tables p WHERE p.pubname = ${FLEET_PUBLICATION}
|
|
51
|
+
AND p.schemaname = n.nspname AND p.tablename = c.relname) AS published
|
|
52
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
53
|
+
WHERE n.nspname = ${sourceSchema} AND c.relname = ${view.source.table}`;
|
|
54
|
+
if (table === undefined)
|
|
55
|
+
return yield* refuse(`${view.name} reads ${source}, which does not exist; apply its drizzle-kit migration`);
|
|
56
|
+
if (!table.published)
|
|
57
|
+
return yield* refuse(`${source} is not in publication ${FLEET_PUBLICATION}; run durable fleet setup`);
|
|
58
|
+
if (table.identity !== "f")
|
|
59
|
+
return yield* refuse(`${source} does not have full replica identity; run durable fleet setup (ALTER TABLE ${source} REPLICA IDENTITY FULL)`);
|
|
60
|
+
const leading = [view.routingColumn, view.tenantColumn, ...view.groupColumns];
|
|
61
|
+
const indexes = yield* sql `
|
|
62
|
+
SELECT array(
|
|
63
|
+
SELECT a.attname FROM unnest(i.indkey::int2[]) WITH ORDINALITY k(attnum, position)
|
|
64
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum
|
|
65
|
+
ORDER BY k.position) AS columns
|
|
66
|
+
FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid
|
|
67
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
68
|
+
WHERE n.nspname = ${sourceSchema} AND c.relname = ${view.source.table}
|
|
69
|
+
AND i.indisvalid AND i.indpred IS NULL`;
|
|
70
|
+
if (!indexes.some(({ columns }) => leading.every((column, index) => columns[index] === column)))
|
|
71
|
+
return yield* refuse(`${view.name} recomputes a group with one indexed read of ${source}; create its index: CREATE INDEX ${view.source.table}_${view.tableName.split(".").at(-1)} ON ${source} (${leading.join(", ")})`);
|
|
72
|
+
const derived = view.tableName.split(".").at(-1);
|
|
73
|
+
const [owner] = yield* sql `
|
|
74
|
+
SELECT ${tenantRole === undefined ? sql `false` : sql `pg_has_role(${tenantRole}, c.relowner, 'MEMBER')`}
|
|
75
|
+
AS owned_by_tenant,
|
|
76
|
+
${tenantRole === undefined ? sql `true` : sql `has_table_privilege(${tenantRole}, c.oid, 'SELECT')`}
|
|
77
|
+
AS readable
|
|
78
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
79
|
+
WHERE n.nspname = ${derivedSchema} AND c.relname = ${derived}`;
|
|
80
|
+
if (owner === undefined)
|
|
81
|
+
return yield* refuse(`the derived table ${derivedSchema}.${derived} of ${view.name} does not exist; add ${view.name}.table to the drizzle-kit schema and apply its migration`);
|
|
82
|
+
if (!owner.readable)
|
|
83
|
+
return yield* refuse(`the tenant role ${tenantRole} cannot read ${derivedSchema}.${derived}, which subscriptions read as that role; GRANT SELECT ON ${derivedSchema}.${derived} TO ${tenantRole}`);
|
|
84
|
+
if (owner.owned_by_tenant)
|
|
85
|
+
return yield* refuse(`the tenant role ${tenantRole} owns ${derivedSchema}.${derived}, so its policy would not bind it; give the table to another role`);
|
|
86
|
+
}
|
|
87
|
+
const [slot] = yield* sql `
|
|
88
|
+
SELECT wal_status FROM pg_replication_slots
|
|
89
|
+
WHERE slot_name = ${FLEET_SLOT} AND database = current_database() AND slot_type = 'logical'`;
|
|
90
|
+
if (slot === undefined)
|
|
91
|
+
return yield* refuse(`replication slot ${FLEET_SLOT} does not exist; run durable fleet setup`);
|
|
92
|
+
if (slot.wal_status === "lost")
|
|
93
|
+
return yield* refuse(`replication slot ${FLEET_SLOT} is lost; run durable fleet setup to recreate it, and every view rebuilds`);
|
|
94
|
+
const at = yield* Clock.currentTimeMillis;
|
|
95
|
+
for (const { view, sourceSchema } of resolved) {
|
|
96
|
+
yield* sql `INSERT INTO actor_fleet_views (view_name, source_schema, source_table, definition_hash,
|
|
97
|
+
status, applied_lsn, updated_at_ms, last_error)
|
|
98
|
+
VALUES (${view.name}, ${sourceSchema}, ${view.source.table}, ${view.definitionHash},
|
|
99
|
+
'building', NULL, ${at}, NULL)
|
|
100
|
+
ON CONFLICT (view_name) DO UPDATE SET source_schema = EXCLUDED.source_schema,
|
|
101
|
+
source_table = EXCLUDED.source_table, definition_hash = EXCLUDED.definition_hash,
|
|
102
|
+
status = 'stale', last_error = NULL, updated_at_ms = EXCLUDED.updated_at_ms
|
|
103
|
+
WHERE actor_fleet_views.definition_hash <> EXCLUDED.definition_hash`;
|
|
104
|
+
}
|
|
105
|
+
return resolved;
|
|
106
|
+
});
|