@sveltebase/sync 1.0.5 → 1.0.7
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 +21 -76
- package/dist/client/index.d.ts +2 -8
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +17 -77
- package/dist/client/live.svelte.d.ts.map +1 -1
- package/dist/client/live.svelte.js +17 -18
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/server/broker.d.ts.map +1 -1
- package/dist/server/broker.js +1 -12
- package/dist/server/handler.d.ts +9 -0
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/handler.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -484,92 +484,37 @@ This approach keeps WebSocket URLs clean of private IDs and ensures all active s
|
|
|
484
484
|
|
|
485
485
|
---
|
|
486
486
|
|
|
487
|
-
## 5.
|
|
487
|
+
## 5. Type-Safe Backend Event Publishing (`createPublisher`)
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
When publishing backend events (e.g. from standard API routes, message queues, or cron triggers) to push updates to connected clients, you can create a type-safe publisher matched to your application's database schema. This checks channels (including dynamic channel patterns like `"todos:user_123"`), actions, and payloads at compile-time:
|
|
490
490
|
|
|
491
|
-
### A. Reactive Connection Status
|
|
492
|
-
You can track the WebSocket's connection state in Svelte 5 reactively using `sync.status`:
|
|
493
|
-
```svelte
|
|
494
|
-
<script lang="ts">
|
|
495
|
-
import { sync } from "$lib/sync-client";
|
|
496
|
-
</script>
|
|
497
|
-
|
|
498
|
-
{#if sync.status === "connecting"}
|
|
499
|
-
<p class="text-amber-500">Connecting to real-time engine...</p>
|
|
500
|
-
{:else if sync.status === "disconnected"}
|
|
501
|
-
<p class="text-rose-500">Offline. Reconnecting...</p>
|
|
502
|
-
{:else}
|
|
503
|
-
<p class="text-emerald-500">Connected and Synced</p>
|
|
504
|
-
{/if}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### B. Manual Instant Reconnection
|
|
508
|
-
To instantly reconnect when the browser detects it has regained network access, you can call `sync.reconnect()`:
|
|
509
491
|
```typescript
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
sync.reconnect(); // Instantly reconnects without waiting for the 2-second backoff
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
```
|
|
492
|
+
import { createPublisher } from "@sveltebase/sync";
|
|
493
|
+
import type { Todo } from "$lib/server/db/schema";
|
|
516
494
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
name: "sveltebase-sync",
|
|
522
|
-
url: async () => {
|
|
523
|
-
const token = await getFreshJWTToken();
|
|
524
|
-
return `/api/sync?token=${token}`;
|
|
525
|
-
},
|
|
526
|
-
tables: { ... }
|
|
527
|
-
});
|
|
528
|
-
```
|
|
495
|
+
// Define schema matching channel names to model types
|
|
496
|
+
type AppSyncSchema = {
|
|
497
|
+
todos: Todo;
|
|
498
|
+
};
|
|
529
499
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
```svelte
|
|
533
|
-
<script lang="ts">
|
|
534
|
-
let queryTerm = $state("");
|
|
535
|
-
|
|
536
|
-
// Re-subscribes and updates the live query automatically when `queryTerm` changes
|
|
537
|
-
const results = todosTable.liveQuery((t) => {
|
|
538
|
-
const term = queryTerm;
|
|
539
|
-
return t.where("title").startsWith(term).toArray();
|
|
540
|
-
});
|
|
541
|
-
</script>
|
|
542
|
-
```
|
|
500
|
+
// Create typed publish function (Option A: Explicit Schema)
|
|
501
|
+
const publish = createPublisher<AppSyncSchema>();
|
|
543
502
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const todosTable = sync.table("todos");
|
|
503
|
+
// Create typed publish function (Option B: Automatically inferred from Sync Handlers)
|
|
504
|
+
import { handlers } from "./lib/server/sync-handlers.js";
|
|
505
|
+
const publish = createPublisher(handlers);
|
|
548
506
|
|
|
549
|
-
// 1.
|
|
550
|
-
|
|
507
|
+
// 1. Publish a create event (expects full Todo payload)
|
|
508
|
+
await publish("todos", "create", todo.id, todo);
|
|
551
509
|
|
|
552
|
-
// 2.
|
|
553
|
-
|
|
510
|
+
// 2. Publish an update event (expects Partial<Todo> payload)
|
|
511
|
+
await publish("todos", "update", todo.id, { completed: true });
|
|
554
512
|
|
|
555
|
-
// 3.
|
|
556
|
-
|
|
557
|
-
const active = await todosTable.rawTable.where("completed").equals(0).toArray();
|
|
558
|
-
```
|
|
513
|
+
// 3. Publish a delete event (expects optional { updatedAt: string } metadata)
|
|
514
|
+
await publish("todos", "delete", todo.id, undefined);
|
|
559
515
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
```typescript
|
|
563
|
-
try {
|
|
564
|
-
await todosTable.add({ title: "" });
|
|
565
|
-
} catch (err: any) {
|
|
566
|
-
if (err.validationErrors) {
|
|
567
|
-
// validationErrors = [{ path: "title", message: "Required" }]
|
|
568
|
-
console.error("Validation issues:", err.validationErrors);
|
|
569
|
-
} else {
|
|
570
|
-
console.error("General error:", err.message);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
516
|
+
// 4. Supports scoped/dynamic channels (e.g. "channelName:scopeId")
|
|
517
|
+
await publish("todos:user_123", "update", todo.id, { title: "New Title" });
|
|
573
518
|
```
|
|
574
519
|
|
|
575
520
|
|
package/dist/client/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type TableConfig = {
|
|
|
7
7
|
};
|
|
8
8
|
export type SyncClientOptions = {
|
|
9
9
|
name: string;
|
|
10
|
-
url: string
|
|
10
|
+
url: string;
|
|
11
11
|
tables: Record<string, TableConfig>;
|
|
12
12
|
};
|
|
13
13
|
export declare class SyncClient<TSchema extends Record<string, any> = Record<string, any>> {
|
|
@@ -19,17 +19,14 @@ export declare class SyncClient<TSchema extends Record<string, any> = Record<str
|
|
|
19
19
|
private pingInterval;
|
|
20
20
|
private closedByClient;
|
|
21
21
|
private activeChannels;
|
|
22
|
-
private _status;
|
|
23
22
|
private pendingMutations;
|
|
24
23
|
private mutationQueue;
|
|
25
24
|
constructor(options: {
|
|
26
25
|
name: string;
|
|
27
|
-
url: string
|
|
26
|
+
url: string;
|
|
28
27
|
tables: Record<keyof TSchema & string, TableConfig>;
|
|
29
28
|
});
|
|
30
|
-
get status(): "connecting" | "connected" | "disconnected";
|
|
31
29
|
private connect;
|
|
32
|
-
reconnect(): void;
|
|
33
30
|
private startHeartbeat;
|
|
34
31
|
private stopHeartbeat;
|
|
35
32
|
private subscribeToChannel;
|
|
@@ -39,10 +36,7 @@ export declare class SyncClient<TSchema extends Record<string, any> = Record<str
|
|
|
39
36
|
private handleServerMessage;
|
|
40
37
|
private findTableByChannel;
|
|
41
38
|
table<TKey extends keyof TSchema & string>(tableName: TKey): {
|
|
42
|
-
rawTable: Table<TSchema[TKey], string, TSchema[TKey]>;
|
|
43
39
|
liveQuery: <TResult = TSchema[TKey][]>(queryFn: (table: Table<TSchema[TKey], string>) => Promise<TResult> | TResult) => LiveQueryResult<TResult>;
|
|
44
|
-
list: () => LiveQueryResult<TSchema[TKey][]>;
|
|
45
|
-
get: (id: string) => Promise<TSchema[TKey] | undefined>;
|
|
46
40
|
add: (row: TSchema[TKey]) => Promise<TSchema[TKey]>;
|
|
47
41
|
put: (id: string, changes: Partial<TSchema[TKey]>) => Promise<TSchema[TKey]>;
|
|
48
42
|
delete: (id: string) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACrC,CAAC;AAaF,qBAAa,UAAU,CACrB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAElD,EAAE,EAAE,KAAK,CAAC;IACjB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,gBAAgB,CAAsC;IAE9D,OAAO,CAAC,aAAa,CAMb;gBAEI,OAAO,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,EAAE,WAAW,CAAC,CAAC;KACrD;IAiBD,OAAO,CAAC,OAAO;IA+Df,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,aAAa;YAOP,kBAAkB;IAoBhC,OAAO,CAAC,kBAAkB;YAkBZ,UAAU;YAgBV,aAAa;YAoBb,mBAAmB;IAkEjC,OAAO,CAAC,kBAAkB;IAOnB,KAAK,CAAC,IAAI,SAAS,MAAM,OAAO,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI;oBASjD,OAAO,6BACR,CAAC,KAAK,EAAE,KAAK,gBAAO,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;qCAK7C,OAAO,eAAM;kBAsBrB,MAAM,WAAW,OAAO,eAAM,KAAG,OAAO,eAAM;qBAyB3C,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;;IAuB7C,OAAO,CAAC,eAAe;IAqChB,UAAU;CAYlB"}
|
package/dist/client/index.js
CHANGED
|
@@ -11,8 +11,6 @@ export class SyncClient {
|
|
|
11
11
|
pingInterval;
|
|
12
12
|
closedByClient = false;
|
|
13
13
|
activeChannels = new Set();
|
|
14
|
-
// Reactive connection status
|
|
15
|
-
_status = $state("connecting");
|
|
16
14
|
// Mutations waiting for ack/reject from server
|
|
17
15
|
pendingMutations = new Map();
|
|
18
16
|
// Mutations queued to be sent when connection is established
|
|
@@ -31,37 +29,17 @@ export class SyncClient {
|
|
|
31
29
|
this.connect();
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
return this._status;
|
|
36
|
-
}
|
|
37
|
-
async connect() {
|
|
32
|
+
connect() {
|
|
38
33
|
if (this.closedByClient)
|
|
39
34
|
return;
|
|
40
|
-
this._status = "connecting";
|
|
41
35
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
42
36
|
const host = window.location.host;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.error("SyncClient: Failed to resolve wsUrl", err);
|
|
49
|
-
this._status = "disconnected";
|
|
50
|
-
if (!this.closedByClient) {
|
|
51
|
-
this.reconnectTimer = setTimeout(() => this.connect(), 2000);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const fullUrl = resolvedUrl.startsWith("ws://") || resolvedUrl.startsWith("wss://")
|
|
56
|
-
? resolvedUrl
|
|
57
|
-
: `${protocol}//${host}${resolvedUrl}`;
|
|
58
|
-
const socket = new WebSocket(fullUrl);
|
|
59
|
-
this.socket = socket;
|
|
60
|
-
socket.addEventListener("open", async () => {
|
|
61
|
-
if (this.socket !== socket)
|
|
62
|
-
return;
|
|
37
|
+
const fullUrl = this.wsUrl.startsWith("ws://") || this.wsUrl.startsWith("wss://")
|
|
38
|
+
? this.wsUrl
|
|
39
|
+
: `${protocol}//${host}${this.wsUrl}`;
|
|
40
|
+
this.socket = new WebSocket(fullUrl);
|
|
41
|
+
this.socket.addEventListener("open", async () => {
|
|
63
42
|
console.log("SyncClient: WebSocket connected");
|
|
64
|
-
this._status = "connected";
|
|
65
43
|
this.activeChannels.clear();
|
|
66
44
|
this.startHeartbeat();
|
|
67
45
|
// Re-subscribe to all tables (delta-sync aware)
|
|
@@ -70,7 +48,7 @@ export class SyncClient {
|
|
|
70
48
|
}
|
|
71
49
|
// Re-send all pending unacknowledged mutations
|
|
72
50
|
for (const mut of this.pendingMutations.values()) {
|
|
73
|
-
socket
|
|
51
|
+
this.socket?.send(JSON.stringify({
|
|
74
52
|
type: "mutate",
|
|
75
53
|
id: mut.id,
|
|
76
54
|
channel: mut.channel,
|
|
@@ -82,9 +60,7 @@ export class SyncClient {
|
|
|
82
60
|
// Flush queued mutations
|
|
83
61
|
this.flushMutationQueue();
|
|
84
62
|
});
|
|
85
|
-
socket.addEventListener("message", async (message) => {
|
|
86
|
-
if (this.socket !== socket)
|
|
87
|
-
return;
|
|
63
|
+
this.socket.addEventListener("message", async (message) => {
|
|
88
64
|
if (typeof message.data !== "string")
|
|
89
65
|
return;
|
|
90
66
|
if (message.data === "pong")
|
|
@@ -94,38 +70,17 @@ export class SyncClient {
|
|
|
94
70
|
return;
|
|
95
71
|
await this.handleServerMessage(msg);
|
|
96
72
|
});
|
|
97
|
-
socket.addEventListener("close", () => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
if (!this.closedByClient) {
|
|
103
|
-
this.reconnectTimer = setTimeout(() => this.connect(), 2000);
|
|
104
|
-
}
|
|
73
|
+
this.socket.addEventListener("close", () => {
|
|
74
|
+
this.socket = undefined;
|
|
75
|
+
this.stopHeartbeat();
|
|
76
|
+
if (!this.closedByClient) {
|
|
77
|
+
this.reconnectTimer = setTimeout(() => this.connect(), 2000);
|
|
105
78
|
}
|
|
106
79
|
});
|
|
107
|
-
socket.addEventListener("error", (err) => {
|
|
108
|
-
|
|
109
|
-
console.error("SyncClient: WebSocket error", err);
|
|
110
|
-
}
|
|
80
|
+
this.socket.addEventListener("error", (err) => {
|
|
81
|
+
console.error("SyncClient: WebSocket error", err);
|
|
111
82
|
});
|
|
112
83
|
}
|
|
113
|
-
reconnect() {
|
|
114
|
-
this.closedByClient = false;
|
|
115
|
-
if (this.reconnectTimer) {
|
|
116
|
-
clearTimeout(this.reconnectTimer);
|
|
117
|
-
this.reconnectTimer = undefined;
|
|
118
|
-
}
|
|
119
|
-
if (this.socket) {
|
|
120
|
-
try {
|
|
121
|
-
this.socket.close();
|
|
122
|
-
}
|
|
123
|
-
catch { }
|
|
124
|
-
this.socket = undefined;
|
|
125
|
-
this._status = "disconnected";
|
|
126
|
-
}
|
|
127
|
-
this.connect();
|
|
128
|
-
}
|
|
129
84
|
startHeartbeat() {
|
|
130
85
|
this.stopHeartbeat();
|
|
131
86
|
this.pingInterval = setInterval(() => {
|
|
@@ -247,11 +202,7 @@ export class SyncClient {
|
|
|
247
202
|
if (pending) {
|
|
248
203
|
console.warn(`Mutation ${msg.id} rejected by server: ${msg.error}`);
|
|
249
204
|
await pending.rollback();
|
|
250
|
-
|
|
251
|
-
if (msg.validationErrors) {
|
|
252
|
-
errorObj.validationErrors = msg.validationErrors;
|
|
253
|
-
}
|
|
254
|
-
pending.reject(errorObj);
|
|
205
|
+
pending.reject(new Error(msg.error));
|
|
255
206
|
this.pendingMutations.delete(msg.id);
|
|
256
207
|
}
|
|
257
208
|
break;
|
|
@@ -289,16 +240,9 @@ export class SyncClient {
|
|
|
289
240
|
throw new Error(`Table ${tableName} not defined in SyncClient config.`);
|
|
290
241
|
}
|
|
291
242
|
return {
|
|
292
|
-
rawTable: dexieTable,
|
|
293
243
|
liveQuery: (queryFn) => {
|
|
294
244
|
return useLiveQuery(() => queryFn(dexieTable));
|
|
295
245
|
},
|
|
296
|
-
list: () => {
|
|
297
|
-
return useLiveQuery(() => dexieTable.toArray());
|
|
298
|
-
},
|
|
299
|
-
get: async (id) => {
|
|
300
|
-
return dexieTable.get(id);
|
|
301
|
-
},
|
|
302
246
|
add: async (row) => {
|
|
303
247
|
const rowData = row;
|
|
304
248
|
const id = rowData.id || crypto.randomUUID();
|
|
@@ -369,17 +313,13 @@ export class SyncClient {
|
|
|
369
313
|
}
|
|
370
314
|
disconnect() {
|
|
371
315
|
this.closedByClient = true;
|
|
372
|
-
this._status = "disconnected";
|
|
373
316
|
this.stopHeartbeat();
|
|
374
317
|
if (this.reconnectTimer) {
|
|
375
318
|
clearTimeout(this.reconnectTimer);
|
|
376
319
|
this.reconnectTimer = undefined;
|
|
377
320
|
}
|
|
378
321
|
if (this.socket) {
|
|
379
|
-
|
|
380
|
-
this.socket.close();
|
|
381
|
-
}
|
|
382
|
-
catch { }
|
|
322
|
+
this.socket.close();
|
|
383
323
|
this.socket = undefined;
|
|
384
324
|
}
|
|
385
325
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.svelte.d.ts","sourceRoot":"","sources":["../../src/client/live.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"live.svelte.d.ts","sourceRoot":"","sources":["../../src/client/live.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,CAAC,CAAC,IACzB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,GACzE;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,KAAK,CAAA;CAAE,GAClE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,KAAK,CAAA;CAAE,CAAC;AAEvE,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAuCjF"}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
+
import { onDestroy } from "svelte";
|
|
1
2
|
import { liveQuery } from "dexie";
|
|
2
3
|
export function useLiveQuery(queryFn) {
|
|
3
4
|
let data = $state(undefined);
|
|
4
5
|
let error = $state(undefined);
|
|
5
6
|
let status = $state("loading");
|
|
6
7
|
if (typeof window !== "undefined") {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
subscription.unsubscribe();
|
|
24
|
-
};
|
|
8
|
+
const observable = liveQuery(queryFn);
|
|
9
|
+
const subscription = observable.subscribe({
|
|
10
|
+
next: (val) => {
|
|
11
|
+
data = val;
|
|
12
|
+
error = undefined;
|
|
13
|
+
status = "success";
|
|
14
|
+
},
|
|
15
|
+
error: (err) => {
|
|
16
|
+
data = undefined;
|
|
17
|
+
error = err;
|
|
18
|
+
status = "error";
|
|
19
|
+
console.error("liveQuery error:", err);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
onDestroy(() => {
|
|
23
|
+
subscription.unsubscribe();
|
|
25
24
|
});
|
|
26
25
|
}
|
|
27
26
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { SyncClient, useLiveQuery } from "./client/index.js";
|
|
2
2
|
export { defineSync } from "./server/index.js";
|
|
3
|
-
export { handleUpgrade, publishEvent } from "./server/handler.js";
|
|
3
|
+
export { handleUpgrade, publishEvent, createPublisher } from "./server/handler.js";
|
|
4
|
+
export type { PublishEventData, InferSchemaFromHandlers } from "./server/handler.js";
|
|
4
5
|
export type { SyncContext, SyncHandler } from "./server/index.js";
|
|
5
6
|
export type { SyncMessage } from "./protocol.js";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACrF,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/server/broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,GAAG,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,qBAAqB,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,mBAAmB,CAAC,CAGV;gBAGhB,QAAQ,EAAE,WAAW,EAAE,EACvB,mBAAmB,CAAC,EAAE,CACpB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,KAC/B,OAAO,CAAC,GAAG,CAAC;IAOZ,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE;IAanC,kBAAkB,CAAC,IAAI,EAAE,eAAe;IAIxC,gBAAgB,CAAC,IAAI,EAAE,eAAe;IAI7C;;OAEG;IACH,OAAO,CAAC,WAAW;IAqCN,aAAa,CACxB,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/server/broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,GAAG,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,qBAAqB,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,mBAAmB,CAAC,CAGV;gBAGhB,QAAQ,EAAE,WAAW,EAAE,EACvB,mBAAmB,CAAC,EAAE,CACpB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,KAC/B,OAAO,CAAC,GAAG,CAAC;IAOZ,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE;IAanC,kBAAkB,CAAC,IAAI,EAAE,eAAe;IAIxC,gBAAgB,CAAC,IAAI,EAAE,eAAe;IAI7C;;OAEG;IACH,OAAO,CAAC,WAAW;IAqCN,aAAa,CACxB,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO;YA0IJ,eAAe;IAoDhB,oBAAoB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG;CAoBZ"}
|
package/dist/server/broker.js
CHANGED
|
@@ -160,21 +160,10 @@ export class SyncBroker {
|
|
|
160
160
|
catch (err) {
|
|
161
161
|
console.error(`SyncBroker: error handling message type=${msg.type}:`, err);
|
|
162
162
|
if (msg.type === "mutate") {
|
|
163
|
-
let errorMessage = err.message || "Server error";
|
|
164
|
-
let validationErrors = undefined;
|
|
165
|
-
// Formats Zod validation errors for structured client rejection
|
|
166
|
-
if (err && err.name === "ZodError" && Array.isArray(err.issues)) {
|
|
167
|
-
validationErrors = err.issues.map((issue) => ({
|
|
168
|
-
path: issue.path.join("."),
|
|
169
|
-
message: issue.message,
|
|
170
|
-
}));
|
|
171
|
-
errorMessage = `Validation failed: ${err.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`;
|
|
172
|
-
}
|
|
173
163
|
conn.send(JSON.stringify({
|
|
174
164
|
type: "reject",
|
|
175
165
|
id: msg.id,
|
|
176
|
-
error:
|
|
177
|
-
validationErrors,
|
|
166
|
+
error: err.message || "Server error",
|
|
178
167
|
}));
|
|
179
168
|
}
|
|
180
169
|
}
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type { SyncHandler } from "./index.js";
|
|
2
|
+
export type PublishEventData<TRecord, TAction extends "create" | "update" | "delete"> = TAction extends "create" ? TRecord : TAction extends "update" ? Partial<TRecord> : {
|
|
3
|
+
updatedAt?: string;
|
|
4
|
+
} | undefined;
|
|
5
|
+
export type InferSchemaFromHandlers<T extends SyncHandler[]> = {
|
|
6
|
+
[K in T[number] as K["config"]["channel"] extends string ? K["config"]["channel"] : K["config"]["channel"] extends (...args: any[]) => infer R ? R extends string ? R : string : string]: K extends SyncHandler<infer TRow> ? TRow : never;
|
|
7
|
+
};
|
|
8
|
+
export declare function createPublisher<TSchema extends Record<string, any>>(): <TChannel extends keyof TSchema & string, TAction extends "create" | "update" | "delete">(channel: TChannel | `${TChannel}:${string}`, action: TAction, key: string | undefined, data: PublishEventData<TSchema[TChannel], TAction>) => Promise<void>;
|
|
9
|
+
export declare function createPublisher<THandlers extends SyncHandler[]>(handlers: THandlers): <TChannel extends keyof InferSchemaFromHandlers<THandlers> & string, TAction extends "create" | "update" | "delete">(channel: TChannel | `${TChannel}:${string}`, action: TAction, key: string | undefined, data: PublishEventData<InferSchemaFromHandlers<THandlers>[TChannel], TAction>) => Promise<void>;
|
|
1
10
|
export declare function publishEvent(channel: string, action: "create" | "update" | "delete", key: string | undefined, data: any): Promise<void>;
|
|
2
11
|
export declare function handleUpgrade(request: Request, platform: App.Platform | undefined): Promise<Response>;
|
|
3
12
|
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"AAAA,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG,iBAgCV;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,GACjC,OAAO,CAAC,QAAQ,CAAC,CAqBnB"}
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,IAClF,OAAO,SAAS,QAAQ,GACpB,OAAO,GACP,OAAO,SAAS,QAAQ,GACtB,OAAO,CAAC,OAAO,CAAC,GAChB;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAE3C,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI;KAC5D,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,SAAS,MAAM,GACpD,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,GACtB,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GACxD,CAAC,SAAS,MAAM,GACd,CAAC,GACD,MAAM,GACR,MAAM,GAAG,CAAC,SAAS,WAAW,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK;CAChE,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CACtE,QAAQ,SAAS,MAAM,OAAO,GAAG,MAAM,EACvC,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAE9C,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,wBAAgB,eAAe,CAAC,SAAS,SAAS,WAAW,EAAE,EAC7D,QAAQ,EAAE,SAAS,GAClB,CACD,QAAQ,SAAS,MAAM,uBAAuB,CAAC,SAAS,CAAC,GAAG,MAAM,EAClE,OAAO,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAE9C,OAAO,EAAE,QAAQ,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,EAC3C,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,KAC1E,OAAO,CAAC,IAAI,CAAC,CAAC;AAcnB,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EACtC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,IAAI,EAAE,GAAG,iBAgCV;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,SAAS,GACjC,OAAO,CAAC,QAAQ,CAAC,CAqBnB"}
|
package/dist/server/handler.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export function createPublisher(handlers) {
|
|
2
|
+
return async (channel, action, key, data) => {
|
|
3
|
+
const resolvedChannel = String(channel);
|
|
4
|
+
return publishEvent(resolvedChannel, action, key, data);
|
|
5
|
+
};
|
|
6
|
+
}
|
|
1
7
|
export async function publishEvent(channel, action, key, data) {
|
|
2
8
|
const envId = "$app/environment";
|
|
3
9
|
let isDev = false;
|