@syncular/client 0.30.12 → 0.30.14

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/src/window.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export const PENDING_EVICTIONS = '_syncular_window_pending_evict';
2
+ export const WINDOWS_TABLE = '_syncular_windows';
1
3
  /**
2
4
  * Windowed subscriptions (SPEC.md §4.8).
3
5
  *
@@ -151,7 +153,7 @@ export function loadWindowUnits(
151
153
  ): WindowUnit[] {
152
154
  return db
153
155
  .query(
154
- 'SELECT unit, sub_id FROM _syncular_windows WHERE base = ? ORDER BY unit ASC',
156
+ `SELECT unit,sub_id FROM ${WINDOWS_TABLE} WHERE base=? ORDER BY unit`,
155
157
  [baseKey],
156
158
  )
157
159
  .map((row) => ({ unit: row.unit as string, subId: row.sub_id as string }));
@@ -163,7 +165,7 @@ export function getWindowUnitBySubId(
163
165
  subId: string,
164
166
  ): RegisteredWindowUnit | undefined {
165
167
  const row = db.query(
166
- `SELECT base, unit, sub_id FROM _syncular_windows
168
+ `SELECT base, unit, sub_id FROM ${WINDOWS_TABLE}
167
169
  WHERE sub_id = ? LIMIT 1`,
168
170
  [subId],
169
171
  )[0];
@@ -183,7 +185,7 @@ export function insertWindowUnit(
183
185
  subId: string,
184
186
  ): void {
185
187
  db.exec(
186
- 'INSERT OR REPLACE INTO _syncular_windows(base, unit, sub_id) VALUES (?, ?, ?)',
188
+ `INSERT OR REPLACE INTO ${WINDOWS_TABLE}(base,unit,sub_id)VALUES(?,?,?)`,
187
189
  [baseKey, unit, subId],
188
190
  );
189
191
  }
@@ -193,7 +195,7 @@ export function deleteWindowUnit(
193
195
  baseKey: string,
194
196
  unit: string,
195
197
  ): void {
196
- db.exec('DELETE FROM _syncular_windows WHERE base = ? AND unit = ?', [
198
+ db.exec(`DELETE FROM ${WINDOWS_TABLE} WHERE base=? AND unit=?`, [
197
199
  baseKey,
198
200
  unit,
199
201
  ]);
@@ -206,10 +208,10 @@ export function isUnitLive(
206
208
  unit: string,
207
209
  ): boolean {
208
210
  return (
209
- db.query(
210
- 'SELECT 1 FROM _syncular_windows WHERE base = ? AND unit = ? LIMIT 1',
211
- [baseKey, unit],
212
- ).length > 0
211
+ db.query(`SELECT 1 FROM ${WINDOWS_TABLE} WHERE base=? AND unit=? LIMIT 1`, [
212
+ baseKey,
213
+ unit,
214
+ ]).length > 0
213
215
  );
214
216
  }
215
217
 
@@ -230,23 +232,19 @@ export function savePendingEviction(
230
232
  effective: ScopeMap,
231
233
  ): void {
232
234
  db.exec(
233
- `INSERT OR REPLACE INTO _syncular_window_pending_evict(sub_id, tbl, effective_scopes)
235
+ `INSERT OR REPLACE INTO ${PENDING_EVICTIONS}(sub_id, tbl, effective_scopes)
234
236
  VALUES (?, ?, ?)`,
235
237
  [subId, table, JSON.stringify(effective)],
236
238
  );
237
239
  }
238
240
 
239
241
  export function deletePendingEviction(db: ClientDatabase, subId: string): void {
240
- db.exec('DELETE FROM _syncular_window_pending_evict WHERE sub_id = ?', [
241
- subId,
242
- ]);
242
+ db.exec(`DELETE FROM ${PENDING_EVICTIONS} WHERE sub_id = ?`, [subId]);
243
243
  }
244
244
 
245
245
  export function loadPendingEvictions(db: ClientDatabase): PendingEviction[] {
246
246
  return db
247
- .query(
248
- 'SELECT sub_id, tbl, effective_scopes FROM _syncular_window_pending_evict',
249
- )
247
+ .query(`SELECT sub_id, tbl, effective_scopes FROM ${PENDING_EVICTIONS}`)
250
248
  .map((row) => ({
251
249
  subId: row.sub_id as string,
252
250
  table: row.tbl as string,
@@ -165,6 +165,7 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
165
165
  if (
166
166
  closed ||
167
167
  client === undefined ||
168
+ offline ||
168
169
  client.securityLifecycle() === 'preflight'
169
170
  ) {
170
171
  return;
@@ -189,6 +190,7 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
189
190
  !autoSync ||
190
191
  closed ||
191
192
  client === undefined ||
193
+ offline ||
192
194
  client.securityLifecycle() === 'preflight' ||
193
195
  intent.kind === 'none'
194
196
  ) {
@@ -235,41 +237,6 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
235
237
  return client;
236
238
  }
237
239
 
238
- function assertOnline(): void {
239
- if (offline) {
240
- throw new ClientSyncError(
241
- 'sync.transport_failed',
242
- 'the worker transport is offline (setOffline(true))',
243
- true,
244
- );
245
- }
246
- }
247
-
248
- function gateOffline<T>(inner: (request: T) => Promise<Uint8Array>) {
249
- return (request: T): Promise<Uint8Array> => {
250
- assertOnline();
251
- return inner(request);
252
- };
253
- }
254
-
255
- /** Offline gate that preserves the §5.4 `fetchUrl` capability marker. */
256
- function gateSegmentsOffline(inner: SegmentDownloader): SegmentDownloader {
257
- const fetchUrl = inner.fetchUrl;
258
- return Object.assign(gateOffline(inner), {
259
- ...(fetchUrl !== undefined
260
- ? {
261
- fetchUrl: (
262
- url: string,
263
- onProgress?: (bytesReceived: number) => void,
264
- ) => {
265
- assertOnline();
266
- return fetchUrl(url, onProgress);
267
- },
268
- }
269
- : {}),
270
- });
271
- }
272
-
273
240
  async function init(config: WorkerInitConfig): Promise<WorkerInitResult> {
274
241
  if (client !== undefined) {
275
242
  throw new ClientSyncError(
@@ -308,11 +275,10 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
308
275
  }
309
276
  }
310
277
  }
311
- const transport = gateOffline(
278
+ const transport =
312
279
  overrides.createTransport !== undefined
313
280
  ? overrides.createTransport(config)
314
- : httpSyncTransport(config.endpoints.syncUrl, http),
315
- );
281
+ : httpSyncTransport(config.endpoints.syncUrl, http);
316
282
  const segments =
317
283
  overrides.createSegments !== undefined
318
284
  ? overrides.createSegments(config)
@@ -329,22 +295,15 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
329
295
  // `{clientId}` URL placeholder — start the client first, then attach
330
296
  // the connector through a mutable slot read at connect time.
331
297
  let realtimeConnector: RealtimeConnector | undefined;
298
+ offline = config.transportEnabled === false;
332
299
  const started = new SyncClient({
300
+ transportEnabled: !offline,
333
301
  database,
334
302
  schema: config.schema,
335
303
  transport,
336
- ...(segments !== undefined
337
- ? { segments: gateSegmentsOffline(segments) }
338
- : {}),
304
+ ...(segments !== undefined ? { segments } : {}),
339
305
  ...(blobs !== undefined ? { blobs } : {}),
340
306
  realtime: (handlers) => {
341
- if (offline) {
342
- throw new ClientSyncError(
343
- 'sync.transport_failed',
344
- 'the worker realtime channel is offline (setOffline(true))',
345
- true,
346
- );
347
- }
348
307
  if (realtimeConnector === undefined) {
349
308
  throw new ClientSyncError(
350
309
  'sync.invalid_request',
@@ -515,8 +474,12 @@ export function startSyncWorker(overrides: SyncWorkerOverrides = {}): void {
515
474
  },
516
475
  setOffline: (value) => {
517
476
  offline = value;
518
- if (offline) client?.disconnectRealtime();
519
- else if (client !== undefined) {
477
+ requireClient().setTransportEnabled(!value);
478
+ if (offline) {
479
+ if (backgroundTimer !== undefined) clearTimeout(backgroundTimer);
480
+ backgroundTimer = undefined;
481
+ backgroundDue = Number.POSITIVE_INFINITY;
482
+ } else if (client !== undefined) {
520
483
  const snapshot = client.diagnosticsSnapshot();
521
484
  post({
522
485
  t: 'event',
@@ -194,6 +194,7 @@ export interface SyncClientHandleConfig {
194
194
  readonly encryption?: EncryptionKeyringConfig;
195
195
  /** Open the worker-owned replica behind the fail-closed security gate. */
196
196
  readonly securityPreflight?: boolean;
197
+ readonly transportEnabled?: boolean;
197
198
  readonly clientId?: string;
198
199
  readonly limits?: SyncClientLimits;
199
200
  readonly retainFailedCommits?: boolean;
@@ -947,6 +948,9 @@ function buildInitConfig(
947
948
  ...(config.encryption !== undefined
948
949
  ? { encryption: config.encryption }
949
950
  : {}),
951
+ ...(config.transportEnabled !== undefined
952
+ ? { transportEnabled: config.transportEnabled }
953
+ : {}),
950
954
  ...(config.securityPreflight !== undefined
951
955
  ? { securityPreflight: config.securityPreflight }
952
956
  : {}),
@@ -112,6 +112,7 @@ export interface WorkerInitConfig {
112
112
  readonly encryption?: EncryptionKeyringConfig;
113
113
  /** Open the worker-owned replica behind the fail-closed security gate. */
114
114
  readonly securityPreflight?: boolean;
115
+ readonly transportEnabled?: boolean;
115
116
  readonly clientId?: string;
116
117
  readonly limits?: SyncClientLimits;
117
118
  readonly retainFailedCommits?: boolean;