@syncular/client 0.30.14 → 0.30.16
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 +26 -0
- package/dist/authority.d.ts +43 -0
- package/dist/authority.js +257 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +18 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -10
- package/dist/worker-entry.d.ts +18 -0
- package/dist/worker-entry.js +44 -41
- package/dist/worker-host.d.ts +3 -0
- package/dist/worker-host.js +8 -0
- package/dist/worker-protocol.d.ts +2 -0
- package/package.json +11 -3
- package/src/authority.ts +414 -0
- package/src/client.ts +24 -27
- package/src/index.ts +5 -0
- package/src/worker-entry.ts +54 -30
- package/src/worker-host.ts +14 -0
- package/src/worker-protocol.ts +2 -0
package/src/worker-entry.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* RPC-driven and auto-driven sync rounds serialize on one queue because
|
|
14
14
|
* the core owns exactly one loop.
|
|
15
15
|
*/
|
|
16
|
+
import type { defineAuthorityReads } from './authority';
|
|
16
17
|
import { SyncClient } from './client';
|
|
17
18
|
import type { ClientDatabase } from './database';
|
|
18
19
|
import {
|
|
@@ -51,6 +52,8 @@ import {
|
|
|
51
52
|
} from './worker-protocol';
|
|
52
53
|
|
|
53
54
|
export interface SyncWorkerOverrides {
|
|
55
|
+
/** Explicit authority reader factory. Required when init declares authorityReads. */
|
|
56
|
+
readonly createAuthorityReads?: typeof defineAuthorityReads;
|
|
54
57
|
/** Database factory indirection: tests inject bun:sqlite here. */
|
|
55
58
|
readonly openDatabase?: (
|
|
56
59
|
config: WorkerInitConfig,
|
|
@@ -238,6 +241,11 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
238
241
|
}
|
|
239
242
|
|
|
240
243
|
async function init(config: WorkerInitConfig): Promise<WorkerInitResult> {
|
|
244
|
+
if (config.authorityReads !== undefined && !overrides.createAuthorityReads)
|
|
245
|
+
throw new ClientSyncError(
|
|
246
|
+
'client.authority_read_forbidden',
|
|
247
|
+
'authorityReads requires startSyncWorker({ createAuthorityReads })',
|
|
248
|
+
);
|
|
241
249
|
if (client !== undefined) {
|
|
242
250
|
throw new ClientSyncError(
|
|
243
251
|
WORKER_FAILED_CODE,
|
|
@@ -323,6 +331,13 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
323
331
|
...(config.encryption !== undefined
|
|
324
332
|
? { encryption: encryptionConfigFromKeyring(config.encryption) }
|
|
325
333
|
: {}),
|
|
334
|
+
...(config.authorityReads !== undefined
|
|
335
|
+
? {
|
|
336
|
+
authorityReads: overrides.createAuthorityReads!(
|
|
337
|
+
config.authorityReads,
|
|
338
|
+
),
|
|
339
|
+
}
|
|
340
|
+
: {}),
|
|
326
341
|
...(config.securityPreflight !== undefined
|
|
327
342
|
? { securityPreflight: config.securityPreflight }
|
|
328
343
|
: {}),
|
|
@@ -389,8 +404,46 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
389
404
|
return { clientId: started.clientId };
|
|
390
405
|
}
|
|
391
406
|
|
|
407
|
+
// Forward every argument to core methods, including authority's zero-argument check.
|
|
408
|
+
const coreMethods = [
|
|
409
|
+
'securityLifecycle',
|
|
410
|
+
'subscribe',
|
|
411
|
+
'unsubscribe',
|
|
412
|
+
'windowState',
|
|
413
|
+
'purgeLocalData',
|
|
414
|
+
'rebootstrapLocalData',
|
|
415
|
+
'previousVersionSnapshot',
|
|
416
|
+
'previousVersionAudit',
|
|
417
|
+
'previousVersionDiscard',
|
|
418
|
+
'query',
|
|
419
|
+
'authoritySnapshot',
|
|
420
|
+
'querySnapshot',
|
|
421
|
+
'statusSnapshot',
|
|
422
|
+
'conflicts',
|
|
423
|
+
'rejections',
|
|
424
|
+
'commitOutcome',
|
|
425
|
+
'commitOutcomes',
|
|
426
|
+
'resolveCommitOutcome',
|
|
427
|
+
'pendingCommits',
|
|
428
|
+
'subscriptions',
|
|
429
|
+
'subscription',
|
|
430
|
+
'connectRealtime',
|
|
431
|
+
'disconnectRealtime',
|
|
432
|
+
'setPresence',
|
|
433
|
+
'presence',
|
|
434
|
+
'uploadBlob',
|
|
435
|
+
'fetchBlob',
|
|
436
|
+
] as const satisfies readonly (keyof WorkerApi & keyof SyncClient)[];
|
|
392
437
|
const api: WorkerApi = {
|
|
393
|
-
|
|
438
|
+
...(Object.fromEntries(
|
|
439
|
+
coreMethods.map((method) => [
|
|
440
|
+
method,
|
|
441
|
+
(...args: unknown[]) => {
|
|
442
|
+
const running = requireClient();
|
|
443
|
+
return Reflect.apply(running[method], running, args);
|
|
444
|
+
},
|
|
445
|
+
]),
|
|
446
|
+
) as Pick<WorkerApi, (typeof coreMethods)[number]>),
|
|
394
447
|
beginSecurityPreflight: async () => {
|
|
395
448
|
if (backgroundTimer !== undefined) clearTimeout(backgroundTimer);
|
|
396
449
|
backgroundTimer = undefined;
|
|
@@ -405,12 +458,9 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
405
458
|
: {}),
|
|
406
459
|
});
|
|
407
460
|
},
|
|
408
|
-
subscribe: (input) => requireClient().subscribe(input),
|
|
409
|
-
unsubscribe: (id) => requireClient().unsubscribe(id),
|
|
410
461
|
setWindow: async (base, units) => {
|
|
411
462
|
await requireClient().setWindowCommand(base, units);
|
|
412
463
|
},
|
|
413
|
-
windowState: (base) => requireClient().windowState(base),
|
|
414
464
|
mutate: (mutations) => {
|
|
415
465
|
return requireClient().mutateCommand(mutations).value;
|
|
416
466
|
},
|
|
@@ -425,13 +475,6 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
425
475
|
);
|
|
426
476
|
return result.value;
|
|
427
477
|
},
|
|
428
|
-
purgeLocalData: (input) => requireClient().purgeLocalData(input),
|
|
429
|
-
rebootstrapLocalData: (input) =>
|
|
430
|
-
requireClient().rebootstrapLocalData(input),
|
|
431
|
-
previousVersionSnapshot: (spec) =>
|
|
432
|
-
requireClient().previousVersionSnapshot(spec),
|
|
433
|
-
previousVersionAudit: () => requireClient().previousVersionAudit(),
|
|
434
|
-
previousVersionDiscard: () => requireClient().previousVersionDiscard(),
|
|
435
478
|
sync: () => {
|
|
436
479
|
const running = requireClient();
|
|
437
480
|
return serializedSync(() => running.sync());
|
|
@@ -440,10 +483,7 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
440
483
|
const running = requireClient();
|
|
441
484
|
return serializedSync(() => running.syncUntilIdle(maxRounds));
|
|
442
485
|
},
|
|
443
|
-
query: (sql, params) => requireClient().query(sql, params),
|
|
444
|
-
querySnapshot: (spec) => requireClient().querySnapshot(spec),
|
|
445
486
|
localRevision: () => requireClient().localRevision,
|
|
446
|
-
statusSnapshot: () => requireClient().statusSnapshot(),
|
|
447
487
|
diagnosticsSnapshot: (request) => {
|
|
448
488
|
const snapshot = requireClient().diagnosticsSnapshot(request);
|
|
449
489
|
return withClientDiagnosticsHost(snapshot, {
|
|
@@ -453,22 +493,6 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
|
|
|
453
493
|
realtime: snapshot.host.realtime,
|
|
454
494
|
});
|
|
455
495
|
},
|
|
456
|
-
conflicts: () => requireClient().conflicts(),
|
|
457
|
-
rejections: () => requireClient().rejections(),
|
|
458
|
-
commitOutcome: (clientCommitId) =>
|
|
459
|
-
requireClient().commitOutcome(clientCommitId),
|
|
460
|
-
commitOutcomes: (query) => requireClient().commitOutcomes(query),
|
|
461
|
-
resolveCommitOutcome: (input) =>
|
|
462
|
-
requireClient().resolveCommitOutcome(input),
|
|
463
|
-
pendingCommits: () => requireClient().pendingCommits(),
|
|
464
|
-
subscriptions: () => requireClient().subscriptions(),
|
|
465
|
-
subscription: (id) => requireClient().subscription(id),
|
|
466
|
-
connectRealtime: () => requireClient().connectRealtime(),
|
|
467
|
-
disconnectRealtime: () => requireClient().disconnectRealtime(),
|
|
468
|
-
setPresence: (scopeKey, doc) => requireClient().setPresence(scopeKey, doc),
|
|
469
|
-
presence: (scopeKey) => requireClient().presence(scopeKey),
|
|
470
|
-
uploadBlob: (bytes, options) => requireClient().uploadBlob(bytes, options),
|
|
471
|
-
fetchBlob: (blobIdOrRef) => requireClient().fetchBlob(blobIdOrRef),
|
|
472
496
|
setHeaders: (next) => {
|
|
473
497
|
headers = next;
|
|
474
498
|
},
|
package/src/worker-host.ts
CHANGED
|
@@ -192,6 +192,8 @@ export interface SyncClientHandleConfig {
|
|
|
192
192
|
readonly headers?: Readonly<Record<string, string>>;
|
|
193
193
|
/** Structured-clone-safe E2EE keyring installed only in the leader worker. */
|
|
194
194
|
readonly encryption?: EncryptionKeyringConfig;
|
|
195
|
+
/** Fixed plain authority reads permitted before security activation. */
|
|
196
|
+
readonly authorityReads?: import('./authority').AuthorityReadPolicy;
|
|
195
197
|
/** Open the worker-owned replica behind the fail-closed security gate. */
|
|
196
198
|
readonly securityPreflight?: boolean;
|
|
197
199
|
readonly transportEnabled?: boolean;
|
|
@@ -656,6 +658,15 @@ export class SyncClientHandle implements PromiseMethods<WorkerApi> {
|
|
|
656
658
|
return this.#call('query', [sql, params]);
|
|
657
659
|
}
|
|
658
660
|
|
|
661
|
+
authoritySnapshot(): Promise<import('./authority').AuthoritySnapshot> {
|
|
662
|
+
if (arguments.length)
|
|
663
|
+
throw new ClientSyncError(
|
|
664
|
+
'client.authority_read_forbidden',
|
|
665
|
+
'authoritySnapshot accepts no arguments',
|
|
666
|
+
);
|
|
667
|
+
return this.#call('authoritySnapshot', []);
|
|
668
|
+
}
|
|
669
|
+
|
|
659
670
|
querySnapshot<Row = SqlRow>(
|
|
660
671
|
spec: QueryReadSpec,
|
|
661
672
|
): Promise<QuerySnapshot<Row>> {
|
|
@@ -951,6 +962,9 @@ function buildInitConfig(
|
|
|
951
962
|
...(config.transportEnabled !== undefined
|
|
952
963
|
? { transportEnabled: config.transportEnabled }
|
|
953
964
|
: {}),
|
|
965
|
+
...(config.authorityReads !== undefined
|
|
966
|
+
? { authorityReads: config.authorityReads.declarations }
|
|
967
|
+
: {}),
|
|
954
968
|
...(config.securityPreflight !== undefined
|
|
955
969
|
? { securityPreflight: config.securityPreflight }
|
|
956
970
|
: {}),
|
package/src/worker-protocol.ts
CHANGED
|
@@ -111,6 +111,7 @@ export interface WorkerInitConfig {
|
|
|
111
111
|
/** Portable raw keyring installed inside the worker-owned client core. */
|
|
112
112
|
readonly encryption?: EncryptionKeyringConfig;
|
|
113
113
|
/** Open the worker-owned replica behind the fail-closed security gate. */
|
|
114
|
+
readonly authorityReads?: readonly import('./authority').AuthorityReadDeclaration[];
|
|
114
115
|
readonly securityPreflight?: boolean;
|
|
115
116
|
readonly transportEnabled?: boolean;
|
|
116
117
|
readonly clientId?: string;
|
|
@@ -186,6 +187,7 @@ export interface WorkerApi extends Omit<
|
|
|
186
187
|
sync(): Promise<SyncSummary>;
|
|
187
188
|
syncUntilIdle(maxRounds?: number): Promise<SyncSummary>;
|
|
188
189
|
query(sql: string, params?: readonly SqlValue[]): SqlRow[];
|
|
190
|
+
authoritySnapshot(): import('./authority').AuthoritySnapshot;
|
|
189
191
|
querySnapshot(spec: QueryReadSpec): QuerySnapshot;
|
|
190
192
|
localRevision(): LocalRevision;
|
|
191
193
|
pendingCommits(): OutboxCommit[];
|