@syncular/client 0.35.0 → 0.36.0
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.d.ts +8 -0
- package/dist/client.js +44 -26
- package/dist/worker-entry.js +1 -0
- package/dist/worker-host.d.ts +1 -0
- package/dist/worker-host.js +3 -0
- package/dist/worker-protocol.d.ts +1 -0
- package/package.json +3 -3
- package/src/client.ts +52 -34
- package/src/worker-entry.ts +1 -0
- package/src/worker-host.ts +4 -0
- package/src/worker-protocol.ts +1 -0
package/dist/client.d.ts
CHANGED
|
@@ -553,6 +553,14 @@ export declare class SyncClient {
|
|
|
553
553
|
subscription(id: string): SubscriptionRecord | undefined;
|
|
554
554
|
pendingCommits(): OutboxCommit[];
|
|
555
555
|
subscribe(input: SubscribeInput): void;
|
|
556
|
+
/**
|
|
557
|
+
* Register several subscriptions in one local transaction: the same rules
|
|
558
|
+
* as `subscribe` for each input, and one commit for all of them (on OPFS a
|
|
559
|
+
* commit costs milliseconds, so a cold start with hundreds of
|
|
560
|
+
* registrations pays it once). An error rolls back every registration of
|
|
561
|
+
* the call.
|
|
562
|
+
*/
|
|
563
|
+
subscribeMany(inputs: readonly SubscribeInput[]): void;
|
|
556
564
|
unsubscribe(id: string): void;
|
|
557
565
|
/**
|
|
558
566
|
* Set the live window units for a base (§4.8): a value-sharded family of
|
package/dist/client.js
CHANGED
|
@@ -1824,34 +1824,52 @@ export class SyncClient {
|
|
|
1824
1824
|
}
|
|
1825
1825
|
// -- subscriptions ----------------------------------------------------------
|
|
1826
1826
|
subscribe(input) {
|
|
1827
|
+
this.subscribeMany([input]);
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Register several subscriptions in one local transaction: the same rules
|
|
1831
|
+
* as `subscribe` for each input, and one commit for all of them (on OPFS a
|
|
1832
|
+
* commit costs milliseconds, so a cold start with hundreds of
|
|
1833
|
+
* registrations pays it once). An error rolls back every registration of
|
|
1834
|
+
* the call.
|
|
1835
|
+
*/
|
|
1836
|
+
subscribeMany(inputs) {
|
|
1827
1837
|
this.#requireActive();
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1838
|
+
let registered = false;
|
|
1839
|
+
this.#db.transaction(() => {
|
|
1840
|
+
for (const input of inputs) {
|
|
1841
|
+
if (!this.#schema.tables.has(input.table)) {
|
|
1842
|
+
throw new ClientSyncError('sync.unknown_table', `subscribe: unknown local table ${JSON.stringify(input.table)}`);
|
|
1843
|
+
}
|
|
1844
|
+
const existing = getSubscription(this.#db, input.id);
|
|
1845
|
+
if (existing !== undefined) {
|
|
1846
|
+
const sameIntent = existing.table === input.table &&
|
|
1847
|
+
canonicalScopeJson(existing.scopes) ===
|
|
1848
|
+
canonicalScopeJson(input.scopes) &&
|
|
1849
|
+
existing.params === input.params;
|
|
1850
|
+
if (!sameIntent) {
|
|
1851
|
+
throw new ClientSyncError('client.subscription_intent_mismatch', 'subscription id already names different table, scopes, or params');
|
|
1852
|
+
}
|
|
1853
|
+
// §3.3: a revoked registration keeps its id and intent but no
|
|
1854
|
+
// progress. Subscribing again with the same intent re-arms it, so
|
|
1855
|
+
// the next pull resolves its scopes from scratch. A `failed`
|
|
1856
|
+
// registration is a configuration error and stays put.
|
|
1857
|
+
if (existing.status !== 'revoked')
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
saveSubscription(this.#db, definedObject({
|
|
1861
|
+
id: input.id,
|
|
1862
|
+
table: input.table,
|
|
1863
|
+
scopes: input.scopes,
|
|
1864
|
+
params: input.params,
|
|
1865
|
+
cursor: -1,
|
|
1866
|
+
status: 'active',
|
|
1867
|
+
}));
|
|
1868
|
+
registered = true;
|
|
1839
1869
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
// configuration error and stays put.
|
|
1844
|
-
if (existing.status !== 'revoked')
|
|
1845
|
-
return;
|
|
1846
|
-
}
|
|
1847
|
-
saveSubscription(this.#db, definedObject({
|
|
1848
|
-
id: input.id,
|
|
1849
|
-
table: input.table,
|
|
1850
|
-
scopes: input.scopes,
|
|
1851
|
-
params: input.params,
|
|
1852
|
-
cursor: -1,
|
|
1853
|
-
status: 'active',
|
|
1854
|
-
}));
|
|
1870
|
+
});
|
|
1871
|
+
if (!registered)
|
|
1872
|
+
return;
|
|
1855
1873
|
this.#setSyncNeeded(true);
|
|
1856
1874
|
this.#emitSyncIntent({ kind: 'interactive' });
|
|
1857
1875
|
this.#emitDiagnostics();
|
package/dist/worker-entry.js
CHANGED
package/dist/worker-host.d.ts
CHANGED
|
@@ -224,6 +224,7 @@ export declare class SyncClientHandle implements PromiseMethods<WorkerApi> {
|
|
|
224
224
|
onRoleChange(listener: (role: HandleRole) => void): () => void;
|
|
225
225
|
onLeadershipChange(listener: (state: LeadershipState) => void): () => void;
|
|
226
226
|
subscribe(input: SubscribeInput): Promise<void>;
|
|
227
|
+
subscribeMany(inputs: readonly SubscribeInput[]): Promise<void>;
|
|
227
228
|
securityLifecycle(): Promise<SecurityLifecycle>;
|
|
228
229
|
beginSecurityPreflight(): Promise<void>;
|
|
229
230
|
activateSecurity(options?: WorkerSecurityActivation): Promise<void>;
|
package/dist/worker-host.js
CHANGED
|
@@ -283,6 +283,9 @@ export class SyncClientHandle {
|
|
|
283
283
|
subscribe(input) {
|
|
284
284
|
return this.#call('subscribe', [input]);
|
|
285
285
|
}
|
|
286
|
+
subscribeMany(inputs) {
|
|
287
|
+
return this.#call('subscribeMany', [inputs]);
|
|
288
|
+
}
|
|
286
289
|
securityLifecycle() {
|
|
287
290
|
return this.#call('securityLifecycle', []);
|
|
288
291
|
}
|
|
@@ -113,6 +113,7 @@ export interface WorkerApi extends Omit<ClientSnapshotMethods, 'querySnapshot'>
|
|
|
113
113
|
beginSecurityPreflight(): Promise<void>;
|
|
114
114
|
activateSecurity(options?: WorkerSecurityActivation): Promise<void>;
|
|
115
115
|
subscribe(input: SubscribeInput): void;
|
|
116
|
+
subscribeMany(inputs: readonly SubscribeInput[]): void;
|
|
116
117
|
unsubscribe(id: string): void;
|
|
117
118
|
/** §4.8 windowed subscriptions: set the live units for a window base. */
|
|
118
119
|
setWindow(base: WindowBase, units: readonly string[]): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
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",
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
100
|
-
"@syncular/core": "0.
|
|
100
|
+
"@syncular/core": "0.36.0"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@syncular/server": "0.
|
|
103
|
+
"@syncular/server": "0.36.0",
|
|
104
104
|
"playwright": "1.63.0",
|
|
105
105
|
"@electric-sql/pglite": "^0.5.4"
|
|
106
106
|
}
|
package/src/client.ts
CHANGED
|
@@ -2894,43 +2894,61 @@ export class SyncClient {
|
|
|
2894
2894
|
// -- subscriptions ----------------------------------------------------------
|
|
2895
2895
|
|
|
2896
2896
|
subscribe(input: SubscribeInput): void {
|
|
2897
|
+
this.subscribeMany([input]);
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
/**
|
|
2901
|
+
* Register several subscriptions in one local transaction: the same rules
|
|
2902
|
+
* as `subscribe` for each input, and one commit for all of them (on OPFS a
|
|
2903
|
+
* commit costs milliseconds, so a cold start with hundreds of
|
|
2904
|
+
* registrations pays it once). An error rolls back every registration of
|
|
2905
|
+
* the call.
|
|
2906
|
+
*/
|
|
2907
|
+
subscribeMany(inputs: readonly SubscribeInput[]): void {
|
|
2897
2908
|
this.#requireActive();
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
existing
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2909
|
+
let registered = false;
|
|
2910
|
+
this.#db.transaction(() => {
|
|
2911
|
+
for (const input of inputs) {
|
|
2912
|
+
if (!this.#schema.tables.has(input.table)) {
|
|
2913
|
+
throw new ClientSyncError(
|
|
2914
|
+
'sync.unknown_table',
|
|
2915
|
+
`subscribe: unknown local table ${JSON.stringify(input.table)}`,
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
const existing = getSubscription(this.#db, input.id);
|
|
2919
|
+
if (existing !== undefined) {
|
|
2920
|
+
const sameIntent =
|
|
2921
|
+
existing.table === input.table &&
|
|
2922
|
+
canonicalScopeJson(existing.scopes) ===
|
|
2923
|
+
canonicalScopeJson(input.scopes) &&
|
|
2924
|
+
existing.params === input.params;
|
|
2925
|
+
if (!sameIntent) {
|
|
2926
|
+
throw new ClientSyncError(
|
|
2927
|
+
'client.subscription_intent_mismatch',
|
|
2928
|
+
'subscription id already names different table, scopes, or params',
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
// §3.3: a revoked registration keeps its id and intent but no
|
|
2932
|
+
// progress. Subscribing again with the same intent re-arms it, so
|
|
2933
|
+
// the next pull resolves its scopes from scratch. A `failed`
|
|
2934
|
+
// registration is a configuration error and stays put.
|
|
2935
|
+
if (existing.status !== 'revoked') continue;
|
|
2936
|
+
}
|
|
2937
|
+
saveSubscription(
|
|
2938
|
+
this.#db,
|
|
2939
|
+
definedObject({
|
|
2940
|
+
id: input.id,
|
|
2941
|
+
table: input.table,
|
|
2942
|
+
scopes: input.scopes,
|
|
2943
|
+
params: input.params,
|
|
2944
|
+
cursor: -1,
|
|
2945
|
+
status: 'active',
|
|
2946
|
+
}),
|
|
2915
2947
|
);
|
|
2948
|
+
registered = true;
|
|
2916
2949
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
// resolves its scopes from scratch. A `failed` registration is a
|
|
2920
|
-
// configuration error and stays put.
|
|
2921
|
-
if (existing.status !== 'revoked') return;
|
|
2922
|
-
}
|
|
2923
|
-
saveSubscription(
|
|
2924
|
-
this.#db,
|
|
2925
|
-
definedObject({
|
|
2926
|
-
id: input.id,
|
|
2927
|
-
table: input.table,
|
|
2928
|
-
scopes: input.scopes,
|
|
2929
|
-
params: input.params,
|
|
2930
|
-
cursor: -1,
|
|
2931
|
-
status: 'active',
|
|
2932
|
-
}),
|
|
2933
|
-
);
|
|
2950
|
+
});
|
|
2951
|
+
if (!registered) return;
|
|
2934
2952
|
this.#setSyncNeeded(true);
|
|
2935
2953
|
this.#emitSyncIntent({ kind: 'interactive' });
|
|
2936
2954
|
this.#emitDiagnostics();
|
package/src/worker-entry.ts
CHANGED
package/src/worker-host.ts
CHANGED
|
@@ -576,6 +576,10 @@ export class SyncClientHandle implements PromiseMethods<WorkerApi> {
|
|
|
576
576
|
return this.#call('subscribe', [input]);
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
+
subscribeMany(inputs: readonly SubscribeInput[]): Promise<void> {
|
|
580
|
+
return this.#call('subscribeMany', [inputs]);
|
|
581
|
+
}
|
|
582
|
+
|
|
579
583
|
securityLifecycle(): Promise<SecurityLifecycle> {
|
|
580
584
|
return this.#call('securityLifecycle', []);
|
|
581
585
|
}
|
package/src/worker-protocol.ts
CHANGED
|
@@ -158,6 +158,7 @@ export interface WorkerApi extends Omit<
|
|
|
158
158
|
beginSecurityPreflight(): Promise<void>;
|
|
159
159
|
activateSecurity(options?: WorkerSecurityActivation): Promise<void>;
|
|
160
160
|
subscribe(input: SubscribeInput): void;
|
|
161
|
+
subscribeMany(inputs: readonly SubscribeInput[]): void;
|
|
161
162
|
unsubscribe(id: string): void;
|
|
162
163
|
/** §4.8 windowed subscriptions: set the live units for a window base. */
|
|
163
164
|
setWindow(base: WindowBase, units: readonly string[]): Promise<void>;
|