diff --git a/vendor/actors/docs/content/docs/general/environment-variables.mdx b/vendor/actors/docs/content/docs/general/environment-variables.mdx index 7c34e807..9f2d1d5d 100644 --- a/vendor/actors/docs/content/docs/general/environment-variables.mdx +++ b/vendor/actors/docs/content/docs/general/environment-variables.mdx @@ -38,6 +38,8 @@ These variables configure how clients connect to your actors. | `RIVET_RUN_ENGINE_HOST` | Host to bind the spawned local engine process to. Defaults to `127.0.0.1`. | | `RIVET_RUN_ENGINE_PORT` | Port to bind the spawned local engine process to. Defaults to `6420`. | | `RIVET_RUN_ENGINE_VERSION` | Version of engine to download | +| `RIVET_RUN_SERVICES` | Set to `1` to run Services with a local Engine, or `0` to disable it. By default, Services follows RivetKit's local-Engine decision. | +| `RIVET_SERVICES_BINARY` | Path to a local `rivet-services` binary. | ## Inspector diff --git a/vendor/actors/docs/content/docs/limits.mdx b/vendor/actors/docs/content/docs/limits.mdx index 16a0bae2..89a2c434 100644 --- a/vendor/actors/docs/content/docs/limits.mdx +++ b/vendor/actors/docs/content/docs/limits.mdx @@ -53,14 +53,9 @@ These limits affect actions that do not use `.connect()` and [low-level HTTP req | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Max request body size | — | 20 MiB | Maximum size of HTTP request bodies. | -| Max response body size | — | 20 MiB | Maximum size of HTTP response bodies. | -| Request timeout | 60 seconds | — | Maximum time for an `onRequest` handler to complete. Defaults to `actionTimeout`; configure with `actionTimeout`. | - -### Actions - -| Name | Soft Limit | Hard Limit | Description | -|------|------------|------------|-------------| -| Max actions per actor | 128 | None | Maximum number of action handlers defined on one actor. Nested action groups count each leaf handler. Configurable via `maxActions`. | +| Max buffered response body size | — | 20 MiB | Maximum size of a non-streaming HTTP response. Streaming responses may transfer more than 20 MiB over their lifetime and are flow controlled instead of buffered in full. | +| Response start timeout | — | 5 minutes by default | Maximum time for `onRequest` to return response headers. It does not limit the lifetime of a response stream after the headers arrive. Self-hosted operators can configure `pegboard.gateway_response_start_timeout_ms`. | +| Streaming response idle timeout | — | None by default | Rivet does not close an otherwise healthy stream because no body chunk was produced. Other proxies and hosting platforms may impose their own idle timeouts, so SSE handlers should send periodic comment heartbeats. | ### Networking @@ -145,7 +140,8 @@ See [Actor Input](/actors/docs/input) for details. | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| | Action timeout | 60 seconds | — | Timeout for RPC actions. Configurable via `actionTimeout`. | -| On request timeout | 60 seconds | — | Timeout for raw `onRequest` handlers. Defaults to `actionTimeout`; configure with `actionTimeout`. | +| Raw HTTP response start timeout | — | 5 minutes by default | Time allowed for a raw `onRequest` handler to return response headers. `actionTimeout` does not apply to raw HTTP handlers, and a streaming response has no fixed total-duration timeout after it starts. Self-hosted operators can configure `pegboard.gateway_response_start_timeout_ms`. | +| Raw HTTP stream idle timeout | — | None by default | Time allowed between response chunks. Self-hosted operators can configure `pegboard.gateway_response_chunk_idle_timeout_ms`; send SSE heartbeats more frequently than that value when enabled. | | On before connect timeout | 5 seconds | — | Timeout for the `onBeforeConnect` hook. Configurable via `onBeforeConnectTimeout`. | | Create vars timeout | 5 seconds | — | Timeout for `createVars` hook. Configurable via `createVarsTimeout`. | | Create conn state timeout | 5 seconds | — | Timeout for `createConnState` hook. Configurable via `createConnStateTimeout`. | diff --git a/vendor/actors/docs/content/docs/request-handler.mdx b/vendor/actors/docs/content/docs/request-handler.mdx index af362c52..d204f71c 100644 --- a/vendor/actors/docs/content/docs/request-handler.mdx +++ b/vendor/actors/docs/content/docs/request-handler.mdx @@ -26,6 +26,17 @@ The `onRequest` handler processes HTTP requests sent to your actor. It receives See also the [raw fetch handler example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-fetch-handler). +## Streaming Responses & Server-Sent Events + +Return a `Response` with a `ReadableStream` body to stream data as it becomes available. For server-sent events (SSE), set `Content-Type` to `text/event-stream` and format each event as one or more fields followed by a blank line. + + + + + + +Rivet keeps the actor and request lifecycle active until the response stream closes, errors, or the client disconnects. The request's `AbortSignal` is aborted when the downstream client disconnects, so stop producers and release request-scoped resources when `request.signal` fires. Also implement the stream's `cancel()` callback for cleanup. + ## Sending Requests To Actors ### Via RivetKit Client @@ -87,7 +98,6 @@ The `onRequest` handler is WinterTC compliant and will work with existing librar ## Limitations -- Does not support streaming responses & server-sent events at the moment. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3529). - `OPTIONS` requests currently are handled by Rivet and are not passed to `onRequest` ## Advanced diff --git a/vendor/actors/docs/content/integrations/durable-streams.mdx b/vendor/actors/docs/content/integrations/durable-streams.mdx new file mode 100644 index 00000000..42a40cd2 --- /dev/null +++ b/vendor/actors/docs/content/integrations/durable-streams.mdx @@ -0,0 +1,121 @@ +--- +title: "Durable Streams" +description: "Run the Durable Streams protocol locally and deploy it with Rivet." +--- + +[Durable Streams](https://github.com/durable-streams/durable-streams) is an open standard for real-time streams with durable, replayable history. Rivet provides an implementation backed by Rivet Actors. + +## Quickstart + + + + + + + + + +Start the local Rivet control plane and the Durable Streams worker: + +```sh +npx @rivet-dev/services dev +``` + +Durable Streams is available at `http://127.0.0.1:8642/durable-streams/v1/stream/`. + + + + + +Already running Rivet Actors with the RivetKit TypeScript SDK? Durable Streams is already available to you on RivetKit 2.3.12. No separate server needed. + + + + + + + + + + + + + +Install the official client: + +```sh +npm install @durable-streams/client +``` + + + + + + + +Create a stream, append a record, and read its contents: + +```sh +curl -i -X PUT \ + -H 'content-type: application/json' \ + --data '[{"message":"hello"}]' \ + http://127.0.0.1:8642/durable-streams/v1/stream/demo + +curl -i -X POST \ + -H 'content-type: application/json' \ + --data '{"message":"world"}' \ + http://127.0.0.1:8642/durable-streams/v1/stream/demo + +curl 'http://127.0.0.1:8642/durable-streams/v1/stream/demo?offset=-1' +``` + + + + + + + + + + + + + +1. Sign up at the [Rivet dashboard](https://dashboard.rivet.dev) and create a new project. +2. When creating the project, choose Durable Streams as the product. +3. Point your Durable Streams client at the services URL you're given, for example `https://xxxxx.rivet.run/durable-streams/`. + + + + + +Run the Rivet control plane with the required feature flags enabled: + +```sh +docker run -p 6420:6420 \ + -e RIVET__FEATURES__GUARD_GATEWAY_V3__MODE=on \ + -e RIVET__FEATURES__GUARD_GATEWAY_V3__PERCENTAGE=100 \ + rivetdev/engine +``` + +See the [self-hosting guides](/actors/self-host/) for other ways to run it. + +Run the Durable Streams worker. It runs your streams and connects to the control plane: + +```sh +docker run -p 8642:8642 \ + --add-host=host.docker.internal:host-gateway \ + -e RIVET_ENDPOINT=http://host.docker.internal:6420 \ + -e HOST=0.0.0.0 \ + rivetdev/services +``` + +Your streams are at `http://:8642/durable-streams/v1/stream/`. + + + + + + + + diff --git a/vendor/actors/docs/sidebar.json b/vendor/actors/docs/sidebar.json index c6aafcd6..13992a1f 100644 --- a/vendor/actors/docs/sidebar.json +++ b/vendor/actors/docs/sidebar.json @@ -423,6 +423,10 @@ { "title": "Integrations", "pages": [ + { + "title": "Durable Streams", + "href": "/actors/integrations/durable-streams" + }, { "title": "Flue", "href": "/actors/integrations/flue" diff --git a/vendor/actors/engine/artifacts/config-schema.json b/vendor/actors/engine/artifacts/config-schema.json index 9147b7a7..ab5b8d43 100644 --- a/vendor/actors/engine/artifacts/config-schema.json +++ b/vendor/actors/engine/artifacts/config-schema.json @@ -73,6 +73,17 @@ } ] }, + "features": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/Features" + }, + { + "type": "null" + } + ] + }, "guard": { "default": null, "anyOf": [ @@ -465,6 +476,24 @@ } ] }, + "Features": { + "type": "object", + "properties": { + "guard_gateway_v3": { + "description": "Controls routing to the streaming Guard Gateway V3 implementation.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/GuardGatewayV3" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "FileSystem": { "type": "object", "required": [ @@ -675,6 +704,35 @@ }, "additionalProperties": false }, + "GuardGatewayV3": { + "type": "object", + "properties": { + "mode": { + "default": "off", + "allOf": [ + { + "$ref": "#/definitions/GuardGatewayV3Mode" + } + ] + }, + "percentage": { + "default": 0, + "type": "integer", + "format": "uint8", + "maximum": 100.0, + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "GuardGatewayV3Mode": { + "type": "string", + "enum": [ + "off", + "opportunistic", + "on" + ] + }, "Https": { "type": "object", "required": [ @@ -1008,6 +1066,24 @@ "format": "uint", "minimum": 0.0 }, + "gateway_http_response_body_channel_capacity": { + "description": "Number of body chunks buffered between a streaming response handler and its HTTP client.", + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0.0 + }, + "gateway_http_response_queue_max_messages": { + "description": "Maximum number of envoy response messages buffered per HTTP request.", + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0.0 + }, "gateway_hws_max_pending_size": { "description": "Max pending message buffer size for hibernating WebSockets in bytes.", "type": [ @@ -1026,6 +1102,15 @@ "format": "uint64", "minimum": 0.0 }, + "gateway_response_chunk_idle_timeout_ms": { + "description": "Timeout between streaming HTTP response chunks in milliseconds.\n\nDisabled when unset so long-lived streams such as SSE may remain idle.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, "gateway_response_start_timeout_ms": { "description": "Timeout for response to start in milliseconds.", "type": [ @@ -1035,6 +1120,15 @@ "format": "uint64", "minimum": 0.0 }, + "gateway_streaming_http_response_queue_max_bytes": { + "description": "Maximum streaming response bytes buffered per HTTP request.", + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0.0 + }, "gateway_tunnel_ping_timeout_ms": { "description": "Tunnel ping timeout in milliseconds.", "type": [ diff --git a/vendor/actors/examples/docs/actors-integrations-durable-streams/client.ts b/vendor/actors/examples/docs/actors-integrations-durable-streams/client.ts new file mode 100644 index 00000000..c541cfdd --- /dev/null +++ b/vendor/actors/examples/docs/actors-integrations-durable-streams/client.ts @@ -0,0 +1,8 @@ +import { DurableStream } from "@durable-streams/client"; + +const stream = await DurableStream.create({ + url: "http://127.0.0.1:8642/durable-streams/v1/stream/demo", + contentType: "application/json", +}); + +await stream.append(JSON.stringify({ message: "hello" })); diff --git a/vendor/actors/examples/docs/actors-limits/actor-options.ts b/vendor/actors/examples/docs/actors-limits/actor-options.ts index 2aa44fbb..25c3b69c 100644 --- a/vendor/actors/examples/docs/actors-limits/actor-options.ts +++ b/vendor/actors/examples/docs/actors-limits/actor-options.ts @@ -2,7 +2,6 @@ import { actor } from "rivetkit"; const myActor = actor({ options: { - maxActions: 128, maxQueueSize: 1000, actionTimeout: 60_000, stateSaveInterval: 1_000, diff --git a/vendor/actors/examples/docs/actors-request-handler/sse-client.ts b/vendor/actors/examples/docs/actors-request-handler/sse-client.ts new file mode 100644 index 00000000..8ccc2664 --- /dev/null +++ b/vendor/actors/examples/docs/actors-request-handler/sse-client.ts @@ -0,0 +1,30 @@ +import { createClient } from "rivetkit/client"; +import type { registry } from "./sse-server"; + +const client = createClient("http://localhost:6420"); +const notifications = client.notifications.getOrCreate(["status"]); +const response = await notifications.fetch("/", { + headers: { Accept: "text/event-stream" }, +}); + +if (!response.ok || !response.body) { + throw new Error(`SSE request failed with status ${response.status}`); +} + +const reader = response.body.getReader(); +const decoder = new TextDecoder(); +let pending = ""; + +for (;;) { + const chunk = await reader.read(); + if (chunk.done) break; + + pending += decoder.decode(chunk.value, { stream: true }); + for (;;) { + const boundary = pending.indexOf("\n\n"); + if (boundary === -1) break; + const event = pending.slice(0, boundary); + pending = pending.slice(boundary + 2); + console.log(event); + } +} diff --git a/vendor/actors/examples/docs/actors-request-handler/sse-server.ts b/vendor/actors/examples/docs/actors-request-handler/sse-server.ts new file mode 100644 index 00000000..52f31c5d --- /dev/null +++ b/vendor/actors/examples/docs/actors-request-handler/sse-server.ts @@ -0,0 +1,8 @@ +import { setup } from "rivetkit"; +import { notificationsActor } from "./sse"; + +export const registry = setup({ + use: { notifications: notificationsActor }, +}); + +registry.start(); diff --git a/vendor/actors/examples/docs/actors-request-handler/sse.ts b/vendor/actors/examples/docs/actors-request-handler/sse.ts new file mode 100644 index 00000000..3114f199 --- /dev/null +++ b/vendor/actors/examples/docs/actors-request-handler/sse.ts @@ -0,0 +1,29 @@ +import { actor } from "rivetkit"; + +const encoder = new TextEncoder(); + +export const notificationsActor = actor({ + state: {}, + onRequest: () => { + const stream = new ReadableStream({ + async start(controller) { + controller.enqueue( + encoder.encode('event: status\ndata: {"ready":true}\n\n'), + ); + await new Promise((resolve) => setTimeout(resolve, 100)); + controller.enqueue( + encoder.encode('event: status\ndata: {"ready":false}\n\n'), + ); + controller.close(); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache, no-transform", + }, + }); + }, + actions: {}, +}); diff --git a/vendor/actors/examples/docs/package.json b/vendor/actors/examples/docs/package.json index d916bf51..769324dd 100644 --- a/vendor/actors/examples/docs/package.json +++ b/vendor/actors/examples/docs/package.json @@ -7,6 +7,7 @@ "check-types": "tsc --noEmit" }, "dependencies": { + "@durable-streams/client": "^0.2.6", "rivetkit": "workspace:*", "@rivetkit/react": "workspace:*", "@rivetkit/engine-api-full": "workspace:*", diff --git a/vendor/actors/rivetkit-typescript/artifacts/actor-config.json b/vendor/actors/rivetkit-typescript/artifacts/actor-config.json index 6b0ce929..a8f4b03a 100644 --- a/vendor/actors/rivetkit-typescript/artifacts/actor-config.json +++ b/vendor/actors/rivetkit-typescript/artifacts/actor-config.json @@ -109,12 +109,6 @@ "description": "Icon for the actor in the Inspector UI. Can be an emoji (e.g., '🚀') or FontAwesome icon name (e.g., 'rocket').", "type": "string" }, - "maxActions": { - "description": "Maximum number of action handlers that may be defined on this actor. Default: 128", - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - }, "enableActorRuntimeSocket": { "description": "Enables the experimental Actor Runtime Socket for this actor. Default: false", "type": "boolean" diff --git a/vendor/actors/rivetkit-typescript/packages/rivetkit/package.json b/vendor/actors/rivetkit-typescript/packages/rivetkit/package.json index b95a85b3..d36490f2 100644 --- a/vendor/actors/rivetkit-typescript/packages/rivetkit/package.json +++ b/vendor/actors/rivetkit-typescript/packages/rivetkit/package.json @@ -210,6 +210,7 @@ "dependencies": { "@hono/zod-openapi": "^1.1.5", "@rivet-dev/agent-os-core": "^0.1.1", + "@rivet-dev/services": "^0.1.3", "@rivetkit/bare-ts": "^0.6.2", "@rivetkit/engine-cli": "workspace:*", "@rivetkit/engine-envoy-protocol": "workspace:*",