diff --git a/.dockerignore b/.dockerignore index fe2aaff472..fdd1e06e24 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,29 @@ # MARK: Docker-specific .git/ +.gitignore +.gitattributes +.dockerignore +**/Dockerfile* +**/docker-compose*.yml + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml +.circleci/ + +# Documentation +*.md +!README.md +docs/ +LICENSE* + +# Editor configs +.vscode/ +.idea/ +*.swp +*.swo +*~ # MARK: Copied from .gitignore **/.DS_Store diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 65224250e4..2ff027202a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -83,17 +83,25 @@ jobs: target: x86_64-unknown-linux-musl binary_ext: "" arch: x86_64 + # TODO: Add back when working + # - platform: linux + # runner: depot-ubuntu-24.04-arm-4 + # target: aarch64-unknown-linux-musl + # binary_ext: "" + # arch: aarch64 - platform: windows runner: depot-ubuntu-24.04-4 target: x86_64-pc-windows-gnu binary_ext: ".exe" arch: x86_64 - platform: macos + # Use Linux instead of macOS builders since macOS does not support Docker runner: depot-ubuntu-24.04-4 target: x86_64-apple-darwin binary_ext: "" arch: x86_64 - platform: macos + # Use Linux instead of macOS builders since macOS does not support Docker runner: depot-ubuntu-24.04-4 target: aarch64-apple-darwin binary_ext: "" @@ -111,9 +119,9 @@ jobs: run: | # Use Docker BuildKit export DOCKER_BUILDKIT=1 - + # Build the binary using our Dockerfile - docker/engine/build.sh ${{ matrix.target }} + engine/docker/engine/build.sh ${{ matrix.target }} # Make sure dist directory exists and binary is there ls -la dist/ @@ -153,15 +161,12 @@ jobs: strategy: matrix: include: - # TODO(RVT-4479): Add back ARM builder once manifest generation fixed - # - platform: linux/arm64 - # runner: depot-ubuntu-24.04-4 - # arch_suffix: -arm64 + - platform: linux/arm64 + runner: depot-ubuntu-24.04-arm-4 + arch_suffix: -arm64 - platform: linux/x86_64 runner: depot-ubuntu-24.04-4 - # TODO: Replace with appropriate arch_suffix when needed - # arch_suffix: -amd64 - arch_suffix: '' + arch_suffix: -amd64 runs-on: ${{ matrix.runner }} steps: - name: Setup Docker on macOS @@ -188,7 +193,7 @@ jobs: context: . push: true tags: rivetkit/engine:full-${{ steps.vars.outputs.sha_short }}${{ matrix.arch_suffix }} - file: docker/universal/Dockerfile + file: engine/docker/universal/Dockerfile target: engine-full platforms: ${{ matrix.platform }} build-args: | @@ -205,7 +210,7 @@ jobs: context: . push: true tags: rivetkit/engine:slim-${{ steps.vars.outputs.sha_short }}${{ matrix.arch_suffix }} - file: docker/universal/Dockerfile + file: engine/docker/universal/Dockerfile target: engine-slim platforms: ${{ matrix.platform }} build-args: | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 745f5ae9cd..646821f1c2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,12 +32,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable components: rustfmt - + - name: Check formatting run: cargo fmt --all -- --check @@ -62,11 +62,11 @@ jobs: runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 - + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - + - uses: Swatinem/rust-cache@v2 - name: Check @@ -80,11 +80,11 @@ jobs: runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 - + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - + - uses: Swatinem/rust-cache@v2 - name: Run tests diff --git a/biome.json b/biome.json index f73b7b236e..bcc74e911b 100644 --- a/biome.json +++ b/biome.json @@ -8,8 +8,7 @@ "!engine/artifacts", "!engine/sdks", "!engine/sdks/typescript/api-full", - "!engine/sdks/typescript/runner-protocol" - "!examples/snippets", + "!engine/sdks/typescript/runner-protocol", "!frontend", "!rivetkit-openapi/openapi.json", "!scripts", @@ -32,7 +31,8 @@ "rules": { "recommended": true, "style": { - "noUselessElse": "off" + "noUselessElse": "off", + "useNodejsImportProtocol": "error" }, "correctness": { "noUnusedImports": "warn" diff --git a/engine/artifacts/errors/api.rate_limited.json b/engine/artifacts/errors/api.rate_limited.json index ddbe2878e0..4f42c1974d 100644 --- a/engine/artifacts/errors/api.rate_limited.json +++ b/engine/artifacts/errors/api.rate_limited.json @@ -1,5 +1,5 @@ { - "code": "rate_limited", - "group": "api", - "message": "\n\tRate limit exceeded.\n\t\n\tThe API rate limit has been exceeded for this endpoint.\n\tPlease wait before making additional requests.\n\t" -} + "code": "rate_limited", + "group": "api", + "message": "\n\tRate limit exceeded.\n\t\n\tThe API rate limit has been exceeded for this endpoint.\n\tPlease wait before making additional requests.\n\t" +} \ No newline at end of file diff --git a/engine/artifacts/errors/namespace.invalid_name.json b/engine/artifacts/errors/namespace.invalid_name.json index c797516a42..537dba4c8f 100644 --- a/engine/artifacts/errors/namespace.invalid_name.json +++ b/engine/artifacts/errors/namespace.invalid_name.json @@ -1,5 +1,5 @@ { - "code": "invalid_name", - "group": "namespace", - "message": "\n\tInvalid namespace name.\n\t\n\tNamespace names must be valid DNS subdomains.\n\t" -} + "code": "invalid_name", + "group": "namespace", + "message": "\n\tInvalid namespace name.\n\t\n\tNamespace names must be valid DNS subdomains.\n\t" +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.input_too_large.json b/engine/artifacts/errors/test.input_too_large.json index c173e92ea4..63f47fcc3a 100644 --- a/engine/artifacts/errors/test.input_too_large.json +++ b/engine/artifacts/errors/test.input_too_large.json @@ -1,5 +1,5 @@ { - "code": "input_too_large", - "group": "test", - "message": "Input too large." -} + "code": "input_too_large", + "group": "test", + "message": "Input too large." +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.key_too_large.json b/engine/artifacts/errors/test.key_too_large.json index 26428df670..d36bf40a1b 100644 --- a/engine/artifacts/errors/test.key_too_large.json +++ b/engine/artifacts/errors/test.key_too_large.json @@ -1,5 +1,5 @@ { - "code": "key_too_large", - "group": "test", - "message": "Key too large." -} + "code": "key_too_large", + "group": "test", + "message": "Key too large." +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.meta_error.json b/engine/artifacts/errors/test.meta_error.json index 575f38a76f..5fa944bfae 100644 --- a/engine/artifacts/errors/test.meta_error.json +++ b/engine/artifacts/errors/test.meta_error.json @@ -1,5 +1,5 @@ { - "code": "meta_error", - "group": "test", - "message": "Default message" -} + "code": "meta_error", + "group": "test", + "message": "Default message" +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.not_found.json b/engine/artifacts/errors/test.not_found.json index 02c454c27d..b24f1a7a1d 100644 --- a/engine/artifacts/errors/test.not_found.json +++ b/engine/artifacts/errors/test.not_found.json @@ -1,5 +1,5 @@ { - "code": "not_found", - "group": "test", - "message": "The resource does not exist." -} + "code": "not_found", + "group": "test", + "message": "The resource does not exist." +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.simple_error.json b/engine/artifacts/errors/test.simple_error.json index 80a6afaed7..f7c8e635e2 100644 --- a/engine/artifacts/errors/test.simple_error.json +++ b/engine/artifacts/errors/test.simple_error.json @@ -1,5 +1,5 @@ { - "code": "simple_error", - "group": "test", - "message": "This is a simple test error" -} + "code": "simple_error", + "group": "test", + "message": "This is a simple test error" +} \ No newline at end of file diff --git a/engine/artifacts/errors/test.test_error.json b/engine/artifacts/errors/test.test_error.json index 33c40e6980..32ae63e73c 100644 --- a/engine/artifacts/errors/test.test_error.json +++ b/engine/artifacts/errors/test.test_error.json @@ -1,5 +1,5 @@ { - "code": "test_error", - "group": "test", - "message": "Test error" -} + "code": "test_error", + "group": "test", + "message": "Test error" +} \ No newline at end of file diff --git a/engine/artifacts/openapi.json b/engine/artifacts/openapi.json index e76e41fab6..d73b549032 100644 --- a/engine/artifacts/openapi.json +++ b/engine/artifacts/openapi.json @@ -1,1564 +1,1760 @@ { - "openapi": "3.1.0", - "info": { - "title": "rivet-api-public", - "description": "", - "contact": { - "name": "Rivet Gaming, LLC", - "email": "developer@rivet.gg" - }, - "license": { - "name": "Apache-2.0", - "identifier": "Apache-2.0" - }, - "version": "25.8.1" - }, - "paths": { - "/actors": { - "get": { - "tags": ["actors::list"], - "summary": " ## Datacenter Round Trips", - "description": " **If key is some & `include_destroyed` is false**\n\n 2 round trips:\n - namespace::ops::resolve_for_name_global\n - GET /actors (multiple DCs based on actor IDs)\n\n\tThis path is optimized because we can read the actor IDs fro the key directly from Epoxy with\n\tstale consistency to determine which datacenter the actor lives in. Under most circumstances,\n\tthis means we don't need to fan out to all datacenters (like normal list does).\n\n\tThe reason `include_destroyed` has to be false is Epoxy only stores currently active actors. If\n\t`include_destroyed` is true, we show all previous iterations of actors with the same key.\n\n **Otherwise**\n\n 2 round trips:\n - namespace::ops::resolve_for_name_global\n - GET /actors (fanout)\n\n ## Optimized Alternative Routes", - "operationId": "actors_list", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "name", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "key", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "actor_ids", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "include_destroyed", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsListResponse" - } - } - } - } - } - }, - "put": { - "tags": ["actors::get_or_create"], - "summary": "## Datacenter Round Trips", - "description": "**If actor exists**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- GET /actors/{}\n\n**If actor does not exist and is created in the current datacenter:**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\n**If actor does not exist and is created in a different datacenter:**\n\n3 round trips:\n- namespace::ops::resolve_for_name_global\n- POST /actors to remote datacenter\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\nactor::get will always be in the same datacenter.\n\n## Optimized Alternative Routes", - "operationId": "actors_get_or_create", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsGetOrCreateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsGetOrCreateResponse" - } - } - } - } - } - }, - "post": { - "tags": ["actors::create"], - "summary": "## Datacenter Round Trips", - "description": "**If actor is created in the current datacenter:**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\n**If actor is created in a different datacenter:**\n\n3 round trips:\n- namespace::ops::resolve_for_name_global\n- POST /actors to remote datacenter\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\nactor::get will always be in the same datacenter.", - "operationId": "actors_create", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsCreateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsCreateResponse" - } - } - } - } - } - } - }, - "/actors/names": { - "get": { - "tags": ["actors::list_names"], - "summary": "## Datacenter Round Trips", - "description": "2 round trips:\n- GET /actors/names (fanout)\n- [api-peer] namespace::ops::resolve_for_name_global", - "operationId": "actors_list_names", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsListNamesResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/actors/{actor_id}": { - "delete": { - "tags": ["actors::delete"], - "summary": "## Datacenter Round Trips", - "description": "2 round trip:\n- DELETE /actors/{}\n- [api-peer] namespace::ops::resolve_for_name_global", - "operationId": "actors_delete", - "parameters": [ - { - "name": "actor_id", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/RivetId" - } - }, - { - "name": "namespace", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActorsDeleteResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/datacenters": { - "get": { - "tags": ["datacenters"], - "operationId": "datacenters_list", - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatacentersListResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/health/fanout": { - "get": { - "tags": ["health"], - "operationId": "health_fanout", - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HealthFanoutResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/namespaces": { - "get": { - "tags": ["namespaces"], - "operationId": "namespaces_list", - "parameters": [ - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "name", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "namespace_ids", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NamespaceListResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - }, - "post": { - "tags": ["namespaces"], - "operationId": "namespaces_create", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NamespacesCreateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NamespacesCreateResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runner-configs": { - "get": { - "tags": ["runner_configs::list"], - "operationId": "runner_configs_list", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "variant", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/RunnerConfigVariant" - } - }, - { - "name": "runner_names", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsListResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runner-configs/serverless-health-check": { - "post": { - "tags": ["runner_configs::serverless_health_check"], - "operationId": "runner_configs_serverless_health_check", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsServerlessHealthCheckRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsServerlessHealthCheckResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runner-configs/{runner_name}": { - "put": { - "tags": ["runner_configs::upsert"], - "operationId": "runner_configs_upsert", - "parameters": [ - { - "name": "runner_name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsUpsertRequestBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsUpsertResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - }, - "delete": { - "tags": ["runner_configs::delete"], - "operationId": "runner_configs_delete", - "parameters": [ - { - "name": "runner_name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsDeleteResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runner-configs/{runner_name}/refresh-metadata": { - "post": { - "tags": ["runner_configs::refresh_metadata"], - "operationId": "runner_configs_refresh_metadata", - "parameters": [ - { - "name": "runner_name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsRefreshMetadataRequestBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnerConfigsRefreshMetadataResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runners": { - "get": { - "tags": ["runners"], - "operationId": "runners_list", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "name", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "runner_ids", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "include_stopped", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnersListResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - }, - "/runners/names": { - "get": { - "tags": ["runners"], - "summary": "## Datacenter Round Trips", - "description": "2 round trips:\n- GET /runners/names (fanout)\n- [api-peer] namespace::ops::resolve_for_name_global", - "operationId": "runners_list_names", - "parameters": [ - { - "name": "namespace", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 0 - } - }, - { - "name": "cursor", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunnersListNamesResponse" - } - } - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] - } - } - }, - "components": { - "schemas": { - "Actor": { - "type": "object", - "required": [ - "actor_id", - "name", - "namespace_id", - "datacenter", - "runner_name_selector", - "crash_policy", - "create_ts" - ], - "properties": { - "actor_id": { - "$ref": "#/components/schemas/RivetId" - }, - "connectable_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "crash_policy": { - "$ref": "#/components/schemas/CrashPolicy" - }, - "create_ts": { - "type": "integer", - "format": "int64" - }, - "datacenter": { - "type": "string" - }, - "destroy_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "key": { - "type": ["string", "null"] - }, - "name": { - "type": "string" - }, - "namespace_id": { - "$ref": "#/components/schemas/RivetId" - }, - "pending_allocation_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "runner_name_selector": { - "type": "string" - }, - "sleep_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "start_ts": { - "type": ["integer", "null"], - "format": "int64" - } - } - }, - "ActorName": { - "type": "object", - "required": ["metadata"], - "properties": { - "metadata": { - "type": "object", - "additionalProperties": {}, - "propertyNames": { - "type": "string" - } - } - } - }, - "ActorsCreateRequest": { - "type": "object", - "required": ["name", "runner_name_selector", "crash_policy"], - "properties": { - "crash_policy": { - "$ref": "#/components/schemas/CrashPolicy" - }, - "datacenter": { - "type": ["string", "null"] - }, - "input": { - "type": ["string", "null"] - }, - "key": { - "type": ["string", "null"] - }, - "name": { - "type": "string" - }, - "runner_name_selector": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ActorsCreateResponse": { - "type": "object", - "required": ["actor"], - "properties": { - "actor": { - "$ref": "#/components/schemas/Actor" - } - }, - "additionalProperties": false - }, - "ActorsDeleteResponse": { - "type": "object" - }, - "ActorsGetOrCreateRequest": { - "type": "object", - "required": [ - "name", - "key", - "runner_name_selector", - "crash_policy" - ], - "properties": { - "crash_policy": { - "$ref": "#/components/schemas/CrashPolicy" - }, - "datacenter": { - "type": ["string", "null"] - }, - "input": { - "type": ["string", "null"] - }, - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "runner_name_selector": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ActorsGetOrCreateResponse": { - "type": "object", - "required": ["actor", "created"], - "properties": { - "actor": { - "$ref": "#/components/schemas/Actor" - }, - "created": { - "type": "boolean" - } - } - }, - "ActorsListNamesResponse": { - "type": "object", - "required": ["names", "pagination"], - "properties": { - "names": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ActorName" - }, - "propertyNames": { - "type": "string" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "additionalProperties": false - }, - "ActorsListResponse": { - "type": "object", - "required": ["actors", "pagination"], - "properties": { - "actors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Actor" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "additionalProperties": false - }, - "CrashPolicy": { - "type": "string", - "enum": ["restart", "sleep", "destroy"] - }, - "Datacenter": { - "type": "object", - "required": ["label", "name", "url"], - "properties": { - "label": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "DatacenterHealth": { - "type": "object", - "required": ["datacenter_label", "datacenter_name", "status"], - "properties": { - "datacenter_label": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "datacenter_name": { - "type": "string" - }, - "error": { - "type": ["string", "null"] - }, - "response": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "#/components/schemas/HealthResponse" - } - ] - }, - "rtt_ms": { - "type": ["number", "null"], - "format": "double" - }, - "status": { - "$ref": "#/components/schemas/HealthStatus" - } - } - }, - "DatacentersListResponse": { - "type": "object", - "required": ["datacenters", "pagination"], - "properties": { - "datacenters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Datacenter" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "additionalProperties": false - }, - "HealthFanoutResponse": { - "type": "object", - "required": ["datacenters"], - "properties": { - "datacenters": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatacenterHealth" - } - } - } - }, - "HealthResponse": { - "type": "object", - "required": ["runtime", "status", "version"], - "properties": { - "runtime": { - "type": "string" - }, - "status": { - "type": "string" - }, - "version": { - "type": "string" - } - } - }, - "HealthStatus": { - "type": "string", - "enum": ["ok", "error"] - }, - "Namespace": { - "type": "object", - "required": [ - "namespace_id", - "name", - "display_name", - "create_ts" - ], - "properties": { - "create_ts": { - "type": "integer", - "format": "int64" - }, - "display_name": { - "type": "string" - }, - "name": { - "type": "string" - }, - "namespace_id": { - "$ref": "#/components/schemas/RivetId" - } - } - }, - "NamespaceListResponse": { - "type": "object", - "required": ["namespaces", "pagination"], - "properties": { - "namespaces": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Namespace" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "additionalProperties": false - }, - "NamespacesCreateRequest": { - "type": "object", - "required": ["name", "display_name"], - "properties": { - "display_name": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "NamespacesCreateResponse": { - "type": "object", - "required": ["namespace"], - "properties": { - "namespace": { - "$ref": "#/components/schemas/Namespace" - } - }, - "additionalProperties": false - }, - "Pagination": { - "type": "object", - "properties": { - "cursor": { - "type": ["string", "null"] - } - }, - "additionalProperties": false - }, - "RivetId": { - "type": "string" - }, - "Runner": { - "type": "object", - "required": [ - "runner_id", - "namespace_id", - "datacenter", - "name", - "key", - "version", - "total_slots", - "remaining_slots", - "create_ts", - "last_ping_ts", - "last_rtt" - ], - "properties": { - "create_ts": { - "type": "integer", - "format": "int64" - }, - "datacenter": { - "type": "string" - }, - "drain_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "key": { - "type": "string" - }, - "last_connected_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "last_ping_ts": { - "type": "integer", - "format": "int64" - }, - "last_rtt": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "metadata": { - "type": ["object", "null"], - "additionalProperties": {}, - "propertyNames": { - "type": "string" - } - }, - "name": { - "type": "string" - }, - "namespace_id": { - "$ref": "#/components/schemas/RivetId" - }, - "remaining_slots": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "runner_id": { - "$ref": "#/components/schemas/RivetId" - }, - "stop_ts": { - "type": ["integer", "null"], - "format": "int64" - }, - "total_slots": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "version": { - "type": "integer", - "format": "int32", - "minimum": 0 - } - }, - "additionalProperties": false - }, - "RunnerConfig": { - "allOf": [ - { - "$ref": "#/components/schemas/RunnerConfigKind" - }, - { - "type": "object", - "properties": { - "metadata": {} - } - } - ] - }, - "RunnerConfigKind": { - "oneOf": [ - { - "type": "object", - "required": ["normal"], - "properties": { - "normal": { - "type": "object" - } - } - }, - { - "type": "object", - "required": ["serverless"], - "properties": { - "serverless": { - "type": "object", - "required": [ - "url", - "request_lifespan", - "slots_per_runner", - "max_runners" - ], - "properties": { - "headers": { - "type": ["object", "null"], - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "max_runners": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "min_runners": { - "type": ["integer", "null"], - "format": "int32", - "minimum": 0 - }, - "request_lifespan": { - "type": "integer", - "format": "int32", - "description": "Seconds.", - "minimum": 0 - }, - "runners_margin": { - "type": ["integer", "null"], - "format": "int32", - "minimum": 0 - }, - "slots_per_runner": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "url": { - "type": "string" - } - } - } - } - } - ] - }, - "RunnerConfigVariant": { - "type": "string", - "enum": ["serverless", "normal"] - }, - "RunnerConfigsDeleteResponse": { - "type": "object" - }, - "RunnerConfigsListResponse": { - "type": "object", - "required": ["runner_configs", "pagination"], - "properties": { - "pagination": { - "$ref": "#/components/schemas/Pagination" - }, - "runner_configs": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/RunnerConfigsListResponseRunnerConfigsValue" - }, - "propertyNames": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "RunnerConfigsListResponseRunnerConfigsValue": { - "type": "object", - "required": ["datacenters"], - "properties": { - "datacenters": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/RunnerConfig" - }, - "propertyNames": { - "type": "string" - } - } - } - }, - "RunnerConfigsRefreshMetadataRequestBody": { - "type": "object", - "additionalProperties": false - }, - "RunnerConfigsRefreshMetadataResponse": { - "type": "object", - "additionalProperties": false - }, - "RunnerConfigsServerlessHealthCheckRequest": { - "type": "object", - "required": ["url"], - "properties": { - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - } - }, - "url": { - "type": "string" - } - }, - "additionalProperties": false - }, - "RunnerConfigsServerlessHealthCheckResponse": { - "oneOf": [ - { - "type": "object", - "required": ["success"], - "properties": { - "success": { - "type": "object", - "required": ["version"], - "properties": { - "version": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": ["failure"], - "properties": { - "failure": { - "type": "object", - "required": ["error"], - "properties": { - "error": { - "$ref": "#/components/schemas/RunnerConfigsServerlessMetadataError" - } - } - } - } - } - ] - }, - "RunnerConfigsServerlessMetadataError": { - "oneOf": [ - { - "type": "object", - "required": ["invalid_request"], - "properties": { - "invalid_request": { - "type": "object" - } - } - }, - { - "type": "object", - "required": ["request_failed"], - "properties": { - "request_failed": { - "type": "object" - } - } - }, - { - "type": "object", - "required": ["request_timed_out"], - "properties": { - "request_timed_out": { - "type": "object" - } - } - }, - { - "type": "object", - "required": ["non_success_status"], - "properties": { - "non_success_status": { - "type": "object", - "required": ["status_code", "body"], - "properties": { - "body": { - "type": "string" - }, - "status_code": { - "type": "integer", - "format": "int32", - "minimum": 0 - } - } - } - } - }, - { - "type": "object", - "required": ["invalid_response_json"], - "properties": { - "invalid_response_json": { - "type": "object", - "required": ["body"], - "properties": { - "body": { - "type": "string" - } - } - } - } - }, - { - "type": "object", - "required": ["invalid_response_schema"], - "properties": { - "invalid_response_schema": { - "type": "object", - "required": ["runtime", "version"], - "properties": { - "runtime": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } - } - ] - }, - "RunnerConfigsUpsertRequestBody": { - "type": "object", - "required": ["datacenters"], - "properties": { - "datacenters": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/RunnerConfig" - }, - "propertyNames": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "RunnerConfigsUpsertResponse": { - "type": "object", - "required": ["endpoint_config_changed"], - "properties": { - "endpoint_config_changed": { - "type": "boolean" - } - } - }, - "RunnersListNamesResponse": { - "type": "object", - "required": ["names", "pagination"], - "properties": { - "names": { - "type": "array", - "items": { - "type": "string" - } - }, - "pagination": { - "$ref": "#/components/schemas/Pagination" - } - }, - "additionalProperties": false - }, - "RunnersListResponse": { - "type": "object", - "required": ["runners", "pagination"], - "properties": { - "pagination": { - "$ref": "#/components/schemas/Pagination" - }, - "runners": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Runner" - } - } - }, - "additionalProperties": false - } - }, - "securitySchemes": { - "bearer_auth": { - "type": "http", - "scheme": "bearer" - } - } - }, - "security": [ - { - "bearer_auth": [] - } - ] -} + "openapi": "3.1.0", + "info": { + "title": "rivet-api-public", + "description": "", + "contact": { + "name": "Rivet Gaming, LLC", + "email": "developer@rivet.gg" + }, + "license": { + "name": "Apache-2.0", + "identifier": "Apache-2.0" + }, + "version": "25.8.1" + }, + "paths": { + "/actors": { + "get": { + "tags": [ + "actors::list" + ], + "summary": " ## Datacenter Round Trips", + "description": " **If key is some & `include_destroyed` is false**\n\n 2 round trips:\n - namespace::ops::resolve_for_name_global\n - GET /actors (multiple DCs based on actor IDs)\n\n\tThis path is optimized because we can read the actor IDs fro the key directly from Epoxy with\n\tstale consistency to determine which datacenter the actor lives in. Under most circumstances,\n\tthis means we don't need to fan out to all datacenters (like normal list does).\n\n\tThe reason `include_destroyed` has to be false is Epoxy only stores currently active actors. If\n\t`include_destroyed` is true, we show all previous iterations of actors with the same key.\n\n **Otherwise**\n\n 2 round trips:\n - namespace::ops::resolve_for_name_global\n - GET /actors (fanout)\n\n ## Optimized Alternative Routes", + "operationId": "actors_list", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "actor_ids", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "include_destroyed", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsListResponse" + } + } + } + } + } + }, + "put": { + "tags": [ + "actors::get_or_create" + ], + "summary": "## Datacenter Round Trips", + "description": "**If actor exists**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- GET /actors/{}\n\n**If actor does not exist and is created in the current datacenter:**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\n**If actor does not exist and is created in a different datacenter:**\n\n3 round trips:\n- namespace::ops::resolve_for_name_global\n- POST /actors to remote datacenter\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\nactor::get will always be in the same datacenter.\n\n## Optimized Alternative Routes", + "operationId": "actors_get_or_create", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsGetOrCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsGetOrCreateResponse" + } + } + } + } + } + }, + "post": { + "tags": [ + "actors::create" + ], + "summary": "## Datacenter Round Trips", + "description": "**If actor is created in the current datacenter:**\n\n2 round trips:\n- namespace::ops::resolve_for_name_global\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\n**If actor is created in a different datacenter:**\n\n3 round trips:\n- namespace::ops::resolve_for_name_global\n- POST /actors to remote datacenter\n- [pegboard::workflows::actor] Create actor workflow (includes Epoxy key allocation)\n\nactor::get will always be in the same datacenter.", + "operationId": "actors_create", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsCreateResponse" + } + } + } + } + } + } + }, + "/actors/names": { + "get": { + "tags": [ + "actors::list_names" + ], + "summary": "## Datacenter Round Trips", + "description": "2 round trips:\n- GET /actors/names (fanout)\n- [api-peer] namespace::ops::resolve_for_name_global", + "operationId": "actors_list_names", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsListNamesResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/actors/{actor_id}": { + "delete": { + "tags": [ + "actors::delete" + ], + "summary": "## Datacenter Round Trips", + "description": "2 round trip:\n- DELETE /actors/{}\n- [api-peer] namespace::ops::resolve_for_name_global", + "operationId": "actors_delete", + "parameters": [ + { + "name": "actor_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/RivetId" + } + }, + { + "name": "namespace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActorsDeleteResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/datacenters": { + "get": { + "tags": [ + "datacenters" + ], + "operationId": "datacenters_list", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatacentersListResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/health/fanout": { + "get": { + "tags": [ + "health" + ], + "operationId": "health_fanout", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthFanoutResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/namespaces": { + "get": { + "tags": [ + "namespaces" + ], + "operationId": "namespaces_list", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "namespace_ids", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NamespaceListResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "namespaces" + ], + "operationId": "namespaces_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NamespacesCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NamespacesCreateResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runner-configs": { + "get": { + "tags": [ + "runner_configs::list" + ], + "operationId": "runner_configs_list", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "variant", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/RunnerConfigVariant" + } + }, + { + "name": "runner_names", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsListResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runner-configs/serverless-health-check": { + "post": { + "tags": [ + "runner_configs::serverless_health_check" + ], + "operationId": "runner_configs_serverless_health_check", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsServerlessHealthCheckRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsServerlessHealthCheckResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runner-configs/{runner_name}": { + "put": { + "tags": [ + "runner_configs::upsert" + ], + "operationId": "runner_configs_upsert", + "parameters": [ + { + "name": "runner_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsUpsertRequestBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsUpsertResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "runner_configs::delete" + ], + "operationId": "runner_configs_delete", + "parameters": [ + { + "name": "runner_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsDeleteResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runner-configs/{runner_name}/refresh-metadata": { + "post": { + "tags": [ + "runner_configs::refresh_metadata" + ], + "operationId": "runner_configs_refresh_metadata", + "parameters": [ + { + "name": "runner_name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsRefreshMetadataRequestBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnerConfigsRefreshMetadataResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runners": { + "get": { + "tags": [ + "runners" + ], + "operationId": "runners_list", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "runner_ids", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "include_stopped", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnersListResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/runners/names": { + "get": { + "tags": [ + "runners" + ], + "summary": "## Datacenter Round Trips", + "description": "2 round trips:\n- GET /runners/names (fanout)\n- [api-peer] namespace::ops::resolve_for_name_global", + "operationId": "runners_list_names", + "parameters": [ + { + "name": "namespace", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunnersListNamesResponse" + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Actor": { + "type": "object", + "required": [ + "actor_id", + "name", + "namespace_id", + "datacenter", + "runner_name_selector", + "crash_policy", + "create_ts" + ], + "properties": { + "actor_id": { + "$ref": "#/components/schemas/RivetId" + }, + "connectable_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "crash_policy": { + "$ref": "#/components/schemas/CrashPolicy" + }, + "create_ts": { + "type": "integer", + "format": "int64" + }, + "datacenter": { + "type": "string" + }, + "destroy_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "key": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "namespace_id": { + "$ref": "#/components/schemas/RivetId" + }, + "pending_allocation_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "runner_name_selector": { + "type": "string" + }, + "sleep_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "start_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "ActorName": { + "type": "object", + "required": [ + "metadata" + ], + "properties": { + "metadata": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string" + } + } + } + }, + "ActorsCreateRequest": { + "type": "object", + "required": [ + "name", + "runner_name_selector", + "crash_policy" + ], + "properties": { + "crash_policy": { + "$ref": "#/components/schemas/CrashPolicy" + }, + "datacenter": { + "type": [ + "string", + "null" + ] + }, + "input": { + "type": [ + "string", + "null" + ] + }, + "key": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "runner_name_selector": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ActorsCreateResponse": { + "type": "object", + "required": [ + "actor" + ], + "properties": { + "actor": { + "$ref": "#/components/schemas/Actor" + } + }, + "additionalProperties": false + }, + "ActorsDeleteResponse": { + "type": "object" + }, + "ActorsGetOrCreateRequest": { + "type": "object", + "required": [ + "name", + "key", + "runner_name_selector", + "crash_policy" + ], + "properties": { + "crash_policy": { + "$ref": "#/components/schemas/CrashPolicy" + }, + "datacenter": { + "type": [ + "string", + "null" + ] + }, + "input": { + "type": [ + "string", + "null" + ] + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "runner_name_selector": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ActorsGetOrCreateResponse": { + "type": "object", + "required": [ + "actor", + "created" + ], + "properties": { + "actor": { + "$ref": "#/components/schemas/Actor" + }, + "created": { + "type": "boolean" + } + } + }, + "ActorsListNamesResponse": { + "type": "object", + "required": [ + "names", + "pagination" + ], + "properties": { + "names": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ActorName" + }, + "propertyNames": { + "type": "string" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "additionalProperties": false + }, + "ActorsListResponse": { + "type": "object", + "required": [ + "actors", + "pagination" + ], + "properties": { + "actors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Actor" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "additionalProperties": false + }, + "CrashPolicy": { + "type": "string", + "enum": [ + "restart", + "sleep", + "destroy" + ] + }, + "Datacenter": { + "type": "object", + "required": [ + "label", + "name", + "url" + ], + "properties": { + "label": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DatacenterHealth": { + "type": "object", + "required": [ + "datacenter_label", + "datacenter_name", + "status" + ], + "properties": { + "datacenter_label": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "datacenter_name": { + "type": "string" + }, + "error": { + "type": [ + "string", + "null" + ] + }, + "response": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/HealthResponse" + } + ] + }, + "rtt_ms": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "status": { + "$ref": "#/components/schemas/HealthStatus" + } + } + }, + "DatacentersListResponse": { + "type": "object", + "required": [ + "datacenters", + "pagination" + ], + "properties": { + "datacenters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Datacenter" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "additionalProperties": false + }, + "HealthFanoutResponse": { + "type": "object", + "required": [ + "datacenters" + ], + "properties": { + "datacenters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DatacenterHealth" + } + } + } + }, + "HealthResponse": { + "type": "object", + "required": [ + "runtime", + "status", + "version" + ], + "properties": { + "runtime": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "HealthStatus": { + "type": "string", + "enum": [ + "ok", + "error" + ] + }, + "Namespace": { + "type": "object", + "required": [ + "namespace_id", + "name", + "display_name", + "create_ts" + ], + "properties": { + "create_ts": { + "type": "integer", + "format": "int64" + }, + "display_name": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespace_id": { + "$ref": "#/components/schemas/RivetId" + } + } + }, + "NamespaceListResponse": { + "type": "object", + "required": [ + "namespaces", + "pagination" + ], + "properties": { + "namespaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Namespace" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "additionalProperties": false + }, + "NamespacesCreateRequest": { + "type": "object", + "required": [ + "name", + "display_name" + ], + "properties": { + "display_name": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "NamespacesCreateResponse": { + "type": "object", + "required": [ + "namespace" + ], + "properties": { + "namespace": { + "$ref": "#/components/schemas/Namespace" + } + }, + "additionalProperties": false + }, + "Pagination": { + "type": "object", + "properties": { + "cursor": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "RivetId": { + "type": "string" + }, + "Runner": { + "type": "object", + "required": [ + "runner_id", + "namespace_id", + "datacenter", + "name", + "key", + "version", + "total_slots", + "remaining_slots", + "create_ts", + "last_ping_ts", + "last_rtt" + ], + "properties": { + "create_ts": { + "type": "integer", + "format": "int64" + }, + "datacenter": { + "type": "string" + }, + "drain_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "key": { + "type": "string" + }, + "last_connected_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "last_ping_ts": { + "type": "integer", + "format": "int64" + }, + "last_rtt": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "metadata": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "propertyNames": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "namespace_id": { + "$ref": "#/components/schemas/RivetId" + }, + "remaining_slots": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "runner_id": { + "$ref": "#/components/schemas/RivetId" + }, + "stop_ts": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "total_slots": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "version": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "RunnerConfig": { + "allOf": [ + { + "$ref": "#/components/schemas/RunnerConfigKind" + }, + { + "type": "object", + "properties": { + "metadata": {} + } + } + ] + }, + "RunnerConfigKind": { + "oneOf": [ + { + "type": "object", + "required": [ + "normal" + ], + "properties": { + "normal": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "serverless" + ], + "properties": { + "serverless": { + "type": "object", + "required": [ + "url", + "request_lifespan", + "slots_per_runner", + "max_runners" + ], + "properties": { + "headers": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "max_runners": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "min_runners": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "request_lifespan": { + "type": "integer", + "format": "int32", + "description": "Seconds.", + "minimum": 0 + }, + "runners_margin": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "slots_per_runner": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "url": { + "type": "string" + } + } + } + } + } + ] + }, + "RunnerConfigVariant": { + "type": "string", + "enum": [ + "serverless", + "normal" + ] + }, + "RunnerConfigsDeleteResponse": { + "type": "object" + }, + "RunnerConfigsListResponse": { + "type": "object", + "required": [ + "runner_configs", + "pagination" + ], + "properties": { + "pagination": { + "$ref": "#/components/schemas/Pagination" + }, + "runner_configs": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RunnerConfigsListResponseRunnerConfigsValue" + }, + "propertyNames": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "RunnerConfigsListResponseRunnerConfigsValue": { + "type": "object", + "required": [ + "datacenters" + ], + "properties": { + "datacenters": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RunnerConfig" + }, + "propertyNames": { + "type": "string" + } + } + } + }, + "RunnerConfigsRefreshMetadataRequestBody": { + "type": "object", + "additionalProperties": false + }, + "RunnerConfigsRefreshMetadataResponse": { + "type": "object", + "additionalProperties": false + }, + "RunnerConfigsServerlessHealthCheckRequest": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "RunnerConfigsServerlessHealthCheckResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "object", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "failure" + ], + "properties": { + "failure": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/RunnerConfigsServerlessMetadataError" + } + } + } + } + } + ] + }, + "RunnerConfigsServerlessMetadataError": { + "oneOf": [ + { + "type": "object", + "required": [ + "invalid_request" + ], + "properties": { + "invalid_request": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "request_failed" + ], + "properties": { + "request_failed": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "request_timed_out" + ], + "properties": { + "request_timed_out": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "non_success_status" + ], + "properties": { + "non_success_status": { + "type": "object", + "required": [ + "status_code", + "body" + ], + "properties": { + "body": { + "type": "string" + }, + "status_code": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + } + }, + { + "type": "object", + "required": [ + "invalid_response_json" + ], + "properties": { + "invalid_response_json": { + "type": "object", + "required": [ + "body" + ], + "properties": { + "body": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "required": [ + "invalid_response_schema" + ], + "properties": { + "invalid_response_schema": { + "type": "object", + "required": [ + "runtime", + "version" + ], + "properties": { + "runtime": { + "type": "string" + }, + "version": { + "type": "string" + } + } + } + } + } + ] + }, + "RunnerConfigsUpsertRequestBody": { + "type": "object", + "required": [ + "datacenters" + ], + "properties": { + "datacenters": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RunnerConfig" + }, + "propertyNames": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "RunnerConfigsUpsertResponse": { + "type": "object", + "required": [ + "endpoint_config_changed" + ], + "properties": { + "endpoint_config_changed": { + "type": "boolean" + } + } + }, + "RunnersListNamesResponse": { + "type": "object", + "required": [ + "names", + "pagination" + ], + "properties": { + "names": { + "type": "array", + "items": { + "type": "string" + } + }, + "pagination": { + "$ref": "#/components/schemas/Pagination" + } + }, + "additionalProperties": false + }, + "RunnersListResponse": { + "type": "object", + "required": [ + "runners", + "pagination" + ], + "properties": { + "pagination": { + "$ref": "#/components/schemas/Pagination" + }, + "runners": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Runner" + } + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] +} \ No newline at end of file diff --git a/engine/contrib-docs/DOCKER.md b/engine/contrib-docs/DOCKER.md index a4397e0f70..381e5a3de1 100644 --- a/engine/contrib-docs/DOCKER.md +++ b/engine/contrib-docs/DOCKER.md @@ -1,5 +1,5 @@ # Runner Docker Compose -`docker compose -f ./docker/dev/docker-compose.yml up --build` +`docker compose -f ../docker/dev/docker-compose.yml up --build` > Sometimes adding `--force-recreate` is required to get it to deploy the new build diff --git a/engine/docker/engine/build.sh b/engine/docker/engine/build.sh index 6aa2491ab7..3d6bdac637 100755 --- a/engine/docker/engine/build.sh +++ b/engine/docker/engine/build.sh @@ -10,30 +10,35 @@ case $TARGET in DOCKERFILE="linux-x86_64.Dockerfile" TARGET_STAGE="x86_64-builder" BINARY="rivet-engine-$TARGET" + PLATFORM="linux/amd64" ;; aarch64-unknown-linux-musl) echo "Building for Linux ARM64 platform" DOCKERFILE="linux-aarch64.Dockerfile" TARGET_STAGE="aarch64-builder" BINARY="rivet-engine-$TARGET" + PLATFORM="linux/arm64" ;; aarch64-apple-darwin) echo "Building for macOS ARM64 platform" DOCKERFILE="macos-aarch64.Dockerfile" TARGET_STAGE="aarch64-builder" BINARY="rivet-engine-$TARGET" + PLATFORM="linux/arm64" ;; x86_64-apple-darwin) echo "Building for macOS x86_64 platform" - DOCKERFILE="macos-x86_64.Dockerfile" + DOCKERFILE="macos-x86_64.Dockerfile" TARGET_STAGE="x86_64-builder" BINARY="rivet-engine-$TARGET" + PLATFORM="linux/amd64" ;; x86_64-pc-windows-gnu) echo "Building for Windows platform" DOCKERFILE="windows.Dockerfile" TARGET_STAGE="" # No target stage for Windows BINARY="rivet-engine-$TARGET.exe" + PLATFORM="linux/amd64" ;; *) echo "Unsupported target: $TARGET" @@ -43,9 +48,9 @@ esac # Build docker image with target stage (if specified) if [ -n "$TARGET_STAGE" ]; then - DOCKER_BUILDKIT=1 docker build --target $TARGET_STAGE -f docker/engine/$DOCKERFILE -t rivet-engine-builder-$TARGET . + DOCKER_BUILDKIT=1 docker build --target $TARGET_STAGE -f engine/docker/engine/$DOCKERFILE -t rivet-engine-builder-$TARGET . else - DOCKER_BUILDKIT=1 docker build -f docker/engine/$DOCKERFILE -t rivet-engine-builder-$TARGET . + DOCKER_BUILDKIT=1 docker build -f engine/docker/engine/$DOCKERFILE -t rivet-engine-builder-$TARGET . fi # Extract binary diff --git a/engine/docker/engine/linux-aarch64.Dockerfile b/engine/docker/engine/linux-aarch64.Dockerfile index 28eabff4ad..54d3e8069b 100644 --- a/engine/docker/engine/linux-aarch64.Dockerfile +++ b/engine/docker/engine/linux-aarch64.Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.10.0 FROM rust:1.88.0 AS base ARG BUILD_FRONTEND=true @@ -16,16 +16,17 @@ RUN apt-get update && apt-get install -y \ protobuf-compiler \ ca-certificates \ g++ \ - g++-multilib \ git-lfs \ - curl && \ + curl \ + linux-libc-dev \ + xz-utils && \ curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y nodejs && \ corepack enable && \ rm -rf /var/lib/apt/lists/* && \ - wget -q https://github.com/cross-tools/musl-cross/releases/download/20250815/aarch64-unknown-linux-musl.tar.xz && \ - tar -xzf aarch64-unknown-linux-musl.tgz -C /opt/ && \ - rm aarch64-unknown-linux-musl.tgz + wget -q https://musl.cc/aarch64-linux-musl-cross.tgz && \ + tar -xzf aarch64-linux-musl-cross.tgz -C /opt/ && \ + rm aarch64-linux-musl-cross.tgz # Disable interactive prompt ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 @@ -34,13 +35,13 @@ ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 RUN rustup target add aarch64-unknown-linux-musl # Set environment variables -ENV PATH="/opt/aarch64-unknown-linux-musl/bin:$PATH" \ +ENV PATH="/opt/aarch64-linux-musl-cross/bin:$PATH" \ LIBCLANG_PATH=/usr/lib/llvm-14/lib \ CLANG_PATH=/usr/bin/clang-14 \ - CC_aarch64_unknown_linux_musl=aarch64-unknown-linux-musl-gcc \ - CXX_aarch64_unknown_linux_musl=aarch64-unknown-linux-musl-g++ \ - AR_aarch64_unknown_linux_musl=aarch64-unknown-linux-musl-ar \ - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-unknown-linux-musl-gcc \ + CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc \ + CXX_aarch64_unknown_linux_musl=aarch64-linux-musl-g++ \ + AR_aarch64_unknown_linux_musl=aarch64-linux-musl-ar \ + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \ CARGO_INCREMENTAL=0 \ RUSTFLAGS="--cfg tokio_unstable -C target-feature=+crt-static -C link-arg=-static-libgcc" \ CARGO_NET_GIT_FETCH_WITH_CLI=true @@ -56,9 +57,11 @@ ENV SSL_VER=1.1.1w RUN wget https://www.openssl.org/source/openssl-$SSL_VER.tar.gz \ && tar -xzf openssl-$SSL_VER.tar.gz \ && cd openssl-$SSL_VER \ - && ./Configure no-shared no-async --prefix=/musl-aarch64 --openssldir=/musl-aarch64/ssl linux-aarch64 \ - && make -j$(nproc) \ - && make install_sw \ + && CC=aarch64-linux-musl-gcc \ + ./Configure no-shared no-async no-tests --prefix=/musl-aarch64 --openssldir=/musl-aarch64/ssl linux-aarch64 \ + && make -j$(nproc) build_libs \ + && make install_dev \ + && mkdir -p /musl-aarch64/ssl \ && cd .. \ && rm -rf openssl-$SSL_VER* @@ -66,6 +69,7 @@ RUN wget https://www.openssl.org/source/openssl-$SSL_VER.tar.gz \ ENV OPENSSL_DIR=/musl-aarch64 \ OPENSSL_INCLUDE_DIR=/musl-aarch64/include \ OPENSSL_LIB_DIR=/musl-aarch64/lib \ + OPENSSL_STATIC=1 \ PKG_CONFIG_ALLOW_CROSS=1 # Copy the source code @@ -73,13 +77,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build for Linux with musl (static binary) - aarch64 diff --git a/engine/docker/engine/linux-x86_64.Dockerfile b/engine/docker/engine/linux-x86_64.Dockerfile index 31675fb20f..a2c2dde596 100644 --- a/engine/docker/engine/linux-x86_64.Dockerfile +++ b/engine/docker/engine/linux-x86_64.Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.10.0 FROM rust:1.88.0 AS base ARG BUILD_FRONTEND=true @@ -73,13 +73,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build for Linux with musl (static binary) - x86_64 diff --git a/engine/docker/engine/macos-aarch64.Dockerfile b/engine/docker/engine/macos-aarch64.Dockerfile index 4878a79236..4b08efde00 100644 --- a/engine/docker/engine/macos-aarch64.Dockerfile +++ b/engine/docker/engine/macos-aarch64.Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.10.0 FROM rust:1.88.0 AS base ARG BUILD_FRONTEND=true @@ -72,13 +72,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build for ARM64 macOS diff --git a/engine/docker/engine/macos-x86_64.Dockerfile b/engine/docker/engine/macos-x86_64.Dockerfile index fdd3b503e4..5e38886bb2 100644 --- a/engine/docker/engine/macos-x86_64.Dockerfile +++ b/engine/docker/engine/macos-x86_64.Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.10.0 FROM rust:1.88.0 AS base ARG BUILD_FRONTEND=true @@ -72,13 +72,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build for x86_64 macOS diff --git a/engine/docker/engine/windows.Dockerfile b/engine/docker/engine/windows.Dockerfile index b5b1a68a9b..e4650e2fc4 100644 --- a/engine/docker/engine/windows.Dockerfile +++ b/engine/docker/engine/windows.Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.4 +# syntax=docker/dockerfile:1.10.0 FROM rust:1.88.0 ARG BUILD_FRONTEND=true @@ -60,13 +60,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build for Windows diff --git a/engine/docker/template/src/context.ts b/engine/docker/template/src/context.ts index bd3a47bcd5..533b9b0b0a 100644 --- a/engine/docker/template/src/context.ts +++ b/engine/docker/template/src/context.ts @@ -1,5 +1,5 @@ -import * as fs from "fs"; -import * as path from "path"; +import * as fs from "node:fs"; +import * as path from "node:path"; import type { TemplateConfig } from "./config"; export const CORE_NETWORK_NAME = "rivet-core-network"; diff --git a/engine/docker/template/src/main.ts b/engine/docker/template/src/main.ts index 2c7dcef0eb..81ce5df379 100644 --- a/engine/docker/template/src/main.ts +++ b/engine/docker/template/src/main.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node -import * as fs from "fs"; -import * as path from "path"; +import * as fs from "node:fs"; +import * as path from "node:path"; import { TEMPLATES, type TemplateConfig } from "./config"; import { TemplateContext } from "./context"; import { generateDockerCompose } from "./docker-compose"; diff --git a/engine/docker/template/src/services/core/grafana.ts b/engine/docker/template/src/services/core/grafana.ts index 76a7d2f021..d5e3ad2fa0 100644 --- a/engine/docker/template/src/services/core/grafana.ts +++ b/engine/docker/template/src/services/core/grafana.ts @@ -1,6 +1,6 @@ -import * as fs from "fs"; +import * as fs from "node:fs"; +import * as path from "node:path"; import * as yaml from "js-yaml"; -import * as path from "path"; import type { TemplateContext } from "../../context"; export function generateCoreGrafana(context: TemplateContext) { diff --git a/engine/docker/universal/Dockerfile b/engine/docker/universal/Dockerfile index 226adaa5b3..e411855677 100644 --- a/engine/docker/universal/Dockerfile +++ b/engine/docker/universal/Dockerfile @@ -2,7 +2,7 @@ # MARK: Builder # TODO(RVT-4168): Compile libfdb from scratch for ARM -FROM --platform=linux/amd64 rust:1.88.0-bookworm AS builder +FROM rust:1.88.0-bookworm AS builder # Docker automatically provides TARGETARCH ARG TARGETARCH @@ -49,13 +49,12 @@ COPY . . # Build frontend RUN if [ "$BUILD_FRONTEND" = "true" ]; then \ - (cd sdks/typescript/api-full && pnpm install && pnpm run build) && \ - (cd frontend && pnpm install && \ + pnpm install && \ if [ -n "$VITE_APP_API_URL" ]; then \ - VITE_APP_API_URL="${VITE_APP_API_URL}" pnpm run build:engine; \ + VITE_APP_API_URL="${VITE_APP_API_URL}" npx turbo build:engine -F @rivetkit/engine-frontend; \ else \ - pnpm run build:engine; \ - fi); \ + npx turbo build:engine -F @rivetkit/engine-frontend; \ + fi; \ fi # Build and copy all binaries from target directory into an empty image (it is not @@ -76,7 +75,7 @@ RUN \ cp target/$CARGO_BUILD_MODE/rivet-engine /app/dist/ # MARK: Engine (full, base) -FROM --platform=linux/amd64 debian:12.9-slim AS engine-full-base +FROM debian:12.9-slim AS engine-full-base # Docker automatically provides TARGETARCH ARG TARGETARCH @@ -99,7 +98,7 @@ RUN apt-get update -y && \ fi # MARK: Engine (Full) -FROM --platform=linux/amd64 engine-full-base AS engine-full +FROM engine-full-base AS engine-full LABEL org.opencontainers.image.source=https://github.com/rivet-gg/rivet @@ -109,7 +108,7 @@ ENTRYPOINT ["/usr/bin/rivet-engine"] CMD ["start"] # MARK: Engine (Slim) -FROM --platform=linux/amd64 debian:12.9-slim AS engine-slim +FROM debian:12.9-slim AS engine-slim LABEL org.opencontainers.image.source=https://github.com/rivet-gg/rivet diff --git a/engine/sdks/typescript/runner/src/mod.ts b/engine/sdks/typescript/runner/src/mod.ts index c451e2c530..8f9d2ff2c8 100644 --- a/engine/sdks/typescript/runner/src/mod.ts +++ b/engine/sdks/typescript/runner/src/mod.ts @@ -45,8 +45,17 @@ export interface RunnerConfig { onConnected: () => void; onDisconnected: () => void; onShutdown: () => void; - fetch: (runner: Runner, actorId: string, request: Request) => Promise; - websocket?: (runner: Runner, actorId: string, ws: any, request: Request) => Promise; + fetch: ( + runner: Runner, + actorId: string, + request: Request, + ) => Promise; + websocket?: ( + runner: Runner, + actorId: string, + ws: any, + request: Request, + ) => Promise; onActorStart: ( actorId: string, generation: number, @@ -183,7 +192,11 @@ export class Runner { getActor(actorId: string, generation?: number): ActorInstance | undefined { const actor = this.#actors.get(actorId); if (!actor) { - logger()?.error({ msg: "actor not found", runnerId: this.runnerId, actorId }); + logger()?.error({ + msg: "actor not found", + runnerId: this.runnerId, + actorId, + }); return undefined; } if (generation !== undefined && actor.generation !== generation) { @@ -214,7 +227,11 @@ export class Runner { ): ActorInstance | undefined { const actor = this.#actors.get(actorId); if (!actor) { - logger()?.error({ msg: "actor not found for removal", runnerId: this.runnerId, actorId }); + logger()?.error({ + msg: "actor not found for removal", + runnerId: this.runnerId, + actorId, + }); return undefined; } if (generation !== undefined && actor.generation !== generation) { @@ -272,7 +289,7 @@ export class Runner { process.on("SIGTERM", () => { logger()?.debug("received SIGTERM"); - for (let handler of SIGNAL_HANDLERS) { + for (const handler of SIGNAL_HANDLERS) { handler(); } @@ -281,7 +298,7 @@ export class Runner { process.on("SIGINT", () => { logger()?.debug("received SIGINT"); - for (let handler of SIGNAL_HANDLERS) { + for (const handler of SIGNAL_HANDLERS) { handler(); } @@ -294,15 +311,20 @@ export class Runner { } SIGNAL_HANDLERS.push(() => { - let weak = new WeakRef(this); - weak.deref()?.shutdown(false, false) + const weak = new WeakRef(this); + weak.deref()?.shutdown(false, false); }); } } // MARK: Shutdown async shutdown(immediate: boolean, exit: boolean = false) { - logger()?.info({ msg: "starting shutdown", runnerId: this.runnerId, immediate, exit }); + logger()?.info({ + msg: "starting shutdown", + runnerId: this.runnerId, + immediate, + exit, + }); this.#shutdown = true; // Clear reconnect timeout @@ -395,12 +417,18 @@ export class Runner { // TODO: Wait for all actors to stop before closing ws - logger()?.info({ msg: "closing WebSocket", runnerId: this.runnerId }); + logger()?.info({ + msg: "closing WebSocket", + runnerId: this.runnerId, + }); pegboardWebSocket.close(1000, "Stopping"); await closePromise; - logger()?.info({ msg: "websocket shutdown completed", runnerId: this.runnerId }); + logger()?.info({ + msg: "websocket shutdown completed", + runnerId: this.runnerId, + }); } catch (error) { logger()?.error({ msg: "error during websocket shutdown:", @@ -506,7 +534,10 @@ export class Runner { }); } else { clearInterval(pingLoop); - logger()?.info({ msg: "WebSocket not open, stopping ping loop", runnerId: this.runnerId }); + logger()?.info({ + msg: "WebSocket not open, stopping ping loop", + runnerId: this.runnerId, + }); } }, pingInterval); this.#pingLoop = pingLoop; @@ -518,7 +549,10 @@ export class Runner { this.#sendCommandAcknowledgment(); } else { clearInterval(ackLoop); - logger()?.info({ msg: "WebSocket not open, stopping ack loop", runnerId: this.runnerId }); + logger()?.info({ + msg: "WebSocket not open, stopping ack loop", + runnerId: this.runnerId, + }); } }, ackInterval); this.#ackInterval = ackLoop; @@ -583,7 +617,10 @@ export class Runner { }); ws.addEventListener("error", (ev) => { - logger()?.error({ msg: `WebSocket error: ${ev.error}`, runnerId: this.runnerId }); + logger()?.error({ + msg: `WebSocket error: ${ev.error}`, + runnerId: this.runnerId, + }); if (!this.#shutdown) { // Start runner lost timeout if we have a threshold and are not shutting down @@ -618,7 +655,10 @@ export class Runner { this.#config.onDisconnected(); if (ev.reason.toString().startsWith("ws.eviction")) { - logger()?.info({ msg: "runner evicted", runnerId: this.runnerId }); + logger()?.info({ + msg: "runner evicted", + runnerId: this.runnerId, + }); await this.shutdown(true); } @@ -666,7 +706,11 @@ export class Runner { }); for (const commandWrapper of commands) { - logger()?.info({ msg: "received command", runnerId: this.runnerId, commandWrapper }); + logger()?.info({ + msg: "received command", + runnerId: this.runnerId, + commandWrapper, + }); if (commandWrapper.inner.tag === "CommandStartActor") { this.#handleCommandStartActor(commandWrapper); } else if (commandWrapper.inner.tag === "CommandStopActor") { @@ -781,7 +825,10 @@ export class Runner { intentType: "sleep" | "stop", ) { if (this.#shutdown) { - logger()?.warn({ msg: "Runner is shut down, cannot send actor intent", runnerId: this.runnerId }); + logger()?.warn({ + msg: "Runner is shut down, cannot send actor intent", + runnerId: this.runnerId, + }); return; } let actorIntent: protocol.ActorIntent; @@ -1363,7 +1410,10 @@ export class Runner { #scheduleReconnect() { if (this.#shutdown) { - logger()?.debug({ msg: "Runner is shut down, not attempting reconnect", runnerId: this.runnerId }); + logger()?.debug({ + msg: "Runner is shut down, not attempting reconnect", + runnerId: this.runnerId, + }); return; } diff --git a/engine/sdks/typescript/runner/src/tunnel.ts b/engine/sdks/typescript/runner/src/tunnel.ts index c41a231680..3e9dfc24e2 100644 --- a/engine/sdks/typescript/runner/src/tunnel.ts +++ b/engine/sdks/typescript/runner/src/tunnel.ts @@ -210,7 +210,11 @@ export class Tunnel { return new Response("Actor not found", { status: 404 }); } - const fetchHandler = this.#runner.config.fetch(this.#runner, actorId, request); + const fetchHandler = this.#runner.config.fetch( + this.#runner, + actorId, + request, + ); if (!fetchHandler) { return new Response("Not Implemented", { status: 501 }); @@ -307,8 +311,8 @@ export class Tunnel { existing.actorId = req.actorId; } else { this.#actorPendingRequests.set(requestIdStr, { - resolve: () => { }, - reject: () => { }, + resolve: () => {}, + reject: () => {}, streamController: controller, actorId: req.actorId, }); @@ -475,7 +479,7 @@ export class Tunnel { const dataBuffer = typeof data === "string" ? (new TextEncoder().encode(data) - .buffer as ArrayBuffer) + .buffer as ArrayBuffer) : data; this.#sendMessage(requestId, { @@ -539,7 +543,12 @@ export class Tunnel { }); // Call websocket handler - await websocketHandler(this.#runner, open.actorId, adapter, request); + await websocketHandler( + this.#runner, + open.actorId, + adapter, + request, + ); } catch (error) { logger()?.error({ msg: "error handling websocket open", error }); // Send close on error diff --git a/engine/sdks/typescript/runner/vitest.config.ts b/engine/sdks/typescript/runner/vitest.config.ts index 1a86637f83..b6fc098098 100644 --- a/engine/sdks/typescript/runner/vitest.config.ts +++ b/engine/sdks/typescript/runner/vitest.config.ts @@ -1,4 +1,4 @@ -import { resolve } from "path"; +import { resolve } from "node:path"; import { defineConfig } from "vitest/config"; import defaultConfig from "../../../../vitest.base.ts"; diff --git a/engine/sdks/typescript/test-runner/package.json b/engine/sdks/typescript/test-runner/package.json index 1ba7437674..a9f557ee16 100644 --- a/engine/sdks/typescript/test-runner/package.json +++ b/engine/sdks/typescript/test-runner/package.json @@ -24,4 +24,4 @@ "typescript": "^5.9.2", "vitest": "^1.6.1" } -} \ No newline at end of file +} diff --git a/engine/sdks/typescript/test-runner/src/index.ts b/engine/sdks/typescript/test-runner/src/index.ts index d30868f232..50aa20c899 100644 --- a/engine/sdks/typescript/test-runner/src/index.ts +++ b/engine/sdks/typescript/test-runner/src/index.ts @@ -1,11 +1,11 @@ import { serve } from "@hono/node-server"; import type { ActorConfig, RunnerConfig } from "@rivetkit/engine-runner"; import { Runner } from "@rivetkit/engine-runner"; -import { Hono, type Context as HonoContext, Next } from "hono"; +import { Hono, type Context as HonoContext, type Next } from "hono"; import { streamSSE } from "hono/streaming"; -import { getLogger } from "./log" -import { type Logger } from "pino"; +import type { Logger } from "pino"; import type WebSocket from "ws"; +import { getLogger } from "./log"; const INTERNAL_SERVER_PORT = process.env.INTERNAL_SERVER_PORT ? Number(process.env.INTERNAL_SERVER_PORT) @@ -106,35 +106,43 @@ if (AUTOSTART_SERVER) { ); } -if (AUTOSTART_RUNNER) [runner, runnerStarted, runnerStopped] = await startRunner(); +if (AUTOSTART_RUNNER) + [runner, runnerStarted, runnerStopped] = await startRunner(); async function autoConfigureServerless() { - let res = await fetch(`http://127.0.0.1:6420/runner-configs/${RIVET_RUNNER_NAME}?namespace=${RIVET_NAMESPACE}`, { - method: "PUT", - headers: { - "Authorization": `Bearer ${RIVET_TOKEN}`, - "Content-Type": "application/json", + const res = await fetch( + `http://127.0.0.1:6420/runner-configs/${RIVET_RUNNER_NAME}?namespace=${RIVET_NAMESPACE}`, + { + method: "PUT", + headers: { + Authorization: `Bearer ${RIVET_TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + datacenters: { + default: { + serverless: { + url: `http://localhost:${INTERNAL_SERVER_PORT}`, + max_runners: 10, + slots_per_runner: 1, + request_lifespan: 15, + }, + }, + }, + }), }, - body: JSON.stringify({ - datacenters: { - default: { - serverless: { - url: `http://localhost:${INTERNAL_SERVER_PORT}`, - max_runners: 10, - slots_per_runner: 1, - request_lifespan: 15, - } - } - } - }), - }); + ); if (!res.ok) { - throw new Error(`request failed: ${res.statusText} (${res.status}):\n${await res.text()}`); + throw new Error( + `request failed: ${res.statusText} (${res.status}):\n${await res.text()}`, + ); } } -async function startRunner(): Promise<[Runner, PromiseWithResolvers, PromiseWithResolvers]> { +async function startRunner(): Promise< + [Runner, PromiseWithResolvers, PromiseWithResolvers] +> { getLogger().info("Starting runner"); const runnerStarted = Promise.withResolvers(); @@ -153,7 +161,7 @@ async function startRunner(): Promise<[Runner, PromiseWithResolvers, Pr onConnected: () => { runnerStarted.resolve(undefined); }, - onDisconnected: () => { }, + onDisconnected: () => {}, onShutdown: () => { runnerStopped.resolve(undefined); }, @@ -177,7 +185,7 @@ async function startRunner(): Promise<[Runner, PromiseWithResolvers, Pr } else if (url.pathname === "/sleep") { runner.sleepActor(actorId); - return new Response('ok', { + return new Response("ok", { status: 200, headers: { "Content-Type": "application/json" }, }); @@ -199,7 +207,12 @@ async function startRunner(): Promise<[Runner, PromiseWithResolvers, Pr `Actor ${_actorId} stopped (generation ${_generation})`, ); }, - websocket: async (_runner: Runner, actorId: string, ws: WebSocket, request: Request) => { + websocket: async ( + _runner: Runner, + actorId: string, + ws: WebSocket, + request: Request, + ) => { getLogger().info(`WebSocket connected for actor ${actorId}`); actorWebSockets.set(actorId, ws); @@ -207,7 +220,8 @@ async function startRunner(): Promise<[Runner, PromiseWithResolvers, Pr ws.addEventListener("message", (event) => { const data = event.data; getLogger().info({ - msg: `WebSocket message from actor ${actorId}`, data + msg: `WebSocket message from actor ${actorId}`, + data, }); ws.send(`Echo: ${data}`); }); @@ -218,7 +232,10 @@ async function startRunner(): Promise<[Runner, PromiseWithResolvers, Pr }); ws.addEventListener("error", (error) => { - getLogger().error({ msg: `WebSocket error for actor ${actorId}:`, error }); + getLogger().error({ + msg: `WebSocket error for actor ${actorId}:`, + error, + }); }); }, }; diff --git a/engine/sdks/typescript/test-runner/src/log.ts b/engine/sdks/typescript/test-runner/src/log.ts index 4e035c5a39..767a67afff 100644 --- a/engine/sdks/typescript/test-runner/src/log.ts +++ b/engine/sdks/typescript/test-runner/src/log.ts @@ -25,7 +25,9 @@ export function getPinoLevel(logLevel?: Level): LevelWithSilent { return configuredLogLevel; } - return (process.env["LOG_LEVEL"] || "warn").toString().toLowerCase() as LevelWithSilent; + return (process.env["LOG_LEVEL"] || "warn") + .toString() + .toLowerCase() as LevelWithSilent; } export function getIncludeTarget(): boolean { @@ -88,9 +90,7 @@ function customWrite(level: string, o: any) { /** * Configure the default logger with optional log level. */ -export async function configureDefaultLogger( - logLevel?: Level, -): Promise { +export async function configureDefaultLogger(logLevel?: Level): Promise { // Store the configured log level if (logLevel) { configuredLogLevel = logLevel; diff --git a/engine/sdks/typescript/test-runner/vitest.config.ts b/engine/sdks/typescript/test-runner/vitest.config.ts index 1a86637f83..b6fc098098 100644 --- a/engine/sdks/typescript/test-runner/vitest.config.ts +++ b/engine/sdks/typescript/test-runner/vitest.config.ts @@ -1,4 +1,4 @@ -import { resolve } from "path"; +import { resolve } from "node:path"; import { defineConfig } from "vitest/config"; import defaultConfig from "../../../../vitest.base.ts"; diff --git a/examples/cursors/tests/cursors.test.ts b/examples/cursors/tests/cursors.test.ts deleted file mode 100644 index 235b87cb2b..0000000000 --- a/examples/cursors/tests/cursors.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { setupTest } from "rivetkit/test"; -import { expect, test } from "vitest"; -import { registry } from "../src/backend/registry"; - -test("Cursor room can handle cursor updates", async (ctx) => { - const { client } = await setupTest(ctx, registry); - const room = client.cursorRoom.getOrCreate(["test-room"]); - - // Test initial state - const initialCursors = await room.getCursors(); - expect(initialCursors).toEqual({}); - - // Update cursor position - const cursor1 = await room.updateCursor("user1", 100, 200); - - // Verify cursor structure - expect(cursor1).toMatchObject({ - userId: "user1", - x: 100, - y: 200, - timestamp: expect.any(Number), - }); - - // Update another cursor - await room.updateCursor("user2", 300, 400); - - // Verify cursors are stored - const cursors = await room.getCursors(); - expect(Object.keys(cursors)).toHaveLength(2); - expect(cursors.user1).toBeDefined(); - expect(cursors.user2).toBeDefined(); - expect(cursors.user1.x).toBe(100); - expect(cursors.user1.y).toBe(200); - expect(cursors.user2.x).toBe(300); - expect(cursors.user2.y).toBe(400); -}); - -test("Cursor room can place text labels", async (ctx) => { - const { client } = await setupTest(ctx, registry); - const room = client.cursorRoom.getOrCreate(["test-text"]); - - // Test initial state - const initialLabels = await room.getTextLabels(); - expect(initialLabels).toEqual([]); - - // Place text - const label1 = await room.placeText("user1", "Hello", 50, 75); - - // Verify label structure - expect(label1).toMatchObject({ - id: expect.any(String), - userId: "user1", - text: "Hello", - x: 50, - y: 75, - timestamp: expect.any(Number), - }); - - // Place another text - const label2 = await room.placeText("user2", "World", 150, 175); - - // Verify labels are stored in order - const labels = await room.getTextLabels(); - expect(labels).toHaveLength(2); - expect(labels[0]).toEqual(label1); - expect(labels[1]).toEqual(label2); -}); - -test("Cursor room can remove cursors", async (ctx) => { - const { client } = await setupTest(ctx, registry); - const room = client.cursorRoom.getOrCreate(["test-remove"]); - - // Add some cursors - await room.updateCursor("user1", 100, 200); - await room.updateCursor("user2", 300, 400); - await room.updateCursor("user3", 500, 600); - - let cursors = await room.getCursors(); - expect(Object.keys(cursors)).toHaveLength(3); - - // Remove one cursor - await room.removeCursor("user2"); - - cursors = await room.getCursors(); - expect(Object.keys(cursors)).toHaveLength(2); - expect(cursors.user1).toBeDefined(); - expect(cursors.user3).toBeDefined(); - expect(cursors.user2).toBeUndefined(); -}); - -test("Cursor updates overwrite previous positions", async (ctx) => { - const { client } = await setupTest(ctx, registry); - const room = client.cursorRoom.getOrCreate(["test-overwrite"]); - - // Update cursor multiple times - await room.updateCursor("user1", 100, 200); - const cursor2 = await room.updateCursor("user1", 300, 400); - const cursor3 = await room.updateCursor("user1", 500, 600); - - const cursors = await room.getCursors(); - expect(Object.keys(cursors)).toHaveLength(1); - expect(cursors.user1.x).toBe(500); - expect(cursors.user1.y).toBe(600); - expect(cursors.user1.timestamp).toBe(cursor3.timestamp); - expect(cursor3.timestamp).toBeGreaterThanOrEqual(cursor2.timestamp); -}); - -test("Multiple users can place text in the same room", async (ctx) => { - const { client } = await setupTest(ctx, registry); - const room = client.cursorRoom.getOrCreate(["test-multiuser-text"]); - - // Multiple users placing text - await room.placeText("Alice", "Hello!", 10, 10); - await room.placeText("Bob", "Hi there!", 50, 50); - await room.placeText("Charlie", "Good day!", 100, 100); - await room.placeText("Alice", "How are you?", 150, 150); - - const labels = await room.getTextLabels(); - expect(labels).toHaveLength(4); - - // Verify users - expect(labels[0].userId).toBe("Alice"); - expect(labels[1].userId).toBe("Bob"); - expect(labels[2].userId).toBe("Charlie"); - expect(labels[3].userId).toBe("Alice"); - - // Verify text content - expect(labels[0].text).toBe("Hello!"); - expect(labels[1].text).toBe("Hi there!"); - expect(labels[2].text).toBe("Good day!"); - expect(labels[3].text).toBe("How are you?"); -}); diff --git a/examples/freestyle/scripts/freestyle-deploy.ts b/examples/freestyle/scripts/freestyle-deploy.ts index 0f3cf23fdf..5aa230cb15 100644 --- a/examples/freestyle/scripts/freestyle-deploy.ts +++ b/examples/freestyle/scripts/freestyle-deploy.ts @@ -1,9 +1,9 @@ +import { execSync } from "node:child_process"; +import { readFileSync } from "node:fs"; import { type Rivet, RivetClient } from "@rivetkit/engine-api-full"; -import { execSync } from "child_process"; import dotenv from "dotenv"; import { FreestyleSandboxes } from "freestyle-sandboxes"; import { prepareDirForDeploymentSync } from "freestyle-sandboxes/utils"; -import { readFileSync } from "fs"; dotenv.config({ path: new URL("../.env", import.meta.url).pathname }); diff --git a/frontend/package.json b/frontend/package.json index db9f8209c9..b041b37066 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -120,7 +120,7 @@ "react-inspector": "^6.0.2", "react-resizable-panels": "^2.1.9", "recharts": "^2.15.4", - "rivetkit": "*", + "rivetkit": "workspace:*", "shiki": "^3.12.2", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", @@ -135,4 +135,4 @@ "vite-tsconfig-paths": "^5.1.4", "zod": "^3.25.76" } -} \ No newline at end of file +} diff --git a/justfile b/justfile index 8b25d83401..cd9ea182f1 100644 --- a/justfile +++ b/justfile @@ -12,11 +12,11 @@ release-nolatest VERSION: [group('docker')] docker-build: - docker build -f docker/universal/Dockerfile --target engine-full -t rivetkit/engine:local --platform linux/x86_64 . + docker build -f engine/docker/universal/Dockerfile --target engine-full -t rivetkit/engine:local --platform linux/x86_64 . [group('docker')] docker-build-frontend: - docker build -f docker/universal/Dockerfile --target engine-full -t rivetkit/engine:local --platform linux/x86_64 --build-arg BUILD_FRONTEND=true . + docker build -f engine/docker/universal/Dockerfile --target engine-full -t rivetkit/engine:local --platform linux/x86_64 --build-arg BUILD_FRONTEND=true . [group('docker')] docker-run: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bda78540f..e5386fc7a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -537,6 +537,104 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + examples/cursors: + dependencies: + rivetkit: + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/rivetkit + devDependencies: + '@rivetkit/react': + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/react + '@types/node': + specifier: ^22.13.9 + version: 22.18.1 + '@types/prompts': + specifier: ^2 + version: 2.4.9 + '@types/react': + specifier: ^18.2.0 + version: 18.3.24 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.24) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.7.0(vite@5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.9.2 + vite: + specifier: ^5.0.0 + version: 5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + + examples/cursors-raw-websocket: + dependencies: + rivetkit: + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/rivetkit + devDependencies: + '@rivetkit/react': + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/react + '@types/node': + specifier: ^22.13.9 + version: 22.18.1 + '@types/prompts': + specifier: ^2 + version: 2.4.9 + '@types/react': + specifier: ^18.2.0 + version: 18.3.24 + '@types/react-dom': + specifier: ^18.2.0 + version: 18.3.7(@types/react@18.3.24) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.7.0(vite@5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0)) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.9.2 + vite: + specifier: ^5.0.0 + version: 5.4.20(@types/node@22.18.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + examples/database: dependencies: '@rivetkit/react': diff --git a/rivetkit-typescript/packages/rivetkit/scripts/compile-bare.ts b/rivetkit-typescript/packages/rivetkit/scripts/compile-bare.ts index 25509f82cc..160b5fdf3c 100755 --- a/rivetkit-typescript/packages/rivetkit/scripts/compile-bare.ts +++ b/rivetkit-typescript/packages/rivetkit/scripts/compile-bare.ts @@ -1,9 +1,9 @@ #!/usr/bin/env -S tsx +import * as fs from "node:fs/promises"; +import * as path from "node:path"; import { type Config, transform } from "@bare-ts/tools"; import { Command } from "commander"; -import * as fs from "fs/promises"; -import * as path from "path"; const program = new Command(); diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts index 4520de7fa8..06b84689ff 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts @@ -303,7 +303,11 @@ export class EngineActorDriver implements ActorDriver { logger().debug({ msg: "runner actor stopped", actorId }); } - async #runnerFetch(actorId: string, request: Request): Promise { + async #runnerFetch( + runner: Runner, + actorId: string, + request: Request, + ): Promise { logger().debug({ msg: "runner fetch", actorId, @@ -314,6 +318,7 @@ export class EngineActorDriver implements ActorDriver { } async #runnerWebSocket( + runner: Runner, actorId: string, websocketRaw: any, request: Request, diff --git a/rivetkit-typescript/packages/rivetkit/vitest.config.ts b/rivetkit-typescript/packages/rivetkit/vitest.config.ts index e3082dcf61..fc6a4f6450 100644 --- a/rivetkit-typescript/packages/rivetkit/vitest.config.ts +++ b/rivetkit-typescript/packages/rivetkit/vitest.config.ts @@ -1,4 +1,4 @@ -import { resolve } from "path"; +import { resolve } from "node:path"; import { defineConfig } from "vitest/config"; import defaultConfig from "../../../vitest.base.ts"; diff --git a/scripts/docker/build-push.sh b/scripts/docker/build-push.sh index 0e64e06abc..d327e4e76e 100755 --- a/scripts/docker/build-push.sh +++ b/scripts/docker/build-push.sh @@ -13,7 +13,7 @@ IMAGE_REPO=$1 shift TAGS=("$@") -DOCKERFILE=${DOCKERFILE:-docker/universal/Dockerfile} +DOCKERFILE=${DOCKERFILE:-engine/docker/universal/Dockerfile} TARGET=${TARGET:-engine-full} CONTEXT=${CONTEXT:-.} diff --git a/scripts/release/sdk.ts b/scripts/release/sdk.ts index 668886ddad..85c0708d2d 100644 --- a/scripts/release/sdk.ts +++ b/scripts/release/sdk.ts @@ -1,6 +1,6 @@ import { $ } from "execa"; -import { readFile } from "fs/promises"; -import { join } from "path"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; import type { ReleaseOpts } from "./main"; async function npmVersionExists( @@ -37,9 +37,9 @@ async function npmVersionExists( export async function publishSdk(opts: ReleaseOpts) { const packagePaths = [ - `${opts.root}/sdks/typescript/runner`, - `${opts.root}/sdks/typescript/runner-protocol`, - `${opts.root}/sdks/typescript/api-full`, + `${opts.root}/engine/sdks/typescript/runner`, + `${opts.root}/engine/sdks/typescript/runner-protocol`, + `${opts.root}/engine/sdks/typescript/api-full`, ]; for (const path of packagePaths) { @@ -70,6 +70,6 @@ export async function publishSdk(opts: ReleaseOpts) { await $({ stdio: "inherit", - })`pnpm --filter ${name} publish --access public --tag ${tag}`; + })`pnpm --filter ${name} publish --access public --tag ${tag} --no-git-checks`; } } diff --git a/scripts/run/backup-postgres.sh b/scripts/run/backup-postgres.sh new file mode 100755 index 0000000000..9704f94226 --- /dev/null +++ b/scripts/run/backup-postgres.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +CONTAINER_NAME="rivet-engine-postgres" + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Example: $0 /path/to/backup/postgres-backup.tar.gz" + exit 1 +fi + +BACKUP_PATH="$1" + +# Check if container exists +if ! docker ps --all --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + echo "error: container '${CONTAINER_NAME}' not found" + exit 1 +fi + +# Get the volume name +VOLUME_NAME=$(docker inspect "${CONTAINER_NAME}" --format '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}') + +if [ -z "${VOLUME_NAME}" ]; then + echo "error: could not find postgres data volume for container '${CONTAINER_NAME}'" + exit 1 +fi + +echo "Backing up postgres data from volume '${VOLUME_NAME}'..." + +# Create backup directory if it doesn't exist +BACKUP_DIR="$(dirname "${BACKUP_PATH}")" +mkdir -p "${BACKUP_DIR}" + +# Backup the volume data +docker run --rm \ + -v "${VOLUME_NAME}":/data \ + -v "${BACKUP_DIR}":/backup \ + alpine \ + tar czf "/backup/$(basename "${BACKUP_PATH}")" -C /data . + +echo "Backup completed: ${BACKUP_PATH}" diff --git a/scripts/run/engine-postgres.sh b/scripts/run/engine-postgres.sh index 317263034a..f93511125d 100755 --- a/scripts/run/engine-postgres.sh +++ b/scripts/run/engine-postgres.sh @@ -11,8 +11,21 @@ fi if ! nc -z localhost 5432 >/dev/null 2>&1; then echo "Postgres is not reachable at localhost:5432." - echo "Hint: run scripts/dev/run-postgres.sh to start the local Postgres container." - exit 1 + echo "Starting postgres container..." + "${SCRIPT_DIR}/postgres.sh" + + echo "Waiting for postgres to be ready..." + for i in {1..30}; do + if nc -z localhost 5432 >/dev/null 2>&1; then + echo "Postgres is ready!" + break + fi + if [ $i -eq 30 ]; then + echo "error: postgres did not become ready in time" + exit 1 + fi + sleep 1 + done fi cd "${REPO_ROOT}" diff --git a/scripts/run/nuke-postgres.sh b/scripts/run/nuke-postgres.sh new file mode 100755 index 0000000000..7e6f098b94 --- /dev/null +++ b/scripts/run/nuke-postgres.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +CONTAINER_NAME="rivet-engine-postgres" + +echo "Nuking postgres container and data..." + +# Get the volume name before removing the container +VOLUME_NAME="" +if docker ps --all --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + VOLUME_NAME=$(docker inspect "${CONTAINER_NAME}" --format '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{.Name}}{{end}}{{end}}' 2>/dev/null || true) +fi + +# Stop and remove container +if docker ps --all --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + echo "Stopping and removing container '${CONTAINER_NAME}'..." + if docker ps --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + docker stop "${CONTAINER_NAME}" >/dev/null 2>&1 || true + fi + docker rm "${CONTAINER_NAME}" >/dev/null 2>&1 || true + echo "Container removed." +else + echo "Container '${CONTAINER_NAME}' not found." +fi + +# Remove volume if it exists +if [ -n "${VOLUME_NAME}" ]; then + echo "Removing volume '${VOLUME_NAME}'..." + docker volume rm "${VOLUME_NAME}" >/dev/null 2>&1 || true + echo "Volume removed." +fi + +echo "Postgres nuked successfully!" diff --git a/scripts/run/restore-postgres.sh b/scripts/run/restore-postgres.sh new file mode 100755 index 0000000000..bd2b50e0b4 --- /dev/null +++ b/scripts/run/restore-postgres.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +CONTAINER_NAME="rivet-engine-postgres" +POSTGRES_IMAGE="postgres:17" + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + echo "Example: $0 /path/to/backup/postgres-backup.tar.gz" + exit 1 +fi + +BACKUP_PATH="$1" + +if [ ! -f "${BACKUP_PATH}" ]; then + echo "error: backup file '${BACKUP_PATH}' not found" + exit 1 +fi + +# Stop and remove existing container if it exists +if docker ps --all --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + echo "Stopping and removing existing container '${CONTAINER_NAME}'..." + if docker ps --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then + docker stop "${CONTAINER_NAME}" >/dev/null 2>&1 || true + fi + docker rm "${CONTAINER_NAME}" >/dev/null 2>&1 || true +fi + +# Create a new volume +echo "Creating new volume..." +VOLUME_NAME=$(docker volume create) +echo "Created volume: ${VOLUME_NAME}" + +# Restore the data to the new volume +echo "Restoring data from '${BACKUP_PATH}'..." +BACKUP_DIR="$(dirname "${BACKUP_PATH}")" +docker run --rm \ + -v "${VOLUME_NAME}":/data \ + -v "${BACKUP_DIR}":/backup \ + alpine \ + tar xzf "/backup/$(basename "${BACKUP_PATH}")" -C /data + +# Create and start the container +echo "Starting container '${CONTAINER_NAME}'..." +docker run \ + --detach \ + --name "${CONTAINER_NAME}" \ + --publish 5432:5432 \ + --env POSTGRES_PASSWORD=postgres \ + --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=postgres \ + -v "${VOLUME_NAME}":/var/lib/postgresql/data \ + "${POSTGRES_IMAGE}" + +echo "Restore completed successfully!" +echo "Container ID: $(docker ps -q -f name=${CONTAINER_NAME})" diff --git a/website/scripts/generateExamples.mjs b/website/scripts/generateExamples.mjs index 778a623eb7..c1d54f0732 100644 --- a/website/scripts/generateExamples.mjs +++ b/website/scripts/generateExamples.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node -import { execSync } from 'child_process'; -import { readFileSync, writeFileSync, existsSync, mkdirSync, cpSync, rmSync } from 'fs'; -import { join } from 'path'; +import { execSync } from 'node:child_process'; +import { readFileSync, writeFileSync, existsSync, mkdirSync, cpSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; import { EXAMPLE_METADATA } from './examplesData.mjs'; const REPO_URL = 'https://github.com/rivet-dev/rivetkit.git'; diff --git a/website/scripts/generateNavigation.ts b/website/scripts/generateNavigation.ts index 70040135e7..79955cf198 100644 --- a/website/scripts/generateNavigation.ts +++ b/website/scripts/generateNavigation.ts @@ -2,7 +2,7 @@ // import engineStyles from '../src/lib/engineStyles.json' assert { type: 'json' }; import { slugifyWithCounter } from "@sindresorhus/slugify"; import glob from "fast-glob"; -import { readFile, writeFile } from "fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; import { toString } from "mdast-util-to-string"; import { remark } from "remark"; import { visit } from "unist-util-visit"; diff --git a/website/scripts/generateReadme.mjs b/website/scripts/generateReadme.mjs index deececad94..40d59a92f7 100755 --- a/website/scripts/generateReadme.mjs +++ b/website/scripts/generateReadme.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { join } from 'path'; +import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { join } from 'node:path'; import { EXAMPLE_METADATA } from './examplesData.mjs'; const RIVET_TEMPLATE_PATH = join(process.cwd(), '..', 'README.rivet.tpl.md');