@syncular/react 0.15.40 → 0.15.42
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 +22 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime-version.d.ts +1 -1
- package/dist/runtime-version.js +1 -1
- package/dist/vite-hmr.d.ts +29 -0
- package/dist/vite-hmr.js +59 -7
- package/package.json +7 -5
- package/src/index.ts +2 -0
- package/src/runtime-version.ts +1 -1
- package/src/vite-hmr.ts +98 -10
package/README.md
CHANGED
|
@@ -153,14 +153,18 @@ old resource before creating another one. Otherwise the old worker and the new
|
|
|
153
153
|
worker can briefly compete for the same persistent OPFS directory. Never wipe
|
|
154
154
|
or rename the database in response to a retryable startup error.
|
|
155
155
|
|
|
156
|
-
For Vite, use `
|
|
157
|
-
createClient)`. It
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
package-upgrade
|
|
161
|
-
`
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
For Vite, use `createViteSyncClientResource(hot.data, schema.version,
|
|
157
|
+
createClient)`. It synchronously returns a pending-or-retained resource, keeps
|
|
158
|
+
a `{ schemaVersion, runtimeVersion, resource }` record, reuses it for
|
|
159
|
+
same-schema HMR on the same published Syncular runtime, and sequences a
|
|
160
|
+
schema-bump or package-upgrade factory behind old-resource disposal. Await its
|
|
161
|
+
`handoff` promise before requesting `hot.invalidate()` when `ownerChanged` is
|
|
162
|
+
true. A failed handoff is also exposed by the resource through the ordinary
|
|
163
|
+
startup boundary and never opens a second owner. This recipe requires no
|
|
164
|
+
top-level await; the official example, optimizer exclusion, and upgrade
|
|
165
|
+
workflow are in the [Vite guide](https://syncular.dev/guide-vite/).
|
|
166
|
+
`retainViteSyncClientResource` remains the async alternative for targets that
|
|
167
|
+
deliberately support top-level await. Either helper may close
|
|
164
168
|
the retained client before React runs cleanup for the old provider; Syncular
|
|
165
169
|
absorbs that teardown-only window-release race while still surfacing failures
|
|
166
170
|
from closing the resource itself.
|
|
@@ -260,6 +264,16 @@ not need a custom retention effect.
|
|
|
260
264
|
|
|
261
265
|
The hooks are SSR-safe: no local query runs during server rendering.
|
|
262
266
|
|
|
267
|
+
### Router scheduling
|
|
268
|
+
|
|
269
|
+
Syncular hooks publish through `useSyncExternalStore`. If React Router owns
|
|
270
|
+
clinical query state, keep the router as the only owner and use its explicit
|
|
271
|
+
synchronous publication policy (`<RouterProvider useTransitions={false}>`) on
|
|
272
|
+
versions where sustained external-store updates can starve a transition. Do
|
|
273
|
+
not repair URL/render disagreement with `window.location`, `flushSync`, or a
|
|
274
|
+
mirrored store. The maintained fixture and upgrade guidance live in the
|
|
275
|
+
[React guide](https://syncular.dev/platform-react/#router-transition-scheduling).
|
|
276
|
+
|
|
263
277
|
### Privacy-safe support view
|
|
264
278
|
|
|
265
279
|
Use application-owned, stable, PHI-free subscription ids to make missing
|
package/dist/index.d.ts
CHANGED
|
@@ -23,4 +23,4 @@ export { type NamedQueryDescriptor, useQuery, } from './use-query.js';
|
|
|
23
23
|
export { type UseRawSqlOptions, type UseRawSqlResult, useRawSql, } from './use-raw-sql.js';
|
|
24
24
|
export { type SyncStatus, useSyncStatus } from './use-sync-status.js';
|
|
25
25
|
export { type UseRetainedWindowResult, type UseWindowResult, useRetainedWindow, useWindow, } from './use-window.js';
|
|
26
|
-
export { type RetainedSyncularResource, retainViteSyncClientResource, type ViteSyncClientResourceResult, } from './vite-hmr.js';
|
|
26
|
+
export { createViteSyncClientResource, type RetainedSyncularResource, retainViteSyncClientResource, type ViteSyncClientResourceBootstrap, type ViteSyncClientResourceResult, } from './vite-hmr.js';
|
package/dist/index.js
CHANGED
|
@@ -16,4 +16,4 @@ export { useQuery, } from './use-query.js';
|
|
|
16
16
|
export { useRawSql, } from './use-raw-sql.js';
|
|
17
17
|
export { useSyncStatus } from './use-sync-status.js';
|
|
18
18
|
export { useRetainedWindow, useWindow, } from './use-window.js';
|
|
19
|
-
export { retainViteSyncClientResource, } from './vite-hmr.js';
|
|
19
|
+
export { createViteSyncClientResource, retainViteSyncClientResource, } from './vite-hmr.js';
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Vite HMR uses it to replace a worker owner after a Syncular package upgrade
|
|
4
4
|
* even when the application's generated schema version did not change.
|
|
5
5
|
*/
|
|
6
|
-
export declare const SYNCULAR_REACT_RUNTIME_VERSION = "0.15.
|
|
6
|
+
export declare const SYNCULAR_REACT_RUNTIME_VERSION = "0.15.42";
|
package/dist/runtime-version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Vite HMR uses it to replace a worker owner after a Syncular package upgrade
|
|
4
4
|
* even when the application's generated schema version did not change.
|
|
5
5
|
*/
|
|
6
|
-
export const SYNCULAR_REACT_RUNTIME_VERSION = '0.15.
|
|
6
|
+
export const SYNCULAR_REACT_RUNTIME_VERSION = '0.15.42';
|
package/dist/vite-hmr.d.ts
CHANGED
|
@@ -20,7 +20,36 @@ export interface ViteSyncClientResourceResult {
|
|
|
20
20
|
/** A failed close is exposed by `resource` as a startup error. */
|
|
21
21
|
readonly disposalError?: Error;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Synchronous module-bootstrap result for Vite targets that do not support
|
|
25
|
+
* top-level await. The resource is immediately usable by `SyncProvider`; it
|
|
26
|
+
* remains pending until an obsolete owner has closed and the replacement
|
|
27
|
+
* factory is allowed to run.
|
|
28
|
+
*/
|
|
29
|
+
export interface ViteSyncClientResourceBootstrap {
|
|
30
|
+
readonly resource: SyncClientResource;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves after an incompatible retained owner has closed. A rejection is
|
|
33
|
+
* also published by `resource` as its startup error, and prevents the
|
|
34
|
+
* replacement factory from running.
|
|
35
|
+
*/
|
|
36
|
+
readonly handoff: Promise<void>;
|
|
37
|
+
/** Compatibility alias; true whenever the retained owner was replaced. */
|
|
38
|
+
readonly schemaChanged: boolean;
|
|
39
|
+
/** True only when a retained owner came from another Syncular release. */
|
|
40
|
+
readonly runtimeChanged: boolean;
|
|
41
|
+
/** True whenever an incompatible retained owner is being replaced. */
|
|
42
|
+
readonly ownerChanged: boolean;
|
|
43
|
+
}
|
|
23
44
|
type HotData = Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Create or reuse a Vite-retained resource without top-level await.
|
|
47
|
+
*
|
|
48
|
+
* On an owner change, the returned resource's factory is sequenced behind the
|
|
49
|
+
* prior resource's complete disposal. A failed close therefore becomes the
|
|
50
|
+
* ordinary resource startup error and never constructs a competing owner.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createViteSyncClientResource(hotData: HotData | undefined, schemaVersion: number, factory: () => SyncClientLike | Promise<SyncClientLike>, runtimeVersion?: string): ViteSyncClientResourceBootstrap;
|
|
24
53
|
/**
|
|
25
54
|
* Reuse a Vite-owned client only while both its captured generated schema and
|
|
26
55
|
* materialized Syncular runtime identity match. On either change, the prior
|
package/dist/vite-hmr.js
CHANGED
|
@@ -30,13 +30,7 @@ function retainedFrom(hotData) {
|
|
|
30
30
|
resource: candidate.resource,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
* Reuse a Vite-owned client only while both its captured generated schema and
|
|
35
|
-
* materialized Syncular runtime identity match. On either change, the prior
|
|
36
|
-
* resource is fully disposed before the replacement resource (and therefore
|
|
37
|
-
* its worker) is constructed.
|
|
38
|
-
*/
|
|
39
|
-
export async function retainViteSyncClientResource(hotData, schemaVersion, factory, runtimeVersion = SYNCULAR_REACT_RUNTIME_VERSION) {
|
|
33
|
+
function validateIdentity(schemaVersion, runtimeVersion) {
|
|
40
34
|
if (!Number.isInteger(schemaVersion) || schemaVersion < 1) {
|
|
41
35
|
throw new TypeError('schemaVersion must be a positive integer');
|
|
42
36
|
}
|
|
@@ -45,6 +39,64 @@ export async function retainViteSyncClientResource(hotData, schemaVersion, facto
|
|
|
45
39
|
runtimeVersion.length > 96) {
|
|
46
40
|
throw new TypeError('runtimeVersion must be a bounded non-empty string');
|
|
47
41
|
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create or reuse a Vite-retained resource without top-level await.
|
|
45
|
+
*
|
|
46
|
+
* On an owner change, the returned resource's factory is sequenced behind the
|
|
47
|
+
* prior resource's complete disposal. A failed close therefore becomes the
|
|
48
|
+
* ordinary resource startup error and never constructs a competing owner.
|
|
49
|
+
*/
|
|
50
|
+
export function createViteSyncClientResource(hotData, schemaVersion, factory, runtimeVersion = SYNCULAR_REACT_RUNTIME_VERSION) {
|
|
51
|
+
validateIdentity(schemaVersion, runtimeVersion);
|
|
52
|
+
const retained = retainedFrom(hotData);
|
|
53
|
+
if (retained?.schemaVersion === schemaVersion &&
|
|
54
|
+
retained.runtimeVersion === runtimeVersion) {
|
|
55
|
+
return {
|
|
56
|
+
resource: retained.resource,
|
|
57
|
+
handoff: Promise.resolve(),
|
|
58
|
+
schemaChanged: false,
|
|
59
|
+
runtimeChanged: false,
|
|
60
|
+
ownerChanged: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const ownerChanged = retained !== undefined;
|
|
64
|
+
const runtimeChanged = retained !== undefined && retained.runtimeVersion !== runtimeVersion;
|
|
65
|
+
const handoff = retained
|
|
66
|
+
? Promise.resolve().then(() => retained.resource.dispose())
|
|
67
|
+
: Promise.resolve();
|
|
68
|
+
// The resource below observes the same rejection. Attaching a handler here
|
|
69
|
+
// also keeps an integration that ignores `handoff` free of an unhandled
|
|
70
|
+
// promise while the provider reports the typed startup failure.
|
|
71
|
+
void handoff.catch(() => undefined);
|
|
72
|
+
const resource = createSyncClientResource(async () => {
|
|
73
|
+
await handoff;
|
|
74
|
+
return factory();
|
|
75
|
+
});
|
|
76
|
+
if (hotData !== undefined) {
|
|
77
|
+
const record = {
|
|
78
|
+
schemaVersion,
|
|
79
|
+
runtimeVersion,
|
|
80
|
+
resource,
|
|
81
|
+
};
|
|
82
|
+
hotData.syncularClientResource = record;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
resource,
|
|
86
|
+
handoff,
|
|
87
|
+
schemaChanged: ownerChanged,
|
|
88
|
+
runtimeChanged,
|
|
89
|
+
ownerChanged,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reuse a Vite-owned client only while both its captured generated schema and
|
|
94
|
+
* materialized Syncular runtime identity match. On either change, the prior
|
|
95
|
+
* resource is fully disposed before the replacement resource (and therefore
|
|
96
|
+
* its worker) is constructed.
|
|
97
|
+
*/
|
|
98
|
+
export async function retainViteSyncClientResource(hotData, schemaVersion, factory, runtimeVersion = SYNCULAR_REACT_RUNTIME_VERSION) {
|
|
99
|
+
validateIdentity(schemaVersion, runtimeVersion);
|
|
48
100
|
const retained = retainedFrom(hotData);
|
|
49
101
|
if (retained?.schemaVersion === schemaVersion &&
|
|
50
102
|
retained.runtimeVersion === runtimeVersion) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/react",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.42",
|
|
4
4
|
"description": "React hooks for Syncular offline-first sync",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -55,18 +55,20 @@
|
|
|
55
55
|
"test": "bun test --preload ./test/setup.ts"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@syncular/client": "0.15.
|
|
58
|
+
"@syncular/client": "0.15.42"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"react": ">=18.0.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
65
|
-
"@syncular/core": "0.15.
|
|
66
|
-
"@syncular/server": "0.15.
|
|
65
|
+
"@syncular/core": "0.15.42",
|
|
66
|
+
"@syncular/server": "0.15.42",
|
|
67
67
|
"@testing-library/react": "^16.1.0",
|
|
68
68
|
"@types/react": "^18.3.0",
|
|
69
69
|
"react": "^18.3.1",
|
|
70
|
-
"react-dom": "^18.3.1"
|
|
70
|
+
"react-dom": "^18.3.1",
|
|
71
|
+
"react-router-dom": "^7.18.1",
|
|
72
|
+
"vite": "^8.1.3"
|
|
71
73
|
}
|
|
72
74
|
}
|
package/src/index.ts
CHANGED
|
@@ -67,7 +67,9 @@ export {
|
|
|
67
67
|
useWindow,
|
|
68
68
|
} from './use-window';
|
|
69
69
|
export {
|
|
70
|
+
createViteSyncClientResource,
|
|
70
71
|
type RetainedSyncularResource,
|
|
71
72
|
retainViteSyncClientResource,
|
|
73
|
+
type ViteSyncClientResourceBootstrap,
|
|
72
74
|
type ViteSyncClientResourceResult,
|
|
73
75
|
} from './vite-hmr';
|
package/src/runtime-version.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Vite HMR uses it to replace a worker owner after a Syncular package upgrade
|
|
4
4
|
* even when the application's generated schema version did not change.
|
|
5
5
|
*/
|
|
6
|
-
export const SYNCULAR_REACT_RUNTIME_VERSION = '0.15.
|
|
6
|
+
export const SYNCULAR_REACT_RUNTIME_VERSION = '0.15.42';
|
package/src/vite-hmr.ts
CHANGED
|
@@ -24,6 +24,28 @@ export interface ViteSyncClientResourceResult {
|
|
|
24
24
|
readonly disposalError?: Error;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Synchronous module-bootstrap result for Vite targets that do not support
|
|
29
|
+
* top-level await. The resource is immediately usable by `SyncProvider`; it
|
|
30
|
+
* remains pending until an obsolete owner has closed and the replacement
|
|
31
|
+
* factory is allowed to run.
|
|
32
|
+
*/
|
|
33
|
+
export interface ViteSyncClientResourceBootstrap {
|
|
34
|
+
readonly resource: SyncClientResource;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves after an incompatible retained owner has closed. A rejection is
|
|
37
|
+
* also published by `resource` as its startup error, and prevents the
|
|
38
|
+
* replacement factory from running.
|
|
39
|
+
*/
|
|
40
|
+
readonly handoff: Promise<void>;
|
|
41
|
+
/** Compatibility alias; true whenever the retained owner was replaced. */
|
|
42
|
+
readonly schemaChanged: boolean;
|
|
43
|
+
/** True only when a retained owner came from another Syncular release. */
|
|
44
|
+
readonly runtimeChanged: boolean;
|
|
45
|
+
/** True whenever an incompatible retained owner is being replaced. */
|
|
46
|
+
readonly ownerChanged: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
type HotData = Record<string, unknown>;
|
|
28
50
|
|
|
29
51
|
function errorOf(value: unknown): Error {
|
|
@@ -72,6 +94,81 @@ function retainedFrom(hotData: HotData | undefined):
|
|
|
72
94
|
};
|
|
73
95
|
}
|
|
74
96
|
|
|
97
|
+
function validateIdentity(schemaVersion: number, runtimeVersion: string): void {
|
|
98
|
+
if (!Number.isInteger(schemaVersion) || schemaVersion < 1) {
|
|
99
|
+
throw new TypeError('schemaVersion must be a positive integer');
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
typeof runtimeVersion !== 'string' ||
|
|
103
|
+
runtimeVersion.length === 0 ||
|
|
104
|
+
runtimeVersion.length > 96
|
|
105
|
+
) {
|
|
106
|
+
throw new TypeError('runtimeVersion must be a bounded non-empty string');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create or reuse a Vite-retained resource without top-level await.
|
|
112
|
+
*
|
|
113
|
+
* On an owner change, the returned resource's factory is sequenced behind the
|
|
114
|
+
* prior resource's complete disposal. A failed close therefore becomes the
|
|
115
|
+
* ordinary resource startup error and never constructs a competing owner.
|
|
116
|
+
*/
|
|
117
|
+
export function createViteSyncClientResource(
|
|
118
|
+
hotData: HotData | undefined,
|
|
119
|
+
schemaVersion: number,
|
|
120
|
+
factory: () => SyncClientLike | Promise<SyncClientLike>,
|
|
121
|
+
runtimeVersion = SYNCULAR_REACT_RUNTIME_VERSION,
|
|
122
|
+
): ViteSyncClientResourceBootstrap {
|
|
123
|
+
validateIdentity(schemaVersion, runtimeVersion);
|
|
124
|
+
|
|
125
|
+
const retained = retainedFrom(hotData);
|
|
126
|
+
if (
|
|
127
|
+
retained?.schemaVersion === schemaVersion &&
|
|
128
|
+
retained.runtimeVersion === runtimeVersion
|
|
129
|
+
) {
|
|
130
|
+
return {
|
|
131
|
+
resource: retained.resource,
|
|
132
|
+
handoff: Promise.resolve(),
|
|
133
|
+
schemaChanged: false,
|
|
134
|
+
runtimeChanged: false,
|
|
135
|
+
ownerChanged: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const ownerChanged = retained !== undefined;
|
|
140
|
+
const runtimeChanged =
|
|
141
|
+
retained !== undefined && retained.runtimeVersion !== runtimeVersion;
|
|
142
|
+
const handoff = retained
|
|
143
|
+
? Promise.resolve().then(() => retained.resource.dispose())
|
|
144
|
+
: Promise.resolve();
|
|
145
|
+
// The resource below observes the same rejection. Attaching a handler here
|
|
146
|
+
// also keeps an integration that ignores `handoff` free of an unhandled
|
|
147
|
+
// promise while the provider reports the typed startup failure.
|
|
148
|
+
void handoff.catch(() => undefined);
|
|
149
|
+
const resource = createSyncClientResource(async () => {
|
|
150
|
+
await handoff;
|
|
151
|
+
return factory();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (hotData !== undefined) {
|
|
155
|
+
const record: RetainedSyncularResource = {
|
|
156
|
+
schemaVersion,
|
|
157
|
+
runtimeVersion,
|
|
158
|
+
resource,
|
|
159
|
+
};
|
|
160
|
+
hotData.syncularClientResource = record;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
resource,
|
|
165
|
+
handoff,
|
|
166
|
+
schemaChanged: ownerChanged,
|
|
167
|
+
runtimeChanged,
|
|
168
|
+
ownerChanged,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
75
172
|
/**
|
|
76
173
|
* Reuse a Vite-owned client only while both its captured generated schema and
|
|
77
174
|
* materialized Syncular runtime identity match. On either change, the prior
|
|
@@ -84,16 +181,7 @@ export async function retainViteSyncClientResource(
|
|
|
84
181
|
factory: () => SyncClientLike | Promise<SyncClientLike>,
|
|
85
182
|
runtimeVersion = SYNCULAR_REACT_RUNTIME_VERSION,
|
|
86
183
|
): Promise<ViteSyncClientResourceResult> {
|
|
87
|
-
|
|
88
|
-
throw new TypeError('schemaVersion must be a positive integer');
|
|
89
|
-
}
|
|
90
|
-
if (
|
|
91
|
-
typeof runtimeVersion !== 'string' ||
|
|
92
|
-
runtimeVersion.length === 0 ||
|
|
93
|
-
runtimeVersion.length > 96
|
|
94
|
-
) {
|
|
95
|
-
throw new TypeError('runtimeVersion must be a bounded non-empty string');
|
|
96
|
-
}
|
|
184
|
+
validateIdentity(schemaVersion, runtimeVersion);
|
|
97
185
|
|
|
98
186
|
const retained = retainedFrom(hotData);
|
|
99
187
|
if (
|