Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions vendor/integrations/docs/content/docs/turso.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: "Turso"
description: "Give each Rivet Actor its own Turso Cloud database."
skill: false
---

[Turso Cloud](https://turso.tech) hosts SQLite-compatible databases you reach over the network, and you can create as many as you need through its Platform API. That fits Rivet Actors well: each actor creates its own Turso database, opens a client to it in `createVars`, and deletes the database when the actor is destroyed.

<Diagram
id="turso"
label="A client calls actions on the todoList Actor whose key is the list id. Each todoList Actor loads and writes its rows in its own Turso database. There is one todoList Actor, and one Turso database, per list."
stackLabel="One per list"
nodes={[
{ id: "client", x: 20, y: 20, w: 110, title: "Client" },
{ id: "list", x: 190, y: 20, w: 160, product: "actors", stack: true, title: "`todoList`", subtitle: "key is the list id" },
{ id: "turso", x: 480, y: 20, w: 150, vendor: "turso", stack: true, title: "Turso", subtitle: "database" },
]}
edges={[
{ from: "client", to: "list", label: "actions" },
{ from: "list", to: "turso", label: "loads and writes", both: true },
]}
/>

## Quickstart

<Steps>

<Step title="Create a Platform API token">

Install the [Turso CLI](https://docs.turso.tech/cli/installation), sign in, and find your organization slug:

```sh
turso auth login
turso org list
```

Mint a Platform API token. The actor uses it to create and delete databases:

```sh
turso auth api-tokens mint rivet-todos
```

</Step>

<Step title="Install">

```sh
npm install rivetkit @libsql/client @tursodatabase/api
```

`@tursodatabase/api` calls the Platform API. `@libsql/client` queries a database. The Platform API creates [libSQL](https://docs.turso.tech/libsql) databases by default, which use that driver.

</Step>

<Step title="Configure Turso">

Create `.env` with your organization slug and token:

<CodeSnippet file="examples/turso/.env.example" title=".env" />

</Step>

<Step title="Define the actor">

Each `todoList` actor owns one list and one Turso database.

<CodeSnippet file="examples/turso/src/index.ts" title="src/index.ts" />

</Step>

<Step title="Run it">

Start the server, then call the actor from a client:

```sh
npx tsx --env-file=.env src/index.ts
```

<CodeSnippet file="examples/turso/scripts/client.ts" title="scripts/client.ts" />

```sh
npx tsx scripts/client.ts
```

</Step>

</Steps>

## Database lifecycle

| Hook | What it does |
| --- | --- |
| `createState` | Runs once, when the actor is created. Creates the actor's database and a token scoped to it, and saves both in [state](/actors/docs/state). A database left over from a failed creation attempt is reused. |
| `createVars` | Runs on every start. Opens a client to the database on `c.vars` and creates the table if it does not exist. When the actor sleeps, `c.vars` is dropped, and the next wake opens a new client. |
| `onDestroy` | Deletes the database. |

The client lives on `c.vars` because it is a live object. [Ephemeral variables](/actors/docs/state#ephemeral-state) are never persisted, so each start gets a fresh client.

Databases are named `todos-<actor id>`, so a new actor with a reused key gets a new database. Each one counts toward your Turso plan's database limit.

## Why the client is never closed

Every action awaits its statement. `execute()` resolves only after the server returns the result, so the write is committed before the action returns.

In Node, `@libsql/client` sends `libsql://` URLs over HTTP. Each statement opens a stream and closes it when its result arrives, and connections come from Node's shared `fetch` pool. An idle client holds no connections, so dropping `c.vars` releases it.

Calling `close()` in `onSleep` or `onDestroy` would not release anything more, and it can break writes. `close()` is synchronous and does not flush: it rejects statements the client has not sent yet. `onSleep` and `onDestroy` run alongside in-flight actions during the [shutdown sequence](/actors/docs/lifecycle#shutdown-sequence), so a write that is still queued would fail.

This relies on the HTTP transport. A `wss://` URL keeps a WebSocket open, and an interactive transaction holds a server-side stream until it commits or rolls back. Close those explicitly, after every statement that uses them has settled.

`onDestroy` is best effort: if the process crashes during destroy, the database stays and must be deleted with the Turso CLI or Platform API.

## Credentials

The Platform API token can create and delete every database in its organization, so it stays in the server's environment. Each actor keeps only the token for its own database in its state.

## Startup timeout

`createVars` must finish within `createVarsTimeout`, which defaults to 5 seconds. Its first query opens the connection to Turso, so a node far from the database's region spends part of that budget on the round trip. Raise `options.createVarsTimeout` if starts time out. See [Options](/actors/docs/lifecycle#options).

## Testing

The test creates real Turso databases, so it reads the same environment variables and is skipped without them. It checks that each list gets its own database and that destroying a list deletes it:

<CodeSnippet file="examples/turso/tests/turso.test.ts" title="tests/turso.test.ts" />

See the [Turso Platform API](https://docs.turso.tech/api-reference/introduction) and [TypeScript reference](https://docs.turso.tech/sdk/ts/reference) for the full APIs.

## Local disk

This guide queries Turso Cloud over the network, so nodes need no local disk. Turso's local options keep a database file on the node's disk instead: [embedded replicas](https://docs.turso.tech/features/embedded-replicas/introduction), `@tursodatabase/database`, and `@tursodatabase/sync`.

If you use one of them, verify that your platform gives nodes a local disk. Some serverless platforms do not by default. Google Cloud Run, for example, uses an [in-memory file system](https://cloud.google.com/run/docs/container-contract#filesystem), so the database file uses the instance's memory and is lost when the instance stops. Rivet Cloud provides local disk to nodes, so local Turso databases work there without extra setup.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { actor } from "rivetkit";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Keep the Actors-only snippet out of the integrations bundle

No page in vendor/integrations/docs/content references this snippet, while the current base already ships the identical file under vendor/actors/ and references it from the Actors performance-monitoring page. This update therefore adds only a dead duplicate and causes the integrations sync to churn whenever an unrelated Actors example changes. Scope the integrations sync's extra paths to snippets referenced by docs/integrations (or otherwise omit this Actors-only file) in the upstream sync configuration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Keep unrelated Actors files out of the integrations sync

The refreshed diff still contains no integration page that references this snippet; the current base ships the identical file under vendor/actors/, where the performance-monitoring page uses it. The accompanying examples/docs/package.json version change is likewise unrelated to the integration bundle's referenced snippets. Both changes are churn from copying the repository-wide examples/docs tree into every bundle rather than integration content. Scope this sync's extra paths to files referenced by docs/integrations (or otherwise omit these Actors-only updates) in the upstream configuration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Medium · Keep unrelated Actors files out of the integrations sync

The rebased diff still has no integration page that references this snippet; the current base ships the identical file under vendor/actors/, where the Actors performance-monitoring page uses it. The accompanying examples/docs/package.json version change is also unrelated to any integration page. These changes are churn from copying the repository-wide examples/docs tree into every bundle. Scope the integrations sync's extra paths to files referenced by docs/integrations (or otherwise omit these Actors-only updates) in the upstream configuration.

import { db } from "rivetkit/db";

export const myActor = actor({
db: db(),
actions: {
logCache: async (c) => {
await c.db.execute("SELECT 1");
const metrics = await c.db.nativeMetrics?.();
if (metrics) {
c.log.info({ msg: "SQLite cache", ...metrics });
}
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Counter } from "../rivet/Counter";

export default function Home() {
return <Counter />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { createRivetKit } from "@rivetkit/next-js/client";
import { useState } from "react";
import type { registry } from "./registry";

const { useActor } = createRivetKit<typeof registry>({
endpoint: process.env.NEXT_PUBLIC_RIVET_ENDPOINT ?? "http://localhost:6420",
});

export function Counter() {
const [count, setCount] = useState(0);
const counter = useActor({ name: "counter", key: ["my-counter"] });

counter.useEvent("newCount", (value: number) => setCount(value));

return (
<div>
<p>Count: {count}</p>
<button onClick={() => counter.connection?.increment(1)}>Increment</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { registry } from "./registry";

registry.start();
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/actors-destroy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const actorId = "00000000-0000-0000-0000-000000000000";
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/actors-list-names.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";

Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/actors-list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";

Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/actors-reschedule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const actorId = "00000000-0000-0000-0000-000000000000";
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/actors-sleep.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const actorId = "00000000-0000-0000-0000-000000000000";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const ws = new WebSocket(
`wss://rivet.example.com/gateway/${process.env.ACTOR_ID}/connect`,
`wss://api.rivet.dev/gateway/${process.env.ACTOR_ID}/connect`,
["rivet", "rivet_encoding.json", `rivet_token.${process.env.RIVET_TOKEN}`],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Open a connection by actor name. The selector query parameters are the same
// ones used by the HTTP endpoints; the token and encoding travel as
// WebSocket subprotocols because browsers cannot set headers on an upgrade.
const url = new URL("wss://rivet.example.com/gateway/counter/connect");
const url = new URL("wss://api.rivet.dev/gateway/counter/connect");
url.searchParams.set("rvt-namespace", process.env.RIVET_NAMESPACE!);
url.searchParams.set("rvt-method", "getOrCreate");
url.searchParams.set("rvt-key", "my-counter");
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/connection-errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const ws = new WebSocket(
`wss://rivet.example.com/gateway/${process.env.ACTOR_ID}/connect`,
`wss://api.rivet.dev/gateway/${process.env.ACTOR_ID}/connect`,
["rivet", "rivet_encoding.json", `rivet_token.${process.env.RIVET_TOKEN}`],
);

Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/connection-events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const ws = new WebSocket(
`wss://rivet.example.com/gateway/${process.env.ACTOR_ID}/connect`,
`wss://api.rivet.dev/gateway/${process.env.ACTOR_ID}/connect`,
["rivet", "rivet_encoding.json", `rivet_token.${process.env.RIVET_TOKEN}`],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const token = process.env.RIVET_TOKEN ?? "";

const response = await fetch(`${endpoint}/datacenters`, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";

const response = await fetch(`${endpoint}/metadata`);
const metadata = await response.json();
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/gateway-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/metadata`,
{
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/gateway-queue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const actorId = process.env.ACTOR_ID;
const response = await fetch(`${endpoint}/gateway/${actorId}/queue/increment`, {
method: "POST",
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/inspector-action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/action/increment`,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/connections`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/database/execute`,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const query = new URLSearchParams({
table: "users",
limit: "100",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/database/schema`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/queue`,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/queue?limit=50`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/queue`,
{
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/inspector-rpcs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/rpcs`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/state`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/state`,
{
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/inspector-summary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/summary`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/inspector-traces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/traces?startMs=0&limit=100`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/workflow-history`,
{ headers: { Authorization: `Bearer ${process.env.INSPECTOR_TOKEN}` } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const response = await fetch(
`${endpoint}/gateway/${process.env.ACTOR_ID}/inspector/workflow/replay`,
{
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/namespaces-create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const token = process.env.RIVET_TOKEN ?? "";

const response = await fetch(`${endpoint}/namespaces`, {
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/namespaces-list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const token = process.env.RIVET_TOKEN ?? "";

const response = await fetch(`${endpoint}/namespaces`, {
Expand Down
2 changes: 1 addition & 1 deletion vendor/integrations/examples/docs/api/tokens-inspect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const token = process.env.RIVET_TOKEN ?? "";

// Inspect reports on the token used to authenticate the request itself.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const workerName = process.env.RUNNER_NAME ?? "default";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const workerName = process.env.RUNNER_NAME ?? "default";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const endpoint = "https://rivet.example.com";
const endpoint = "https://api.rivet.dev";
const namespace = process.env.RIVET_NAMESPACE ?? "default";
const token = process.env.RIVET_TOKEN ?? "";
const workerName = process.env.RUNNER_NAME ?? "default";
Expand Down
Loading
Loading