@syncular/tauri 0.30.4 → 0.30.6

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
@@ -53,6 +53,13 @@ export interface TauriSyncClientConfig {
53
53
  * an existing database fails with `client.identity_mismatch`.
54
54
  */
55
55
  readonly clientId?: string;
56
+ /**
57
+ * Name of the native database to open, for example one replica per
58
+ * signed-in actor. The plugin opens `<database_dir>/<database>.db` from its
59
+ * `SyncularConfig`; a name is 1 to 128 ASCII letters, digits, `-`, `_` or
60
+ * `.` and starts with a letter or digit. Absent ⇒ the plugin's `db_path`.
61
+ */
62
+ readonly database?: string;
56
63
  /** §4.2 client limits, forwarded to the native `create`. */
57
64
  readonly limits?: Record<string, unknown>;
58
65
  /**
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',
@@ -185,7 +183,13 @@ export class TauriSyncClient {
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
  }
@@ -698,13 +710,14 @@ export async function createTauriSyncClient(config) {
698
710
  }
699
711
  const client = new TauriSyncClient(tauri, unlisten, config.securityPreflight === true ? 'preflight' : 'active');
700
712
  clientRef.client = client;
701
- // The native side owns the db path (plugin config); the JS side supplies the
702
- // schema, clientId, and limits. `dbPath` is injected by the plugin.
713
+ // The native side owns every path (plugin config); the JS side names a
714
+ // database and supplies the schema, clientId, and limits.
703
715
  const reply = await tauri.invoke(`${PLUGIN}syncular_command`, {
704
716
  command: {
705
717
  method: 'create',
706
718
  params: {
707
719
  ...(config.clientId !== undefined ? { clientId: config.clientId } : {}),
720
+ ...(config.database !== undefined ? { database: config.database } : {}),
708
721
  schema: config.schema,
709
722
  ...(config.limits !== undefined ? { limits: config.limits } : {}),
710
723
  ...(config.realtimePolicy !== undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/tauri",
3
- "version": "0.30.4",
3
+ "version": "0.30.6",
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.4"
51
+ "@syncular/client": "0.30.6"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@tauri-apps/api": ">=2.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@syncular/react": "0.30.4"
57
+ "@syncular/react": "0.30.6"
58
58
  }
59
59
  }
package/src/index.ts CHANGED
@@ -114,6 +114,13 @@ export interface TauriSyncClientConfig {
114
114
  * an existing database fails with `client.identity_mismatch`.
115
115
  */
116
116
  readonly clientId?: string;
117
+ /**
118
+ * Name of the native database to open, for example one replica per
119
+ * signed-in actor. The plugin opens `<database_dir>/<database>.db` from its
120
+ * `SyncularConfig`; a name is 1 to 128 ASCII letters, digits, `-`, `_` or
121
+ * `.` and starts with a letter or digit. Absent ⇒ the plugin's `db_path`.
122
+ */
123
+ readonly database?: string;
117
124
  /** §4.2 client limits, forwarded to the native `create`. */
118
125
  readonly limits?: Record<string, unknown>;
119
126
  /**
@@ -328,12 +335,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
328
335
  method: string,
329
336
  params: Record<string, unknown>,
330
337
  ): Promise<unknown> {
331
- if (this.#closed) {
332
- throw new TauriSyncError(
333
- 'client.closed',
334
- 'the Tauri sync client is closed',
335
- );
336
- }
338
+ this.#requireOpen();
337
339
  if (
338
340
  this.#securityLifecycle === 'preflight' &&
339
341
  ![
@@ -366,7 +368,17 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
366
368
  );
367
369
  }
368
370
 
371
+ #requireOpen(): void {
372
+ if (this.#closed) {
373
+ throw new TauriSyncError(
374
+ 'client.closed',
375
+ 'the Tauri sync client is closed',
376
+ );
377
+ }
378
+ }
379
+
369
380
  #requireActive(): void {
381
+ this.#requireOpen();
370
382
  if (this.#securityLifecycle === 'preflight') this.#throwSecurityPreflight();
371
383
  }
372
384
 
@@ -431,7 +443,8 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
431
443
 
432
444
  // -- SyncClientLike --------------------------------------------------------
433
445
 
434
- securityLifecycle(): Promise<SecurityLifecycle> {
446
+ async securityLifecycle(): Promise<SecurityLifecycle> {
447
+ this.#requireOpen();
435
448
  return Promise.resolve(this.#securityLifecycle);
436
449
  }
437
450
 
@@ -472,6 +485,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
472
485
  readonly headers?: Readonly<Record<string, string>>;
473
486
  } = {},
474
487
  ): Promise<void> {
488
+ this.#requireOpen();
475
489
  if (this.#securityLifecycle === 'active') {
476
490
  throw new TauriSyncError(
477
491
  'sync.invalid_request',
@@ -489,23 +503,28 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
489
503
  }
490
504
 
491
505
  onInvalidate(listener: InvalidationListener): () => void {
506
+ this.#requireOpen();
492
507
  this.#invalidationListeners.add(listener);
493
508
  return () => this.#invalidationListeners.delete(listener);
494
509
  }
495
510
 
496
511
  onProgress(listener: SyncProgressListener): () => void {
512
+ this.#requireOpen();
497
513
  return this.#progress.on(listener);
498
514
  }
499
515
  progressSnapshot(): SyncProgress | undefined {
516
+ this.#requireOpen();
500
517
  return this.#progress.snapshot();
501
518
  }
502
519
 
503
520
  onChange(listener: ClientChangeListener): () => void {
521
+ this.#requireOpen();
504
522
  this.#changeListeners.add(listener);
505
523
  return () => this.#changeListeners.delete(listener);
506
524
  }
507
525
 
508
526
  onDiagnostics(listener: ClientDiagnosticsListener): () => void {
527
+ this.#requireOpen();
509
528
  this.#diagnosticsListeners.add(listener);
510
529
  this.#enableDiagnosticsPush();
511
530
  return () => this.#diagnosticsListeners.delete(listener);
@@ -535,6 +554,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
535
554
  }
536
555
 
537
556
  onPresence(listener: (scopeKey: string) => void): () => void {
557
+ this.#requireOpen();
538
558
  this.#presenceListeners.add(listener);
539
559
  return () => this.#presenceListeners.delete(listener);
540
560
  }
@@ -1037,13 +1057,14 @@ export async function createTauriSyncClient(
1037
1057
  );
1038
1058
  clientRef.client = client;
1039
1059
 
1040
- // The native side owns the db path (plugin config); the JS side supplies the
1041
- // schema, clientId, and limits. `dbPath` is injected by the plugin.
1060
+ // The native side owns every path (plugin config); the JS side names a
1061
+ // database and supplies the schema, clientId, and limits.
1042
1062
  const reply = await tauri.invoke<CommandReply>(`${PLUGIN}syncular_command`, {
1043
1063
  command: {
1044
1064
  method: 'create',
1045
1065
  params: {
1046
1066
  ...(config.clientId !== undefined ? { clientId: config.clientId } : {}),
1067
+ ...(config.database !== undefined ? { database: config.database } : {}),
1047
1068
  schema: config.schema,
1048
1069
  ...(config.limits !== undefined ? { limits: config.limits } : {}),
1049
1070
  ...(config.realtimePolicy !== undefined