@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,19 @@
|
|
|
1
|
+
import { dual } from "effect/Function";
|
|
2
|
+
/** A read set that has recorded nothing. */
|
|
3
|
+
export const emptyReadSet = () => ({
|
|
4
|
+
state: false,
|
|
5
|
+
events: new Set(),
|
|
6
|
+
tables: new Set(),
|
|
7
|
+
blobs: new Set(),
|
|
8
|
+
group: false,
|
|
9
|
+
caller: false,
|
|
10
|
+
});
|
|
11
|
+
/** Whether a commit that wrote `writes` may have changed the result of a query that read `reads`. */
|
|
12
|
+
export const invalidates = dual(2, (writes, reads) => (writes.state && reads.state) ||
|
|
13
|
+
writes.events.some((tag) => reads.events.has(tag)) ||
|
|
14
|
+
writes.tables.some((table) => reads.tables.has(table)) ||
|
|
15
|
+
writes.blobs.some((blob) => reads.blobs.has(blob)));
|
|
16
|
+
/** The read that makes a query unwatchable, named for the refusal; undefined when every read is supported. */
|
|
17
|
+
export const unsupportedRead = (reads) => reads.group
|
|
18
|
+
? "The query reads its placement group, which has no commit signal per actor"
|
|
19
|
+
: undefined;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import type { SqlClient } from "effect/sql";
|
|
3
|
+
import { ActorError } from "../../errors/actor.ts";
|
|
4
|
+
import type { ActorRef, Caller } from "../../identity/caller.ts";
|
|
5
|
+
import type { Registration, StreamInput } from "../members.ts";
|
|
6
|
+
import type { Activation } from "./owner.ts";
|
|
7
|
+
import type { Transport } from "./transport.ts";
|
|
8
|
+
/** One live subscription, owned until its subscriber or activation ends. */
|
|
9
|
+
export interface Subscription {
|
|
10
|
+
readonly member: string;
|
|
11
|
+
readonly caller: Caller;
|
|
12
|
+
/** When authorization last succeeded, on the framework clock. */
|
|
13
|
+
lastAuthorized: number;
|
|
14
|
+
checking: boolean;
|
|
15
|
+
/** Since when the handler has waited on a full window. */
|
|
16
|
+
stalledSince: number | undefined;
|
|
17
|
+
readonly end: (error: ActorError, discard: boolean) => Effect.Effect<void>;
|
|
18
|
+
}
|
|
19
|
+
/** Decides whether a caller may open a stream or renew its access; `false` refuses. */
|
|
20
|
+
export type Authorize = (request: {
|
|
21
|
+
readonly caller: Caller;
|
|
22
|
+
readonly ref: ActorRef;
|
|
23
|
+
readonly command: string;
|
|
24
|
+
readonly kind: "stream" | "reauthorize";
|
|
25
|
+
/** A stream tag must not be interpreted as a feed event tag. */
|
|
26
|
+
readonly of?: "stream";
|
|
27
|
+
}) => Effect.Effect<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Owns stream admission, producer lifetime, bounded delivery and revocation.
|
|
30
|
+
* The connection owner supplies the committed read context only after access
|
|
31
|
+
* and capacity are checked; it retains generation acquisition and the ordered
|
|
32
|
+
* broadcast channel. Streams have no durable session rows or replay promise.
|
|
33
|
+
*/
|
|
34
|
+
export declare const streamSubscriptions: ({ registration, activations, transport, authorize, now, prepare, }: {
|
|
35
|
+
readonly registration: Registration;
|
|
36
|
+
readonly activations: ReadonlyMap<string, Activation>;
|
|
37
|
+
readonly transport: Pick<Transport, "holder" | "epoch">;
|
|
38
|
+
readonly authorize: Authorize;
|
|
39
|
+
readonly now: Effect.Effect<number>;
|
|
40
|
+
readonly prepare: (activation: Activation) => Effect.Effect<Omit<StreamInput, "caller">, ActorError, SqlClient.SqlClient>;
|
|
41
|
+
}) => {
|
|
42
|
+
subscribe: (activation: Activation, request: {
|
|
43
|
+
readonly member: string;
|
|
44
|
+
readonly caller: Caller;
|
|
45
|
+
readonly input: string;
|
|
46
|
+
readonly authorizedUntil: number;
|
|
47
|
+
}) => Stream.Stream<{
|
|
48
|
+
readonly _tag: "Done";
|
|
49
|
+
} | {
|
|
50
|
+
readonly _tag: "Element";
|
|
51
|
+
readonly value: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly _tag: "Started";
|
|
54
|
+
readonly owner: string;
|
|
55
|
+
readonly ownerEpoch: string;
|
|
56
|
+
}, ActorError | {
|
|
57
|
+
readonly _tag: "StreamFailed";
|
|
58
|
+
readonly value: string;
|
|
59
|
+
}, SqlClient.SqlClient>;
|
|
60
|
+
end: (activation: Activation) => Effect.Effect<void, never, never>;
|
|
61
|
+
watch: Effect.Effect<void, never, never>;
|
|
62
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Fiber, Queue, Schedule, Stream } from "effect";
|
|
2
|
+
import { ActorError, RunnerAtCapacity, SessionEnded, Unauthorized } from "../../errors/actor.js";
|
|
3
|
+
import { StreamFailed, StreamItem } from "./protocol.js";
|
|
4
|
+
/** Open stream subscriptions one actor may have. */
|
|
5
|
+
const MAX_ACTOR_STREAMS = 256;
|
|
6
|
+
/** Stream elements held for one subscriber before its handler waits. */
|
|
7
|
+
const STREAM_WINDOW = 256;
|
|
8
|
+
/** How long a stream's window may stay full before the subscription ends. */
|
|
9
|
+
const STREAM_STALL_MS = 30_000;
|
|
10
|
+
/** How often the owner checks subscriptions' authorization and windows. */
|
|
11
|
+
const STREAM_TICK = "100 millis";
|
|
12
|
+
const unauthorized = (code) => ActorError.make({ reason: Unauthorized.make({ code }) });
|
|
13
|
+
const ended = (cause, resync) => ActorError.make({ reason: SessionEnded.make({ cause, resync }) });
|
|
14
|
+
/**
|
|
15
|
+
* Owns stream admission, producer lifetime, bounded delivery and revocation.
|
|
16
|
+
* The connection owner supplies the committed read context only after access
|
|
17
|
+
* and capacity are checked; it retains generation acquisition and the ordered
|
|
18
|
+
* broadcast channel. Streams have no durable session rows or replay promise.
|
|
19
|
+
*/
|
|
20
|
+
export const streamSubscriptions = ({ registration, activations, transport, authorize, now, prepare, }) => {
|
|
21
|
+
/** Natural completion follows all elements; every other end is an explicit error. */
|
|
22
|
+
const subscribe = (activation, request) => Stream.unwrap(Effect.gen(function* () {
|
|
23
|
+
const stream = registration.streams.get(request.member);
|
|
24
|
+
if (stream === undefined)
|
|
25
|
+
return yield* Effect.die(new Error(`Unregistered stream ${request.member}`));
|
|
26
|
+
if ((yield* now) >= request.authorizedUntil)
|
|
27
|
+
return yield* unauthorized("reauthorization_unavailable");
|
|
28
|
+
const allowed = yield* authorize({
|
|
29
|
+
caller: request.caller,
|
|
30
|
+
ref: activation.ref,
|
|
31
|
+
command: request.member,
|
|
32
|
+
kind: "stream",
|
|
33
|
+
});
|
|
34
|
+
if (!allowed)
|
|
35
|
+
return yield* unauthorized("access_denied");
|
|
36
|
+
const queue = yield* Queue.bounded(STREAM_WINDOW);
|
|
37
|
+
let producer;
|
|
38
|
+
let closed = false;
|
|
39
|
+
const subscription = {
|
|
40
|
+
member: request.member,
|
|
41
|
+
caller: request.caller,
|
|
42
|
+
lastAuthorized: yield* now,
|
|
43
|
+
checking: false,
|
|
44
|
+
stalledSince: undefined,
|
|
45
|
+
end: (error, discard) => Effect.gen(function* () {
|
|
46
|
+
if (closed)
|
|
47
|
+
return;
|
|
48
|
+
closed = true;
|
|
49
|
+
activation.streams.delete(subscription);
|
|
50
|
+
if (producer !== undefined)
|
|
51
|
+
yield* Fiber.interrupt(producer);
|
|
52
|
+
if (discard)
|
|
53
|
+
yield* Queue.clear(queue).pipe(Effect.ignore);
|
|
54
|
+
yield* Queue.fail(queue, error);
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
const admitted = yield* Effect.sync(() => {
|
|
58
|
+
if (activation.streams.size >= MAX_ACTOR_STREAMS)
|
|
59
|
+
return false;
|
|
60
|
+
activation.streams.add(subscription);
|
|
61
|
+
return true;
|
|
62
|
+
});
|
|
63
|
+
if (!admitted)
|
|
64
|
+
return yield* ActorError.make({ reason: RunnerAtCapacity.make({}) });
|
|
65
|
+
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
66
|
+
closed = true;
|
|
67
|
+
activation.streams.delete(subscription);
|
|
68
|
+
if (producer !== undefined)
|
|
69
|
+
yield* Fiber.interrupt(producer);
|
|
70
|
+
}));
|
|
71
|
+
const input = yield* prepare(activation);
|
|
72
|
+
const offer = (item) => Effect.gen(function* () {
|
|
73
|
+
if (Queue.offerUnsafe(queue, item))
|
|
74
|
+
return;
|
|
75
|
+
subscription.stalledSince = yield* now;
|
|
76
|
+
yield* Queue.offer(queue, item);
|
|
77
|
+
subscription.stalledSince = undefined;
|
|
78
|
+
});
|
|
79
|
+
producer = yield* stream.run(request.input, { ...input, caller: request.caller }).pipe(Stream.runForEach((value) => offer(StreamItem.cases.Element.make({ value }))), Effect.andThen(offer(StreamItem.cases.Done.make({}))), Effect.andThen(Queue.end(queue)), Effect.catch(({ failure }) => Queue.fail(queue, StreamFailed.make({ value: failure }))), Effect.catchDefect((cause) => Effect.andThen(Effect.logError("Stream handler defect", Cause.die(cause)), Queue.fail(queue, ended("Defect", false)))), Effect.asVoid, Effect.ensuring(Effect.sync(() => {
|
|
80
|
+
closed = true;
|
|
81
|
+
activation.streams.delete(subscription);
|
|
82
|
+
})), Effect.forkDetach);
|
|
83
|
+
return Stream.succeed(StreamItem.cases.Started.make({ owner: transport.holder, ownerEpoch: transport.epoch })).pipe(Stream.concat(Stream.fromQueue(queue)));
|
|
84
|
+
}));
|
|
85
|
+
const end = (activation) => Effect.forEach([...activation.streams], (subscription) => subscription.end(ended("ActivationEnded", false), false), { discard: true });
|
|
86
|
+
const reauthorize = (activation, subscription, at) => Effect.gen(function* () {
|
|
87
|
+
subscription.checking = true;
|
|
88
|
+
const every = registration.policy.reauthorizeMs;
|
|
89
|
+
const allowed = yield* authorize({
|
|
90
|
+
caller: subscription.caller,
|
|
91
|
+
ref: activation.ref,
|
|
92
|
+
command: subscription.member,
|
|
93
|
+
kind: "reauthorize",
|
|
94
|
+
of: "stream",
|
|
95
|
+
}).pipe(Effect.timeout(Math.min(10_000, every / 2)), Effect.exit);
|
|
96
|
+
subscription.checking = false;
|
|
97
|
+
const bound = subscription.lastAuthorized + every;
|
|
98
|
+
if (Exit.isSuccess(allowed) && allowed.value && (yield* now) >= bound)
|
|
99
|
+
yield* subscription.end(unauthorized("reauthorization_unavailable"), true);
|
|
100
|
+
else if (Exit.isSuccess(allowed) && allowed.value)
|
|
101
|
+
subscription.lastAuthorized = at;
|
|
102
|
+
else if (Exit.isSuccess(allowed))
|
|
103
|
+
yield* subscription.end(unauthorized("access_denied"), true);
|
|
104
|
+
});
|
|
105
|
+
/** Revocation discards undelivered elements, including while a check is in flight. */
|
|
106
|
+
const watch = Effect.gen(function* () {
|
|
107
|
+
const at = yield* now;
|
|
108
|
+
const every = registration.policy.reauthorizeMs;
|
|
109
|
+
for (const activation of activations.values())
|
|
110
|
+
for (const subscription of activation.streams) {
|
|
111
|
+
if (at >= subscription.lastAuthorized + every) {
|
|
112
|
+
yield* subscription
|
|
113
|
+
.end(unauthorized("reauthorization_unavailable"), true)
|
|
114
|
+
.pipe(Effect.forkDetach);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (subscription.stalledSince !== undefined &&
|
|
118
|
+
at - subscription.stalledSince >= STREAM_STALL_MS) {
|
|
119
|
+
yield* subscription.end(ended("SlowConsumer", true), true).pipe(Effect.forkDetach);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!subscription.checking &&
|
|
123
|
+
at >= subscription.lastAuthorized + every - Math.min(10_000, every / 2))
|
|
124
|
+
yield* reauthorize(activation, subscription, at).pipe(Effect.forkDetach);
|
|
125
|
+
}
|
|
126
|
+
}).pipe(Effect.repeat(Schedule.spaced(STREAM_TICK)), Effect.asVoid);
|
|
127
|
+
return { subscribe, end, watch };
|
|
128
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Crypto, Effect, Schema } from "effect";
|
|
2
|
+
import { Sharding, ShardingConfig } from "effect/cluster";
|
|
3
|
+
import { type Deliver, type Delivered } from "./protocol.ts";
|
|
4
|
+
declare const HolderUnreachable_base: Schema.Class<HolderUnreachable, Schema.TaggedStruct<"HolderUnreachable", {
|
|
5
|
+
readonly message: Schema.String;
|
|
6
|
+
}>, import("effect/Cause").YieldableError>;
|
|
7
|
+
/** A holder could not be reached: the owner treats its connections as lost. */
|
|
8
|
+
export declare class HolderUnreachable extends HolderUnreachable_base {
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* This runner's transport identity and the channel to every other runner's
|
|
12
|
+
* holder. A runner with a Cluster address hosts a holder entity in a shard
|
|
13
|
+
* group named after that address; an unclustered runtime reaches only itself.
|
|
14
|
+
*/
|
|
15
|
+
export interface Transport {
|
|
16
|
+
readonly holder: string;
|
|
17
|
+
readonly epoch: string;
|
|
18
|
+
readonly deliver: (holder: string, epoch: string, message: Deliver) => Effect.Effect<Delivered, HolderUnreachable>;
|
|
19
|
+
/** Whether the runner `holder` is still running the process that took `epoch`. */
|
|
20
|
+
readonly ping: (holder: string, epoch: string) => Effect.Effect<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A runner that hosts no actors still hosts its own holder, so its group is
|
|
24
|
+
* assigned regardless of the configured ones.
|
|
25
|
+
*/
|
|
26
|
+
export declare const holderShardGroups: (config: Partial<ShardingConfig.ShardingConfig["Service"]>) => {} | {
|
|
27
|
+
availableShardGroups: string[];
|
|
28
|
+
assignedShardGroups: string[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Shard placement for the holder's own group can lag a moment behind registration,
|
|
32
|
+
* so an unassigned entity is retried.
|
|
33
|
+
*/
|
|
34
|
+
export declare const holderTransport: (local: (message: Deliver) => Effect.Effect<Delivered>) => Effect.Effect<{
|
|
35
|
+
holder: string;
|
|
36
|
+
epoch: string;
|
|
37
|
+
deliver: (holder: string, epoch: string, message: Deliver) => Effect.Effect<Delivered, HolderUnreachable>;
|
|
38
|
+
ping: (holder: string, epoch: string) => Effect.Effect<boolean>;
|
|
39
|
+
}, never, Crypto.Crypto | import("effect/Scope").Scope | Sharding.Sharding | ShardingConfig.ShardingConfig>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Crypto, Effect, Option, Schedule, Schema } from "effect";
|
|
2
|
+
import { ClusterError, EntityId, Sharding, ShardingConfig } from "effect/cluster";
|
|
3
|
+
import { HolderEntity, holderEntityId, holderGroup, } from "./protocol.js";
|
|
4
|
+
const LOCAL = "local";
|
|
5
|
+
/** How long an owner waits for a holder to acknowledge one message, before one retry. */
|
|
6
|
+
const ACK_TIMEOUT = "1 second";
|
|
7
|
+
/** A holder could not be reached: the owner treats its connections as lost. */
|
|
8
|
+
export class HolderUnreachable extends Schema.TaggedError()("HolderUnreachable", { message: Schema.String }) {
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A runner that hosts no actors still hosts its own holder, so its group is
|
|
12
|
+
* assigned regardless of the configured ones.
|
|
13
|
+
*/
|
|
14
|
+
export const holderShardGroups = (config) => Option.match(config.runnerAddress ?? Option.none(), {
|
|
15
|
+
onNone: () => ({}),
|
|
16
|
+
onSome: (address) => ({
|
|
17
|
+
availableShardGroups: ["default", holderGroup(address)],
|
|
18
|
+
assignedShardGroups: [...(config.assignedShardGroups ?? ["default"]), holderGroup(address)],
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Shard placement for the holder's own group can lag a moment behind registration,
|
|
23
|
+
* so an unassigned entity is retried.
|
|
24
|
+
*/
|
|
25
|
+
export const holderTransport = Effect.fnUntraced(function* (local) {
|
|
26
|
+
const config = yield* ShardingConfig.ShardingConfig;
|
|
27
|
+
const sharding = yield* Sharding.Sharding;
|
|
28
|
+
const crypto = yield* Crypto.Crypto;
|
|
29
|
+
const epoch = yield* crypto.randomUUIDv7.pipe(Effect.orDie);
|
|
30
|
+
const holder = Option.match(config.runnerAddress, {
|
|
31
|
+
onNone: () => LOCAL,
|
|
32
|
+
onSome: holderGroup,
|
|
33
|
+
});
|
|
34
|
+
const clustered = holder !== LOCAL && config.assignedShardGroups.includes(holder);
|
|
35
|
+
if (clustered)
|
|
36
|
+
yield* sharding.registerEntity(HolderEntity, Effect.succeed(HolderEntity.of({
|
|
37
|
+
Deliver: ({ payload }) => payload.epoch === epoch
|
|
38
|
+
? local(payload)
|
|
39
|
+
: Effect.succeed({ wrongEpoch: true, unknown: [] }),
|
|
40
|
+
Ping: ({ payload }) => Effect.succeed(payload.epoch === epoch),
|
|
41
|
+
})), { concurrency: "unbounded" });
|
|
42
|
+
const client = clustered ? yield* sharding.makeClient(HolderEntity) : undefined;
|
|
43
|
+
const deliver = (target, targetEpoch, message) => {
|
|
44
|
+
if (target === holder)
|
|
45
|
+
return targetEpoch === epoch
|
|
46
|
+
? local(message)
|
|
47
|
+
: Effect.succeed({ wrongEpoch: true, unknown: [] });
|
|
48
|
+
if (client === undefined || target === LOCAL)
|
|
49
|
+
return Effect.fail(HolderUnreachable.make({ message: `Holder ${target} is not reachable` }));
|
|
50
|
+
return client(EntityId.make(holderEntityId({ holder: target, epoch: targetEpoch })))
|
|
51
|
+
.Deliver(message)
|
|
52
|
+
.pipe(Effect.retry({
|
|
53
|
+
while: (error) => ClusterError.EntityNotAssignedToRunner.is(error) ||
|
|
54
|
+
ClusterError.RunnerUnavailable.is(error),
|
|
55
|
+
schedule: Schedule.spaced("50 millis"),
|
|
56
|
+
}), Effect.timeout(ACK_TIMEOUT), Effect.retry({ times: 1 }), Effect.mapError(() => HolderUnreachable.make({ message: `Holder ${target} did not acknowledge` })));
|
|
57
|
+
};
|
|
58
|
+
const ping = (target, targetEpoch) => {
|
|
59
|
+
if (target === holder)
|
|
60
|
+
return Effect.succeed(targetEpoch === epoch);
|
|
61
|
+
if (client === undefined || target === LOCAL)
|
|
62
|
+
return Effect.succeed(false);
|
|
63
|
+
return client(EntityId.make(holderEntityId({ holder: target, epoch: targetEpoch })))
|
|
64
|
+
.Ping({ epoch: targetEpoch })
|
|
65
|
+
.pipe(Effect.timeout(ACK_TIMEOUT), Effect.retry({ times: 1 }), Effect.orElseSucceed(() => false));
|
|
66
|
+
};
|
|
67
|
+
return { holder, epoch, deliver, ping };
|
|
68
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Context, Effect, Stream } from "effect";
|
|
2
|
+
import { ActorError } from "../../errors/actor.ts";
|
|
3
|
+
import { Outcome, type Request } from "../request.ts";
|
|
4
|
+
import { type WatchResult } from "../members.ts";
|
|
5
|
+
import type { Holder } from "./holder.ts";
|
|
6
|
+
import { type ReadSet } from "./reads.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Asked before an owner broadcasts a commit's `Committed` frame; `true` drops
|
|
9
|
+
* it, as a lost best-effort broadcast would. Tests drop frames here to show
|
|
10
|
+
* the reconcile rerun recovers them.
|
|
11
|
+
*/
|
|
12
|
+
export declare const WatchTap: Context.Reference<{
|
|
13
|
+
readonly dropsCommitted: () => boolean;
|
|
14
|
+
}>;
|
|
15
|
+
/** What a watch needs from its runner: the holder its connection parks at, and one rerun of the query. */
|
|
16
|
+
export interface WatchOptions {
|
|
17
|
+
readonly holder: Holder;
|
|
18
|
+
/** The request to watch: `command` is the query and `payload` its encoded input. */
|
|
19
|
+
readonly request: Request;
|
|
20
|
+
readonly minIntervalMs: number;
|
|
21
|
+
readonly reconcileMs: number;
|
|
22
|
+
/** A commit version the first result must reflect at least. */
|
|
23
|
+
readonly minVersion: string | undefined;
|
|
24
|
+
/** The credential's own expiry in epoch milliseconds, if it has one. */
|
|
25
|
+
readonly expiresAt: number | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Reruns the query for this watch and records what it read. `version` is
|
|
28
|
+
* called once the rerun holds its place among the runner's reruns, so a rerun
|
|
29
|
+
* that waited for one starts from the newest version seen meanwhile.
|
|
30
|
+
*/
|
|
31
|
+
readonly rerun: (version: () => string | undefined, reads: ReadSet) => Effect.Effect<Outcome, ActorError>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A watch on one query: its current result first, then the newest result after
|
|
35
|
+
* each commit whose writes intersect what the last run read. The watch is a
|
|
36
|
+
* parked framework connection, so the owner sends it one `Committed` frame per
|
|
37
|
+
* commit and nothing keeps the actor resident.
|
|
38
|
+
*
|
|
39
|
+
* Reruns are coalesced: at most one runs and one waits, frames that arrive
|
|
40
|
+
* meanwhile only raise the version the next rerun waits for, and reruns of one
|
|
41
|
+
* watch start at least `minIntervalMs` apart. A result equal to the last one
|
|
42
|
+
* sent is not sent, and the newest result replaces one nobody has read yet, so
|
|
43
|
+
* a slow reader never ends the watch. The broadcast is best-effort, so the
|
|
44
|
+
* watch also reruns every `reconcileMs`, after a holder resync, and after a
|
|
45
|
+
* reopen, which is how a lost frame or a change no commit reports is shown.
|
|
46
|
+
* A rerun that reads what a commit signal cannot cover ends the watch with
|
|
47
|
+
* `not_watchable`, and one that fails transiently is retried. A result read
|
|
48
|
+
* for a session past its authorization bound is dropped.
|
|
49
|
+
*/
|
|
50
|
+
export declare const watchStream: (options: WatchOptions) => Effect.Effect<Stream.Stream<WatchResult, ActorError | {
|
|
51
|
+
readonly failure: string;
|
|
52
|
+
}, never>, ActorError, never>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Clock, Context, Deferred, Effect, Match, Option, Predicate, Queue, Result, Schema, Stream, } from "effect";
|
|
2
|
+
import { ActorError, InvalidInput, SessionEnded } from "../../errors/actor.js";
|
|
3
|
+
import { Outcome } from "../request.js";
|
|
4
|
+
import {} from "../members.js";
|
|
5
|
+
import { Committed, watchMember } from "./protocol.js";
|
|
6
|
+
import { emptyReadSet, invalidates, unsupportedRead } from "./reads.js";
|
|
7
|
+
/**
|
|
8
|
+
* Asked before an owner broadcasts a commit's `Committed` frame; `true` drops
|
|
9
|
+
* it, as a lost best-effort broadcast would. Tests drop frames here to show
|
|
10
|
+
* the reconcile rerun recovers them.
|
|
11
|
+
*/
|
|
12
|
+
export const WatchTap = Context.Reference("@rikalabs/akter/runtime/connections/watch/WatchTap", { defaultValue: () => ({ dropsCommitted: () => false }) });
|
|
13
|
+
/** How long a watch waits before it reruns after a rerun failed for a reason a later attempt may not share. */
|
|
14
|
+
const RERUN_RETRY_MS = 1_000;
|
|
15
|
+
const decodeCommitted = Schema.decodeEffect(Schema.fromJsonString(Committed));
|
|
16
|
+
/** Ends that a watch survives by reopening at its holder and rerunning, as a feed does. */
|
|
17
|
+
const recoverable = (error) => Predicate.isTagged(error.reason, "SessionEnded") &&
|
|
18
|
+
["SlowConsumer", "OwnerLost", "ActorUnavailable"].includes(error.reason.cause);
|
|
19
|
+
/** Failures of a rerun that the next attempt may not repeat. */
|
|
20
|
+
const transient = (error) => Predicate.isTagged(error.reason, "ActorUnavailable") ||
|
|
21
|
+
Predicate.isTagged(error.reason, "Timeout") ||
|
|
22
|
+
Predicate.isTagged(error.reason, "RunnerAtCapacity");
|
|
23
|
+
/**
|
|
24
|
+
* A watch on one query: its current result first, then the newest result after
|
|
25
|
+
* each commit whose writes intersect what the last run read. The watch is a
|
|
26
|
+
* parked framework connection, so the owner sends it one `Committed` frame per
|
|
27
|
+
* commit and nothing keeps the actor resident.
|
|
28
|
+
*
|
|
29
|
+
* Reruns are coalesced: at most one runs and one waits, frames that arrive
|
|
30
|
+
* meanwhile only raise the version the next rerun waits for, and reruns of one
|
|
31
|
+
* watch start at least `minIntervalMs` apart. A result equal to the last one
|
|
32
|
+
* sent is not sent, and the newest result replaces one nobody has read yet, so
|
|
33
|
+
* a slow reader never ends the watch. The broadcast is best-effort, so the
|
|
34
|
+
* watch also reruns every `reconcileMs`, after a holder resync, and after a
|
|
35
|
+
* reopen, which is how a lost frame or a change no commit reports is shown.
|
|
36
|
+
* A rerun that reads what a commit signal cannot cover ends the watch with
|
|
37
|
+
* `not_watchable`, and one that fails transiently is retried. A result read
|
|
38
|
+
* for a session past its authorization bound is dropped.
|
|
39
|
+
*/
|
|
40
|
+
export const watchStream = Effect.fnUntraced(function* (options) {
|
|
41
|
+
const { holder, request } = options;
|
|
42
|
+
const open = holder
|
|
43
|
+
.open({
|
|
44
|
+
ref: request.ref,
|
|
45
|
+
member: watchMember(request.command),
|
|
46
|
+
caller: request.caller,
|
|
47
|
+
params: "",
|
|
48
|
+
expiresAt: options.expiresAt,
|
|
49
|
+
})
|
|
50
|
+
.pipe(Effect.catchTag("OpenRejected", () => Effect.die(new Error("A watch has no open handler"))));
|
|
51
|
+
const first = yield* open;
|
|
52
|
+
return Stream.callback((out) => Effect.gen(function* () {
|
|
53
|
+
let held = first;
|
|
54
|
+
let pending = options.minVersion === undefined ? undefined : BigInt(options.minVersion);
|
|
55
|
+
let reads;
|
|
56
|
+
let dirty = true;
|
|
57
|
+
let sent;
|
|
58
|
+
let settling = [];
|
|
59
|
+
const signal = yield* Queue.sliding(1);
|
|
60
|
+
yield* Effect.addFinalizer(() => held.close);
|
|
61
|
+
const raise = () => {
|
|
62
|
+
dirty = true;
|
|
63
|
+
Queue.offerUnsafe(signal, undefined);
|
|
64
|
+
};
|
|
65
|
+
const note = (committed) => {
|
|
66
|
+
const version = BigInt(committed.version);
|
|
67
|
+
if (pending === undefined || version > pending)
|
|
68
|
+
pending = version;
|
|
69
|
+
if (reads === undefined || invalidates(committed.writes, reads))
|
|
70
|
+
raise();
|
|
71
|
+
};
|
|
72
|
+
const listen = (connection) => connection.messages.pipe(Stream.runForEach((item) => Match.value(item).pipe(Match.tagsExhaustive({
|
|
73
|
+
Frame: (frame) => decodeCommitted(frame.frame).pipe(Effect.orDie, Effect.map(note)),
|
|
74
|
+
Resync: () => Effect.void,
|
|
75
|
+
ResyncReplayed: () => Effect.gen(function* () {
|
|
76
|
+
const done = yield* Deferred.make();
|
|
77
|
+
settling.push(done);
|
|
78
|
+
raise();
|
|
79
|
+
yield* Deferred.await(done);
|
|
80
|
+
yield* connection.resyncDone;
|
|
81
|
+
}),
|
|
82
|
+
Progress: () => Effect.void,
|
|
83
|
+
}))));
|
|
84
|
+
const follow = Effect.gen(function* () {
|
|
85
|
+
while (true) {
|
|
86
|
+
const ended = yield* listen(held).pipe(Effect.flip, Effect.option);
|
|
87
|
+
if (Option.isNone(ended))
|
|
88
|
+
return;
|
|
89
|
+
if (!recoverable(ended.value))
|
|
90
|
+
return yield* ended.value;
|
|
91
|
+
held = yield* open;
|
|
92
|
+
raise();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const refuse = (reason) => ActorError.make({
|
|
96
|
+
reason: InvalidInput.make({
|
|
97
|
+
code: "not_watchable",
|
|
98
|
+
issues: [{ path: request.command, message: reason }],
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
const rerun = Effect.gen(function* () {
|
|
102
|
+
const read = emptyReadSet();
|
|
103
|
+
let version;
|
|
104
|
+
const outcome = yield* options
|
|
105
|
+
.rerun(() => (version = pending?.toString()), read)
|
|
106
|
+
.pipe(Effect.result);
|
|
107
|
+
if (Result.isFailure(outcome)) {
|
|
108
|
+
if (!transient(outcome.failure))
|
|
109
|
+
return yield* outcome.failure;
|
|
110
|
+
raise();
|
|
111
|
+
yield* Effect.sleep(RERUN_RETRY_MS);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const result = outcome.success;
|
|
115
|
+
if (Outcome.guards.Failure(result))
|
|
116
|
+
return yield* Effect.fail({ failure: result.value });
|
|
117
|
+
if (Outcome.guards.Defect(result)) {
|
|
118
|
+
yield* Effect.logError("Watch handler defect", result.cause);
|
|
119
|
+
return yield* ActorError.make({
|
|
120
|
+
reason: SessionEnded.make({ cause: "Defect", resync: false }),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (!Outcome.guards.Success(result))
|
|
124
|
+
return yield* Effect.die(new Error("A query cannot acknowledge"));
|
|
125
|
+
const unsupported = unsupportedRead(read);
|
|
126
|
+
if (unsupported !== undefined)
|
|
127
|
+
return yield* refuse(unsupported);
|
|
128
|
+
reads = read;
|
|
129
|
+
if (result.value === sent || !(yield* held.authorized))
|
|
130
|
+
return;
|
|
131
|
+
sent = result.value;
|
|
132
|
+
yield* Queue.offer(out, { version, value: result.value });
|
|
133
|
+
});
|
|
134
|
+
const reruns = Effect.gen(function* () {
|
|
135
|
+
let startedAt = Number.NEGATIVE_INFINITY;
|
|
136
|
+
while (true) {
|
|
137
|
+
while (!dirty) {
|
|
138
|
+
const woke = yield* Queue.take(signal).pipe(Effect.timeoutOption(options.reconcileMs));
|
|
139
|
+
if (Option.isNone(woke))
|
|
140
|
+
dirty = true;
|
|
141
|
+
}
|
|
142
|
+
const wait = startedAt + options.minIntervalMs - (yield* Clock.currentTimeMillis);
|
|
143
|
+
if (wait > 0)
|
|
144
|
+
yield* Effect.sleep(wait);
|
|
145
|
+
startedAt = yield* Clock.currentTimeMillis;
|
|
146
|
+
dirty = false;
|
|
147
|
+
const covered = settling;
|
|
148
|
+
settling = [];
|
|
149
|
+
yield* rerun;
|
|
150
|
+
yield* Effect.forEach(covered, (done) => Deferred.succeed(done, undefined), {
|
|
151
|
+
discard: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
yield* Effect.all([follow, reruns], { concurrency: "unbounded", discard: true }).pipe(Effect.catch((error) => Schema.is(ActorError)(error) && Predicate.isTagged(error.reason, "Unauthorized")
|
|
156
|
+
? Effect.andThen(Effect.ignore(Queue.clear(out)), Queue.fail(out, error))
|
|
157
|
+
: Queue.fail(out, error)), Effect.catchCause((cause) => Queue.failCause(out, cause)), Effect.forkScoped);
|
|
158
|
+
}), { bufferSize: 1, strategy: "sliding" });
|
|
159
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Effect, Redacted, Result } from "effect";
|
|
2
|
+
import type { InvalidContentRef } from "../../errors/content.ts";
|
|
3
|
+
/** How long a grant lasts from when it is issued. */
|
|
4
|
+
export declare const GRANT_LIFETIME_MS = 3600000;
|
|
5
|
+
/** One deployment secret grants are signed or verified with. */
|
|
6
|
+
export interface GrantKey {
|
|
7
|
+
/** Named in every grant it signs, so a rotated-out key still verifies its grants. */
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** At least 32 bytes of UTF-8. */
|
|
10
|
+
readonly secret: Redacted.Redacted<string>;
|
|
11
|
+
}
|
|
12
|
+
/** What a grant binds. The deployment id keeps grants of one database out of another. */
|
|
13
|
+
export interface Granted {
|
|
14
|
+
readonly tenant: string;
|
|
15
|
+
readonly hash: string;
|
|
16
|
+
readonly size: number;
|
|
17
|
+
readonly expiresAt: number;
|
|
18
|
+
}
|
|
19
|
+
/** Signs and verifies content grants. Verification compares MACs in constant time (WebCrypto). */
|
|
20
|
+
export interface Grants {
|
|
21
|
+
/** Signs with the first key. */
|
|
22
|
+
readonly sign: (granted: Granted) => Effect.Effect<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Checks a grant's MAC under any configured key; returns its expiry. The
|
|
25
|
+
* caller checks the expiry against the database clock.
|
|
26
|
+
*/
|
|
27
|
+
readonly verify: (grant: string, bound: Omit<Granted, "expiresAt">) => Effect.Effect<Result.Result<number, InvalidContentRef["reason"]>>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates `keys` and imports them once. The first key signs; every key
|
|
31
|
+
* verifies, so an operator adds the new key first, keeps the old one listed
|
|
32
|
+
* for one grant lifetime, and then removes it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const grantKeys: (keys: readonly GrantKey[], deployment: string) => Effect.Effect<Grants, never, never>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Effect, Redacted, Result } from "effect";
|
|
2
|
+
import { Base64Url } from "effect/encoding";
|
|
3
|
+
/** How long a grant lasts from when it is issued. */
|
|
4
|
+
export const GRANT_LIFETIME_MS = 3_600_000;
|
|
5
|
+
const KEY_ID = /^[A-Za-z0-9_-]{1,32}$/;
|
|
6
|
+
/** A grant is `g1.<key id>.<expires ms>.<mac>`, the MAC as unpadded base64url of 32 bytes. */
|
|
7
|
+
const GRANT = /^g1\.([A-Za-z0-9_-]{1,32})\.([1-9][0-9]{0,15})\.([A-Za-z0-9_-]{43})$/;
|
|
8
|
+
const utf8 = new TextEncoder();
|
|
9
|
+
/** The signed message: length-prefixed parts, so no two bindings encode to the same message. */
|
|
10
|
+
const message = (deployment, granted) => utf8.encode([
|
|
11
|
+
"durable-content/v1",
|
|
12
|
+
deployment,
|
|
13
|
+
granted.tenant,
|
|
14
|
+
granted.hash,
|
|
15
|
+
String(granted.size),
|
|
16
|
+
String(granted.expiresAt),
|
|
17
|
+
]
|
|
18
|
+
.map((part) => `${part.length}:${part}`)
|
|
19
|
+
.join(""));
|
|
20
|
+
/**
|
|
21
|
+
* Validates `keys` and imports them once. The first key signs; every key
|
|
22
|
+
* verifies, so an operator adds the new key first, keeps the old one listed
|
|
23
|
+
* for one grant lifetime, and then removes it.
|
|
24
|
+
*/
|
|
25
|
+
export const grantKeys = Effect.fnUntraced(function* (keys, deployment) {
|
|
26
|
+
if (keys.length === 0)
|
|
27
|
+
return yield* Effect.die(new Error("content.keys needs at least one key"));
|
|
28
|
+
const imported = new Map();
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (!KEY_ID.test(key.id))
|
|
31
|
+
return yield* Effect.die(new Error("content.keys ids are 1-32 letters, digits, - or _"));
|
|
32
|
+
if (imported.has(key.id))
|
|
33
|
+
return yield* Effect.die(new Error(`content.keys lists ${key.id} twice`));
|
|
34
|
+
const secret = utf8.encode(Redacted.value(key.secret));
|
|
35
|
+
if (secret.byteLength < 32)
|
|
36
|
+
return yield* Effect.die(new Error(`content.keys ${key.id} needs a secret of at least 32 bytes`));
|
|
37
|
+
imported.set(key.id, yield* Effect.promise(() => globalThis.crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"])));
|
|
38
|
+
}
|
|
39
|
+
const signer = keys[0].id;
|
|
40
|
+
const grants = {
|
|
41
|
+
sign: (granted) => Effect.promise(() => globalThis.crypto.subtle.sign("HMAC", imported.get(signer), message(deployment, granted))).pipe(Effect.map((mac) => `g1.${signer}.${granted.expiresAt}.${Base64Url.encode(new Uint8Array(mac))}`)),
|
|
42
|
+
verify: (grant, bound) => Effect.gen(function* () {
|
|
43
|
+
const parts = GRANT.exec(grant);
|
|
44
|
+
if (parts === null)
|
|
45
|
+
return Result.fail("malformed");
|
|
46
|
+
const expiresAt = Number(parts[2]);
|
|
47
|
+
const key = imported.get(parts[1]);
|
|
48
|
+
const mac = Base64Url.decode(parts[3]);
|
|
49
|
+
if (!Number.isSafeInteger(expiresAt) || Result.isFailure(mac))
|
|
50
|
+
return Result.fail("malformed");
|
|
51
|
+
if (key === undefined)
|
|
52
|
+
return Result.fail("invalid");
|
|
53
|
+
const valid = yield* Effect.promise(() => globalThis.crypto.subtle.verify("HMAC", key, new Uint8Array(mac.success), message(deployment, { ...bound, expiresAt })));
|
|
54
|
+
return valid ? Result.succeed(expiresAt) : Result.fail("invalid");
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
return grants;
|
|
58
|
+
});
|