@syncular/client 0.15.28 → 0.15.30

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
@@ -144,11 +144,12 @@ deterministic tuple for diagnostics and host integration. Replica IDs are
144
144
  stable code-like values (`A-Z`, `a-z`, `0-9`, dot, underscore, dash).
145
145
 
146
146
  During Vite development, retain the React client resource only while its
147
- captured generated schema version matches. The
148
- [schema-aware Vite guide](https://syncular.dev/guide-vite/) uses
147
+ captured generated schema version and published Syncular runtime identity both
148
+ match. The
149
+ [schema-and-runtime-aware Vite guide](https://syncular.dev/guide-vite/) uses
149
150
  `retainViteSyncClientResource` to close the old worker before constructing a
150
- schema-bump replacement; hot-reloading query code alone does not migrate the
151
- worker-owned database.
151
+ schema-bump or package-upgrade replacement; hot-reloading query code alone does
152
+ not migrate the worker-owned database.
152
153
 
153
154
  Set `multiTab: false` to opt out. A losing tab then becomes an
154
155
  `isLeader === false` handle whose calls reject with `client.not_leader`. This
@@ -360,6 +361,11 @@ in-memory one**; the local database and pending outbox may be perfectly healthy.
360
361
  Missing or obsolete OPFS APIs instead use the non-retryable
361
362
  `client.storage_unavailable` code.
362
363
 
364
+ A worker graph that still names a retired Vite optimizer chunk instead uses
365
+ the non-retryable `client.worker_restart_required` code. Restart the dev server
366
+ and reload the page; do not clear OPFS. The original bundler message and chunk
367
+ URL are deliberately not copied into the public error.
368
+
363
369
  When using `@syncular/react`, `createSyncClientResource()` exposes `retry()` and
364
370
  passes the same action as the second argument to `SyncProvider.renderError`.
365
371
  Applications may use a small bounded backoff for errors whose `retryable` flag
package/dist/client.js CHANGED
@@ -1745,7 +1745,8 @@ export class SyncClient {
1745
1745
  return (code === 'transport.failed' ||
1746
1746
  code === 'transport.unavailable' ||
1747
1747
  code === 'sync.transport_failed' ||
1748
- code === 'client.worker_failed');
1748
+ code === 'client.worker_failed' ||
1749
+ code === 'client.worker_restart_required');
1749
1750
  }
1750
1751
  #diagnosticCode(code) {
1751
1752
  return code.length <= 96 && /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$/.test(code)
@@ -27,6 +27,7 @@ import type { ConflictRecord, LeaseState, MutationInput, PresencePeer, QueryRead
27
27
  import type { SqlRow, SqlValue } from './database.js';
28
28
  import { ClientDiagnosticsEmitter, type ClientDiagnosticsListener, type ClientDiagnosticsRequest, type ClientDiagnosticsSnapshot } from './diagnostics.js';
29
29
  import type { EncryptionKeyringConfig } from './encryption.js';
30
+ import { ClientSyncError } from './errors.js';
30
31
  import { ChangeEmitter, type ClientChangeListener, InvalidationEmitter, type InvalidationListener, type LocalRevision, type SyncStatusSnapshot } from './invalidation.js';
31
32
  import { type LeaderLease, type LeaderLock } from './leader-lock.js';
32
33
  import type { LocalDataPurgeInput, LocalDataPurgeResult } from './local-purge.js';
@@ -38,6 +39,8 @@ import type { ClientSchema } from './schema.js';
38
39
  import type { SubscriptionRecord } from './state.js';
39
40
  import type { WindowBase } from './window.js';
40
41
  import { type SyncWorkerEvent, type WorkerDatabaseInit, type WorkerEndpoints, type WorkerErrorShape, type WorkerSecurityActivation } from './worker-protocol.js';
42
+ /** Classify startup without echoing a chunk URL or bundler text to UI/logs. */
43
+ export declare function workerStartupError(message: unknown): ClientSyncError;
41
44
  export type HandleRole = 'leader' | 'follower';
42
45
  export type BrowserReplicaMode = {
43
46
  readonly mode: 'shared';
@@ -4,7 +4,15 @@ import { ClientSyncError } from './errors.js';
4
4
  import { ChangeEmitter, InvalidationEmitter, invalidationFromChange, } from './invalidation.js';
5
5
  import { singleOwnerLock, webLocksLeaderLock, } from './leader-lock.js';
6
6
  import { broadcastChannelFactory, FollowerLink, LeaderBridge, multiTabChannelName, newTabId, } from './multi-tab.js';
7
- import { NOT_LEADER_CODE, WORKER_FAILED_CODE, } from './worker-protocol.js';
7
+ import { NOT_LEADER_CODE, WORKER_FAILED_CODE, WORKER_RESTART_REQUIRED_CODE, } from './worker-protocol.js';
8
+ const WORKER_BUNDLE_LOAD_FAILURE = /(?:failed to fetch dynamically imported module|error loading dynamically imported module|importing a module script failed|failed to load module script)/iu;
9
+ /** Classify startup without echoing a chunk URL or bundler text to UI/logs. */
10
+ export function workerStartupError(message) {
11
+ if (typeof message === 'string' && WORKER_BUNDLE_LOAD_FAILURE.test(message)) {
12
+ return new ClientSyncError(WORKER_RESTART_REQUIRED_CODE, 'the sync worker bundle changed; fully reload this application');
13
+ }
14
+ return new ClientSyncError(WORKER_FAILED_CODE, 'the sync worker failed');
15
+ }
8
16
  /** Derive the complete ownership tuple for an independently owned replica. */
9
17
  export function isolatedReplicaNames(options) {
10
18
  if (!/^[A-Za-z0-9._-]+$/.test(options.replicaId)) {
@@ -403,7 +411,7 @@ async function startWorkerCore(options) {
403
411
  }
404
412
  };
405
413
  const onError = (event) => {
406
- const error = new ClientSyncError(WORKER_FAILED_CODE, `the sync worker failed: ${event.message ?? 'unknown error'}`);
414
+ const error = workerStartupError(event.message);
407
415
  reject(error);
408
416
  for (const entry of pending.values())
409
417
  entry.reject(error);
@@ -34,6 +34,8 @@ import type { WindowBase } from './window.js';
34
34
  export declare const NOT_LEADER_CODE = "client.not_leader";
35
35
  /** The worker (or its RPC channel) failed outside protocol semantics. */
36
36
  export declare const WORKER_FAILED_CODE = "client.worker_failed";
37
+ /** A page/worker bundle identity changed and a full host reload is required. */
38
+ export declare const WORKER_RESTART_REQUIRED_CODE = "client.worker_restart_required";
37
39
  export type WorkerDatabaseInit = {
38
40
  /** THE persistent mode: opfs-sahpool, named database. */
39
41
  readonly mode: 'persistent';
@@ -5,3 +5,5 @@
5
5
  export const NOT_LEADER_CODE = 'client.not_leader';
6
6
  /** The worker (or its RPC channel) failed outside protocol semantics. */
7
7
  export const WORKER_FAILED_CODE = 'client.worker_failed';
8
+ /** A page/worker bundle identity changed and a full host reload is required. */
9
+ export const WORKER_RESTART_REQUIRED_CODE = 'client.worker_restart_required';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/client",
3
- "version": "0.15.28",
3
+ "version": "0.15.30",
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.28"
84
+ "@syncular/core": "0.15.30"
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.28",
95
+ "@syncular/server": "0.15.30",
96
96
  "@types/better-sqlite3": "^7.6.13",
97
97
  "better-sqlite3": "^12.11.1"
98
98
  }
package/src/client.ts CHANGED
@@ -2524,7 +2524,8 @@ export class SyncClient {
2524
2524
  code === 'transport.failed' ||
2525
2525
  code === 'transport.unavailable' ||
2526
2526
  code === 'sync.transport_failed' ||
2527
- code === 'client.worker_failed'
2527
+ code === 'client.worker_failed' ||
2528
+ code === 'client.worker_restart_required'
2528
2529
  );
2529
2530
  }
2530
2531
 
@@ -92,6 +92,7 @@ import {
92
92
  NOT_LEADER_CODE,
93
93
  type SyncWorkerEvent,
94
94
  WORKER_FAILED_CODE,
95
+ WORKER_RESTART_REQUIRED_CODE,
95
96
  type WorkerApi,
96
97
  type WorkerDatabaseInit,
97
98
  type WorkerEndpoints,
@@ -103,6 +104,20 @@ import {
103
104
  type WorkerToMainMessage,
104
105
  } from './worker-protocol';
105
106
 
107
+ const WORKER_BUNDLE_LOAD_FAILURE =
108
+ /(?:failed to fetch dynamically imported module|error loading dynamically imported module|importing a module script failed|failed to load module script)/iu;
109
+
110
+ /** Classify startup without echoing a chunk URL or bundler text to UI/logs. */
111
+ export function workerStartupError(message: unknown): ClientSyncError {
112
+ if (typeof message === 'string' && WORKER_BUNDLE_LOAD_FAILURE.test(message)) {
113
+ return new ClientSyncError(
114
+ WORKER_RESTART_REQUIRED_CODE,
115
+ 'the sync worker bundle changed; fully reload this application',
116
+ );
117
+ }
118
+ return new ClientSyncError(WORKER_FAILED_CODE, 'the sync worker failed');
119
+ }
120
+
106
121
  export type HandleRole = 'leader' | 'follower';
107
122
 
108
123
  export type BrowserReplicaMode =
@@ -717,10 +732,7 @@ async function startWorkerCore(options: {
717
732
  }
718
733
  };
719
734
  const onError = (event: ErrorEvent) => {
720
- const error = new ClientSyncError(
721
- WORKER_FAILED_CODE,
722
- `the sync worker failed: ${event.message ?? 'unknown error'}`,
723
- );
735
+ const error = workerStartupError(event.message);
724
736
  reject(error);
725
737
  for (const entry of pending.values()) entry.reject(error);
726
738
  pending.clear();
@@ -67,6 +67,8 @@ import type { WindowBase } from './window';
67
67
  export const NOT_LEADER_CODE = 'client.not_leader';
68
68
  /** The worker (or its RPC channel) failed outside protocol semantics. */
69
69
  export const WORKER_FAILED_CODE = 'client.worker_failed';
70
+ /** A page/worker bundle identity changed and a full host reload is required. */
71
+ export const WORKER_RESTART_REQUIRED_CODE = 'client.worker_restart_required';
70
72
 
71
73
  // ---------------------------------------------------------------------------
72
74
  // Init configuration (structured-clone safe)