@syncular/client 0.15.43 → 0.15.44
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/dist/worker-host.d.ts +8 -0
- package/dist/worker-host.js +25 -0
- package/package.json +3 -3
- package/src/worker-host.ts +26 -0
package/dist/worker-host.d.ts
CHANGED
|
@@ -157,6 +157,14 @@ export declare class SyncClientHandle {
|
|
|
157
157
|
leadershipListeners?: Set<(state: LeadershipState) => void>;
|
|
158
158
|
leadership?: LeadershipState;
|
|
159
159
|
});
|
|
160
|
+
/**
|
|
161
|
+
* @internal — whether `close()` has run.
|
|
162
|
+
*
|
|
163
|
+
* A follower keeps a blocking `acquire` outstanding for the whole time it is a
|
|
164
|
+
* follower, so its promotion can fire long after the application has discarded
|
|
165
|
+
* the handle. The promotion path consults this before opening anything.
|
|
166
|
+
*/
|
|
167
|
+
get __isClosed(): boolean;
|
|
160
168
|
/** @internal — swap this handle from follower to leader (promotion). */
|
|
161
169
|
__becomeLeader(core: LeaderCore): void;
|
|
162
170
|
/** @internal — apply a follower reachability snapshot in place. */
|
package/dist/worker-host.js
CHANGED
|
@@ -113,6 +113,16 @@ export class SyncClientHandle {
|
|
|
113
113
|
onInvalidate: (listener) => this.onInvalidate(listener),
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* @internal — whether `close()` has run.
|
|
118
|
+
*
|
|
119
|
+
* A follower keeps a blocking `acquire` outstanding for the whole time it is a
|
|
120
|
+
* follower, so its promotion can fire long after the application has discarded
|
|
121
|
+
* the handle. The promotion path consults this before opening anything.
|
|
122
|
+
*/
|
|
123
|
+
get __isClosed() {
|
|
124
|
+
return this.#closed;
|
|
125
|
+
}
|
|
116
126
|
/** @internal — swap this handle from follower to leader (promotion). */
|
|
117
127
|
__becomeLeader(core) {
|
|
118
128
|
this.#follower?.close();
|
|
@@ -652,6 +662,14 @@ async function bootFollower(config, lockName, lock, parts) {
|
|
|
652
662
|
await lease.release();
|
|
653
663
|
return;
|
|
654
664
|
}
|
|
665
|
+
if (handle.__isClosed) {
|
|
666
|
+
// The application discarded this handle while it was queued for
|
|
667
|
+
// leadership. Promoting now would open a database nobody is holding and
|
|
668
|
+
// then keep the lock forever, so no other tab could ever take over.
|
|
669
|
+
// Release instead, and let the next waiter have it.
|
|
670
|
+
await lease.release();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
655
673
|
// The follower saw the departing leader's epoch; the new leader must
|
|
656
674
|
// strictly exceed it so stale replies/events are discarded everywhere.
|
|
657
675
|
const nextEpoch = follower.maxEpochSeen + 1;
|
|
@@ -681,6 +699,13 @@ async function bootFollower(config, lockName, lock, parts) {
|
|
|
681
699
|
}
|
|
682
700
|
: {}),
|
|
683
701
|
});
|
|
702
|
+
if (handle.__isClosed) {
|
|
703
|
+
// Closed while the worker was starting. `close()` already ran and found
|
|
704
|
+
// no core to shut down, so this one would leak: tear it down here rather
|
|
705
|
+
// than hand it to a discarded handle. `close(true)` releases the lease.
|
|
706
|
+
await core.close(true);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
684
709
|
handle.__becomeLeader(core);
|
|
685
710
|
}
|
|
686
711
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.44",
|
|
4
4
|
"description": "Syncular TypeScript client core — offline-first sync over SQLite (WASM/OPFS, Bun, Node)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
92
|
-
"@syncular/core": "0.15.
|
|
92
|
+
"@syncular/core": "0.15.44"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
95
|
"better-sqlite3": ">=11"
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@syncular/server": "0.15.
|
|
103
|
+
"@syncular/server": "0.15.44",
|
|
104
104
|
"@types/better-sqlite3": "^7.6.13",
|
|
105
105
|
"better-sqlite3": "^12.11.1"
|
|
106
106
|
}
|
package/src/worker-host.ts
CHANGED
|
@@ -334,6 +334,17 @@ export class SyncClientHandle {
|
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/**
|
|
338
|
+
* @internal — whether `close()` has run.
|
|
339
|
+
*
|
|
340
|
+
* A follower keeps a blocking `acquire` outstanding for the whole time it is a
|
|
341
|
+
* follower, so its promotion can fire long after the application has discarded
|
|
342
|
+
* the handle. The promotion path consults this before opening anything.
|
|
343
|
+
*/
|
|
344
|
+
get __isClosed(): boolean {
|
|
345
|
+
return this.#closed;
|
|
346
|
+
}
|
|
347
|
+
|
|
337
348
|
/** @internal — swap this handle from follower to leader (promotion). */
|
|
338
349
|
__becomeLeader(core: LeaderCore): void {
|
|
339
350
|
this.#follower?.close();
|
|
@@ -1020,6 +1031,14 @@ async function bootFollower(
|
|
|
1020
1031
|
await lease.release();
|
|
1021
1032
|
return;
|
|
1022
1033
|
}
|
|
1034
|
+
if (handle.__isClosed) {
|
|
1035
|
+
// The application discarded this handle while it was queued for
|
|
1036
|
+
// leadership. Promoting now would open a database nobody is holding and
|
|
1037
|
+
// then keep the lock forever, so no other tab could ever take over.
|
|
1038
|
+
// Release instead, and let the next waiter have it.
|
|
1039
|
+
await lease.release();
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1023
1042
|
// The follower saw the departing leader's epoch; the new leader must
|
|
1024
1043
|
// strictly exceed it so stale replies/events are discarded everywhere.
|
|
1025
1044
|
const nextEpoch = follower.maxEpochSeen + 1;
|
|
@@ -1058,6 +1077,13 @@ async function bootFollower(
|
|
|
1058
1077
|
}
|
|
1059
1078
|
: {}),
|
|
1060
1079
|
});
|
|
1080
|
+
if (handle.__isClosed) {
|
|
1081
|
+
// Closed while the worker was starting. `close()` already ran and found
|
|
1082
|
+
// no core to shut down, so this one would leak: tear it down here rather
|
|
1083
|
+
// than hand it to a discarded handle. `close(true)` releases the lease.
|
|
1084
|
+
await core.close(true);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1061
1087
|
handle.__becomeLeader(core);
|
|
1062
1088
|
} catch {
|
|
1063
1089
|
// Promotion failed to spawn a worker — release so the next tab tries.
|