@blujosi/rivetkit-svelte 0.0.7 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -213
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +1 -0
- package/dist/{rivet.svelte.d.ts → svelte/rivet.svelte.d.ts} +30 -6
- package/dist/{rivet.svelte.js → svelte/rivet.svelte.js} +41 -28
- package/dist/sveltekit/handler.server.d.ts +15 -0
- package/dist/sveltekit/handler.server.js +62 -0
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.js +1 -0
- package/package.json +28 -20
package/README.md
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @blujosi/rivetkit-svelte
|
|
2
2
|
|
|
3
|
-
A Svelte 5 integration for [RivetKit](https://
|
|
3
|
+
A Svelte 5 integration for [RivetKit](https://rivet.dev) that provides reactive actor connections using Svelte 5's runes system, plus a built-in SvelteKit handler for serverless deployment.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
pnpm add @rivetkit
|
|
8
|
+
pnpm add @blujosi/rivetkit-svelte rivetkit
|
|
9
9
|
|
|
10
10
|
# or
|
|
11
11
|
|
|
12
|
-
npm i @rivetkit
|
|
12
|
+
npm i @blujosi/rivetkit-svelte rivetkit
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
`@blujosi/rivetkit-svelte` provides two main pieces:
|
|
18
|
+
|
|
19
|
+
1. **Svelte 5 client** (`@blujosi/rivetkit-svelte`) — `useActor` hook for reactive actor connections with real-time events
|
|
20
|
+
2. **SvelteKit handler** (`@blujosi/rivetkit-svelte/sveltekit`) — `createRivetKitHandler` to serve RivetKit as a SvelteKit API route
|
|
18
21
|
|
|
19
22
|
## Features
|
|
20
23
|
|
|
21
|
-
- **Svelte 5 Runes
|
|
22
|
-
- **Real-time Actor Connections**
|
|
23
|
-
- **Event Handling**
|
|
24
|
-
- **Type Safety**
|
|
25
|
-
- **SSR Compatible**
|
|
26
|
-
- **
|
|
24
|
+
- **Svelte 5 Runes** — Built for `$state`, `$effect`, and `$derived`
|
|
25
|
+
- **Real-time Actor Connections** — Connect to RivetKit actors with automatic state sync
|
|
26
|
+
- **Event Handling** — `useEvent` with automatic cleanup
|
|
27
|
+
- **Type Safety** — Full TypeScript support with registry type inference
|
|
28
|
+
- **SSR Compatible** — Browser guard for SvelteKit SSR
|
|
29
|
+
- **SvelteKit Handler** — Run RivetKit serverless inside your SvelteKit app
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
---
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
## Quick Start
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
### 1. Define Your Actors & Registry
|
|
33
36
|
|
|
34
37
|
```typescript
|
|
35
38
|
// backend/registry.ts
|
|
36
|
-
import { actor, setup } from "
|
|
39
|
+
import { actor, setup } from "rivetkit";
|
|
37
40
|
|
|
38
41
|
export const counter = actor({
|
|
39
|
-
|
|
40
|
-
// Configure auth here if needed
|
|
41
|
-
},
|
|
42
|
-
state: { count: 0 },
|
|
42
|
+
state: { count: 0, countDouble: 0 },
|
|
43
43
|
actions: {
|
|
44
44
|
increment: (c, x: number) => {
|
|
45
|
-
console.log("incrementing by", x);
|
|
46
45
|
c.state.count += x;
|
|
47
46
|
c.broadcast("newCount", c.state.count);
|
|
48
47
|
return c.state.count;
|
|
49
48
|
},
|
|
49
|
+
doubleIncrement: (c, y: number) => {
|
|
50
|
+
c.state.countDouble += y;
|
|
51
|
+
c.broadcast("newDoubleCount", c.state.countDouble);
|
|
52
|
+
return c.state.countDouble;
|
|
53
|
+
},
|
|
50
54
|
reset: (c) => {
|
|
51
55
|
c.state.count = 0;
|
|
56
|
+
c.state.countDouble = 0;
|
|
52
57
|
c.broadcast("newCount", c.state.count);
|
|
58
|
+
c.broadcast("newDoubleCount", c.state.countDouble);
|
|
53
59
|
return c.state.count;
|
|
54
60
|
},
|
|
55
61
|
},
|
|
@@ -58,285 +64,252 @@ export const counter = actor({
|
|
|
58
64
|
export const registry = setup({
|
|
59
65
|
use: { counter },
|
|
60
66
|
});
|
|
67
|
+
|
|
68
|
+
export type Registry = typeof registry;
|
|
61
69
|
```
|
|
62
70
|
|
|
63
|
-
###
|
|
71
|
+
### 2. Set Up the SvelteKit Handler
|
|
72
|
+
|
|
73
|
+
Create a catch-all API route to proxy RivetKit requests through SvelteKit:
|
|
64
74
|
|
|
65
75
|
```typescript
|
|
66
|
-
//
|
|
67
|
-
import {
|
|
76
|
+
// src/routes/api/rivet/[...rest]/+server.ts
|
|
77
|
+
import { createRivetKitHandler } from "@blujosi/rivetkit-svelte/sveltekit";
|
|
78
|
+
import { dev } from "$app/environment";
|
|
79
|
+
import { registry } from "$backend/registry";
|
|
80
|
+
|
|
81
|
+
export const { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } =
|
|
82
|
+
createRivetKitHandler({
|
|
83
|
+
isDev: !!dev,
|
|
84
|
+
registry,
|
|
85
|
+
rivetSiteUrl: "http://localhost:5173",
|
|
86
|
+
});
|
|
87
|
+
```
|
|
68
88
|
|
|
69
|
-
|
|
89
|
+
The handler automatically:
|
|
90
|
+
- Spawns the RivetKit engine in dev mode
|
|
91
|
+
- Configures the serverless runner pool
|
|
92
|
+
- Proxies requests to the registry's built-in handler
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
cors: {
|
|
73
|
-
origin: "http://localhost:5173", // Your Svelte app URL
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
```
|
|
94
|
+
#### Handler Options
|
|
77
95
|
|
|
78
|
-
|
|
96
|
+
| Option | Type | Description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `registry` | `Registry` | Your RivetKit registry instance |
|
|
99
|
+
| `isDev` | `boolean` | Enables auto-engine spawn and runner pool config |
|
|
100
|
+
| `rivetSiteUrl` | `string?` | Base URL for the site. Falls back to `PUBLIC_RIVET_ENDPOINT` env var |
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
### 3. Create the Client
|
|
81
103
|
|
|
82
104
|
```typescript
|
|
83
|
-
// src/lib/actor
|
|
84
|
-
import { createClient,
|
|
85
|
-
import type {
|
|
105
|
+
// src/lib/actor.client.ts
|
|
106
|
+
import { createClient, createRivetKitWithClient } from "@blujosi/rivetkit-svelte";
|
|
107
|
+
import type { Client } from "rivetkit/client";
|
|
108
|
+
import { browser } from "$app/environment";
|
|
109
|
+
import type { Registry } from "$backend/registry";
|
|
110
|
+
|
|
111
|
+
let rivetClient: Client<Registry> | undefined;
|
|
112
|
+
|
|
113
|
+
export const getRivetClient = () => {
|
|
114
|
+
if (!browser) return { useActor: () => {} };
|
|
86
115
|
|
|
87
|
-
const
|
|
88
|
-
|
|
116
|
+
const origin = `${location.origin}/api/rivet`;
|
|
117
|
+
rivetClient = createClient<Registry>(origin);
|
|
118
|
+
const { useActor } = createRivetKitWithClient(rivetClient);
|
|
119
|
+
|
|
120
|
+
return { rivetClient, useActor };
|
|
121
|
+
};
|
|
89
122
|
```
|
|
90
123
|
|
|
91
|
-
|
|
124
|
+
> **Important:** The client must only be created in the browser. The `browser` guard ensures SSR doesn't attempt a connection.
|
|
92
125
|
|
|
93
|
-
|
|
126
|
+
### 4. Use Actors in Svelte Components
|
|
94
127
|
|
|
95
128
|
```svelte
|
|
96
129
|
<!-- src/routes/+page.svelte -->
|
|
97
130
|
<script lang="ts">
|
|
98
|
-
import {
|
|
131
|
+
import { getRivetClient } from "../lib/actor.client";
|
|
132
|
+
import { browser } from "$app/environment";
|
|
99
133
|
|
|
100
134
|
let count = $state(0);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
counter
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
135
|
+
let countDouble = $state(0);
|
|
136
|
+
|
|
137
|
+
const { useActor } = getRivetClient();
|
|
138
|
+
const counterActor = browser
|
|
139
|
+
? useActor({ name: "counter", key: ["test-counter"] })
|
|
140
|
+
: undefined;
|
|
141
|
+
|
|
142
|
+
// Listen for events (call at top-level, NOT inside $effect)
|
|
143
|
+
counterActor?.useEvent("newCount", (x: number) => {
|
|
144
|
+
count = x;
|
|
109
145
|
});
|
|
110
146
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
147
|
+
counterActor?.useEvent("newDoubleCount", (y: number) => {
|
|
148
|
+
countDouble = y;
|
|
149
|
+
});
|
|
114
150
|
|
|
115
|
-
|
|
116
|
-
|
|
151
|
+
// Call actions through the connection
|
|
152
|
+
const increment = async () => {
|
|
153
|
+
await counterActor?.current?.connection?.increment(1);
|
|
154
|
+
};
|
|
155
|
+
const reset = async () => {
|
|
156
|
+
await counterActor?.current?.connection?.reset();
|
|
157
|
+
};
|
|
158
|
+
const doubleCountClick = async () => {
|
|
159
|
+
await counterActor?.current?.connection?.doubleIncrement(2);
|
|
117
160
|
};
|
|
118
|
-
|
|
119
|
-
// Debug the connection status
|
|
120
|
-
$inspect('useActor is connected', counter?.isConnected);
|
|
121
161
|
</script>
|
|
122
162
|
|
|
123
163
|
<div>
|
|
124
164
|
<h1>Counter: {count}</h1>
|
|
125
165
|
<button onclick={increment}>Increment</button>
|
|
126
166
|
<button onclick={reset}>Reset</button>
|
|
167
|
+
|
|
168
|
+
<h1>Counter 2: {countDouble}</h1>
|
|
169
|
+
<button onclick={doubleCountClick}>Double Count</button>
|
|
127
170
|
</div>
|
|
128
171
|
```
|
|
129
172
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
### useActor Hook
|
|
173
|
+
---
|
|
133
174
|
|
|
134
|
-
|
|
175
|
+
## API Reference
|
|
135
176
|
|
|
136
|
-
|
|
137
|
-
- **`handle`** - The actor handle for advanced operations
|
|
138
|
-
- **`isConnected`** - Boolean indicating if the actor is connected
|
|
139
|
-
- **`isConnecting`** - Boolean indicating if the actor is currently connecting
|
|
140
|
-
- **`isError`** - Boolean indicating if there's an error
|
|
141
|
-
- **`error`** - The error object if one exists
|
|
142
|
-
- **`useEvent`** - Function to listen for actor events
|
|
177
|
+
### Client Exports (`@blujosi/rivetkit-svelte`)
|
|
143
178
|
|
|
144
|
-
|
|
179
|
+
#### `createClient<Registry>(url)`
|
|
145
180
|
|
|
146
|
-
|
|
181
|
+
Creates a RivetKit client connection.
|
|
147
182
|
|
|
148
183
|
```typescript
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
key: ['test-counter'], // Unique key for this actor instance
|
|
152
|
-
params: { /* ... */ }, // Optional parameters
|
|
153
|
-
enabled: true // Optional, defaults to true
|
|
154
|
-
});
|
|
184
|
+
import { createClient } from "@blujosi/rivetkit-svelte";
|
|
185
|
+
const client = createClient<Registry>("http://localhost:5173/api/rivet");
|
|
155
186
|
```
|
|
156
187
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
### Using useEvent
|
|
188
|
+
#### `createRivetKit<Registry>(url, opts?)`
|
|
160
189
|
|
|
161
|
-
|
|
190
|
+
Shorthand that creates both the client and the `useActor` hook.
|
|
162
191
|
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const chatActor = useActor({ name: 'chat', key: ['room-1'] });
|
|
168
|
-
|
|
169
|
-
$effect(() => {
|
|
170
|
-
// Listen for new messages
|
|
171
|
-
chatActor?.useEvent('newMessage', (message) => {
|
|
172
|
-
console.log('New message:', message);
|
|
173
|
-
// Update your component state
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Listen for user joined events
|
|
177
|
-
chatActor?.useEvent('userJoined', (user) => {
|
|
178
|
-
console.log('User joined:', user);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
</script>
|
|
192
|
+
```typescript
|
|
193
|
+
import { createRivetKit } from "@blujosi/rivetkit-svelte";
|
|
194
|
+
export const { useActor } = createRivetKit<Registry>("http://localhost:5173/api/rivet");
|
|
182
195
|
```
|
|
183
196
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
You can also listen to events directly on the connection:
|
|
187
|
-
|
|
188
|
-
```svelte
|
|
189
|
-
<script lang="ts">
|
|
190
|
-
const actor = useActor({ name: 'counter', key: ['test'] });
|
|
197
|
+
#### `createRivetKitWithClient<Registry>(client, opts?)`
|
|
191
198
|
|
|
192
|
-
|
|
193
|
-
const unsubscribe = actor.connection?.on('newCount', (count) => {
|
|
194
|
-
console.log('Count updated:', count);
|
|
195
|
-
});
|
|
199
|
+
Creates the `useActor` hook from an existing client instance. Use this when you need access to the client elsewhere.
|
|
196
200
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
```typescript
|
|
202
|
+
import { createClient, createRivetKitWithClient } from "@blujosi/rivetkit-svelte";
|
|
203
|
+
const client = createClient<Registry>(url);
|
|
204
|
+
export const { useActor } = createRivetKitWithClient(client);
|
|
201
205
|
```
|
|
202
206
|
|
|
203
|
-
|
|
207
|
+
### `useActor(options)`
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
Connects to a RivetKit actor and returns reactive state. **Must be called at the top level of a component script** (not inside `onMount` or other callbacks) so runes attach correctly.
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
```typescript
|
|
212
|
+
const actor = useActor({
|
|
213
|
+
name: "counter", // Actor name from your registry
|
|
214
|
+
key: ["test-counter"], // Unique key for this instance
|
|
215
|
+
params: { /* ... */ }, // Optional parameters
|
|
216
|
+
createInRegion: "us-east-1", // Optional region
|
|
217
|
+
createWithInput: { /* */ }, // Optional input data
|
|
218
|
+
enabled: true, // Optional, defaults to true
|
|
219
|
+
});
|
|
220
|
+
```
|
|
208
221
|
|
|
209
|
-
|
|
210
|
-
<script lang="ts">
|
|
211
|
-
let userId = $state<string | null>(null);
|
|
222
|
+
**Returns:**
|
|
212
223
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
| Property | Type | Description |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `current.connection` | `ActorConn` | Call actions on the actor |
|
|
227
|
+
| `current.handle` | `ActorHandle` | Advanced actor operations |
|
|
228
|
+
| `current.isConnected` | `boolean` | Whether the actor is connected |
|
|
229
|
+
| `current.isConnecting` | `boolean` | Whether a connection is in progress |
|
|
230
|
+
| `current.isError` | `boolean` | Whether there's an error |
|
|
231
|
+
| `current.error` | `Error \| null` | The error object, if any |
|
|
232
|
+
| `useEvent(name, handler)` | `function` | Listen for actor events |
|
|
220
233
|
|
|
221
|
-
###
|
|
234
|
+
### `useEvent(eventName, handler)`
|
|
222
235
|
|
|
223
|
-
|
|
236
|
+
Registers an event listener with automatic cleanup. **Call at the top level of the component script, not inside `$effect`.**
|
|
224
237
|
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const privateChat = useActor({ name: 'chat', key: ['private', userId] });
|
|
230
|
-
</script>
|
|
238
|
+
```typescript
|
|
239
|
+
counterActor?.useEvent("newCount", (value: number) => {
|
|
240
|
+
count = value;
|
|
241
|
+
});
|
|
231
242
|
```
|
|
232
243
|
|
|
233
|
-
###
|
|
244
|
+
### SvelteKit Exports (`@blujosi/rivetkit-svelte/sveltekit`)
|
|
234
245
|
|
|
235
|
-
|
|
246
|
+
#### `createRivetKitHandler(opts)`
|
|
236
247
|
|
|
237
|
-
|
|
238
|
-
<script lang="ts">
|
|
239
|
-
const actor = useActor({ name: 'counter', key: ['test'] });
|
|
248
|
+
Creates SvelteKit request handlers for all HTTP methods.
|
|
240
249
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
console.error('Actor connection error:', actor.error);
|
|
244
|
-
// Show error message to user
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
</script>
|
|
250
|
+
```typescript
|
|
251
|
+
import { createRivetKitHandler } from "@blujosi/rivetkit-svelte/sveltekit";
|
|
248
252
|
|
|
249
|
-
{
|
|
250
|
-
|
|
251
|
-
Connection failed: {actor.error?.message}
|
|
252
|
-
</div>
|
|
253
|
-
{:else if actor?.isConnecting}
|
|
254
|
-
<div class="loading">Connecting...</div>
|
|
255
|
-
{:else if actor?.isConnected}
|
|
256
|
-
<div class="success">Connected!</div>
|
|
257
|
-
{/if}
|
|
253
|
+
export const { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } =
|
|
254
|
+
createRivetKitHandler({ isDev: true, registry, rivetSiteUrl: "http://localhost:5173" });
|
|
258
255
|
```
|
|
259
256
|
|
|
260
|
-
|
|
257
|
+
---
|
|
261
258
|
|
|
262
|
-
|
|
259
|
+
## Common Pitfalls
|
|
263
260
|
|
|
264
|
-
|
|
261
|
+
### Don't call `useActor` inside `onMount`
|
|
262
|
+
|
|
263
|
+
`useActor` uses `$effect` runes internally. Runes must be initialized during synchronous component setup, not in deferred callbacks.
|
|
265
264
|
|
|
266
265
|
```typescript
|
|
267
|
-
|
|
266
|
+
// BAD
|
|
267
|
+
onMount(() => {
|
|
268
|
+
const actor = useActor({ name: "counter", key: ["test"] });
|
|
269
|
+
});
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
// GOOD
|
|
272
|
+
const actor = browser
|
|
273
|
+
? useActor({ name: "counter", key: ["test"] })
|
|
274
|
+
: undefined;
|
|
270
275
|
```
|
|
271
276
|
|
|
272
|
-
###
|
|
277
|
+
### Don't call `useEvent` inside `$effect`
|
|
273
278
|
|
|
274
|
-
|
|
279
|
+
`useEvent` manages its own internal effects. Wrapping it in another `$effect` causes duplicate listeners and broken state.
|
|
275
280
|
|
|
276
281
|
```typescript
|
|
277
|
-
|
|
282
|
+
// BAD
|
|
283
|
+
$effect(() => {
|
|
284
|
+
actor?.useEvent("newCount", (x) => { count = x; });
|
|
285
|
+
});
|
|
278
286
|
|
|
279
|
-
|
|
287
|
+
// GOOD
|
|
288
|
+
actor?.useEvent("newCount", (x) => { count = x; });
|
|
280
289
|
```
|
|
281
290
|
|
|
282
|
-
###
|
|
283
|
-
|
|
284
|
-
Connects to a RivetKit actor and returns reactive state.
|
|
285
|
-
|
|
286
|
-
**Parameters:**
|
|
287
|
-
- `name: string` - The actor name from your registry
|
|
288
|
-
- `key: string | string[]` - Unique identifier for the actor instance
|
|
289
|
-
- `params?: Record<string, string>` - Optional parameters to pass to the actor
|
|
290
|
-
- `enabled?: boolean` - Whether the connection is enabled (default: true)
|
|
291
|
-
|
|
292
|
-
**Returns:**
|
|
293
|
-
- `connection` - Actor connection for calling actions
|
|
294
|
-
- `handle` - Actor handle for advanced operations
|
|
295
|
-
- `isConnected` - Connection status
|
|
296
|
-
- `isConnecting` - Loading state
|
|
297
|
-
- `isError` - Error state
|
|
298
|
-
- `error` - Error object
|
|
299
|
-
- `useEvent` - Function to listen for events
|
|
300
|
-
|
|
301
|
-
## TypeScript Support
|
|
291
|
+
### Don't call `.connect()` on the connection
|
|
302
292
|
|
|
303
|
-
|
|
293
|
+
The connection is automatically managed by `useActor`. Calling `.connect()` sends an RPC action named "connect" to your actor, which doesn't exist.
|
|
304
294
|
|
|
305
295
|
```typescript
|
|
306
|
-
//
|
|
307
|
-
|
|
296
|
+
// BAD
|
|
297
|
+
await actor?.current?.connection?.connect();
|
|
308
298
|
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
const client = createClient<Registry>("http://localhost:8080");
|
|
299
|
+
// GOOD — just call actions directly
|
|
300
|
+
await actor?.current?.connection?.increment(1);
|
|
312
301
|
```
|
|
313
302
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
RivetKit Svelte works seamlessly with SvelteKit. The library automatically detects browser environment and handles SSR appropriately.
|
|
317
|
-
|
|
318
|
-
```svelte
|
|
319
|
-
<!-- +layout.svelte -->
|
|
320
|
-
<script lang="ts">
|
|
321
|
-
import { useActor } from "$lib/actor-client";
|
|
322
|
-
|
|
323
|
-
// This will only connect in the browser
|
|
324
|
-
const globalActor = useActor({ name: 'global', key: ['app'] });
|
|
325
|
-
</script>
|
|
326
|
-
```
|
|
303
|
+
---
|
|
327
304
|
|
|
328
|
-
##
|
|
305
|
+
## TODO
|
|
329
306
|
|
|
330
|
-
|
|
331
|
-
- Backend RivetKit server setup
|
|
332
|
-
- Frontend Svelte integration
|
|
333
|
-
- Real-time counter with events
|
|
334
|
-
- TypeScript configuration
|
|
307
|
+
- [ ] Figure out using `useActor` and `rivetClient` in SSR mode, maybe using context?
|
|
335
308
|
|
|
336
|
-
##
|
|
309
|
+
## License
|
|
337
310
|
|
|
338
|
-
|
|
311
|
+
MIT
|
|
339
312
|
|
|
340
|
-
|
|
313
|
+
---
|
|
341
314
|
|
|
342
|
-
|
|
315
|
+
Inspired by the Rivet core implementation for React and Next.js.
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './svelte';
|
|
2
|
+
export * from './sveltekit';
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './svelte';
|
|
2
|
+
export * from './sveltekit';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./rivet.svelte.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./rivet.svelte.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ActorConn, ActorHandle, AnyActorDefinition, Client, ExtractActorsFromRegistry } from "@rivetkit/core/client";
|
|
2
1
|
import { type ActorOptions, type AnyActorRegistry, type CreateRivetKitOptions } from "@rivetkit/framework-base";
|
|
2
|
+
import type { ActorConn, ActorHandle, AnyActorDefinition, Client, ExtractActorsFromRegistry } from "rivetkit/client";
|
|
3
3
|
export interface ActorStateReference<AD extends AnyActorDefinition> {
|
|
4
4
|
/**
|
|
5
5
|
* The unique identifier for the actor.
|
|
@@ -61,12 +61,36 @@ export interface ActorStateReference<AD extends AnyActorDefinition> {
|
|
|
61
61
|
enabled?: boolean;
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
export
|
|
64
|
+
import { createClient } from "rivetkit/client";
|
|
65
|
+
export { createClient } from "rivetkit/client";
|
|
66
|
+
export declare function createRivetKit<Registry extends AnyActorRegistry>(clientInput?: Parameters<typeof createClient>[0], opts?: CreateRivetKitOptions<Registry>): {
|
|
66
67
|
useActor: <ActorName extends keyof ExtractActorsFromRegistry<Registry>>(opts: ActorOptions<Registry, ActorName>) => {
|
|
67
|
-
current:
|
|
68
|
-
|
|
69
|
-
connection:
|
|
68
|
+
current: {
|
|
69
|
+
connect(): void;
|
|
70
|
+
readonly connection: any;
|
|
71
|
+
readonly handle: any;
|
|
72
|
+
readonly isConnected: boolean;
|
|
73
|
+
readonly isConnecting: boolean;
|
|
74
|
+
readonly isError: boolean;
|
|
75
|
+
readonly error: any;
|
|
76
|
+
readonly opts: any;
|
|
77
|
+
readonly hash: any;
|
|
78
|
+
};
|
|
79
|
+
useEvent: (eventName: string, handler: (...args: any[]) => void) => void;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export declare function createRivetKitWithClient<Registry extends AnyActorRegistry>(client: Client<Registry>, opts?: CreateRivetKitOptions<Registry>): {
|
|
83
|
+
useActor: <ActorName extends keyof ExtractActorsFromRegistry<Registry>>(opts: ActorOptions<Registry, ActorName>) => {
|
|
84
|
+
current: {
|
|
85
|
+
connect(): void;
|
|
86
|
+
readonly connection: any;
|
|
87
|
+
readonly handle: any;
|
|
88
|
+
readonly isConnected: boolean;
|
|
89
|
+
readonly isConnecting: boolean;
|
|
90
|
+
readonly isError: boolean;
|
|
91
|
+
readonly error: any;
|
|
92
|
+
readonly opts: any;
|
|
93
|
+
readonly hash: any;
|
|
70
94
|
};
|
|
71
95
|
useEvent: (eventName: string, handler: (...args: any[]) => void) => void;
|
|
72
96
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createRivetKit as createVanillaRivetKit, } from "@rivetkit/framework-base";
|
|
2
|
-
import {
|
|
3
|
-
export { createClient } from "
|
|
4
|
-
export function createRivetKit(
|
|
2
|
+
import { createClient } from "rivetkit/client";
|
|
3
|
+
export { createClient } from "rivetkit/client";
|
|
4
|
+
export function createRivetKit(clientInput = undefined, opts = {}) {
|
|
5
|
+
return createRivetKitWithClient(createClient(clientInput), opts);
|
|
6
|
+
}
|
|
7
|
+
export function createRivetKitWithClient(client, opts = {}) {
|
|
5
8
|
const { getOrCreateActor } = createVanillaRivetKit(client, opts);
|
|
6
9
|
/**
|
|
7
10
|
* Svelte 5 rune-based function to connect to an actor and retrieve its state.
|
|
@@ -12,63 +15,73 @@ export function createRivetKit(client, opts = {}) {
|
|
|
12
15
|
* @returns An object containing reactive state and event listener function.
|
|
13
16
|
*/
|
|
14
17
|
function useActor(opts) {
|
|
15
|
-
const { mount,
|
|
18
|
+
const { mount, state } = getOrCreateActor(opts);
|
|
16
19
|
// Update options reactively
|
|
17
|
-
$effect.root(() => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
20
|
+
// $effect.root(() => {
|
|
21
|
+
// setState((prev) => {
|
|
22
|
+
// prev.opts = {
|
|
23
|
+
// ...opts,
|
|
24
|
+
// enabled: opts.enabled ?? true,
|
|
25
|
+
// } as any
|
|
26
|
+
// return prev
|
|
27
|
+
// })s
|
|
28
|
+
// })
|
|
26
29
|
// Mount and subscribe to state changes
|
|
27
30
|
$effect.root(() => {
|
|
28
31
|
mount();
|
|
29
32
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
let actorState = $state(undefined);
|
|
34
|
+
const unsubscribe = state?.subscribe((res) => {
|
|
35
|
+
actorState = res.currentVal;
|
|
36
|
+
});
|
|
37
|
+
$effect(() => {
|
|
38
|
+
return () => unsubscribe?.();
|
|
39
|
+
});
|
|
40
|
+
function useEvent(eventName, handler) {
|
|
34
41
|
let ref = $state(handler);
|
|
35
|
-
|
|
42
|
+
let actorState = $state(undefined);
|
|
43
|
+
state.subscribe((s) => {
|
|
44
|
+
actorState = s.currentVal;
|
|
45
|
+
});
|
|
36
46
|
$effect(() => {
|
|
37
47
|
ref = handler;
|
|
38
48
|
});
|
|
39
49
|
$effect(() => {
|
|
40
|
-
if (!actorState?.
|
|
50
|
+
if (!actorState?.connection)
|
|
41
51
|
return;
|
|
42
52
|
function eventHandler(...args) {
|
|
43
53
|
ref(...args);
|
|
44
54
|
}
|
|
45
|
-
return actorState.
|
|
55
|
+
return actorState.connection?.on(eventName, eventHandler);
|
|
46
56
|
});
|
|
47
57
|
}
|
|
48
58
|
const current = {
|
|
59
|
+
connect() {
|
|
60
|
+
actorState?.connection?.connect();
|
|
61
|
+
},
|
|
49
62
|
get connection() {
|
|
50
|
-
return actorState
|
|
63
|
+
return actorState?.connection;
|
|
51
64
|
},
|
|
52
65
|
get handle() {
|
|
53
|
-
return actorState
|
|
66
|
+
return actorState?.handle;
|
|
54
67
|
},
|
|
55
68
|
get isConnected() {
|
|
56
|
-
return actorState
|
|
69
|
+
return actorState?.connStatus === "connected";
|
|
57
70
|
},
|
|
58
71
|
get isConnecting() {
|
|
59
|
-
return actorState
|
|
72
|
+
return actorState?.connStatus === "connecting";
|
|
60
73
|
},
|
|
61
74
|
get isError() {
|
|
62
|
-
return actorState
|
|
75
|
+
return !!actorState?.error;
|
|
63
76
|
},
|
|
64
77
|
get error() {
|
|
65
|
-
return actorState
|
|
78
|
+
return actorState?.error;
|
|
66
79
|
},
|
|
67
80
|
get opts() {
|
|
68
|
-
return actorState
|
|
81
|
+
return actorState?.opts;
|
|
69
82
|
},
|
|
70
83
|
get hash() {
|
|
71
|
-
return actorState
|
|
84
|
+
return actorState?.hash;
|
|
72
85
|
},
|
|
73
86
|
};
|
|
74
87
|
return {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import type { Registry } from "rivetkit";
|
|
3
|
+
export declare const createRivetKitHandler: (opts?: {
|
|
4
|
+
registry: Registry<any>;
|
|
5
|
+
isDev: boolean;
|
|
6
|
+
rivetSiteUrl?: string;
|
|
7
|
+
}) => {
|
|
8
|
+
GET: RequestHandler;
|
|
9
|
+
POST: RequestHandler;
|
|
10
|
+
PUT: RequestHandler;
|
|
11
|
+
DELETE: RequestHandler;
|
|
12
|
+
PATCH: RequestHandler;
|
|
13
|
+
HEAD: RequestHandler;
|
|
14
|
+
OPTIONS: RequestHandler;
|
|
15
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getLogger } from "rivetkit/log";
|
|
2
|
+
import { PUBLIC_RIVET_ENDPOINT } from "$env/static/public";
|
|
3
|
+
const _devRunnerVersion = Math.floor(Date.now() / 1000);
|
|
4
|
+
const _logger = getLogger("driver-sveltekit");
|
|
5
|
+
const handler = async (request, opts) => {
|
|
6
|
+
const _requestUrl = new URL(request.url);
|
|
7
|
+
const rivetSiteUrl = opts?.rivetSiteUrl ?? PUBLIC_RIVET_ENDPOINT;
|
|
8
|
+
if (!rivetSiteUrl) {
|
|
9
|
+
throw new Error('PUBLIC_RIVET_ENDPOINT environment variable is not set');
|
|
10
|
+
}
|
|
11
|
+
const registry = opts?.registry;
|
|
12
|
+
if (!registry) {
|
|
13
|
+
throw new Error("registry is not set");
|
|
14
|
+
}
|
|
15
|
+
registry.config.serveManager = false;
|
|
16
|
+
registry.config.serverless = {
|
|
17
|
+
...registry.config.serverless,
|
|
18
|
+
basePath: "/api/rivet",
|
|
19
|
+
};
|
|
20
|
+
if (opts?.isDev) {
|
|
21
|
+
_logger.debug("detected development environment, auto-starting engine and auto-configuring serverless");
|
|
22
|
+
// Set these on the registry's config directly since the legacy inputConfig
|
|
23
|
+
// isn't used by the serverless router
|
|
24
|
+
registry.config.serverless.spawnEngine = true;
|
|
25
|
+
registry.config.serverless.configureRunnerPool = {
|
|
26
|
+
url: `${rivetSiteUrl}/api/rivet`,
|
|
27
|
+
minRunners: 0,
|
|
28
|
+
maxRunners: 100_000,
|
|
29
|
+
requestLifespan: 300,
|
|
30
|
+
slotsPerRunner: 1,
|
|
31
|
+
metadata: { provider: "sveltekit" },
|
|
32
|
+
};
|
|
33
|
+
// Set runner version to enable hot-reloading on code changes
|
|
34
|
+
registry.config.runner = {
|
|
35
|
+
...registry.config.runner,
|
|
36
|
+
version: _devRunnerVersion,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
_logger.debug("detected production environment, will not auto-start engine and auto-configure serverless");
|
|
41
|
+
}
|
|
42
|
+
const newUrl = `${rivetSiteUrl}${_requestUrl.pathname}`;
|
|
43
|
+
const newRequest = new Request(newUrl, request);
|
|
44
|
+
newRequest.headers.set("host", new URL(newUrl).host);
|
|
45
|
+
newRequest.headers.set("accept-encoding", "application/json");
|
|
46
|
+
return await registry.handler(newRequest);
|
|
47
|
+
// return fetch(newRequest, { method: request.method, redirect: "manual" })
|
|
48
|
+
};
|
|
49
|
+
export const createRivetKitHandler = (opts) => {
|
|
50
|
+
const requestHandler = async ({ request }) => {
|
|
51
|
+
return handler(request, opts);
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
GET: requestHandler,
|
|
55
|
+
POST: requestHandler,
|
|
56
|
+
PUT: requestHandler,
|
|
57
|
+
DELETE: requestHandler,
|
|
58
|
+
PATCH: requestHandler,
|
|
59
|
+
HEAD: requestHandler,
|
|
60
|
+
OPTIONS: requestHandler,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './handler.server';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './handler.server';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blujosi/rivetkit-svelte",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "vite build && npm run prepack",
|
|
6
6
|
"preview": "vite preview",
|
|
@@ -17,37 +17,45 @@
|
|
|
17
17
|
"sideEffects": [
|
|
18
18
|
"**/*.css"
|
|
19
19
|
],
|
|
20
|
-
"svelte": "./dist/index.js",
|
|
21
|
-
"types": "./dist/index.d.ts",
|
|
20
|
+
"svelte": "./dist/svelte/index.js",
|
|
21
|
+
"types": "./dist/svelte/index.d.ts",
|
|
22
22
|
"type": "module",
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
|
-
"types": "./dist/index.d.ts",
|
|
26
|
-
"svelte": "./dist/index.js"
|
|
25
|
+
"types": "./dist/svelte/index.d.ts",
|
|
26
|
+
"svelte": "./dist/svelte/index.js",
|
|
27
|
+
"default": "./dist/svelte/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./svelte": {
|
|
30
|
+
"types": "./dist/svelte/index.d.ts",
|
|
31
|
+
"svelte": "./dist/svelte/index.js",
|
|
32
|
+
"default": "./dist/svelte/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./sveltekit": {
|
|
35
|
+
"types": "./dist/sveltekit/index.d.ts",
|
|
36
|
+
"svelte": "./dist/sveltekit/index.js"
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
39
|
"peerDependencies": {
|
|
30
40
|
"svelte": "^5.0.0"
|
|
31
41
|
},
|
|
32
42
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@sveltejs/
|
|
35
|
-
"@sveltejs/
|
|
36
|
-
"@sveltejs/
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"svelte
|
|
40
|
-
"
|
|
41
|
-
"
|
|
43
|
+
"@rivetkit/framework-base": "^2.1.3",
|
|
44
|
+
"@sveltejs/adapter-auto": "^7.0.1",
|
|
45
|
+
"@sveltejs/kit": "^2.53.4",
|
|
46
|
+
"@sveltejs/package": "^2.5.7",
|
|
47
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
48
|
+
"publint": "^0.3.18",
|
|
49
|
+
"svelte": "^5.53.6",
|
|
50
|
+
"svelte-check": "^4.4.4",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vite": "^7.3.1"
|
|
42
53
|
},
|
|
43
54
|
"keywords": [
|
|
44
55
|
"svelte"
|
|
45
56
|
],
|
|
46
57
|
"dependencies": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"@rivetkit/framework-base": "^2.0.2",
|
|
50
|
-
"@tanstack/svelte-store": "^0.7.4",
|
|
51
|
-
"esm-env": "^1.2.2"
|
|
58
|
+
"esm-env": "^1.2.2",
|
|
59
|
+
"rivetkit": "^2.1.3"
|
|
52
60
|
}
|
|
53
|
-
}
|
|
61
|
+
}
|