@syncular/tauri 0.30.5 → 0.30.7

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 CHANGED
@@ -117,8 +117,11 @@ still owns OS secure-store deletion and any key buffers it supplied.
117
117
 
118
118
  Runtime `setHeaders()` is an active-session operation and is rejected during
119
119
  preflight at both the JavaScript and native command boundaries. Supply bootstrap
120
- headers through trusted plugin configuration; rotate them only after successful
121
- activation.
120
+ headers through trusted plugin configuration or `activateSecurity({ headers })`;
121
+ the latter installs the current bearer before the startup round. Rotate them
122
+ with `setHeaders()` only after successful activation. Closed clients reject data
123
+ and control calls with `client.closed` before testing preflight; listener
124
+ registration and local progress reads throw that code. `close()` is idempotent.
122
125
 
123
126
  ## Privacy-safe diagnostics
124
127
 
package/dist/index.d.ts CHANGED
@@ -184,7 +184,8 @@ export declare class TauriSyncClient implements PromiseMethods<ClientSnapshotMet
184
184
  /** The error a `{error}` reply surfaces (mirrors the web-client `ClientSyncError`). */
185
185
  export declare class TauriSyncError extends Error {
186
186
  readonly code: string;
187
- constructor(code: string, message: string);
187
+ readonly details: Readonly<Record<string, unknown>> | undefined;
188
+ constructor(code: string, message: string, details?: Readonly<Record<string, unknown>>);
188
189
  }
189
190
  /**
190
191
  * Construct the bridge and issue the native `create` (opening/attaching the
package/dist/index.js CHANGED
@@ -160,9 +160,7 @@ export class TauriSyncClient {
160
160
  }
161
161
  /** Dispatch a `syncular_command` and unwrap `{result}` / throw on `{error}`. */
162
162
  async #command(method, params) {
163
- if (this.#closed) {
164
- throw new TauriSyncError('client.closed', 'the Tauri sync client is closed');
165
- }
163
+ this.#requireOpen();
166
164
  if (this.#securityLifecycle === 'preflight' &&
167
165
  ![
168
166
  'securityLifecycle',
@@ -178,14 +176,20 @@ export class TauriSyncClient {
178
176
  }
179
177
  const reply = await this.#tauri.invoke(`${PLUGIN}syncular_command`, { command: { method, params } });
180
178
  if (reply.error !== undefined) {
181
- throw new TauriSyncError(reply.error.code, reply.error.message);
179
+ throw new TauriSyncError(reply.error.code, reply.error.message, reply.error.details ?? undefined);
182
180
  }
183
181
  return reply.result;
184
182
  }
185
183
  #throwSecurityPreflight() {
186
184
  throw new TauriSyncError(SECURITY_PREFLIGHT_REQUIRED_CODE, 'the local replica is in security preflight; complete quarantine checks and call activateSecurity before accessing protected data');
187
185
  }
186
+ #requireOpen() {
187
+ if (this.#closed) {
188
+ throw new TauriSyncError('client.closed', 'the Tauri sync client is closed');
189
+ }
190
+ }
188
191
  #requireActive() {
192
+ this.#requireOpen();
189
193
  if (this.#securityLifecycle === 'preflight')
190
194
  this.#throwSecurityPreflight();
191
195
  }
@@ -254,7 +258,8 @@ export class TauriSyncClient {
254
258
  }
255
259
  }
256
260
  // -- SyncClientLike --------------------------------------------------------
257
- securityLifecycle() {
261
+ async securityLifecycle() {
262
+ this.#requireOpen();
258
263
  return Promise.resolve(this.#securityLifecycle);
259
264
  }
260
265
  beginSecurityPreflight() {
@@ -284,6 +289,7 @@ export class TauriSyncClient {
284
289
  * credentials (the direct `setHeaders` path stays refused while gated).
285
290
  */
286
291
  async activateSecurity(options = {}) {
292
+ this.#requireOpen();
287
293
  if (this.#securityLifecycle === 'active') {
288
294
  throw new TauriSyncError('sync.invalid_request', 'activateSecurity requires the client to be in security preflight');
289
295
  }
@@ -297,20 +303,25 @@ export class TauriSyncClient {
297
303
  this.#securityLifecycle = 'active';
298
304
  }
299
305
  onInvalidate(listener) {
306
+ this.#requireOpen();
300
307
  this.#invalidationListeners.add(listener);
301
308
  return () => this.#invalidationListeners.delete(listener);
302
309
  }
303
310
  onProgress(listener) {
311
+ this.#requireOpen();
304
312
  return this.#progress.on(listener);
305
313
  }
306
314
  progressSnapshot() {
315
+ this.#requireOpen();
307
316
  return this.#progress.snapshot();
308
317
  }
309
318
  onChange(listener) {
319
+ this.#requireOpen();
310
320
  this.#changeListeners.add(listener);
311
321
  return () => this.#changeListeners.delete(listener);
312
322
  }
313
323
  onDiagnostics(listener) {
324
+ this.#requireOpen();
314
325
  this.#diagnosticsListeners.add(listener);
315
326
  this.#enableDiagnosticsPush();
316
327
  return () => this.#diagnosticsListeners.delete(listener);
@@ -339,6 +350,7 @@ export class TauriSyncClient {
339
350
  });
340
351
  }
341
352
  onPresence(listener) {
353
+ this.#requireOpen();
342
354
  this.#presenceListeners.add(listener);
343
355
  return () => this.#presenceListeners.delete(listener);
344
356
  }
@@ -346,7 +358,7 @@ export class TauriSyncClient {
346
358
  this.#requireActive();
347
359
  const reply = await this.#tauri.invoke(`${PLUGIN}syncular_query`, { sql, params: (params ?? []).map(encodeParam) });
348
360
  if (reply.error !== undefined) {
349
- throw new TauriSyncError(reply.error.code, reply.error.message);
361
+ throw new TauriSyncError(reply.error.code, reply.error.message, reply.error.details ?? undefined);
350
362
  }
351
363
  const rows = reply.result.rows ?? [];
352
364
  return rows.map((r) => decodeRow(r));
@@ -360,7 +372,7 @@ export class TauriSyncClient {
360
372
  ...(spec.owner !== undefined ? { owner: spec.owner } : {}),
361
373
  });
362
374
  if (reply.error !== undefined) {
363
- throw new TauriSyncError(reply.error.code, reply.error.message);
375
+ throw new TauriSyncError(reply.error.code, reply.error.message, reply.error.details ?? undefined);
364
376
  }
365
377
  const result = reply.result;
366
378
  return {
@@ -430,7 +442,7 @@ export class TauriSyncClient {
430
442
  this.#requireActive();
431
443
  const reply = await this.#tauri.invoke(`${PLUGIN}syncular_set_headers`, { headers });
432
444
  if (reply.error !== undefined) {
433
- throw new TauriSyncError(reply.error.code, reply.error.message);
445
+ throw new TauriSyncError(reply.error.code, reply.error.message, reply.error.details ?? undefined);
434
446
  }
435
447
  }
436
448
  // -- Native CRDT (SPEC.md §5.10.5; needs the plugin `crdt-yjs` feature) ------
@@ -587,10 +599,12 @@ function decodeDiagnosticsSnapshot(value, kind) {
587
599
  /** The error a `{error}` reply surfaces (mirrors the web-client `ClientSyncError`). */
588
600
  export class TauriSyncError extends Error {
589
601
  code;
590
- constructor(code, message) {
602
+ details;
603
+ constructor(code, message, details) {
591
604
  super(message);
592
605
  this.name = 'TauriSyncError';
593
606
  this.code = code;
607
+ this.details = details;
594
608
  }
595
609
  }
596
610
  function toStringSet(value) {
@@ -725,7 +739,7 @@ export async function createTauriSyncClient(config) {
725
739
  });
726
740
  if (reply.error !== undefined) {
727
741
  unlisten();
728
- throw new TauriSyncError(reply.error.code, reply.error.message);
742
+ throw new TauriSyncError(reply.error.code, reply.error.message, reply.error.details ?? undefined);
729
743
  }
730
744
  return client;
731
745
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/tauri",
3
- "version": "0.30.5",
3
+ "version": "0.30.7",
4
4
  "description": "Tauri integration for the Syncular client",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -48,12 +48,12 @@
48
48
  "test": "bun test"
49
49
  },
50
50
  "dependencies": {
51
- "@syncular/client": "0.30.5"
51
+ "@syncular/client": "0.30.7"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@tauri-apps/api": ">=2.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@syncular/react": "0.30.5"
57
+ "@syncular/react": "0.30.7"
58
58
  }
59
59
  }
package/src/index.ts CHANGED
@@ -81,7 +81,11 @@ import {
81
81
  /** A driver-protocol reply: `{result}` on success or `{error}` on failure. */
82
82
  interface CommandReply {
83
83
  readonly result?: unknown;
84
- readonly error?: { readonly code: string; readonly message: string };
84
+ readonly error?: {
85
+ readonly code: string;
86
+ readonly message: string;
87
+ readonly details?: Readonly<Record<string, unknown>> | null;
88
+ };
85
89
  }
86
90
 
87
91
  /** One event pushed on `syncular://event` (the derived client-observable set). */
@@ -335,12 +339,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
335
339
  method: string,
336
340
  params: Record<string, unknown>,
337
341
  ): Promise<unknown> {
338
- if (this.#closed) {
339
- throw new TauriSyncError(
340
- 'client.closed',
341
- 'the Tauri sync client is closed',
342
- );
343
- }
342
+ this.#requireOpen();
344
343
  if (
345
344
  this.#securityLifecycle === 'preflight' &&
346
345
  ![
@@ -361,7 +360,11 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
361
360
  { command: { method, params } },
362
361
  );
363
362
  if (reply.error !== undefined) {
364
- throw new TauriSyncError(reply.error.code, reply.error.message);
363
+ throw new TauriSyncError(
364
+ reply.error.code,
365
+ reply.error.message,
366
+ reply.error.details ?? undefined,
367
+ );
365
368
  }
366
369
  return reply.result;
367
370
  }
@@ -373,7 +376,17 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
373
376
  );
374
377
  }
375
378
 
379
+ #requireOpen(): void {
380
+ if (this.#closed) {
381
+ throw new TauriSyncError(
382
+ 'client.closed',
383
+ 'the Tauri sync client is closed',
384
+ );
385
+ }
386
+ }
387
+
376
388
  #requireActive(): void {
389
+ this.#requireOpen();
377
390
  if (this.#securityLifecycle === 'preflight') this.#throwSecurityPreflight();
378
391
  }
379
392
 
@@ -438,7 +451,8 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
438
451
 
439
452
  // -- SyncClientLike --------------------------------------------------------
440
453
 
441
- securityLifecycle(): Promise<SecurityLifecycle> {
454
+ async securityLifecycle(): Promise<SecurityLifecycle> {
455
+ this.#requireOpen();
442
456
  return Promise.resolve(this.#securityLifecycle);
443
457
  }
444
458
 
@@ -479,6 +493,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
479
493
  readonly headers?: Readonly<Record<string, string>>;
480
494
  } = {},
481
495
  ): Promise<void> {
496
+ this.#requireOpen();
482
497
  if (this.#securityLifecycle === 'active') {
483
498
  throw new TauriSyncError(
484
499
  'sync.invalid_request',
@@ -496,23 +511,28 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
496
511
  }
497
512
 
498
513
  onInvalidate(listener: InvalidationListener): () => void {
514
+ this.#requireOpen();
499
515
  this.#invalidationListeners.add(listener);
500
516
  return () => this.#invalidationListeners.delete(listener);
501
517
  }
502
518
 
503
519
  onProgress(listener: SyncProgressListener): () => void {
520
+ this.#requireOpen();
504
521
  return this.#progress.on(listener);
505
522
  }
506
523
  progressSnapshot(): SyncProgress | undefined {
524
+ this.#requireOpen();
507
525
  return this.#progress.snapshot();
508
526
  }
509
527
 
510
528
  onChange(listener: ClientChangeListener): () => void {
529
+ this.#requireOpen();
511
530
  this.#changeListeners.add(listener);
512
531
  return () => this.#changeListeners.delete(listener);
513
532
  }
514
533
 
515
534
  onDiagnostics(listener: ClientDiagnosticsListener): () => void {
535
+ this.#requireOpen();
516
536
  this.#diagnosticsListeners.add(listener);
517
537
  this.#enableDiagnosticsPush();
518
538
  return () => this.#diagnosticsListeners.delete(listener);
@@ -542,6 +562,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
542
562
  }
543
563
 
544
564
  onPresence(listener: (scopeKey: string) => void): () => void {
565
+ this.#requireOpen();
545
566
  this.#presenceListeners.add(listener);
546
567
  return () => this.#presenceListeners.delete(listener);
547
568
  }
@@ -553,7 +574,11 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
553
574
  { sql, params: (params ?? []).map(encodeParam) },
554
575
  );
555
576
  if (reply.error !== undefined) {
556
- throw new TauriSyncError(reply.error.code, reply.error.message);
577
+ throw new TauriSyncError(
578
+ reply.error.code,
579
+ reply.error.message,
580
+ reply.error.details ?? undefined,
581
+ );
557
582
  }
558
583
  const rows = (reply.result as { rows?: unknown[] }).rows ?? [];
559
584
  return rows.map((r) => decodeRow(r as Record<string, unknown>));
@@ -573,7 +598,11 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
573
598
  },
574
599
  );
575
600
  if (reply.error !== undefined) {
576
- throw new TauriSyncError(reply.error.code, reply.error.message);
601
+ throw new TauriSyncError(
602
+ reply.error.code,
603
+ reply.error.message,
604
+ reply.error.details ?? undefined,
605
+ );
577
606
  }
578
607
  const result = reply.result as {
579
608
  revision: string;
@@ -689,7 +718,11 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
689
718
  { headers },
690
719
  );
691
720
  if (reply.error !== undefined) {
692
- throw new TauriSyncError(reply.error.code, reply.error.message);
721
+ throw new TauriSyncError(
722
+ reply.error.code,
723
+ reply.error.message,
724
+ reply.error.details ?? undefined,
725
+ );
693
726
  }
694
727
  }
695
728
 
@@ -917,10 +950,16 @@ function decodeDiagnosticsSnapshot(
917
950
  /** The error a `{error}` reply surfaces (mirrors the web-client `ClientSyncError`). */
918
951
  export class TauriSyncError extends Error {
919
952
  readonly code: string;
920
- constructor(code: string, message: string) {
953
+ readonly details: Readonly<Record<string, unknown>> | undefined;
954
+ constructor(
955
+ code: string,
956
+ message: string,
957
+ details?: Readonly<Record<string, unknown>>,
958
+ ) {
921
959
  super(message);
922
960
  this.name = 'TauriSyncError';
923
961
  this.code = code;
962
+ this.details = details;
924
963
  }
925
964
  }
926
965
 
@@ -1071,7 +1110,11 @@ export async function createTauriSyncClient(
1071
1110
  });
1072
1111
  if (reply.error !== undefined) {
1073
1112
  unlisten();
1074
- throw new TauriSyncError(reply.error.code, reply.error.message);
1113
+ throw new TauriSyncError(
1114
+ reply.error.code,
1115
+ reply.error.message,
1116
+ reply.error.details ?? undefined,
1117
+ );
1075
1118
  }
1076
1119
 
1077
1120
  return client;