@blujosi/rivetkit-svelte 2.2.1 → 2.3.2

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 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
+ }
@@ -1 +1,2 @@
1
+ export * from "./crud-transforms";
1
2
  export * from "./rivet.svelte.js";
@@ -1 +1,2 @@
1
+ export * from "./crud-transforms";
1
2
  export * from "./rivet.svelte.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blujosi/rivetkit-svelte",
3
- "version": "2.2.1",
3
+ "version": "2.3.2",
4
4
  "scripts": {
5
5
  "build": "vite build && npm run prepack",
6
6
  "preview": "vite preview",