@syncular/client 0.15.11 → 0.15.13
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/client.js +2 -4
- package/dist/state.d.ts +3 -2
- package/dist/state.js +23 -13
- package/package.json +3 -3
- package/src/client.ts +6 -4
- package/src/state.ts +25 -13
package/dist/client.js
CHANGED
|
@@ -181,9 +181,7 @@ export class SyncClient {
|
|
|
181
181
|
this.#detectAndResetSchema();
|
|
182
182
|
// A registration remains app intent only while its table exists in the
|
|
183
183
|
// running schema. Removed-table registrations would poison every pull.
|
|
184
|
-
this.#db
|
|
185
|
-
pruneUnknownSubscriptions(this.#db, new Set(this.#schema.tables.keys()));
|
|
186
|
-
});
|
|
184
|
+
const subscriptions = pruneUnknownSubscriptions(this.#db, loadSubscriptions(this.#db), new Set(this.#schema.tables.keys()));
|
|
187
185
|
this.#started = true;
|
|
188
186
|
// A persisted active subscription needs one catch-up round on every open:
|
|
189
187
|
// realtime only covers changes after the socket connects, and an
|
|
@@ -193,7 +191,7 @@ export class SyncClient {
|
|
|
193
191
|
// an application-issued sync() call.
|
|
194
192
|
const startupWork = this.#schemaFloor === undefined &&
|
|
195
193
|
(listOutbox(this.#db).length > 0 ||
|
|
196
|
-
|
|
194
|
+
subscriptions.some((sub) => sub.status === 'active'));
|
|
197
195
|
if (startupWork) {
|
|
198
196
|
this.#needsPull = true;
|
|
199
197
|
this.#config.onSyncNeeded?.('startup');
|
package/dist/state.d.ts
CHANGED
|
@@ -42,9 +42,10 @@ export declare function resetSubscriptionsForBump(db: ClientDatabase): void;
|
|
|
42
42
|
* Remove registrations whose table no longer exists in the running schema.
|
|
43
43
|
* Keeping one would make every subsequent pull fail with
|
|
44
44
|
* `sync.unknown_table`. Window bookkeeping belongs to the registration and
|
|
45
|
-
* is removed with it.
|
|
45
|
+
* is removed with it. The caller supplies its startup subscription snapshot
|
|
46
|
+
* so pruning and startup-work detection stay a single read.
|
|
46
47
|
*/
|
|
47
|
-
export declare function pruneUnknownSubscriptions(db: ClientDatabase, tableNames: ReadonlySet<string>):
|
|
48
|
+
export declare function pruneUnknownSubscriptions(db: ClientDatabase, subscriptions: readonly SubscriptionRecord[], tableNames: ReadonlySet<string>): SubscriptionRecord[];
|
|
48
49
|
export declare function getMeta(db: ClientDatabase, key: string): string | undefined;
|
|
49
50
|
export declare function setMeta(db: ClientDatabase, key: string, value: string): void;
|
|
50
51
|
/** Read the durable client-local observer revision (SPEC §7.5). */
|
package/dist/state.js
CHANGED
|
@@ -69,20 +69,30 @@ export function resetSubscriptionsForBump(db) {
|
|
|
69
69
|
* Remove registrations whose table no longer exists in the running schema.
|
|
70
70
|
* Keeping one would make every subsequent pull fail with
|
|
71
71
|
* `sync.unknown_table`. Window bookkeeping belongs to the registration and
|
|
72
|
-
* is removed with it.
|
|
72
|
+
* is removed with it. The caller supplies its startup subscription snapshot
|
|
73
|
+
* so pruning and startup-work detection stay a single read.
|
|
73
74
|
*/
|
|
74
|
-
export function pruneUnknownSubscriptions(db, tableNames) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
.filter((
|
|
78
|
-
.map((
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
export function pruneUnknownSubscriptions(db, subscriptions, tableNames) {
|
|
76
|
+
const retained = subscriptions.filter((record) => tableNames.has(record.table));
|
|
77
|
+
const staleIds = subscriptions
|
|
78
|
+
.filter((record) => !tableNames.has(record.table))
|
|
79
|
+
.map((record) => record.id);
|
|
80
|
+
// A redundant subscription scan plus an empty write transaction cut the
|
|
81
|
+
// fresh-client bootstrap lane by more than half on bun:sqlite. The supplied
|
|
82
|
+
// snapshot and this branch are synchronous, so no application work can
|
|
83
|
+
// interleave before a real pruning transaction begins.
|
|
84
|
+
if (staleIds.length === 0)
|
|
85
|
+
return retained;
|
|
86
|
+
db.transaction(() => {
|
|
87
|
+
for (const id of staleIds) {
|
|
88
|
+
db.exec('DELETE FROM _syncular_windows WHERE sub_id = ?', [id]);
|
|
89
|
+
db.exec('DELETE FROM _syncular_window_pending_evict WHERE sub_id = ?', [
|
|
90
|
+
id,
|
|
91
|
+
]);
|
|
92
|
+
db.exec('DELETE FROM _syncular_subscriptions WHERE id = ?', [id]);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return retained;
|
|
86
96
|
}
|
|
87
97
|
export function getMeta(db, key) {
|
|
88
98
|
const row = db.query('SELECT value FROM _syncular_meta WHERE key = ?', [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.13",
|
|
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",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
84
|
-
"@syncular/core": "0.15.
|
|
84
|
+
"@syncular/core": "0.15.13"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"better-sqlite3": ">=11"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@syncular/server": "0.15.
|
|
95
|
+
"@syncular/server": "0.15.13",
|
|
96
96
|
"@types/better-sqlite3": "^7.6.13",
|
|
97
97
|
"better-sqlite3": "^12.11.1"
|
|
98
98
|
}
|
package/src/client.ts
CHANGED
|
@@ -597,9 +597,11 @@ export class SyncClient {
|
|
|
597
597
|
this.#detectAndResetSchema();
|
|
598
598
|
// A registration remains app intent only while its table exists in the
|
|
599
599
|
// running schema. Removed-table registrations would poison every pull.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
const subscriptions = pruneUnknownSubscriptions(
|
|
601
|
+
this.#db,
|
|
602
|
+
loadSubscriptions(this.#db),
|
|
603
|
+
new Set(this.#schema.tables.keys()),
|
|
604
|
+
);
|
|
603
605
|
this.#started = true;
|
|
604
606
|
// A persisted active subscription needs one catch-up round on every open:
|
|
605
607
|
// realtime only covers changes after the socket connects, and an
|
|
@@ -610,7 +612,7 @@ export class SyncClient {
|
|
|
610
612
|
const startupWork =
|
|
611
613
|
this.#schemaFloor === undefined &&
|
|
612
614
|
(listOutbox(this.#db).length > 0 ||
|
|
613
|
-
|
|
615
|
+
subscriptions.some((sub) => sub.status === 'active'));
|
|
614
616
|
if (startupWork) {
|
|
615
617
|
this.#needsPull = true;
|
|
616
618
|
this.#config.onSyncNeeded?.('startup');
|
package/src/state.ts
CHANGED
|
@@ -119,23 +119,35 @@ export function resetSubscriptionsForBump(db: ClientDatabase): void {
|
|
|
119
119
|
* Remove registrations whose table no longer exists in the running schema.
|
|
120
120
|
* Keeping one would make every subsequent pull fail with
|
|
121
121
|
* `sync.unknown_table`. Window bookkeeping belongs to the registration and
|
|
122
|
-
* is removed with it.
|
|
122
|
+
* is removed with it. The caller supplies its startup subscription snapshot
|
|
123
|
+
* so pruning and startup-work detection stay a single read.
|
|
123
124
|
*/
|
|
124
125
|
export function pruneUnknownSubscriptions(
|
|
125
126
|
db: ClientDatabase,
|
|
127
|
+
subscriptions: readonly SubscriptionRecord[],
|
|
126
128
|
tableNames: ReadonlySet<string>,
|
|
127
|
-
):
|
|
128
|
-
const
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
): SubscriptionRecord[] {
|
|
130
|
+
const retained = subscriptions.filter((record) =>
|
|
131
|
+
tableNames.has(record.table),
|
|
132
|
+
);
|
|
133
|
+
const staleIds = subscriptions
|
|
134
|
+
.filter((record) => !tableNames.has(record.table))
|
|
135
|
+
.map((record) => record.id);
|
|
136
|
+
// A redundant subscription scan plus an empty write transaction cut the
|
|
137
|
+
// fresh-client bootstrap lane by more than half on bun:sqlite. The supplied
|
|
138
|
+
// snapshot and this branch are synchronous, so no application work can
|
|
139
|
+
// interleave before a real pruning transaction begins.
|
|
140
|
+
if (staleIds.length === 0) return retained;
|
|
141
|
+
db.transaction(() => {
|
|
142
|
+
for (const id of staleIds) {
|
|
143
|
+
db.exec('DELETE FROM _syncular_windows WHERE sub_id = ?', [id]);
|
|
144
|
+
db.exec('DELETE FROM _syncular_window_pending_evict WHERE sub_id = ?', [
|
|
145
|
+
id,
|
|
146
|
+
]);
|
|
147
|
+
db.exec('DELETE FROM _syncular_subscriptions WHERE id = ?', [id]);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return retained;
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
export function getMeta(db: ClientDatabase, key: string): string | undefined {
|