create-syncular-app 0.15.36 → 0.15.38
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-syncular-app",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.38",
|
|
4
4
|
"description": "Scaffold a new Syncular app: `create-syncular-app`",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
"!dist/**/*.test.d.ts"
|
|
50
50
|
],
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@syncular/typegen": "0.15.
|
|
52
|
+
"@syncular/typegen": "0.15.38"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
* the web build never ships the Tauri bridge, and the Tauri webview never
|
|
19
19
|
* loads sqlite-wasm.
|
|
20
20
|
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
browserConnectivitySignal,
|
|
24
|
+
documentLifecycleSignal,
|
|
25
|
+
installRealtimeSupervisor,
|
|
26
|
+
} from '@syncular/client';
|
|
21
27
|
import type { SyncClientLike } from '@syncular/react';
|
|
22
28
|
import { schema } from '../syncular.generated';
|
|
23
29
|
|
|
@@ -26,7 +32,9 @@ import { schema } from '../syncular.generated';
|
|
|
26
32
|
* lifecycle both concrete clients share, still fully host-agnostic.
|
|
27
33
|
*/
|
|
28
34
|
export interface Engine extends SyncClientLike {
|
|
35
|
+
syncUntilIdle(maxRounds?: number): unknown | Promise<unknown>;
|
|
29
36
|
connectRealtime(): Promise<void>;
|
|
37
|
+
disconnectRealtime(): void | Promise<void>;
|
|
30
38
|
close(): Promise<void>;
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -34,22 +42,29 @@ export interface Engine extends SyncClientLike {
|
|
|
34
42
|
const isTauri = () => '__TAURI_INTERNALS__' in window;
|
|
35
43
|
|
|
36
44
|
export async function createEngine(): Promise<Engine> {
|
|
45
|
+
const supervise = <Client extends Engine>(client: Client): Client =>
|
|
46
|
+
installRealtimeSupervisor(client, {
|
|
47
|
+
connectivity: browserConnectivitySignal(),
|
|
48
|
+
lifecycle: documentLifecycleSignal(),
|
|
49
|
+
});
|
|
37
50
|
if (isTauri()) {
|
|
38
51
|
// Desktop: the native Rust core in the Tauri process.
|
|
39
52
|
const { createTauriSyncClient } = await import('@syncular/tauri');
|
|
40
|
-
return createTauriSyncClient({ schema });
|
|
53
|
+
return supervise(await createTauriSyncClient({ schema }));
|
|
41
54
|
}
|
|
42
55
|
// Web: the whole core in a worker, persisted on OPFS.
|
|
43
56
|
const { createSyncClientHandle } = await import('@syncular/client');
|
|
44
57
|
const WS = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
return supervise(
|
|
59
|
+
await createSyncClientHandle({
|
|
60
|
+
worker: () => new Worker('/worker.js', { type: 'module' }),
|
|
61
|
+
schema,
|
|
62
|
+
database: { mode: 'persistent', name: 'app' },
|
|
63
|
+
endpoints: {
|
|
64
|
+
syncUrl: '/sync',
|
|
65
|
+
segmentsUrl: '/segments',
|
|
66
|
+
realtimeUrl: `${WS}://${location.host}/realtime?clientId={clientId}`,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
55
70
|
}
|
|
@@ -142,13 +142,9 @@ function TodoApp() {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const engineResource = createSyncClientResource(async () => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
} catch {
|
|
149
|
-
// offline / no socket — the host loop keeps syncing over HTTP
|
|
150
|
-
}
|
|
151
|
-
return engine;
|
|
145
|
+
// createEngine installs the supported reconnect/catch-up supervisor. React
|
|
146
|
+
// renders from local SQLite immediately; connectivity never gates startup.
|
|
147
|
+
return createEngine();
|
|
152
148
|
});
|
|
153
149
|
|
|
154
150
|
function Root() {
|
|
@@ -21,7 +21,7 @@ tauri = { version = "2", features = [] }
|
|
|
21
21
|
# The syncular plugin from crates.io. `native-transport` gives the core its
|
|
22
22
|
# real HTTP + WebSocket transport (ureq + tungstenite) inside the host
|
|
23
23
|
# process — the webview never talks to the sync server directly.
|
|
24
|
-
tauri-plugin-syncular = { version = "0.15.
|
|
24
|
+
tauri-plugin-syncular = { version = "0.15.38", features = ["native-transport"] }
|
|
25
25
|
|
|
26
26
|
[features]
|
|
27
27
|
default = []
|
|
@@ -13,9 +13,14 @@
|
|
|
13
13
|
* to watch them converge over the realtime socket.
|
|
14
14
|
*/
|
|
15
15
|
import {
|
|
16
|
+
browserConnectivitySignal,
|
|
16
17
|
ClientSyncError,
|
|
17
18
|
createSyncClientHandle,
|
|
19
|
+
documentLifecycleSignal,
|
|
20
|
+
installRealtimeSupervisor,
|
|
21
|
+
realtimeSupervisorSnapshot,
|
|
18
22
|
type SqlRow,
|
|
23
|
+
subscribeRealtimeSupervisor,
|
|
19
24
|
} from '@syncular/client';
|
|
20
25
|
import { schema, todoListSubscription } from '../syncular.generated';
|
|
21
26
|
|
|
@@ -63,15 +68,25 @@ async function main(): Promise<void> {
|
|
|
63
68
|
scopes: todoListSubscription.scopes({ listId: LIST_ID }),
|
|
64
69
|
});
|
|
65
70
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
// The supported host policy owns connect + catch-up, retries transient
|
|
72
|
+
// startup/socket loss, and suspends while this page is offline or hidden.
|
|
73
|
+
// Rendering remains local-first: no network promise blocks app startup.
|
|
74
|
+
installRealtimeSupervisor(handle, {
|
|
75
|
+
connectivity: browserConnectivitySignal(),
|
|
76
|
+
lifecycle: documentLifecycleSignal(),
|
|
77
|
+
});
|
|
78
|
+
const renderRealtimeStatus = () => {
|
|
79
|
+
const phase = realtimeSupervisorSnapshot(handle).phase;
|
|
80
|
+
setStatus(
|
|
81
|
+
phase === 'connected'
|
|
82
|
+
? 'in sync'
|
|
83
|
+
: phase === 'offline'
|
|
84
|
+
? 'offline — edits queue in the outbox'
|
|
85
|
+
: phase,
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
subscribeRealtimeSupervisor(handle, renderRealtimeStatus);
|
|
89
|
+
renderRealtimeStatus();
|
|
75
90
|
await refresh();
|
|
76
91
|
|
|
77
92
|
let offline = false;
|
|
@@ -82,14 +97,9 @@ async function main(): Promise<void> {
|
|
|
82
97
|
if (offline) {
|
|
83
98
|
setStatus('offline — edits queue in the outbox');
|
|
84
99
|
} else {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} catch {
|
|
89
|
-
// socket optional
|
|
90
|
-
}
|
|
91
|
-
await handle.syncUntilIdle();
|
|
92
|
-
setStatus('in sync');
|
|
100
|
+
// The online diagnostic wakes the supervisor. Its successful reconnect
|
|
101
|
+
// always performs syncUntilIdle before publishing `connected`.
|
|
102
|
+
setStatus('back online — reconnecting…');
|
|
93
103
|
}
|
|
94
104
|
await refresh();
|
|
95
105
|
});
|