@syncular/client 0.1.0 → 0.1.2

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.
Files changed (54) hide show
  1. package/dist/blob-limits.js +1 -1
  2. package/dist/bridge-client.js +2 -2
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +48 -12
  5. package/dist/client.js.map +1 -1
  6. package/dist/command-history.js +1 -1
  7. package/dist/console-diagnostics.js +1 -1
  8. package/dist/crdt-yjs/index.js +3 -3
  9. package/dist/database.js +47 -9
  10. package/dist/database.js.map +1 -1
  11. package/dist/index.js +14 -14
  12. package/dist/runtime-contract.d.ts +1 -1
  13. package/dist/runtime-contract.js +4 -4
  14. package/dist/rust-client.d.ts.map +1 -1
  15. package/dist/rust-client.js +11 -9
  16. package/dist/rust-client.js.map +1 -1
  17. package/dist/syncular-runtime-artifacts.json +8 -8
  18. package/dist/types.d.ts +38 -1
  19. package/dist/types.d.ts.map +1 -1
  20. package/dist/wasm/syncular-runtime-artifact.json +2 -2
  21. package/dist/wasm/syncular.d.ts +5 -5
  22. package/dist/wasm/syncular.js +17 -17
  23. package/dist/wasm/syncular_bg.wasm +0 -0
  24. package/dist/wasm/syncular_bg.wasm.d.ts +5 -5
  25. package/dist/wasm-bindings/runtime-contract.d.ts +1 -1
  26. package/dist/wasm-bindings/runtime-contract.js +1 -1
  27. package/dist/wasm-core/syncular-runtime-artifact.json +2 -2
  28. package/dist/wasm-core/syncular.d.ts +5 -5
  29. package/dist/wasm-core/syncular.js +18 -18
  30. package/dist/wasm-core/syncular_bg.wasm +0 -0
  31. package/dist/wasm-core/syncular_bg.wasm.d.ts +5 -5
  32. package/dist/wasm-perf/syncular-runtime-artifact.json +2 -2
  33. package/dist/wasm-perf/syncular.d.ts +5 -5
  34. package/dist/wasm-perf/syncular.js +16 -16
  35. package/dist/wasm-perf/syncular_bg.wasm +0 -0
  36. package/dist/wasm-perf/syncular_bg.wasm.d.ts +5 -5
  37. package/dist/wasm-runtime.d.ts +6 -1
  38. package/dist/wasm-runtime.d.ts.map +1 -1
  39. package/dist/wasm-runtime.js +44 -3
  40. package/dist/wasm-runtime.js.map +1 -1
  41. package/dist/worker-client.d.ts +1 -1
  42. package/dist/worker-client.d.ts.map +1 -1
  43. package/dist/worker-client.js +65 -16
  44. package/dist/worker-client.js.map +1 -1
  45. package/dist/worker-entry.js +9 -9
  46. package/dist/worker-realtime.js +2 -2
  47. package/package.json +3 -3
  48. package/src/client.ts +54 -10
  49. package/src/database.ts +55 -1
  50. package/src/rust-client.ts +4 -1
  51. package/src/types.ts +42 -1
  52. package/src/wasm-bindings/runtime-contract.ts +1 -1
  53. package/src/wasm-runtime.ts +71 -2
  54. package/src/worker-client.ts +82 -8
@@ -23,7 +23,7 @@ export {
23
23
  } from './wasm-bindings/runtime-contract';
24
24
 
25
25
  export interface SyncularWasmGlue {
26
- default(moduleOrPath?: string | URL | Request): Promise<unknown>;
26
+ default(moduleOrPath?: SyncularWasmInitInput): Promise<unknown>;
27
27
  syncularRuntimeInfoJson(): string;
28
28
  syncularBuildYjsTextUpdateJson(argsJson: string): string;
29
29
  syncularApplyYjsTextUpdatesJson(argsJson: string): string;
@@ -35,6 +35,25 @@ export interface SyncularWasmGlue {
35
35
  ): Promise<SyncularRustOwnedSqliteClient>;
36
36
  }
37
37
 
38
+ export type SyncularWasmModuleInput =
39
+ | string
40
+ | URL
41
+ | Request
42
+ | Response
43
+ | BufferSource
44
+ | WebAssembly.Module;
45
+
46
+ export type SyncularWasmInitInput =
47
+ | SyncularWasmModuleInput
48
+ | Promise<SyncularWasmModuleInput>
49
+ | {
50
+ module_or_path:
51
+ | SyncularWasmModuleInput
52
+ | Promise<SyncularWasmModuleInput>;
53
+ };
54
+
55
+ type SyncularWasmFetchInput = string | URL | Request;
56
+
38
57
  let modulePromise: Promise<SyncularWasmGlue> | undefined;
39
58
 
40
59
  export function loadSyncularWasmGlue(): Promise<SyncularWasmGlue> {
@@ -65,12 +84,62 @@ export async function getSyncularRustRuntimeInfo(
65
84
  wasmUrl: string | URL | Request = getSyncularWasmUrl()
66
85
  ): Promise<SyncularRustRuntimeInfo> {
67
86
  const resolved = await (mod ?? loadSyncularWasmGlue());
68
- await resolved.default(wasmUrl);
87
+ await resolved.default({
88
+ module_or_path: await prepareSyncularWasmModuleInput(wasmUrl),
89
+ });
69
90
  return readSyncularRustRuntimeInfo(resolved);
70
91
  }
71
92
 
93
+ export async function prepareSyncularWasmModuleInput(
94
+ wasmUrl: SyncularWasmModuleInput = getSyncularWasmUrl()
95
+ ): Promise<SyncularWasmModuleInput> {
96
+ if (!shouldReadWasmUrlAsBytes(wasmUrl)) return wasmUrl;
97
+ const response = await fetch(wasmUrl);
98
+ if (!response.ok) {
99
+ throw new Error(
100
+ `Syncular WASM runtime artifact could not be loaded from ${describeSyncularWasmInput(
101
+ wasmUrl
102
+ )} (${response.status} ${response.statusText})`
103
+ );
104
+ }
105
+ return response.arrayBuffer();
106
+ }
107
+
72
108
  export function readSyncularRustRuntimeInfo(
73
109
  mod: SyncularWasmGlue
74
110
  ): SyncularRustRuntimeInfo {
75
111
  return JSON.parse(mod.syncularRuntimeInfoJson()) as SyncularRustRuntimeInfo;
76
112
  }
113
+
114
+ function shouldReadWasmUrlAsBytes(
115
+ input: SyncularWasmModuleInput
116
+ ): input is SyncularWasmFetchInput {
117
+ return isBunRuntime() && isFileUrlInput(input);
118
+ }
119
+
120
+ function isBunRuntime(): boolean {
121
+ return (globalThis as typeof globalThis & { Bun?: unknown }).Bun != null;
122
+ }
123
+
124
+ function isFileUrlInput(
125
+ input: SyncularWasmModuleInput
126
+ ): input is SyncularWasmFetchInput {
127
+ if (input instanceof URL) return input.protocol === 'file:';
128
+ if (input instanceof Request) return isFileUrlString(input.url);
129
+ if (typeof input === 'string') return isFileUrlString(input);
130
+ return false;
131
+ }
132
+
133
+ function isFileUrlString(input: string): boolean {
134
+ try {
135
+ return new URL(input).protocol === 'file:';
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ function describeSyncularWasmInput(input: string | URL | Request): string {
142
+ if (input instanceof URL) return input.href;
143
+ if (input instanceof Request) return input.url;
144
+ return input;
145
+ }
@@ -20,6 +20,7 @@ import {
20
20
  import {
21
21
  isGeneratedSyncularOperationalStateWorkerRequestType,
22
22
  type SyncularGeneratedWorkerRequestInput,
23
+ type SyncularGeneratedWorkerRequestType,
23
24
  } from './generated-bridge';
24
25
  import { browserSyncularNetworkStatusSource } from './network';
25
26
  import { assertSyncularReadonlySql } from './sql-safety';
@@ -94,6 +95,8 @@ import type {
94
95
  SyncularSyncResult,
95
96
  SyncularSyncTimings,
96
97
  SyncularTransportStats,
98
+ SyncularWorkerRequestTimeoutMs,
99
+ SyncularWorkerRequestTimeouts,
97
100
  } from './types';
98
101
  import {
99
102
  getSyncularRuntimeArtifact,
@@ -132,6 +135,70 @@ type BlobOutboxRow = {
132
135
  type SyncularWorkerRequestInput = SyncularGeneratedWorkerRequestInput;
133
136
 
134
137
  const DEFAULT_SYNCULAR_WORKER_REQUEST_TIMEOUT_MS = 30_000;
138
+ const NO_SYNCULAR_WORKER_REQUEST_TIMEOUT = 0;
139
+
140
+ const SYNCULAR_SYNC_WORKER_REQUEST_TYPES = new Set<string>([
141
+ 'forceSubscriptionsBootstrap',
142
+ 'syncPull',
143
+ 'syncPush',
144
+ 'syncOnce',
145
+ ]);
146
+
147
+ const SYNCULAR_BLOB_WORKER_REQUEST_TYPES = new Set<string>([
148
+ 'storeBlob',
149
+ 'retrieveBlob',
150
+ 'processBlobUploadQueue',
151
+ ]);
152
+
153
+ const SYNCULAR_STORAGE_MAINTENANCE_WORKER_REQUEST_TYPES = new Set<string>([
154
+ 'open',
155
+ 'compactStorage',
156
+ 'exportLocalSupportBundle',
157
+ 'importLocalSupportBundle',
158
+ 'repairLocalHealth',
159
+ 'resetLocalSyncState',
160
+ ]);
161
+
162
+ function syncularWorkerRequestTimeoutMs(
163
+ config:
164
+ | SyncularWorkerRequestTimeoutMs
165
+ | SyncularWorkerRequestTimeouts
166
+ | undefined,
167
+ type: SyncularGeneratedWorkerRequestType
168
+ ): number {
169
+ if (typeof config === 'number' || config === false) {
170
+ return normalizeSyncularWorkerTimeoutMs(config);
171
+ }
172
+
173
+ const byType = config?.byType?.[type];
174
+ if (byType !== undefined) return normalizeSyncularWorkerTimeoutMs(byType);
175
+
176
+ if (SYNCULAR_SYNC_WORKER_REQUEST_TYPES.has(type)) {
177
+ return normalizeSyncularWorkerTimeoutMs(config?.syncMs ?? false);
178
+ }
179
+ if (SYNCULAR_BLOB_WORKER_REQUEST_TYPES.has(type)) {
180
+ return normalizeSyncularWorkerTimeoutMs(config?.blobMs ?? false);
181
+ }
182
+ if (SYNCULAR_STORAGE_MAINTENANCE_WORKER_REQUEST_TYPES.has(type)) {
183
+ return normalizeSyncularWorkerTimeoutMs(
184
+ config?.storageMaintenanceMs ?? false
185
+ );
186
+ }
187
+
188
+ return normalizeSyncularWorkerTimeoutMs(
189
+ config?.defaultMs ?? DEFAULT_SYNCULAR_WORKER_REQUEST_TIMEOUT_MS
190
+ );
191
+ }
192
+
193
+ function normalizeSyncularWorkerTimeoutMs(
194
+ value: SyncularWorkerRequestTimeoutMs
195
+ ): number {
196
+ if (value === false) return NO_SYNCULAR_WORKER_REQUEST_TIMEOUT;
197
+ if (!Number.isFinite(value) || value <= 0) {
198
+ return NO_SYNCULAR_WORKER_REQUEST_TIMEOUT;
199
+ }
200
+ return Math.trunc(value);
201
+ }
135
202
 
136
203
  export async function createSyncularWorkerClient(
137
204
  options: CreateSyncularDatabaseOptions
@@ -223,7 +290,10 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
223
290
  #nextId = 1;
224
291
  #pending = new Map<number, PendingRequest>();
225
292
  #closed = false;
226
- #requestTimeoutMs: number;
293
+ #requestTimeouts:
294
+ | SyncularWorkerRequestTimeoutMs
295
+ | SyncularWorkerRequestTimeouts
296
+ | undefined;
227
297
  #getHeaders: CreateSyncularDatabaseOptions['getHeaders'] | undefined;
228
298
  #authHeaders: SyncularAuthHeaders = {};
229
299
  #authLifecycle: CreateSyncularDatabaseOptions['authLifecycle'] | undefined;
@@ -275,7 +345,7 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
275
345
  private readonly worker: Worker,
276
346
  options: {
277
347
  ownsWorker: boolean;
278
- requestTimeoutMs?: number;
348
+ requestTimeoutMs?: CreateSyncularDatabaseOptions['requestTimeoutMs'];
279
349
  getHeaders?: CreateSyncularDatabaseOptions['getHeaders'];
280
350
  authLifecycle?: CreateSyncularDatabaseOptions['authLifecycle'];
281
351
  diagnostics?: SyncularDiagnosticSink;
@@ -284,8 +354,7 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
284
354
  blobLimits?: CreateSyncularDatabaseOptions['blobLimits'];
285
355
  }
286
356
  ) {
287
- this.#requestTimeoutMs =
288
- options.requestTimeoutMs ?? DEFAULT_SYNCULAR_WORKER_REQUEST_TIMEOUT_MS;
357
+ this.#requestTimeouts = options.requestTimeoutMs;
289
358
  this.#getHeaders = options.getHeaders;
290
359
  this.#authLifecycle = options.authLifecycle;
291
360
  this.#blobLimits = options.blobLimits;
@@ -1465,8 +1534,9 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
1465
1534
  protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
1466
1535
  } as SyncularWorkerRequest;
1467
1536
  return new Promise<T>((resolve, reject) => {
1537
+ const requestTimeoutMs = this.#timeoutMsForRequest(request.type);
1468
1538
  const timeout =
1469
- this.#requestTimeoutMs > 0
1539
+ requestTimeoutMs > 0
1470
1540
  ? setTimeout(() => {
1471
1541
  this.#pending.delete(id);
1472
1542
  this.#sendCancel(id);
@@ -1478,13 +1548,13 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
1478
1548
  details: {
1479
1549
  requestId: id,
1480
1550
  requestType: request.type,
1481
- timeoutMs: this.#requestTimeoutMs,
1551
+ timeoutMs: requestTimeoutMs,
1482
1552
  },
1483
1553
  });
1484
1554
  const error = new SyncularWorkerError(
1485
1555
  createSyncularWorkerErrorPayload(
1486
1556
  'worker.request_timeout',
1487
- `Syncular worker request ${request.type} timed out after ${this.#requestTimeoutMs}ms`,
1557
+ `Syncular worker request ${request.type} timed out after ${requestTimeoutMs}ms`,
1488
1558
  { details: { requestId: id, requestType: request.type } }
1489
1559
  )
1490
1560
  );
@@ -1494,7 +1564,7 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
1494
1564
  };
1495
1565
  this.#emitLifecycleChanged();
1496
1566
  reject(error);
1497
- }, this.#requestTimeoutMs)
1567
+ }, requestTimeoutMs)
1498
1568
  : undefined;
1499
1569
  this.#pending.set(id, {
1500
1570
  type: request.type,
@@ -1507,6 +1577,10 @@ export class SyncularWorkerClient implements SyncularRuntimeClient {
1507
1577
  });
1508
1578
  }
1509
1579
 
1580
+ #timeoutMsForRequest(type: SyncularGeneratedWorkerRequestType): number {
1581
+ return syncularWorkerRequestTimeoutMs(this.#requestTimeouts, type);
1582
+ }
1583
+
1510
1584
  #handleWorkerMessage(message: SyncularWorkerOutboundMessage): void {
1511
1585
  if (message.protocolVersion !== SYNCULAR_WORKER_PROTOCOL_VERSION) {
1512
1586
  this.#rejectAll(