diff --git a/Cargo.lock b/Cargo.lock index f4f49e4c53..27fa61189d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8493,6 +8493,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rivet-actors-sdk-embed" +version = "0.0.1" +dependencies = [ + "anyhow", + "deno-embed", + "fs_extra", + "include_dir", + "merkle_hash", + "sha2 0.10.8", + "tempfile", + "tokio", + "walkdir", +] + [[package]] name = "rivet-api" version = "0.0.1" @@ -9182,6 +9197,7 @@ dependencies = [ "futures-util", "humansize", "ignore", + "include_dir", "indicatif", "jsonc-parser", "kv-str", @@ -9192,6 +9208,7 @@ dependencies = [ "pkg-version", "regex", "reqwest 0.11.27", + "rivet-actors-sdk-embed", "rivet-api", "rivet-js-utils-embed", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6ec480540a..0b29dcb598 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["packages/api/actor","packages/api/auth","packages/api/cf-verification","packages/api/cloud","packages/api/games","packages/api/group","packages/api/identity","packages/api/job","packages/api/matchmaker","packages/api/monolith-edge","packages/api/monolith-public","packages/api/portal","packages/api/provision","packages/api/status","packages/api/traefik-provider","packages/api/ui","packages/common/api-helper/build","packages/common/api-helper/macros","packages/common/cache/build","packages/common/cache/result","packages/common/chirp-workflow/core","packages/common/chirp-workflow/macros","packages/common/chirp/client","packages/common/chirp/metrics","packages/common/chirp/perf","packages/common/chirp/types","packages/common/chirp/worker","packages/common/chirp/worker-attributes","packages/common/claims","packages/common/config","packages/common/connection","packages/common/convert","packages/common/deno-embed","packages/common/env","packages/common/formatted-error","packages/common/global-error","packages/common/health-checks","packages/common/hub-embed","packages/common/kv-str","packages/common/metrics","packages/common/migrate","packages/common/nomad-util","packages/common/operation/core","packages/common/operation/macros","packages/common/pools","packages/common/redis-util","packages/common/runtime","packages/common/s3-util","packages/common/schemac","packages/common/service-manager","packages/common/smithy-output/api-auth/rust","packages/common/smithy-output/api-auth/rust-server","packages/common/smithy-output/api-cf-verification/rust","packages/common/smithy-output/api-cf-verification/rust-server","packages/common/smithy-output/api-cloud/rust","packages/common/smithy-output/api-cloud/rust-server","packages/common/smithy-output/api-group/rust","packages/common/smithy-output/api-group/rust-server","packages/common/smithy-output/api-identity/rust","packages/common/smithy-output/api-identity/rust-server","packages/common/smithy-output/api-job/rust","packages/common/smithy-output/api-job/rust-server","packages/common/smithy-output/api-kv/rust","packages/common/smithy-output/api-kv/rust-server","packages/common/smithy-output/api-matchmaker/rust","packages/common/smithy-output/api-matchmaker/rust-server","packages/common/smithy-output/api-party/rust","packages/common/smithy-output/api-party/rust-server","packages/common/smithy-output/api-portal/rust","packages/common/smithy-output/api-portal/rust-server","packages/common/smithy-output/api-status/rust","packages/common/smithy-output/api-status/rust-server","packages/common/smithy-output/api-traefik-provider/rust","packages/common/smithy-output/api-traefik-provider/rust-server","packages/common/test","packages/common/test-images","packages/common/types-proto/build","packages/common/types-proto/core","packages/common/util/core","packages/common/util/macros","packages/common/util/search","packages/infra/legacy/job-runner","packages/infra/server","packages/services/build","packages/services/build/ops/create","packages/services/build/ops/get","packages/services/build/ops/list-for-env","packages/services/build/ops/list-for-game","packages/services/build/standalone/default-create","packages/services/build/util","packages/services/captcha/ops/hcaptcha-config-get","packages/services/captcha/ops/hcaptcha-verify","packages/services/captcha/ops/request","packages/services/captcha/ops/turnstile-config-get","packages/services/captcha/ops/turnstile-verify","packages/services/captcha/ops/verify","packages/services/captcha/util","packages/services/cdn/ops/namespace-auth-user-remove","packages/services/cdn/ops/namespace-auth-user-update","packages/services/cdn/ops/namespace-create","packages/services/cdn/ops/namespace-domain-create","packages/services/cdn/ops/namespace-domain-remove","packages/services/cdn/ops/namespace-get","packages/services/cdn/ops/namespace-resolve-domain","packages/services/cdn/ops/ns-auth-type-set","packages/services/cdn/ops/ns-enable-domain-public-auth-set","packages/services/cdn/ops/site-create","packages/services/cdn/ops/site-get","packages/services/cdn/ops/site-list-for-game","packages/services/cdn/ops/version-get","packages/services/cdn/ops/version-prepare","packages/services/cdn/ops/version-publish","packages/services/cdn/util","packages/services/cdn/worker","packages/services/cf-custom-hostname/ops/get","packages/services/cf-custom-hostname/ops/list-for-namespace-id","packages/services/cf-custom-hostname/ops/resolve-hostname","packages/services/cf-custom-hostname/worker","packages/services/cloud/ops/device-link-create","packages/services/cloud/ops/game-config-create","packages/services/cloud/ops/game-config-get","packages/services/cloud/ops/game-token-create","packages/services/cloud/ops/namespace-create","packages/services/cloud/ops/namespace-get","packages/services/cloud/ops/namespace-token-development-create","packages/services/cloud/ops/namespace-token-public-create","packages/services/cloud/ops/version-get","packages/services/cloud/ops/version-publish","packages/services/cloud/standalone/default-create","packages/services/cloud/worker","packages/services/cluster","packages/services/cluster/standalone/datacenter-tls-renew","packages/services/cluster/standalone/default-update","packages/services/cluster/standalone/gc","packages/services/cluster/standalone/metrics-publish","packages/services/custom-user-avatar/ops/list-for-game","packages/services/custom-user-avatar/ops/upload-complete","packages/services/debug/ops/email-res","packages/services/ds","packages/services/ds-log/ops/export","packages/services/ds-log/ops/read","packages/services/dynamic-config","packages/services/email-verification/ops/complete","packages/services/email-verification/ops/create","packages/services/email/ops/send","packages/services/external/ops/request-validate","packages/services/external/worker","packages/services/faker/ops/build","packages/services/faker/ops/cdn-site","packages/services/faker/ops/game","packages/services/faker/ops/game-namespace","packages/services/faker/ops/game-version","packages/services/faker/ops/job-run","packages/services/faker/ops/job-template","packages/services/faker/ops/mm-lobby","packages/services/faker/ops/mm-lobby-row","packages/services/faker/ops/mm-player","packages/services/faker/ops/region","packages/services/faker/ops/team","packages/services/faker/ops/user","packages/services/game/ops/banner-upload-complete","packages/services/game/ops/create","packages/services/game/ops/get","packages/services/game/ops/list-all","packages/services/game/ops/list-for-team","packages/services/game/ops/logo-upload-complete","packages/services/game/ops/namespace-create","packages/services/game/ops/namespace-get","packages/services/game/ops/namespace-list","packages/services/game/ops/namespace-resolve-name-id","packages/services/game/ops/namespace-resolve-url","packages/services/game/ops/namespace-validate","packages/services/game/ops/namespace-version-history-list","packages/services/game/ops/namespace-version-set","packages/services/game/ops/recommend","packages/services/game/ops/resolve-name-id","packages/services/game/ops/resolve-namespace-id","packages/services/game/ops/token-development-validate","packages/services/game/ops/validate","packages/services/game/ops/version-create","packages/services/game/ops/version-get","packages/services/game/ops/version-list","packages/services/game/ops/version-validate","packages/services/ip/ops/info","packages/services/job-log/ops/read","packages/services/job-log/worker","packages/services/job-run","packages/services/job/standalone/gc","packages/services/job/util","packages/services/linode","packages/services/linode/standalone/gc","packages/services/load-test/standalone/api-cloud","packages/services/load-test/standalone/mm","packages/services/load-test/standalone/mm-sustain","packages/services/load-test/standalone/sqlx","packages/services/load-test/standalone/watch-requests","packages/services/mm-config/ops/game-get","packages/services/mm-config/ops/game-upsert","packages/services/mm-config/ops/lobby-group-get","packages/services/mm-config/ops/lobby-group-resolve-name-id","packages/services/mm-config/ops/lobby-group-resolve-version","packages/services/mm-config/ops/namespace-config-set","packages/services/mm-config/ops/namespace-config-validate","packages/services/mm-config/ops/namespace-create","packages/services/mm-config/ops/namespace-get","packages/services/mm-config/ops/version-get","packages/services/mm-config/ops/version-prepare","packages/services/mm-config/ops/version-publish","packages/services/mm/ops/dev-player-token-create","packages/services/mm/ops/lobby-find-fail","packages/services/mm/ops/lobby-find-lobby-query-list","packages/services/mm/ops/lobby-find-try-complete","packages/services/mm/ops/lobby-for-run-id","packages/services/mm/ops/lobby-get","packages/services/mm/ops/lobby-history","packages/services/mm/ops/lobby-idle-update","packages/services/mm/ops/lobby-list-for-namespace","packages/services/mm/ops/lobby-list-for-user-id","packages/services/mm/ops/lobby-player-count","packages/services/mm/ops/lobby-runtime-aggregate","packages/services/mm/ops/lobby-state-get","packages/services/mm/ops/player-count-for-namespace","packages/services/mm/ops/player-get","packages/services/mm/standalone/gc","packages/services/mm/util","packages/services/mm/worker","packages/services/monolith/standalone/worker","packages/services/monolith/standalone/workflow-worker","packages/services/nomad/standalone/monitor","packages/services/pegboard","packages/services/pegboard/standalone/dc-init","packages/services/pegboard/standalone/gc","packages/services/pegboard/standalone/ws","packages/services/region/ops/get","packages/services/region/ops/list","packages/services/region/ops/list-for-game","packages/services/region/ops/recommend","packages/services/region/ops/resolve","packages/services/region/ops/resolve-for-game","packages/services/server-spec","packages/services/team-invite/ops/get","packages/services/team-invite/worker","packages/services/team/ops/avatar-upload-complete","packages/services/team/ops/get","packages/services/team/ops/join-request-list","packages/services/team/ops/member-count","packages/services/team/ops/member-get","packages/services/team/ops/member-list","packages/services/team/ops/member-relationship-get","packages/services/team/ops/profile-validate","packages/services/team/ops/recommend","packages/services/team/ops/resolve-display-name","packages/services/team/ops/user-ban-get","packages/services/team/ops/user-ban-list","packages/services/team/ops/validate","packages/services/team/util","packages/services/team/worker","packages/services/telemetry/standalone/beacon","packages/services/tier","packages/services/token/ops/create","packages/services/token/ops/exchange","packages/services/token/ops/get","packages/services/token/ops/revoke","packages/services/upload/ops/complete","packages/services/upload/ops/file-list","packages/services/upload/ops/get","packages/services/upload/ops/list-for-user","packages/services/upload/ops/prepare","packages/services/upload/worker","packages/services/user","packages/services/user-identity/ops/create","packages/services/user-identity/ops/delete","packages/services/user-identity/ops/get","packages/services/user/ops/avatar-upload-complete","packages/services/user/ops/get","packages/services/user/ops/pending-delete-toggle","packages/services/user/ops/profile-validate","packages/services/user/ops/resolve-email","packages/services/user/ops/team-list","packages/services/user/ops/token-create","packages/services/user/standalone/delete-pending","packages/services/user/worker","packages/services/workflow/standalone/gc","packages/services/workflow/standalone/metrics-publish","packages/toolchain/cli","packages/toolchain/js-utils-embed","packages/toolchain/toolchain","sdks/api/full/rust"] +members = ["packages/api/actor","packages/api/auth","packages/api/cf-verification","packages/api/cloud","packages/api/games","packages/api/group","packages/api/identity","packages/api/job","packages/api/matchmaker","packages/api/monolith-edge","packages/api/monolith-public","packages/api/portal","packages/api/provision","packages/api/status","packages/api/traefik-provider","packages/api/ui","packages/common/api-helper/build","packages/common/api-helper/macros","packages/common/cache/build","packages/common/cache/result","packages/common/chirp-workflow/core","packages/common/chirp-workflow/macros","packages/common/chirp/client","packages/common/chirp/metrics","packages/common/chirp/perf","packages/common/chirp/types","packages/common/chirp/worker","packages/common/chirp/worker-attributes","packages/common/claims","packages/common/config","packages/common/connection","packages/common/convert","packages/common/deno-embed","packages/common/env","packages/common/formatted-error","packages/common/global-error","packages/common/health-checks","packages/common/hub-embed","packages/common/kv-str","packages/common/metrics","packages/common/migrate","packages/common/nomad-util","packages/common/operation/core","packages/common/operation/macros","packages/common/pools","packages/common/redis-util","packages/common/runtime","packages/common/s3-util","packages/common/schemac","packages/common/service-manager","packages/common/smithy-output/api-auth/rust","packages/common/smithy-output/api-auth/rust-server","packages/common/smithy-output/api-cf-verification/rust","packages/common/smithy-output/api-cf-verification/rust-server","packages/common/smithy-output/api-cloud/rust","packages/common/smithy-output/api-cloud/rust-server","packages/common/smithy-output/api-group/rust","packages/common/smithy-output/api-group/rust-server","packages/common/smithy-output/api-identity/rust","packages/common/smithy-output/api-identity/rust-server","packages/common/smithy-output/api-job/rust","packages/common/smithy-output/api-job/rust-server","packages/common/smithy-output/api-kv/rust","packages/common/smithy-output/api-kv/rust-server","packages/common/smithy-output/api-matchmaker/rust","packages/common/smithy-output/api-matchmaker/rust-server","packages/common/smithy-output/api-party/rust","packages/common/smithy-output/api-party/rust-server","packages/common/smithy-output/api-portal/rust","packages/common/smithy-output/api-portal/rust-server","packages/common/smithy-output/api-status/rust","packages/common/smithy-output/api-status/rust-server","packages/common/smithy-output/api-traefik-provider/rust","packages/common/smithy-output/api-traefik-provider/rust-server","packages/common/test","packages/common/test-images","packages/common/types-proto/build","packages/common/types-proto/core","packages/common/util/core","packages/common/util/macros","packages/common/util/search","packages/infra/legacy/job-runner","packages/infra/server","packages/services/build","packages/services/build/ops/create","packages/services/build/ops/get","packages/services/build/ops/list-for-env","packages/services/build/ops/list-for-game","packages/services/build/standalone/default-create","packages/services/build/util","packages/services/captcha/ops/hcaptcha-config-get","packages/services/captcha/ops/hcaptcha-verify","packages/services/captcha/ops/request","packages/services/captcha/ops/turnstile-config-get","packages/services/captcha/ops/turnstile-verify","packages/services/captcha/ops/verify","packages/services/captcha/util","packages/services/cdn/ops/namespace-auth-user-remove","packages/services/cdn/ops/namespace-auth-user-update","packages/services/cdn/ops/namespace-create","packages/services/cdn/ops/namespace-domain-create","packages/services/cdn/ops/namespace-domain-remove","packages/services/cdn/ops/namespace-get","packages/services/cdn/ops/namespace-resolve-domain","packages/services/cdn/ops/ns-auth-type-set","packages/services/cdn/ops/ns-enable-domain-public-auth-set","packages/services/cdn/ops/site-create","packages/services/cdn/ops/site-get","packages/services/cdn/ops/site-list-for-game","packages/services/cdn/ops/version-get","packages/services/cdn/ops/version-prepare","packages/services/cdn/ops/version-publish","packages/services/cdn/util","packages/services/cdn/worker","packages/services/cf-custom-hostname/ops/get","packages/services/cf-custom-hostname/ops/list-for-namespace-id","packages/services/cf-custom-hostname/ops/resolve-hostname","packages/services/cf-custom-hostname/worker","packages/services/cloud/ops/device-link-create","packages/services/cloud/ops/game-config-create","packages/services/cloud/ops/game-config-get","packages/services/cloud/ops/game-token-create","packages/services/cloud/ops/namespace-create","packages/services/cloud/ops/namespace-get","packages/services/cloud/ops/namespace-token-development-create","packages/services/cloud/ops/namespace-token-public-create","packages/services/cloud/ops/version-get","packages/services/cloud/ops/version-publish","packages/services/cloud/standalone/default-create","packages/services/cloud/worker","packages/services/cluster","packages/services/cluster/standalone/datacenter-tls-renew","packages/services/cluster/standalone/default-update","packages/services/cluster/standalone/gc","packages/services/cluster/standalone/metrics-publish","packages/services/custom-user-avatar/ops/list-for-game","packages/services/custom-user-avatar/ops/upload-complete","packages/services/debug/ops/email-res","packages/services/ds","packages/services/ds-log/ops/export","packages/services/ds-log/ops/read","packages/services/dynamic-config","packages/services/email-verification/ops/complete","packages/services/email-verification/ops/create","packages/services/email/ops/send","packages/services/external/ops/request-validate","packages/services/external/worker","packages/services/faker/ops/build","packages/services/faker/ops/cdn-site","packages/services/faker/ops/game","packages/services/faker/ops/game-namespace","packages/services/faker/ops/game-version","packages/services/faker/ops/job-run","packages/services/faker/ops/job-template","packages/services/faker/ops/mm-lobby","packages/services/faker/ops/mm-lobby-row","packages/services/faker/ops/mm-player","packages/services/faker/ops/region","packages/services/faker/ops/team","packages/services/faker/ops/user","packages/services/game/ops/banner-upload-complete","packages/services/game/ops/create","packages/services/game/ops/get","packages/services/game/ops/list-all","packages/services/game/ops/list-for-team","packages/services/game/ops/logo-upload-complete","packages/services/game/ops/namespace-create","packages/services/game/ops/namespace-get","packages/services/game/ops/namespace-list","packages/services/game/ops/namespace-resolve-name-id","packages/services/game/ops/namespace-resolve-url","packages/services/game/ops/namespace-validate","packages/services/game/ops/namespace-version-history-list","packages/services/game/ops/namespace-version-set","packages/services/game/ops/recommend","packages/services/game/ops/resolve-name-id","packages/services/game/ops/resolve-namespace-id","packages/services/game/ops/token-development-validate","packages/services/game/ops/validate","packages/services/game/ops/version-create","packages/services/game/ops/version-get","packages/services/game/ops/version-list","packages/services/game/ops/version-validate","packages/services/ip/ops/info","packages/services/job-log/ops/read","packages/services/job-log/worker","packages/services/job-run","packages/services/job/standalone/gc","packages/services/job/util","packages/services/linode","packages/services/linode/standalone/gc","packages/services/load-test/standalone/api-cloud","packages/services/load-test/standalone/mm","packages/services/load-test/standalone/mm-sustain","packages/services/load-test/standalone/sqlx","packages/services/load-test/standalone/watch-requests","packages/services/mm-config/ops/game-get","packages/services/mm-config/ops/game-upsert","packages/services/mm-config/ops/lobby-group-get","packages/services/mm-config/ops/lobby-group-resolve-name-id","packages/services/mm-config/ops/lobby-group-resolve-version","packages/services/mm-config/ops/namespace-config-set","packages/services/mm-config/ops/namespace-config-validate","packages/services/mm-config/ops/namespace-create","packages/services/mm-config/ops/namespace-get","packages/services/mm-config/ops/version-get","packages/services/mm-config/ops/version-prepare","packages/services/mm-config/ops/version-publish","packages/services/mm/ops/dev-player-token-create","packages/services/mm/ops/lobby-find-fail","packages/services/mm/ops/lobby-find-lobby-query-list","packages/services/mm/ops/lobby-find-try-complete","packages/services/mm/ops/lobby-for-run-id","packages/services/mm/ops/lobby-get","packages/services/mm/ops/lobby-history","packages/services/mm/ops/lobby-idle-update","packages/services/mm/ops/lobby-list-for-namespace","packages/services/mm/ops/lobby-list-for-user-id","packages/services/mm/ops/lobby-player-count","packages/services/mm/ops/lobby-runtime-aggregate","packages/services/mm/ops/lobby-state-get","packages/services/mm/ops/player-count-for-namespace","packages/services/mm/ops/player-get","packages/services/mm/standalone/gc","packages/services/mm/util","packages/services/mm/worker","packages/services/monolith/standalone/worker","packages/services/monolith/standalone/workflow-worker","packages/services/nomad/standalone/monitor","packages/services/pegboard","packages/services/pegboard/standalone/dc-init","packages/services/pegboard/standalone/gc","packages/services/pegboard/standalone/ws","packages/services/region/ops/get","packages/services/region/ops/list","packages/services/region/ops/list-for-game","packages/services/region/ops/recommend","packages/services/region/ops/resolve","packages/services/region/ops/resolve-for-game","packages/services/server-spec","packages/services/team-invite/ops/get","packages/services/team-invite/worker","packages/services/team/ops/avatar-upload-complete","packages/services/team/ops/get","packages/services/team/ops/join-request-list","packages/services/team/ops/member-count","packages/services/team/ops/member-get","packages/services/team/ops/member-list","packages/services/team/ops/member-relationship-get","packages/services/team/ops/profile-validate","packages/services/team/ops/recommend","packages/services/team/ops/resolve-display-name","packages/services/team/ops/user-ban-get","packages/services/team/ops/user-ban-list","packages/services/team/ops/validate","packages/services/team/util","packages/services/team/worker","packages/services/telemetry/standalone/beacon","packages/services/tier","packages/services/token/ops/create","packages/services/token/ops/exchange","packages/services/token/ops/get","packages/services/token/ops/revoke","packages/services/upload/ops/complete","packages/services/upload/ops/file-list","packages/services/upload/ops/get","packages/services/upload/ops/list-for-user","packages/services/upload/ops/prepare","packages/services/upload/worker","packages/services/user","packages/services/user-identity/ops/create","packages/services/user-identity/ops/delete","packages/services/user-identity/ops/get","packages/services/user/ops/avatar-upload-complete","packages/services/user/ops/get","packages/services/user/ops/pending-delete-toggle","packages/services/user/ops/profile-validate","packages/services/user/ops/resolve-email","packages/services/user/ops/team-list","packages/services/user/ops/token-create","packages/services/user/standalone/delete-pending","packages/services/user/worker","packages/services/workflow/standalone/gc","packages/services/workflow/standalone/metrics-publish","packages/toolchain/actors-sdk-embed","packages/toolchain/cli","packages/toolchain/js-utils-embed","packages/toolchain/toolchain","sdks/api/full/rust"] [workspace.package] version = "0.0.1" @@ -926,6 +926,9 @@ path = "packages/services/workflow/standalone/gc" [workspace.dependencies.workflow-metrics-publish] path = "packages/services/workflow/standalone/metrics-publish" +[workspace.dependencies.rivet-actors-sdk-embed] +path = "packages/toolchain/actors-sdk-embed" + [workspace.dependencies.rivet-cli] path = "packages/toolchain/cli" diff --git a/examples/actor-simple/archive/client.ts b/examples/actor-simple/archive/client.ts deleted file mode 100644 index 4634f69981..0000000000 --- a/examples/actor-simple/archive/client.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { RivetClientClient } from "@rivet-gg/api"; -import { ActorClient } from "../../sdks/actors/client/src/mod.ts" - -// TODO: Get rid of this ugly garbage -const TOKEN = "env_svc.eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.CPy6y_yYQBD8ksXhtjIaEgoQ7vu2pTW9Sh-klx_juRpufSIXmgEUChIKEOaWqUrqXkn7iZjUQS58f40.gmHt4P3wSbfDY7gbGxvEdp4xW-SzFSFABUoOJBlxrv9WnnE_vWhwYIbgHRmYHTsNrbYiWZaOWpt4PVi9AdNcAA"; -const client = new RivetClientClient({ - environment: "http://localhost:8080", - // TODO: Allow not providing token in dev - token: TOKEN, -}); - - -async function main() { - const actorClient = new ActorClient(client); - - // Broadcast event - let broadcastActor; - { - const mod = 3; - broadcastActor = await actorClient.getOrCreate({ name: "counter" }).connect(mod); - broadcastActor.on("directCount", (count: unknown) => { - console.log(`Direct (n % ${mod}):`, count); - }); - } - - // Direct event - let directActor; - { - const mod = 3; - directActor = await actorClient.getOrCreate({ name: "counter" }).connect(mod); - directActor.on("directCount", (count: unknown) => { - console.log(`Broadcast:`, count); - }); - } - - // Simple RPC - { - const newCount: number = await actorClient.getOrCreate({ name: "counter" }).rpc("increment", 5); - console.log('Simple RPC:', newCount); - } - - // Multiple RPC calls - { - const actor = actorClient.getOrCreate({ name: "counter" }); - - for (let i = 0; i < 10; i++) { - const output = await actor.rpc("increment", 5); - console.log('Reusing handle:', output); - } - } - - // WebSocket - { - const actor = await actorClient.getOrCreate({ name: "counter" }).connect(); - for (let i = 0; i < 10; i++) { - const newOutput = await actor.rpc("increment", 5); - console.log('WebSocket:', newOutput); - } - - actor.disconnect(); - } - - // Disconnect all actors before - broadcastActor.disconnect(); - directActor.disconnect(); - - // Destroy - { - const actor = await actorClient.getOrCreate({ name: "counter" }).connect(); - await actor.destroy(); - } -} - -await main(); diff --git a/examples/actor-simple/client.ts b/examples/actor-simple/client.ts index edd00cb1ab..a28bbc738d 100644 --- a/examples/actor-simple/client.ts +++ b/examples/actor-simple/client.ts @@ -1,7 +1,7 @@ import { ActorClient } from "../../sdks/actors/client/src/mod.ts" async function main() { - const actorClient = new ActorClient("http://127.0.0.1:20079"); + const actorClient = new ActorClient("http://127.0.0.1:20025"); // Broadcast event let broadcastActor; diff --git a/examples/actor-simple/manager/mod.ts b/examples/actor-simple/manager/mod.ts deleted file mode 100644 index c4b2919806..0000000000 --- a/examples/actor-simple/manager/mod.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Context as HonoContext, Hono } from "hono"; -import { cors } from "hono/cors"; -import { RivetClientClient } from "@rivet-gg/api"; -import { GetOrCreateRequest, queryActor } from "./query.ts"; -import { - PORT_NAME, - RivetEnvironment, -} from "../../../sdks/actors/client/src/utils.ts"; -import { assertExists } from "@std/assert/exists"; -import { assertUnreachable } from "./utils.ts"; - -const portStr = Deno.env.get("PORT_http") ?? Deno.env.get("HTTP_PORT"); -if (!portStr) throw "Missing port"; -const port = parseInt(portStr); -if (!isFinite(port)) throw "Invalid port"; - -// TODO: api endpoint & service token -// TODO: Get rid of this ugly garbage -const TOKEN = - "env_svc.eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.CPy6y_yYQBD8ksXhtjIaEgoQ7vu2pTW9Sh-klx_juRpufSIXmgEUChIKEOaWqUrqXkn7iZjUQS58f40.gmHt4P3wSbfDY7gbGxvEdp4xW-SzFSFABUoOJBlxrv9WnnE_vWhwYIbgHRmYHTsNrbYiWZaOWpt4PVi9AdNcAA"; -const rivetClient = new RivetClientClient({ - environment: "http://rivet-server:8080", - // TODO: Allow not providing token in dev - token: TOKEN, -}); - -// TODO: Read environment -const environment: RivetEnvironment = {}; - -const app = new Hono(); - -app.use("/*", cors()); - -// TODO: This is insecure -export type ActorsRequest = GetOrCreateRequest; - -interface ActorsResponse { - endpoint: string; -} - -app.post("/actors", async (c: HonoContext) => { - // Get actor - const body: ActorsRequest = await c.req.json(); - const actor = await queryActor(rivetClient, environment, { getOrCreate: body }); - - // Fetch port - const httpPort = actor.network.ports[PORT_NAME]; - assertExists(httpPort, "missing port"); - const hostname = httpPort.publicHostname; - assertExists(hostname); - const port = httpPort.publicPort; - assertExists(port); - - let isTls: boolean = false; - switch (httpPort.protocol) { - case "https": - isTls = true; - break; - case "http": - case "tcp": - isTls = false; - break; - case "tcp_tls": - case "udp": - throw new Error(`Invalid protocol ${httpPort.protocol}`); - default: - assertUnreachable(httpPort.protocol); - } - - const endpoint = `${isTls ? "https" : "http"}://${hostname}:${port}`; - - return c.json({ endpoint } satisfies ActorsResponse); -}); - -Deno.serve({ - port, - hostname: "0.0.0.0", -}, app.fetch); diff --git a/examples/actor-simple/manager/query.ts b/examples/actor-simple/manager/query.ts deleted file mode 100644 index 5e3e30056a..0000000000 --- a/examples/actor-simple/manager/query.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { RivetClient, RivetClientClient } from "@rivet-gg/api"; -import { assertUnreachable, PORT_NAME, RivetEnvironment, Tags } from "./utils.ts"; - -export type ActorQuery = - | { actorId: { actorId: string } } - | { get: { tags: Tags } } - | { getOrCreate: GetOrCreateRequest } - | { create: CreateRequest }; - -export interface GetOrCreateRequest { tags: Tags, create?: CreateRequest } - -// TODO(RVT-4250): -export type CreateRequest = Omit & { - resources?: RivetClient.actor.Resources, -}; - -export async function queryActor( - client: RivetClientClient, - environment: RivetEnvironment, - query: ActorQuery, -): Promise { - console.log('Query', query); - if ("actorId" in query) { - const res = await client.actor.get( - query.actorId.actorId, - environment, - ); - if (res.actor.destroyedAt) { - throw new Error(`Actor with ID ${query.actorId.actorId} already destroyed`); - } - return res.actor; - } else if ("get" in query) { - const getActor = await getWithTags(client, environment, query.get.tags); - if (!getActor) throw new Error("Actor not found with tags"); - return getActor; - } else if ("getOrCreate" in query) { - const tags = query.getOrCreate.tags; - if (!tags) throw new Error("Must define tags in getOrCreate"); - const getActor = await getWithTags(client, environment, tags as Tags); - if (getActor) { - return getActor; - } else { - return await createActor(client, environment, query.getOrCreate.create); - } - } else if ("create" in query) { - return await createActor(client, environment, query.create); - } else { - assertUnreachable(query); - } -} - -async function getWithTags( - client: RivetClientClient, - environment: RivetEnvironment, - tags: Tags, -): Promise { - const req = { - tagsJson: JSON.stringify(tags), - ...environment, - }; - console.log("List request", req); - let { actors } = await client.actor.list(req); - - // TODO(RVT-4248): Don't return actors that aren't networkable yet - actors = actors.filter((a) => { - for (const portName in a.network.ports) { - const port = a.network.ports[portName]; - if (!port.publicHostname || !port.publicPort) return false; - } - return true; - }); - - if (actors.length == 0) { - return undefined; - } - if (actors.length > 1) { - actors.sort((a, b) => a.id.localeCompare(b.id)); - } - - return actors[0]; -} - -async function createActor( - client: RivetClientClient, - environment: RivetEnvironment, - createRequest: CreateRequest = {} satisfies CreateRequest -): Promise { - // TODO(RVT-4250): - if (!createRequest.network) { - createRequest.network = {}; - } - if (!createRequest.network.mode) { - // TODO: Don't force host - createRequest.network.mode = "host"; - } - if (!createRequest.network.ports) { - createRequest.network.ports = {}; - } - if (!(PORT_NAME in createRequest.network.ports)) { - // TODO: Don't force TCP protocol - createRequest.network.ports[PORT_NAME] = { - protocol: "tcp", - routing: { host: {} }, - } - } - - const req = { - ...environment, - body: { - // Add fallback resources if one is not provided - resources: { cpu: 100, memory: 100 }, - ...createRequest, - }, - }; - console.log("Create actor", req); - const { actor } = await client.actor.create(req); - return actor; -} - diff --git a/examples/actor-simple/manager/utils.ts b/examples/actor-simple/manager/utils.ts deleted file mode 100644 index 518c5ed611..0000000000 --- a/examples/actor-simple/manager/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type Tags = Record; - -export const PORT_NAME = "http"; - -export interface RivetEnvironment { - project?: string; - environment?: string; -} - -export function assertUnreachable(x: never): never { - throw new Error(`Unreachable case: ${x}`); -} - diff --git a/examples/actor-simple/rivet.jsonc b/examples/actor-simple/rivet.jsonc index 1e3e2f46be..159164fc1f 100644 --- a/examples/actor-simple/rivet.jsonc +++ b/examples/actor-simple/rivet.jsonc @@ -1,17 +1,5 @@ { "builds": [ - { - "tags": { "name": "manager" }, - "runtime": "javascript", - "script": "../../sdks/actors/manager/src/mod.ts", - "deno": { - "config_path": "../../sdks/actors/manager/deno.jsonc", - "lock_path": "../../sdks/actors/deno.lock" - }, - "unstable": { - "minify": false - } - }, { "tags": { "name": "counter" }, "runtime": "javascript", diff --git a/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js b/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js index c442c18060..0fabbfc9f9 100644 --- a/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js +++ b/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js @@ -2,6 +2,26 @@ // // Generated with scripts/pegboard/compile_bridge.ts +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + import { op_rivet_kv_delete, op_rivet_kv_delete_all, op_rivet_kv_delete_batch, op_rivet_kv_get, op_rivet_kv_get_batch, op_rivet_kv_list, op_rivet_kv_put, op_rivet_kv_put_batch, } from "ext:core/ops"; import { core } from "ext:core/mod.js"; /** diff --git a/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js b/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js index 8ca5dcb28e..5a95cc3fc4 100644 --- a/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js +++ b/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js @@ -2,6 +2,26 @@ // // Generated with scripts/pegboard/compile_bridge.ts +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + import { KV_NAMESPACE } from "ext:rivet_kv/40_rivet_kv.js"; import { env } from "ext:runtime/30_os.js"; function parseMetadata() { diff --git a/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js b/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js index c0fcbd79ab..db766ee351 100644 --- a/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js +++ b/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js @@ -2,6 +2,26 @@ // // Generated with scripts/pegboard/compile_bridge.ts +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + +// DO NOT MODIFY +// +// Generated with scripts/pegboard/compile_bridge.ts + import { core, primordials } from "ext:core/mod.js"; import { RIVET_NAMESPACE } from "ext:rivet_runtime/90_rivet_ns.js"; const { ObjectDefineProperty } = primordials; diff --git a/packages/toolchain/actors-sdk-embed/Cargo.toml b/packages/toolchain/actors-sdk-embed/Cargo.toml new file mode 100644 index 0000000000..73d198a9cb --- /dev/null +++ b/packages/toolchain/actors-sdk-embed/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rivet-actors-sdk-embed" +build = "build.rs" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +anyhow = "1.0" +include_dir = "0.7.4" +tokio = { version = "1.40.0", default-features = false, features = ["fs", "rt-multi-thread"] } + +[build-dependencies] +anyhow = "1.0" +fs_extra = "1.3.0" +merkle_hash = "3.7.0" +deno-embed.workspace = true +sha2 = "0.10.8" +tempfile = "3.13.0" +tokio = { version = "1.40.0", default-features = false, features = ["fs", "rt-multi-thread"] } +walkdir = "2.5.0" diff --git a/packages/toolchain/actors-sdk-embed/build.rs b/packages/toolchain/actors-sdk-embed/build.rs new file mode 100644 index 0000000000..b254c795ba --- /dev/null +++ b/packages/toolchain/actors-sdk-embed/build.rs @@ -0,0 +1,57 @@ +use anyhow::*; +use fs_extra::dir::{copy, CopyOptions}; +use merkle_hash::MerkleTree; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +#[tokio::main] +async fn main() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + let out_dir = std::env::var("OUT_DIR")?; + + let sdk_path = PathBuf::from(manifest_dir.clone()).join("../../../sdks/actors"); + + // Copy SDK directory to out_dir + let out_sdk_path = Path::new(&out_dir).join("actors-sdk"); + + // Remove old dir + if out_sdk_path.is_dir() { + fs::remove_dir_all(&out_sdk_path).context("fs::remove_dir_all")?; + } + + // Copy sdk directory to out_dir + let mut copy_options = CopyOptions::new(); + copy_options.overwrite = true; + copy_options.copy_inside = true; + copy(&sdk_path, &out_sdk_path, ©_options).with_context(|| { + format!( + "failed to copy directory from {} to {}", + sdk_path.display(), + out_sdk_path.display() + ) + })?; + + println!("cargo:rerun-if-changed={}", sdk_path.display()); + println!("cargo:rustc-env=ACTORS_SDK_PATH={}", out_sdk_path.display()); + println!( + "cargo:rustc-env=ACTORS_SDK_HASH={}", + hash_directory(&out_sdk_path)? + ); + + Ok(()) +} + +fn hash_directory>(path: P) -> Result { + let tree = MerkleTree::builder(&path.as_ref().display().to_string()).build()?; + let hash = tree + .root + .item + .hash + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(""); + Ok(hash) +} diff --git a/packages/toolchain/actors-sdk-embed/src/lib.rs b/packages/toolchain/actors-sdk-embed/src/lib.rs new file mode 100644 index 0000000000..e489d904d1 --- /dev/null +++ b/packages/toolchain/actors-sdk-embed/src/lib.rs @@ -0,0 +1,22 @@ +use anyhow::*; +use include_dir::{include_dir, Dir}; +use std::path::PathBuf; +use tokio::fs; + +const ACTORS_SDK_DIR: Dir = include_dir!("$ACTORS_SDK_PATH"); +const ACTORS_SDK_HASH: &'static str = env!("ACTORS_SDK_HASH"); + +/// Return a path for the source dir. If one does not exist, the source dir will automatically be +/// extracted and executables will be set. +pub async fn src_path(data_dir: &PathBuf) -> Result { + // Create path to src based on hash + let src_dir = data_dir.join("actors-sdk").join(ACTORS_SDK_HASH); + + // Write actors-sdk if does not exist + if !src_dir.exists() { + fs::create_dir_all(&src_dir).await?; + tokio::task::block_in_place(|| ACTORS_SDK_DIR.extract(&src_dir))?; + } + + Ok(src_dir) +} diff --git a/packages/toolchain/toolchain/Cargo.toml b/packages/toolchain/toolchain/Cargo.toml index 5e51d681dd..5d8d38bc2e 100644 --- a/packages/toolchain/toolchain/Cargo.toml +++ b/packages/toolchain/toolchain/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] async-stream = "0.3.3" +rivet-actors-sdk-embed.workspace = true console = "0.15" dirs = "5.0" futures-util = "0.3" @@ -40,6 +41,7 @@ lazy_static = "1.5.0" sha1 = "0.10.6" jsonc-parser = { version = "0.26.2", features = ["serde"] } kv-str.workspace = true +include_dir = "0.7.4" [target.'cfg(unix)'.dependencies] nix = { version = "0.27", default-features = false, features = ["user", "signal"] } diff --git a/packages/toolchain/toolchain/src/config/mod.rs b/packages/toolchain/toolchain/src/config/mod.rs index 2c1cbbe3b4..9ef5d9fec9 100644 --- a/packages/toolchain/toolchain/src/config/mod.rs +++ b/packages/toolchain/toolchain/src/config/mod.rs @@ -76,4 +76,20 @@ pub struct Build { #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] -pub struct Unstable {} +pub struct Unstable { + #[serde(default)] + pub manager: ManagerUnstable, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", deny_unknown_fields)] +pub struct ManagerUnstable { + pub enable: Option +} + +impl ManagerUnstable { + pub fn enable(&self) -> bool { + self.enable.unwrap_or(true) + } + +} diff --git a/packages/toolchain/toolchain/src/tasks/deploy/docker.rs b/packages/toolchain/toolchain/src/tasks/deploy/docker.rs index d43f0d197f..2517b1d0b9 100644 --- a/packages/toolchain/toolchain/src/tasks/deploy/docker.rs +++ b/packages/toolchain/toolchain/src/tasks/deploy/docker.rs @@ -1,6 +1,7 @@ use anyhow::*; use std::path::Path; use uuid::Uuid; +use std::collections::HashMap; use crate::{ config, paths, @@ -16,6 +17,7 @@ use crate::{ pub struct BuildAndUploadOpts { pub env: TEMPEnvironment, pub config: config::Config, + pub tags: HashMap, pub build_config: config::build::docker::Build, pub version_name: String, } @@ -26,6 +28,8 @@ pub async fn build_and_upload( task: task::TaskCtx, opts: BuildAndUploadOpts, ) -> Result { + task.log(format!("[Building] {}", kv_str::to_str(&opts.tags)?)); + let project_root = paths::project_root()?; // Determine build attributes diff --git a/packages/toolchain/toolchain/src/tasks/deploy/js.rs b/packages/toolchain/toolchain/src/tasks/deploy/js.rs index a5fdeedeea..1052076d0b 100644 --- a/packages/toolchain/toolchain/src/tasks/deploy/js.rs +++ b/packages/toolchain/toolchain/src/tasks/deploy/js.rs @@ -1,7 +1,7 @@ use anyhow::*; use futures_util::{StreamExt, TryStreamExt}; use rivet_api::{apis, models}; -use std::{path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc, collections::HashMap}; use tokio::fs; use uuid::Uuid; @@ -17,6 +17,7 @@ const BUILD_INDEX_NAME: &str = "index.js"; pub struct BuildAndUploadOpts { pub env: TEMPEnvironment, + pub tags: HashMap, pub build_config: config::build::javascript::Build, pub version_name: String, } @@ -27,6 +28,8 @@ pub async fn build_and_upload( task: task::TaskCtx, opts: BuildAndUploadOpts, ) -> Result { + task.log(format!("[Building] {}", kv_str::to_str(&opts.tags)?)); + let project_root = paths::project_root()?; // Create dir to write build artifacts to diff --git a/packages/toolchain/toolchain/src/tasks/deploy/manager.rs b/packages/toolchain/toolchain/src/tasks/deploy/manager.rs new file mode 100644 index 0000000000..c208b1dae1 --- /dev/null +++ b/packages/toolchain/toolchain/src/tasks/deploy/manager.rs @@ -0,0 +1,189 @@ +use anyhow::*; +use rivet_api::{apis, models}; +use std::collections::HashMap; +use uuid::Uuid; + +use crate::{ + config, paths, project::environment::TEMPEnvironment, toolchain_ctx::ToolchainCtx, util::task, +}; + +const HTTP_PORT: &str = "http"; + +pub struct DeployOpts { + pub env: TEMPEnvironment, + pub version_name: String, +} + +pub struct DeployOutput { + pub endpoint: String, +} + +pub async fn deploy( + ctx: &ToolchainCtx, + task: task::TaskCtx, + opts: DeployOpts, +) -> Result { + // Get source + let manager_src_path = rivet_actors_sdk_embed::src_path(&paths::data_dir()?).await?; + + let tags = HashMap::from([ + ("name".to_string(), "manager".to_string()), + ("owner".to_string(), "rivet".to_string()), + ]); + + // Deploy manager + let build_id = super::js::build_and_upload( + ctx, + task.clone(), + super::js::BuildAndUploadOpts { + env: opts.env.clone(), + version_name: opts.version_name, + tags: tags.clone(), + build_config: config::build::javascript::Build { + script: manager_src_path + .join("manager") + .join("src") + .join("mod.ts") + .display() + .to_string(), + bundler: Some(config::build::javascript::Bundler::Deno), + deno: config::build::javascript::Deno { + config_path: Some(manager_src_path.join("deno.jsonc").display().to_string()), + import_map_url: None, + lock_path: Some(manager_src_path.join("deno.lock").display().to_string()), + }, + unstable: Default::default(), + }, + }, + ) + .await?; + + // Check if manager exists + let res = apis::actor_api::actor_list( + &ctx.openapi_config_cloud, + Some(&ctx.project.name_id), + Some(&opts.env.slug), + Some(&serde_json::to_string(&serde_json::json!({ + "name": "manager", + }))?), + Some(false), + None, + ) + .await?; + if res.actors.len() > 1 { + eprintln!("WARNING: More than 1 manager actor is running. We recommend manually stopping one of them.") + } + let actor = if let Some(actor) = res.actors.into_iter().next() { + // Upgrade manager actor + apis::actor_api::actor_upgrade( + &ctx.openapi_config_cloud, + &actor.id.to_string(), + models::ActorUpgradeActorRequest { + build: Some(build_id), + build_tags: None, + }, + Some(&ctx.project.name_id), + Some(&opts.env.slug), + ) + .await?; + + actor + } else { + // Create new actor + + // Choose a region that's closest to the core Rivet datacenter. This actor makes a lot of API + // requests to Rivet, so we want to reduce that RTT. + let regions = apis::actor_regions_api::actor_regions_list( + &ctx.openapi_config_cloud, + Some(&ctx.project.name_id), + Some(&opts.env.slug), + ) + .await?; + let region = if let Some(ideal_region) = regions + .regions + .iter() + .filter(|r| r.id == "atl" || r.id == "local") + .next() + { + ideal_region.id.clone() + } else { + regions.regions.first().context("no regions")?.id.clone() + }; + + // Issue service token + let service_token = apis::games_environments_tokens_api::games_environments_tokens_create_service_token( + &ctx.openapi_config_cloud, + &ctx.project.game_id.to_string(), + &opts.env.id.to_string() + ).await?; + + // TODO(RVT-4263): Auto-determine TCP or HTTP networking + // Get or create actor + let request = models::ActorCreateActorRequest { + region: Some(region), + tags: Some(serde_json::json!(tags)), + build: Some(build_id), + build_tags: None, + runtime: None, + network: Some(Box::new(models::ActorCreateActorNetworkRequest { + mode: Some(models::ActorNetworkMode::Host), + ports: Some(HashMap::from([ + // TODO(RVT-4263): + ( + HTTP_PORT.to_string(), + models::ActorCreateActorPortRequest { + protocol: models::ActorPortProtocol::Tcp, + internal_port: None, + routing: Some(Box::new(models::ActorPortRouting { + host: Some(serde_json::json!({})), + guard: None, + })), + }, + ), + ])), + })), + resources: Box::new(models::ActorResources { + cpu: 100, + memory: 100, + }), + lifecycle: Some(Box::new(models::ActorLifecycle { + durable: Some(true), + kill_timeout: None, + })), + }; + let response = apis::actor_api::actor_create( + &ctx.openapi_config_cloud, + request, + Some(&ctx.project.name_id), + Some(&opts.env.slug), + ) + .await?; + + *response.actor + }; + + // Get endpoitn + let http_port = actor + .network + .ports + .get(HTTP_PORT) + .context("missing http port")?; + let protocol = match http_port.protocol { + models::ActorPortProtocol::Http | models::ActorPortProtocol::Tcp => "http", + models::ActorPortProtocol::Https => "https", + models::ActorPortProtocol::TcpTls | models::ActorPortProtocol::Udp => { + bail!("unsupported protocol") + } + }; + let public_hostname = http_port + .public_hostname + .as_ref() + .context("missing public_hostname")?; + let public_port = http_port + .public_port + .as_ref() + .context("missing public_port")?; + let endpoint = format!("{protocol}://{public_hostname}:{public_port}"); + + Ok(DeployOutput { endpoint }) +} diff --git a/packages/toolchain/toolchain/src/tasks/deploy/mod.rs b/packages/toolchain/toolchain/src/tasks/deploy/mod.rs index 43029ee3c9..e8bb762bdf 100644 --- a/packages/toolchain/toolchain/src/tasks/deploy/mod.rs +++ b/packages/toolchain/toolchain/src/tasks/deploy/mod.rs @@ -13,6 +13,7 @@ use crate::{ mod docker; mod js; +mod manager; #[derive(Deserialize)] pub struct Input { @@ -50,6 +51,23 @@ impl task::Task for Task { .await?; let version_name = reserve_res.version_display_name; + // Manager + let manager_res = if input.config.unstable().manager.enable() { + Some( + manager::deploy( + &ctx, + task.clone(), + manager::DeployOpts { + env: env.clone(), + version_name: version_name.clone(), + }, + ) + .await?, + ) + } else { + None + }; + // Build let mut build_ids = Vec::new(); for build in &input.config.builds { @@ -60,8 +78,6 @@ impl task::Task for Task { } } - task.log(format!("[Building] {}", kv_str::to_str(&build.tags)?)); - // Build let build_id = build_and_upload( &ctx, @@ -79,6 +95,19 @@ impl task::Task for Task { task.log("[Deploy Finished]"); + if let Some(manager_res) = &manager_res { + task.log(""); + task.log(format!("Manager endpoint: {}", manager_res.endpoint)); + task.log(""); + task.log("Exapmle code:"); + task.log(""); + task.log(r#" import ActorClient from "@rivet-gg/actors-client";"#); + task.log(format!( + r#" const actorClient = new ActorClient("{}");"#, + manager_res.endpoint + )); + } + Ok(Output { build_ids }) } } @@ -125,6 +154,7 @@ async fn build_and_upload( docker::BuildAndUploadOpts { env: env.clone(), config: config.clone(), + tags: build.tags.clone(), build_config: docker.clone(), version_name: version_name.to_string(), }, @@ -137,6 +167,7 @@ async fn build_and_upload( task.clone(), js::BuildAndUploadOpts { env: env.clone(), + tags: build.tags.clone(), build_config: js.clone(), version_name: version_name.to_string(), }, diff --git a/packages/toolchain/toolchain/src/util/actor_manager.rs b/packages/toolchain/toolchain/src/util/actor_manager.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/packages/toolchain/toolchain/src/util/actor_manager.rs @@ -0,0 +1 @@ + diff --git a/scripts/pegboard/compile_bridge.ts b/scripts/sdk_actors/compile_bridge.ts similarity index 54% rename from scripts/pegboard/compile_bridge.ts rename to scripts/sdk_actors/compile_bridge.ts index 70fd014588..ed1a5eda35 100755 --- a/scripts/pegboard/compile_bridge.ts +++ b/scripts/sdk_actors/compile_bridge.ts @@ -1,25 +1,35 @@ #!/usr/bin/env -S deno run -A import $ from "dax"; -import { exists, walk } from "@std/fs"; +import { copy, exists, walk } from "@std/fs"; import { resolve } from "@std/path"; -const actorBridgePath = resolve( +const ACTORS_SDK_PATH = resolve( import.meta.dirname, - "../../sdks/actors-bridge/", + "../../sdks/actors", ); -const actorBridgeTypesPath = resolve(actorBridgePath, "types"); + +const ACTOR_BRIDGE_PATH = resolve(ACTORS_SDK_PATH, "bridge"); +const ACTOR_BRIDGE_TYPES_PATH = resolve(ACTOR_BRIDGE_PATH, "types"); + +const ACTOR_CORE_TYPES_PATH = resolve(ACTORS_SDK_PATH, "core", "src", "types"); // Compile JS bridge await $`npx -p typescript@5.7.2 tsc -p tsconfig.bridge.json` - .cwd(actorBridgePath); + .cwd(ACTOR_BRIDGE_PATH); // Add header to JS bridge for await ( - const entry of walk(resolve(import.meta.dirname, "../../packages/infra/client/isolate-v8-runner/js/"), { - exts: [".js"], - includeDirs: false, - }) + const entry of walk( + resolve( + import.meta.dirname, + "../../packages/infra/client/isolate-v8-runner/js/", + ), + { + exts: [".js"], + includeDirs: false, + }, + ) ) { const content = await Deno.readTextFile(entry.path); await Deno.writeTextFile( @@ -30,18 +40,18 @@ for await ( } // Clean types -if (await exists(actorBridgeTypesPath, { directory: true })) { - await Deno.remove(actorBridgeTypesPath, { recursive: true }); +if (await exists(ACTOR_BRIDGE_TYPES_PATH, { directory: true })) { + await Deno.remove(ACTOR_BRIDGE_TYPES_PATH, { recursive: true }); } // Compile TypeScript types await $`npx -p typescript@5.7.2 tsc -p tsconfig.types.json` - .cwd(actorBridgePath); + .cwd(ACTOR_BRIDGE_PATH); // Replace imports from `ext:*` to `./*.d.ts`. This is required for this // package to be usable on JSR. for await ( - const entry of walk(actorBridgeTypesPath, { + const entry of walk(ACTOR_BRIDGE_TYPES_PATH, { exts: [".ts"], includeDirs: false, }) @@ -57,3 +67,6 @@ for await ( newContent, ); } + +// Copy types to core repo +copy(ACTOR_BRIDGE_TYPES_PATH, ACTOR_CORE_TYPES_PATH, { recursive: true }); diff --git a/sdks/actors/core/src/types b/sdks/actors/core/src/types deleted file mode 120000 index e5d3ea71bb..0000000000 --- a/sdks/actors/core/src/types +++ /dev/null @@ -1 +0,0 @@ -../../actors-bridge/types/ \ No newline at end of file diff --git a/sdks/actors/core/src/types/40_rivet_kv.d.ts b/sdks/actors/core/src/types/40_rivet_kv.d.ts new file mode 100644 index 0000000000..6d98b7fd18 --- /dev/null +++ b/sdks/actors/core/src/types/40_rivet_kv.d.ts @@ -0,0 +1,107 @@ +// DO NOT MODIFY +// +// Generated from sdks/actors-bridge/ + +export type Key = any; +export type Entry = any; +/** + * Options for the `get` function. + */ +export interface GetOptions { + format?: "value" | "arrayBuffer"; +} +/** + * Retrieves a value from the key-value store. + * + * @param {Key} key - The key to retrieve the value for. + * @param {GetOptions} [options] - Options. + * @returns {Promise} The retrieved value, or undefined if the key does not exist. + */ +declare function get(key: Key, options?: GetOptions): Promise; +/** + * Options for the `getBatch` function. + */ +export interface GetBatchOptions { + format?: "value" | "arrayBuffer"; +} +/** + * Retrieves a batch of key-value pairs. + * + * @param {Key[]} keys - A list of keys to retrieve. + * @param {GetBatchOptions} [options] - Options. + * @returns {Promise>} The retrieved values. + */ +declare function getBatch(keys: Key[], options?: GetBatchOptions): Promise>; +/** + * Options for the `list` function. + */ +export interface ListOptions { + format?: "value" | "arrayBuffer"; + start?: Key; + startAfter?: Key; + end?: Key; + prefix?: Key; + reverse?: boolean; + limit?: number; +} +/** + * Retrieves all key-value pairs in the KV store. + * + * @param {ListOptions} [options] - Options. + * @returns {Promise>} The retrieved values. + */ +declare function list(options?: ListOptions): Promise>; +/** + * Options for the `put` function. + */ +export interface PutOptions { + format?: "value" | "arrayBuffer"; +} +/** + * Stores a key-value pair in the key-value store. + * + * @param {Key} key - The key under which the value will be stored. + * @param {Entry | ArrayBuffer} value - The value to be stored, which will be serialized. + * @param {PutOptions} [options] - Options. + * @returns {Promise} A promise that resolves when the operation is complete. + */ +declare function put(key: Key, value: Entry | ArrayBuffer, options?: PutOptions): Promise; +/** + * Options for the `putBatch` function. + */ +export interface PutBatchOptions { + format?: "value" | "arrayBuffer"; +} +/** + * Asynchronously stores a batch of key-value pairs. + * + * @param {Record} obj - An object containing key-value pairs to be stored. + * @param {PutBatchOptions} [options] - Options. + * @returns {Promise} A promise that resolves when the batch operation is complete. + */ +declare function putBatch(obj: Map, options?: PutBatchOptions): Promise; +/** + * Deletes a key-value pair from the key-value store. + * + * @param {Key} key - The key of the key-value pair to delete. + * @returns {Promise} A promise that resolves when the operation is complete. + */ +declare function delete_(key: Key): Promise; +declare function deleteBatch(keys: Key[]): Promise; +/** + * Deletes all data from the key-value store. **This CANNOT be undone.** + * + * @returns {Promise} A promise that resolves when the operation is complete. + */ +declare function deleteAll(): Promise; +export declare const KV_NAMESPACE: { + get: typeof get; + getBatch: typeof getBatch; + list: typeof list; + put: typeof put; + putBatch: typeof putBatch; + delete: typeof delete_; + deleteBatch: typeof deleteBatch; + deleteAll: typeof deleteAll; +}; +export {}; diff --git a/sdks/actors/core/src/types/90_rivet_ns.d.ts b/sdks/actors/core/src/types/90_rivet_ns.d.ts new file mode 100644 index 0000000000..06932bcc85 --- /dev/null +++ b/sdks/actors/core/src/types/90_rivet_ns.d.ts @@ -0,0 +1,16 @@ +// DO NOT MODIFY +// +// Generated from sdks/actors-bridge/ + +export declare const RIVET_NAMESPACE: { + kv: { + get: (key: import("./40_rivet_kv.d.ts").Key, options?: import("./40_rivet_kv.d.ts").GetOptions) => Promise; + getBatch: (keys: import("./40_rivet_kv.d.ts").Key[], options?: import("./40_rivet_kv.d.ts").GetBatchOptions) => Promise>; + list: (options?: import("./40_rivet_kv.d.ts").ListOptions) => Promise>; + put: (key: import("./40_rivet_kv.d.ts").Key, value: import("./40_rivet_kv.d.ts").Entry | ArrayBuffer, options?: import("./40_rivet_kv.d.ts").PutOptions) => Promise; + putBatch: (obj: Map, options?: import("./40_rivet_kv.d.ts").PutBatchOptions) => Promise; + delete: (key: import("./40_rivet_kv.d.ts").Key) => Promise; + deleteBatch: (keys: import("./40_rivet_kv.d.ts").Key[]) => Promise; + deleteAll: () => Promise; + }; +}; diff --git a/sdks/actors/core/src/types/99_rivet_main.d.ts b/sdks/actors/core/src/types/99_rivet_main.d.ts new file mode 100644 index 0000000000..46b5a36e54 --- /dev/null +++ b/sdks/actors/core/src/types/99_rivet_main.d.ts @@ -0,0 +1,5 @@ +// DO NOT MODIFY +// +// Generated from sdks/actors-bridge/ + +export {}; diff --git a/sdks/actors/manager/src/manager.ts b/sdks/actors/manager/src/manager.ts new file mode 100644 index 0000000000..1950bd6511 --- /dev/null +++ b/sdks/actors/manager/src/manager.ts @@ -0,0 +1,88 @@ +import { Context as HonoContext, Hono } from "hono"; +import { cors } from "hono/cors"; +import { RivetClient, RivetClientClient } from "@rivet-gg/api"; +import { queryActor } from "./query_exec.ts"; +import { assertExists } from "@std/assert/exists"; +import { + assertUnreachable, + PORT_NAME, + RivetEnvironment, +} from "../../common/src/utils.ts"; +import { + ActorsRequest, + ActorsResponse, +} from "../../manager-protocol/src/mod.ts"; + +export class Manager { + private readonly rivetClient: RivetClientClient; + private readonly environment: RivetEnvironment; + + constructor() { + //const endpoint = Deno.env.get("RIVET_API_ENDPOINT"); + //assertExists(endpoint, "missing RIVET_API_ENDPOINT"); + //const token = Deno.env.get("RIVET_SERVICE_TOKEN"); + //assertExists(token, "missing RIVET_SERVICE_TOKEN"); + //const project = Deno.env.get("RIVET_PROJECT"); + //const environment = Deno.env.get("RIVET_ENVIRONMENT"); + + this.rivetClient = new RivetClientClient({ + environment: endpoint, + token, + }); + + this.environment = { + project, + environment, + }; + } + + run() { + const portStr = Deno.env.get("PORT_http") ?? Deno.env.get("HTTP_PORT"); + if (!portStr) throw "Missing port"; + const port = parseInt(portStr); + if (!isFinite(port)) throw "Invalid port"; + + const app = new Hono(); + + app.use("/*", cors()); + + app.post("/actors", async (c: HonoContext) => { + // Get actor + const body: ActorsRequest = await c.req.json(); + const actor = await queryActor(rivetClient, environment, body.query); + + // Fetch port + const httpPort = actor.network.ports[PORT_NAME]; + assertExists(httpPort, "missing port"); + const hostname = httpPort.publicHostname; + assertExists(hostname); + const port = httpPort.publicPort; + assertExists(port); + + let isTls: boolean = false; + switch (httpPort.protocol) { + case "https": + isTls = true; + break; + case "http": + case "tcp": + isTls = false; + break; + case "tcp_tls": + case "udp": + throw new Error(`Invalid protocol ${httpPort.protocol}`); + default: + assertUnreachable(httpPort.protocol); + } + + const endpoint = `${isTls ? "https" : "http"}://${hostname}:${port}`; + + return c.json({ endpoint } satisfies ActorsResponse); + }); + + Deno.serve({ + port, + hostname: "0.0.0.0", + }, app.fetch); + } +} diff --git a/sdks/actors/manager/src/mod.ts b/sdks/actors/manager/src/mod.ts index 87b6e61136..e69de29bb2 100644 --- a/sdks/actors/manager/src/mod.ts +++ b/sdks/actors/manager/src/mod.ts @@ -1,68 +0,0 @@ -import { Context as HonoContext, Hono } from "hono"; -import { cors } from "hono/cors"; -import { RivetClientClient } from "@rivet-gg/api"; -import { queryActor } from "./query_exec.ts"; -import { assertExists } from "@std/assert/exists"; -import { assertUnreachable, PORT_NAME, RivetEnvironment } from "../../common/src/utils.ts"; -import { ActorsRequest, ActorsResponse } from "../../manager-protocol/src/mod.ts"; - -const portStr = Deno.env.get("PORT_http") ?? Deno.env.get("HTTP_PORT"); -if (!portStr) throw "Missing port"; -const port = parseInt(portStr); -if (!isFinite(port)) throw "Invalid port"; - -// TODO: api endpoint & service token -// TODO: Get rid of this ugly garbage -const TOKEN = - "env_svc.eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.CPy6y_yYQBD8ksXhtjIaEgoQ7vu2pTW9Sh-klx_juRpufSIXmgEUChIKEOaWqUrqXkn7iZjUQS58f40.gmHt4P3wSbfDY7gbGxvEdp4xW-SzFSFABUoOJBlxrv9WnnE_vWhwYIbgHRmYHTsNrbYiWZaOWpt4PVi9AdNcAA"; -const rivetClient = new RivetClientClient({ - environment: "http://rivet-server:8080", - // TODO: Allow not providing token in dev - token: TOKEN, -}); - -// TODO: Read environment -const environment: RivetEnvironment = {}; - -const app = new Hono(); - -app.use("/*", cors()); - -app.post("/actors", async (c: HonoContext) => { - // Get actor - const body: ActorsRequest = await c.req.json(); - const actor = await queryActor(rivetClient, environment, body.query); - - // Fetch port - const httpPort = actor.network.ports[PORT_NAME]; - assertExists(httpPort, "missing port"); - const hostname = httpPort.publicHostname; - assertExists(hostname); - const port = httpPort.publicPort; - assertExists(port); - - let isTls: boolean = false; - switch (httpPort.protocol) { - case "https": - isTls = true; - break; - case "http": - case "tcp": - isTls = false; - break; - case "tcp_tls": - case "udp": - throw new Error(`Invalid protocol ${httpPort.protocol}`); - default: - assertUnreachable(httpPort.protocol); - } - - const endpoint = `${isTls ? "https" : "http"}://${hostname}:${port}`; - - return c.json({ endpoint } satisfies ActorsResponse); -}); - -Deno.serve({ - port, - hostname: "0.0.0.0", -}, app.fetch); diff --git a/sdks/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js b/sdks/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js new file mode 100644 index 0000000000..3e8531eeca --- /dev/null +++ b/sdks/packages/infra/client/isolate-v8-runner/js/40_rivet_kv.js @@ -0,0 +1,226 @@ +import { op_rivet_kv_delete, op_rivet_kv_delete_all, op_rivet_kv_delete_batch, op_rivet_kv_get, op_rivet_kv_get_batch, op_rivet_kv_list, op_rivet_kv_put, op_rivet_kv_put_batch, } from "ext:core/ops"; +import { core } from "ext:core/mod.js"; +/** + * Retrieves a value from the key-value store. + * + * @param {Key} key - The key to retrieve the value for. + * @param {GetOptions} [options] - Options. + * @returns {Promise} The retrieved value, or undefined if the key does not exist. + */ +async function get(key, options) { + let entry = await op_rivet_kv_get(serializeKey(key)); + if (entry == null) + return null; + return deserializeValue(key, entry.value, options?.format); +} +/** + * Retrieves a batch of key-value pairs. + * + * @param {Key[]} keys - A list of keys to retrieve. + * @param {GetBatchOptions} [options] - Options. + * @returns {Promise>} The retrieved values. + */ +async function getBatch(keys, options) { + let entries = await op_rivet_kv_get_batch(keys.map((x) => serializeKey(x))); + let deserializedValues = new Map(); + for (let [key, entry] of entries) { + let jsKey = deserializeKey(key); + deserializedValues.set(jsKey, deserializeValue(jsKey, entry.value, options?.format)); + } + return deserializedValues; +} +/** + * Retrieves all key-value pairs in the KV store. + * + * @param {ListOptions} [options] - Options. + * @returns {Promise>} The retrieved values. + */ +async function list(options) { + // Build query + let query; + if (options?.prefix) { + query = { + prefix: serializeListKey(options.prefix), + }; + } + else if (options?.start) { + if (!options.end) { + throw new Error("must set options.end with options.start"); + } + query = { + rangeInclusive: [serializeListKey(options.start), serializeKey(options.end)], + }; + } + else if (options?.startAfter) { + if (!options.end) { + throw new Error("must set options.end with options.startAfter"); + } + query = { + rangeExclusive: [serializeListKey(options.startAfter), serializeKey(options.end)], + }; + } + else if (options?.end) { + throw new Error("must set options.start or options.startAfter with options.end"); + } + else { + query = { all: {} }; + } + let entries = await op_rivet_kv_list(query, options?.reverse ?? false, options?.limit); + let deserializedValues = new Map(); + for (let [key, entry] of entries) { + let jsKey = deserializeKey(key); + deserializedValues.set(jsKey, deserializeValue(jsKey, entry.value, options?.format)); + } + return deserializedValues; +} +/** + * Stores a key-value pair in the key-value store. + * + * @param {Key} key - The key under which the value will be stored. + * @param {Entry | ArrayBuffer} value - The value to be stored, which will be serialized. + * @param {PutOptions} [options] - Options. + * @returns {Promise} A promise that resolves when the operation is complete. + */ +async function put(key, value, options) { + validateType(value, null, options?.format); + let format = options?.format ?? "value"; + let serializedValue; + if (format == "value") { + serializedValue = core.serialize(value, { forStorage: true }); + } + else if (format == "arrayBuffer") { + if (value instanceof ArrayBuffer) + serializedValue = new Uint8Array(value); + else + throw new Error(`value must be of type \`ArrayBuffer\` if format is "arrayBuffer"`); + } + await op_rivet_kv_put(serializeKey(key), serializedValue); +} +/** + * Asynchronously stores a batch of key-value pairs. + * + * @param {Record} obj - An object containing key-value pairs to be stored. + * @param {PutBatchOptions} [options] - Options. + * @returns {Promise} A promise that resolves when the batch operation is complete. + */ +async function putBatch(obj, options) { + let serializedObj = new Map(); + let format = options?.format ?? "value"; + for (let [key, value] of obj) { + validateType(value, key, format); + let serializedValue; + if (format == "value") { + serializedValue = core.serialize(value, { forStorage: true }); + } + else if (format == "arrayBuffer") { + if (value instanceof ArrayBuffer) + serializedValue = new Uint8Array(value); + else + throw new Error(`value in key "${key}" must be of type \`ArrayBuffer\` if format is "arrayBuffer"`); + } + serializedObj.set(serializeKey(key), serializedValue); + } + await op_rivet_kv_put_batch(serializedObj); +} +/** + * Deletes a key-value pair from the key-value store. + * + * @param {Key} key - The key of the key-value pair to delete. + * @returns {Promise} A promise that resolves when the operation is complete. + */ +async function delete_(key) { + return await op_rivet_kv_delete(serializeKey(key)); +} +async function deleteBatch(keys) { + return await op_rivet_kv_delete_batch(keys.map((x) => serializeKey(x))); +} +/** + * Deletes all data from the key-value store. **This CANNOT be undone.** + * + * @returns {Promise} A promise that resolves when the operation is complete. + */ +async function deleteAll() { + return await op_rivet_kv_delete_all(); +} +function validateType(value, key, format = "value") { + let keyText = key ? ` in key "{key}"` : ""; + if (format == "value") { + if (value instanceof Blob) { + throw new Error(`the type ${value.constructor.name}${keyText} is not serializable in Deno, but you can use a TypedArray instead. See https://github.com/denoland/deno/issues/12067#issuecomment-1975001079.`); + } + if (value instanceof CryptoKey || + value instanceof DOMException || + // Not defined in Deno + // value instanceof RTCCertificate || + // We don't load in the canvas ext into the the Deno runtime for Rivet + // value instanceof ImageBitmap || + value instanceof ImageData) { + throw new Error(`the type ${value.constructor.name}${keyText} is not serializable in Deno. See https://github.com/denoland/deno/issues/12067#issuecomment-1975001079.`); + } + } + else if (format == "arrayBuffer") { + if (!(value instanceof ArrayBuffer)) { + throw new Error(`value must be an ArrayBuffer if options.format = "arrayBuffer".`); + } + } + else { + throw new Error("unexpected key type from KV driver"); + } +} +function serializeKey(key) { + if (key instanceof Array) { + return { jsInKey: key.map((x) => core.serialize(x)) }; + } + else { + return { jsInKey: [core.serialize(key)] }; + } +} +function serializeListKey(key) { + if (key instanceof Array) { + return key.map((x) => core.serialize(x)); + } + else { + return [core.serialize(key)]; + } +} +function deserializeKey(key) { + if ("inKey" in key || "outKey" in key) { + let jsKey = key.inKey ?? key.outKey; + let tuple = jsKey.map((x) => core.deserialize(x)); + if (tuple.length == 1) + return tuple[0]; + else + return tuple; + } + else { + throw new Error("unexpected key type from KV driver"); + } +} +function deserializeValue(key, value, format = "value") { + if (value == undefined) + return value; + if (format == "value") { + try { + return core.deserialize(value, { forStorage: true }); + } + catch (e) { + throw new Error(`could not deserialize value in key "${key}". you must use options.format = "arrayBuffer".`, { cause: e }); + } + } + else if (format == "arrayBuffer") { + return value.buffer; + } + else { + throw Error(`invalid format: "${format}". expected "value" or "arrayBuffer".`); + } +} +export const KV_NAMESPACE = { + get, + getBatch, + list, + put, + putBatch, + delete: delete_, + deleteBatch, + deleteAll, +}; diff --git a/sdks/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js b/sdks/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js new file mode 100644 index 0000000000..6d9c1ef337 --- /dev/null +++ b/sdks/packages/infra/client/isolate-v8-runner/js/90_rivet_ns.js @@ -0,0 +1,4 @@ +import { KV_NAMESPACE } from "ext:rivet_kv/40_rivet_kv.js"; +export const RIVET_NAMESPACE = { + kv: KV_NAMESPACE, +}; diff --git a/sdks/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js b/sdks/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js new file mode 100644 index 0000000000..bf1c53c1ee --- /dev/null +++ b/sdks/packages/infra/client/isolate-v8-runner/js/99_rivet_main.js @@ -0,0 +1,4 @@ +import { core, primordials } from "ext:core/mod.js"; +import { RIVET_NAMESPACE } from "ext:rivet_runtime/90_rivet_ns.js"; +const { ObjectDefineProperty } = primordials; +ObjectDefineProperty(globalThis, "Rivet", core.propReadOnly(RIVET_NAMESPACE));