@blujosi/rivetkit-svelte 2.2.1 → 2.3.4
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 +134 -0
- package/dist/svelte/crud-transforms.d.ts +81 -0
- package/dist/svelte/crud-transforms.js +127 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ npm i @blujosi/rivetkit-svelte rivetkit
|
|
|
24
24
|
- **Svelte 5 Runes** — Built for `$state`, `$effect`, and `$derived`
|
|
25
25
|
- **Real-time Actor Connections** — Connect to RivetKit actors with automatic state sync
|
|
26
26
|
- **Event Handling** — `useEvent` with automatic cleanup
|
|
27
|
+
- **CRUD Transforms** — Pre-built transform factories for managing lists via create/update/delete events
|
|
27
28
|
- **Type Safety** — Full TypeScript support with registry type inference
|
|
28
29
|
- **SSR Compatible** — Browser guard for SvelteKit SSR
|
|
29
30
|
- **SvelteKit Handler** — Run RivetKit serverless inside your SvelteKit app
|
|
@@ -470,6 +471,139 @@ count?.refetch();
|
|
|
470
471
|
| **Best for** | Most use-cases | High-frequency events where refetch would be wasteful |
|
|
471
472
|
| **Refetch** | `.refetch()` available | Not available |
|
|
472
473
|
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
### CRUD Transforms (`@blujosi/rivetkit-svelte`)
|
|
477
|
+
|
|
478
|
+
Pre-built `transform` factories for managing lists of items via create/update/delete events. Use with `useQuery` or `rivetLoad`.
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import {
|
|
482
|
+
crudTransform,
|
|
483
|
+
createTransform,
|
|
484
|
+
updateTransform,
|
|
485
|
+
deleteTransform,
|
|
486
|
+
} from "@blujosi/rivetkit-svelte";
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### `crudTransform<T>(opts?)`
|
|
490
|
+
|
|
491
|
+
A unified transform that handles all three CRUD operations in a single function. The actor broadcasts events wrapped in a `CrudEvent<T>`:
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
// In the actor:
|
|
495
|
+
c.broadcast("taskChanged", { type: "created", data: newTask });
|
|
496
|
+
c.broadcast("taskChanged", { type: "updated", data: updatedTask });
|
|
497
|
+
c.broadcast("taskChanged", { type: "deleted", data: deletedTask });
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
```svelte
|
|
501
|
+
<script lang="ts">
|
|
502
|
+
import { crudTransform } from "@blujosi/rivetkit-svelte";
|
|
503
|
+
|
|
504
|
+
interface Task { id: string; title: string; done: boolean }
|
|
505
|
+
|
|
506
|
+
// With useQuery (client-side)
|
|
507
|
+
const tasks = actor?.useQuery({
|
|
508
|
+
action: "getTasks",
|
|
509
|
+
event: "taskChanged",
|
|
510
|
+
initialValue: [] as Task[],
|
|
511
|
+
transform: crudTransform<Task>({ key: "id" }),
|
|
512
|
+
});
|
|
513
|
+
</script>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// With rivetLoad (SSR + live)
|
|
518
|
+
const tasks = await rivetLoad(client, {
|
|
519
|
+
actor: "taskList",
|
|
520
|
+
key: ["my-list"],
|
|
521
|
+
action: "getTasks",
|
|
522
|
+
event: "taskChanged",
|
|
523
|
+
transform: crudTransform<Task>({ key: "id" }),
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
If the incoming payload is **not** wrapped in `{ type, data }`, `crudTransform` falls back to an **upsert** — it updates the item if it exists, or appends it otherwise.
|
|
528
|
+
|
|
529
|
+
**Options (`CrudTransformOptions<T>`):**
|
|
530
|
+
|
|
531
|
+
| Option | Type | Default | Description |
|
|
532
|
+
|---|---|---|---|
|
|
533
|
+
| `key` | `keyof T \| (item: T) => unknown` | `"id"` | Property name or accessor used to uniquely identify items |
|
|
534
|
+
|
|
535
|
+
**`CrudEvent<T>` shape:**
|
|
536
|
+
|
|
537
|
+
| Field | Type | Description |
|
|
538
|
+
|---|---|---|
|
|
539
|
+
| `type` | `"created" \| "updated" \| "deleted"` | The operation type |
|
|
540
|
+
| `data` | `T` | The item (or key value for deletes) |
|
|
541
|
+
|
|
542
|
+
#### Individual Transforms
|
|
543
|
+
|
|
544
|
+
Use these when you have **separate events** for each operation:
|
|
545
|
+
|
|
546
|
+
##### `createTransform<T>(opts?)`
|
|
547
|
+
|
|
548
|
+
Appends the incoming item to the list. Duplicates (same key) are ignored.
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const tasks = actor?.useQuery({
|
|
552
|
+
action: "getTasks",
|
|
553
|
+
event: "taskCreated",
|
|
554
|
+
initialValue: [],
|
|
555
|
+
transform: createTransform<Task>({ key: "id" }),
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
##### `updateTransform<T>(opts?)`
|
|
560
|
+
|
|
561
|
+
Replaces the matching item in-place. If no match is found, the list is returned unchanged.
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
const tasks = actor?.useQuery({
|
|
565
|
+
action: "getTasks",
|
|
566
|
+
event: "taskUpdated",
|
|
567
|
+
initialValue: [],
|
|
568
|
+
transform: updateTransform<Task>({ key: "id" }),
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
##### `deleteTransform<T>(opts?)`
|
|
573
|
+
|
|
574
|
+
Removes the matching item. Accepts either a full object or a raw key value.
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
const tasks = actor?.useQuery({
|
|
578
|
+
action: "getTasks",
|
|
579
|
+
event: "taskDeleted",
|
|
580
|
+
initialValue: [],
|
|
581
|
+
transform: deleteTransform<Task>({ key: "id" }),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// The actor can broadcast just the key:
|
|
585
|
+
c.broadcast("taskDeleted", taskId);
|
|
586
|
+
// Or the full object:
|
|
587
|
+
c.broadcast("taskDeleted", task);
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### Custom Key Functions
|
|
591
|
+
|
|
592
|
+
For items keyed by something other than a single property:
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const items = actor?.useQuery({
|
|
596
|
+
action: "getItems",
|
|
597
|
+
event: "itemChanged",
|
|
598
|
+
initialValue: [],
|
|
599
|
+
transform: crudTransform<Item>({
|
|
600
|
+
key: (item) => `${item.type}:${item.slug}`,
|
|
601
|
+
}),
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
473
607
|
### SvelteKit Exports (`@blujosi/rivetkit-svelte/sveltekit`)
|
|
474
608
|
|
|
475
609
|
#### `createRivetKitHandler(opts)`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic CRUD transform factories for use with `useQuery` and `rivetLoad`.
|
|
3
|
+
*
|
|
4
|
+
* These produce `transform` functions that handle incoming create/update/delete
|
|
5
|
+
* events against a list of items, keyed by an identifier field.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const tasks = await rivetLoad(client, {
|
|
10
|
+
* actor: "taskList",
|
|
11
|
+
* key: ["my-list"],
|
|
12
|
+
* action: "getTasks",
|
|
13
|
+
* event: "taskChanged",
|
|
14
|
+
* transform: crudTransform<Task>({ key: "id" }),
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* An incoming event payload that carries a CRUD operation type.
|
|
20
|
+
*
|
|
21
|
+
* Actors should broadcast events in this shape:
|
|
22
|
+
* ```ts
|
|
23
|
+
* c.broadcast("todoListUpdate", { data: todo, type: "created" })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface CrudEvent<T> {
|
|
27
|
+
data: T;
|
|
28
|
+
type: "created" | "updated" | "deleted";
|
|
29
|
+
}
|
|
30
|
+
/** Options shared by all CRUD transform factories. */
|
|
31
|
+
export interface CrudTransformOptions<T> {
|
|
32
|
+
/**
|
|
33
|
+
* Property name (or accessor) used to uniquely identify items.
|
|
34
|
+
* Defaults to `"id"`.
|
|
35
|
+
*/
|
|
36
|
+
key?: keyof T | ((item: T) => unknown);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Transform for a **create** event.
|
|
40
|
+
* - **Array:** appends the incoming item; duplicates (same key) are ignored.
|
|
41
|
+
* - **Single item:** replaces the current value with the incoming item.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createTransform<T>(opts?: CrudTransformOptions<T>): <C extends T[] | T>(current: C, incoming: unknown) => C;
|
|
44
|
+
/**
|
|
45
|
+
* Transform for an **update** event.
|
|
46
|
+
* - **Array:** replaces the matching item in-place; returns unchanged if no match.
|
|
47
|
+
* - **Single item:** replaces the current value with the incoming item.
|
|
48
|
+
*/
|
|
49
|
+
export declare function updateTransform<T>(opts?: CrudTransformOptions<T>): <C extends T[] | T>(current: C, incoming: unknown) => C;
|
|
50
|
+
/**
|
|
51
|
+
* Transform for a **delete** event.
|
|
52
|
+
* - **Array:** removes the matching item. `incoming` can be the full item or just the key value.
|
|
53
|
+
* - **Single item:** returns the current value unchanged (cannot delete a scalar).
|
|
54
|
+
*/
|
|
55
|
+
export declare function deleteTransform<T>(opts?: CrudTransformOptions<T>): <C extends T[] | T>(current: C, incoming: unknown) => C;
|
|
56
|
+
/**
|
|
57
|
+
* A single transform that handles create, update, and delete events.
|
|
58
|
+
*
|
|
59
|
+
* Incoming payloads must be a `CrudEvent<T>` with `{ data, type }`:
|
|
60
|
+
* ```ts
|
|
61
|
+
* { data: item, type: "created" }
|
|
62
|
+
* { data: item, type: "updated" }
|
|
63
|
+
* { data: item, type: "deleted" }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Actors should broadcast in this shape:
|
|
67
|
+
* ```ts
|
|
68
|
+
* c.broadcast("todoListUpdate", { data: todo, type: "created" })
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const users = todoActor?.useQuery({
|
|
74
|
+
* action: "getUsers",
|
|
75
|
+
* event: "userListUpdate",
|
|
76
|
+
* initialValue: [],
|
|
77
|
+
* transform: crudTransform<User>({ key: "id" }),
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function crudTransform<T>(opts?: CrudTransformOptions<T>): <C extends T[] | T>(current: C, incoming: CrudEvent<T>) => C;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic CRUD transform factories for use with `useQuery` and `rivetLoad`.
|
|
3
|
+
*
|
|
4
|
+
* These produce `transform` functions that handle incoming create/update/delete
|
|
5
|
+
* events against a list of items, keyed by an identifier field.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const tasks = await rivetLoad(client, {
|
|
10
|
+
* actor: "taskList",
|
|
11
|
+
* key: ["my-list"],
|
|
12
|
+
* action: "getTasks",
|
|
13
|
+
* event: "taskChanged",
|
|
14
|
+
* transform: crudTransform<Task>({ key: "id" }),
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function getKey(item, key) {
|
|
22
|
+
return typeof key === "function" ? key(item) : item[key];
|
|
23
|
+
}
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Individual transforms
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Transform for a **create** event.
|
|
29
|
+
* - **Array:** appends the incoming item; duplicates (same key) are ignored.
|
|
30
|
+
* - **Single item:** replaces the current value with the incoming item.
|
|
31
|
+
*/
|
|
32
|
+
export function createTransform(opts = {}) {
|
|
33
|
+
const keyProp = opts.key ?? "id";
|
|
34
|
+
return ((current, incoming) => {
|
|
35
|
+
const item = incoming;
|
|
36
|
+
if (Array.isArray(current)) {
|
|
37
|
+
const id = getKey(item, keyProp);
|
|
38
|
+
if (current.some((c) => getKey(c, keyProp) === id))
|
|
39
|
+
return current;
|
|
40
|
+
return [...current, item];
|
|
41
|
+
}
|
|
42
|
+
return item;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Transform for an **update** event.
|
|
47
|
+
* - **Array:** replaces the matching item in-place; returns unchanged if no match.
|
|
48
|
+
* - **Single item:** replaces the current value with the incoming item.
|
|
49
|
+
*/
|
|
50
|
+
export function updateTransform(opts = {}) {
|
|
51
|
+
const keyProp = opts.key ?? "id";
|
|
52
|
+
return ((current, incoming) => {
|
|
53
|
+
const item = incoming;
|
|
54
|
+
if (Array.isArray(current)) {
|
|
55
|
+
const id = getKey(item, keyProp);
|
|
56
|
+
const idx = current.findIndex((c) => getKey(c, keyProp) === id);
|
|
57
|
+
if (idx === -1)
|
|
58
|
+
return current;
|
|
59
|
+
const next = [...current];
|
|
60
|
+
next[idx] = item;
|
|
61
|
+
return next;
|
|
62
|
+
}
|
|
63
|
+
return item;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Transform for a **delete** event.
|
|
68
|
+
* - **Array:** removes the matching item. `incoming` can be the full item or just the key value.
|
|
69
|
+
* - **Single item:** returns the current value unchanged (cannot delete a scalar).
|
|
70
|
+
*/
|
|
71
|
+
export function deleteTransform(opts = {}) {
|
|
72
|
+
const keyProp = opts.key ?? "id";
|
|
73
|
+
return ((current, incoming) => {
|
|
74
|
+
if (Array.isArray(current)) {
|
|
75
|
+
const id = typeof incoming === "object" && incoming !== null
|
|
76
|
+
? getKey(incoming, keyProp)
|
|
77
|
+
: incoming;
|
|
78
|
+
return current.filter((c) => getKey(c, keyProp) !== id);
|
|
79
|
+
}
|
|
80
|
+
return current;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Unified CRUD transform
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* A single transform that handles create, update, and delete events.
|
|
88
|
+
*
|
|
89
|
+
* Incoming payloads must be a `CrudEvent<T>` with `{ data, type }`:
|
|
90
|
+
* ```ts
|
|
91
|
+
* { data: item, type: "created" }
|
|
92
|
+
* { data: item, type: "updated" }
|
|
93
|
+
* { data: item, type: "deleted" }
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* Actors should broadcast in this shape:
|
|
97
|
+
* ```ts
|
|
98
|
+
* c.broadcast("todoListUpdate", { data: todo, type: "created" })
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* const users = todoActor?.useQuery({
|
|
104
|
+
* action: "getUsers",
|
|
105
|
+
* event: "userListUpdate",
|
|
106
|
+
* initialValue: [],
|
|
107
|
+
* transform: crudTransform<User>({ key: "id" }),
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function crudTransform(opts = {}) {
|
|
112
|
+
const create = createTransform(opts);
|
|
113
|
+
const update = updateTransform(opts);
|
|
114
|
+
const del = deleteTransform(opts);
|
|
115
|
+
return ((current, incoming) => {
|
|
116
|
+
switch (incoming.type) {
|
|
117
|
+
case "created":
|
|
118
|
+
return create(current, incoming.data);
|
|
119
|
+
case "updated":
|
|
120
|
+
return update(current, incoming.data);
|
|
121
|
+
case "deleted":
|
|
122
|
+
return del(current, incoming.data);
|
|
123
|
+
default:
|
|
124
|
+
return current;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
package/dist/svelte/index.d.ts
CHANGED
package/dist/svelte/index.js
CHANGED