@syncular/client 0.30.11 → 0.30.13

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/schema.ts CHANGED
@@ -92,6 +92,10 @@ const ALLOWED_FTS_TOKENIZERS = new Set([
92
92
  'trigram',
93
93
  ]);
94
94
 
95
+ function invalidSchema(message: string): never {
96
+ throw new Error(message);
97
+ }
98
+
95
99
  export function compileClientSchema(
96
100
  schema: ClientSchema,
97
101
  ): CompiledClientSchema {
@@ -100,10 +104,10 @@ export function compileClientSchema(
100
104
  for (const table of schema.tables) {
101
105
  const tableLabel = `table ${table.name}`;
102
106
  if (tables.has(table.name)) {
103
- throw new Error(`duplicate table ${JSON.stringify(table.name)}`);
107
+ invalidSchema(`duplicate table ${JSON.stringify(table.name)}`);
104
108
  }
105
109
  if (schemaObjectNames.has(table.name)) {
106
- throw new Error(
110
+ invalidSchema(
107
111
  `table ${JSON.stringify(table.name)} conflicts with an index or FTS projection`,
108
112
  );
109
113
  }
@@ -111,7 +115,7 @@ export function compileClientSchema(
111
115
  const columnIndex = new Map<string, number>();
112
116
  table.columns.forEach((column, index) => {
113
117
  if (columnIndex.has(column.name)) {
114
- throw new Error(
118
+ invalidSchema(
115
119
  `${tableLabel}: duplicate column ${JSON.stringify(column.name)}`,
116
120
  );
117
121
  }
@@ -119,27 +123,23 @@ export function compileClientSchema(
119
123
  });
120
124
  const primaryKeyIndex = columnIndex.get(table.primaryKey);
121
125
  if (primaryKeyIndex === undefined) {
122
- throw new Error(
126
+ invalidSchema(
123
127
  `${tableLabel}: primary key ${JSON.stringify(table.primaryKey)} is not a column`,
124
128
  );
125
129
  }
126
130
  // §2.4 primary-key eligibility: a primary key MUST render to a `rowId`
127
131
  // string every implementation produces identically and local storage
128
132
  // resolves without deferring to a SQLite build.
129
- const primaryKeyColumn = table.columns[primaryKeyIndex];
133
+ const primaryKeyColumn = table.columns[primaryKeyIndex] as RowColumn;
130
134
  if (
131
- primaryKeyColumn !== undefined &&
132
- primaryKeyColumn.type !== 'string' &&
133
- primaryKeyColumn.type !== 'integer' &&
134
- primaryKeyColumn.type !== 'boolean' &&
135
- primaryKeyColumn.type !== 'json'
135
+ !['string', 'integer', 'boolean', 'json'].includes(primaryKeyColumn.type)
136
136
  ) {
137
- throw new Error(
138
- `${tableLabel}: primary key ${JSON.stringify(table.primaryKey)} has an ineligible column type (§2.4); a primary key must be string, integer, boolean, or json`,
137
+ invalidSchema(
138
+ `${tableLabel}: primary key ${JSON.stringify(table.primaryKey)} has an ineligible type (§2.4)`,
139
139
  );
140
140
  }
141
141
  if (table.scopes.length === 0) {
142
- throw new Error(
142
+ invalidSchema(
143
143
  `${tableLabel}: every synced table declares at least one scope pattern (§3.1)`,
144
144
  );
145
145
  }
@@ -149,7 +149,7 @@ export function compileClientSchema(
149
149
  const pattern = typeof spec === 'string' ? spec : spec.pattern;
150
150
  const match = PATTERN_RE.exec(pattern);
151
151
  if (match === null || match[1] === undefined || match[2] === undefined) {
152
- throw new Error(
152
+ invalidSchema(
153
153
  `${tableLabel}: scope pattern ${JSON.stringify(pattern)} must be 'prefix:{variable}'`,
154
154
  );
155
155
  }
@@ -157,13 +157,13 @@ export function compileClientSchema(
157
157
  const variable = match[2];
158
158
  const column = typeof spec === 'string' ? variable : spec.column;
159
159
  if (!columnIndex.has(column)) {
160
- throw new Error(
160
+ invalidSchema(
161
161
  `${tableLabel}: scope pattern ${JSON.stringify(pattern)} names unknown column ${JSON.stringify(column)}`,
162
162
  );
163
163
  }
164
164
  const existing = scopeColumnByVariable.get(variable);
165
165
  if (existing !== undefined && existing !== column) {
166
- throw new Error(
166
+ invalidSchema(
167
167
  `${tableLabel}: variable ${JSON.stringify(variable)} maps to two different columns (§3.1)`,
168
168
  );
169
169
  }
@@ -184,60 +184,34 @@ export function compileClientSchema(
184
184
  });
185
185
  for (const alias of ambiguous) columnIndexByCamel.delete(alias);
186
186
  const indexes = table.indexes ?? [];
187
- for (const index of indexes) {
188
- if (schemaObjectNames.has(index.name)) {
189
- throw new Error(
190
- `${tableLabel}: index ${JSON.stringify(index.name)} conflicts with another schema object`,
191
- );
192
- }
193
- schemaObjectNames.add(index.name);
194
- for (const column of index.columns) {
195
- if (!columnIndex.has(column)) {
196
- throw new Error(
197
- `${tableLabel}: index ${JSON.stringify(index.name)} names unknown column ${JSON.stringify(column)}`,
198
- );
199
- }
200
- }
201
- }
202
187
  const ftsIndexes = table.ftsIndexes ?? [];
203
- for (const index of ftsIndexes) {
204
- const projectionLabel = `${tableLabel}: FTS projection ${JSON.stringify(index.name)}`;
205
- if (schemaObjectNames.has(index.name)) {
206
- throw new Error(
207
- `${projectionLabel} conflicts with another schema object`,
208
- );
209
- }
188
+ for (const index of [...indexes, ...ftsIndexes]) {
189
+ const fts = 'tokenize' in index;
190
+ const label = `${tableLabel}: ${fts ? 'FTS projection' : 'index'} ${JSON.stringify(index.name)}`;
191
+ if (schemaObjectNames.has(index.name))
192
+ invalidSchema(`${label} conflicts with another schema object`);
210
193
  schemaObjectNames.add(index.name);
211
- if (index.columns.length === 0 || index.columns.length > 32) {
212
- throw new Error(`${projectionLabel} needs between 1 and 32 columns`);
213
- }
214
- const seenColumns = new Set<string>();
215
- for (const columnName of index.columns) {
216
- if (seenColumns.has(columnName)) {
217
- throw new Error(
218
- `${projectionLabel} repeats column ${JSON.stringify(columnName)}`,
194
+ if (fts && (!index.columns.length || index.columns.length > 32))
195
+ invalidSchema(`${label} needs between 1 and 32 columns`);
196
+ const seen = new Set<string>();
197
+ for (const name of index.columns) {
198
+ if (fts && seen.has(name))
199
+ invalidSchema(`${label} repeats column ${JSON.stringify(name)}`);
200
+ seen.add(name);
201
+ const column = table.columns[columnIndex.get(name) ?? -1];
202
+ if (!column)
203
+ invalidSchema(
204
+ `${label} names unknown column ${JSON.stringify(name)}`,
219
205
  );
220
- }
221
- seenColumns.add(columnName);
222
- const column = table.columns.find(
223
- (candidate) => candidate.name === columnName,
224
- );
225
- if (column === undefined) {
226
- throw new Error(
227
- `${projectionLabel} names unknown column ${JSON.stringify(columnName)}`,
228
- );
229
- }
230
- if (localColumnType(column) !== 'string') {
231
- throw new Error(
232
- `${projectionLabel} column ${JSON.stringify(columnName)} must have string type`,
206
+ if (fts && localColumnType(column) !== 'string')
207
+ invalidSchema(
208
+ `${label} column ${JSON.stringify(name)} must have string type`,
233
209
  );
234
- }
235
210
  }
236
- if (!ALLOWED_FTS_TOKENIZERS.has(index.tokenize)) {
237
- throw new Error(
238
- `${projectionLabel} tokenizer ${JSON.stringify(index.tokenize)} is not allowlisted`,
211
+ if (fts && !ALLOWED_FTS_TOKENIZERS.has(index.tokenize))
212
+ invalidSchema(
213
+ `${label} tokenizer ${JSON.stringify(index.tokenize)} is not allowlisted`,
239
214
  );
240
- }
241
215
  }
242
216
  tables.set(table.name, {
243
217
  name: table.name,
@@ -291,6 +265,21 @@ export function stripSyncColumns(rows: SqlRow[]): SqlRow[] {
291
265
  });
292
266
  }
293
267
 
268
+ export function matchesLocalScopes(
269
+ table: CompiledClientTable,
270
+ scopes: Readonly<Record<string, readonly string[]>>,
271
+ values: Readonly<Record<string, JsonRowValue>>,
272
+ ): boolean {
273
+ const entries = Object.entries(scopes);
274
+ return (
275
+ entries.length > 0 &&
276
+ entries.every(([variable, allowed]) => {
277
+ const column = table.scopeColumnByVariable.get(variable);
278
+ return column !== undefined && allowed.includes(String(values[column]));
279
+ })
280
+ );
281
+ }
282
+
294
283
  export function quoteIdent(name: string): string {
295
284
  return `"${name.replaceAll('"', '""')}"`;
296
285
  }
@@ -382,6 +371,9 @@ function createFtsProjection(
382
371
  table: CompiledClientTable,
383
372
  index: ClientFtsIndexSpec,
384
373
  ): void {
374
+ const projection = quoteIdent(index.name);
375
+ const sourceTable = quoteIdent(table.name);
376
+ const primaryKey = quoteIdent(table.primaryKey);
385
377
  const existed =
386
378
  db.query(
387
379
  "SELECT 1 AS present FROM sqlite_master WHERE type='table' AND name=?",
@@ -390,7 +382,7 @@ function createFtsProjection(
390
382
  const indexedColumns = index.columns.map(quoteIdent);
391
383
  const tokenizer = index.tokenize.replaceAll("'", "''");
392
384
  db.exec(
393
- `CREATE VIRTUAL TABLE IF NOT EXISTS ${quoteIdent(index.name)} USING fts5(${quoteIdent(FTS_SOURCE_ID_COLUMN)} UNINDEXED,${indexedColumns.join(', ')},tokenize='${tokenizer}')`,
385
+ `CREATE VIRTUAL TABLE IF NOT EXISTS ${projection} USING fts5(${quoteIdent(FTS_SOURCE_ID_COLUMN)} UNINDEXED,${indexedColumns.join(', ')},tokenize='${tokenizer}')`,
394
386
  );
395
387
 
396
388
  const mapping = quoteIdent(`_syncular_fts_${index.name}`);
@@ -401,9 +393,9 @@ function createFtsProjection(
401
393
  db.exec(
402
394
  `CREATE TABLE IF NOT EXISTS ${mapping}(id INTEGER PRIMARY KEY,source_id TEXT NOT NULL UNIQUE)`,
403
395
  );
404
- const sourceId = `CAST(${quoteIdent(table.primaryKey)} AS TEXT)`;
405
- const newSourceId = `CAST(new.${quoteIdent(table.primaryKey)} AS TEXT)`;
406
- const oldSourceId = `CAST(old.${quoteIdent(table.primaryKey)} AS TEXT)`;
396
+ const sourceId = `CAST(${primaryKey} AS TEXT)`;
397
+ const newSourceId = `CAST(new.${primaryKey} AS TEXT)`;
398
+ const oldSourceId = `CAST(old.${primaryKey} AS TEXT)`;
407
399
  const projectionColumns = [
408
400
  quoteIdent(FTS_SOURCE_ID_COLUMN),
409
401
  ...indexedColumns,
@@ -413,10 +405,10 @@ function createFtsProjection(
413
405
  ...index.columns.map((column) => `new.${quoteIdent(column)}`),
414
406
  ].join(', ');
415
407
  const deleteFor = (value: string) =>
416
- `DELETE FROM ${quoteIdent(index.name)} WHERE rowid=(SELECT id FROM ${mapping} WHERE source_id=${value});DELETE FROM ${mapping} WHERE source_id=${value}`;
408
+ `DELETE FROM ${projection} WHERE rowid=(SELECT id FROM ${mapping} WHERE source_id=${value});DELETE FROM ${mapping} WHERE source_id=${value}`;
417
409
  const deleteDisplaced = (select: string) =>
418
- `DELETE FROM ${quoteIdent(index.name)} WHERE rowid IN (SELECT id FROM ${mapping} WHERE source_id IN (${select}));DELETE FROM ${mapping} WHERE source_id IN (${select})`;
419
- const insertNew = `INSERT INTO ${mapping}(source_id)VALUES(${newSourceId});INSERT INTO ${quoteIdent(index.name)}(rowid,${projectionColumns})VALUES((SELECT id FROM ${mapping} WHERE source_id=${newSourceId}),${newValues})`;
410
+ `DELETE FROM ${projection} WHERE rowid IN (SELECT id FROM ${mapping} WHERE source_id IN (${select}));DELETE FROM ${mapping} WHERE source_id IN (${select})`;
411
+ const insertNew = `INSERT INTO ${mapping}(source_id)VALUES(${newSourceId});INSERT INTO ${projection}(rowid,${projectionColumns})VALUES((SELECT id FROM ${mapping} WHERE source_id=${newSourceId}),${newValues})`;
420
412
 
421
413
  // A clean insert cannot already have a projection row because the source
422
414
  // primary key is unique. Keep clean inserts linear by moving replacement
@@ -445,7 +437,7 @@ function createFtsProjection(
445
437
  for (const suffix of ['bi', 'ai', 'ad', 'au', 'bu']) {
446
438
  db.exec(`DROP TRIGGER IF EXISTS ${quoteIdent(`${index.name}_${suffix}`)}`);
447
439
  }
448
- const replacementExists = `EXISTS (SELECT 1 FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(table.primaryKey)} = new.${quoteIdent(table.primaryKey)})`;
440
+ const replacementExists = `EXISTS (SELECT 1 FROM ${sourceTable} WHERE ${primaryKey} = new.${primaryKey})`;
449
441
  // Per secondary UNIQUE index: the different-PK rows about to be displaced
450
442
  // via that unique key. `=` makes NULL unique values match nothing, which is
451
443
  // exactly SQLite's unique-index semantics (NULLs never conflict).
@@ -454,7 +446,7 @@ function createFtsProjection(
454
446
  const match = spec.columns
455
447
  .map((column) => `${quoteIdent(column)} = new.${quoteIdent(column)}`)
456
448
  .join(' AND ');
457
- return `SELECT ${sourceId} FROM ${quoteIdent(table.name)} WHERE ${match} AND ${quoteIdent(table.primaryKey)} !=new.${quoteIdent(table.primaryKey)}`;
449
+ return `SELECT ${sourceId} FROM ${sourceTable} WHERE ${match} AND ${primaryKey} !=new.${primaryKey}`;
458
450
  });
459
451
  const insertGuardCondition = [
460
452
  replacementExists,
@@ -472,7 +464,7 @@ function createFtsProjection(
472
464
  when = '',
473
465
  ) =>
474
466
  db.exec(
475
- `CREATE TRIGGER ${quoteIdent(`${index.name}_${suffix}`)} ${timing} ON ${quoteIdent(table.name)}${when ? ` WHEN ${when}` : ''} BEGIN ${body};END`,
467
+ `CREATE TRIGGER ${quoteIdent(`${index.name}_${suffix}`)} ${timing} ON ${sourceTable}${when ? ` WHEN ${when}` : ''} BEGIN ${body};END`,
476
468
  );
477
469
  createTrigger('bi', 'BEFORE INSERT', insertGuardBody, insertGuardCondition);
478
470
  createTrigger('ai', 'AFTER INSERT', insertNew);
@@ -496,13 +488,13 @@ function createFtsProjection(
496
488
 
497
489
  if (!existed) {
498
490
  db.exec(
499
- `INSERT INTO ${quoteIdent(index.name)}(${projectionColumns})SELECT ${sourceId},${indexedColumns.join(', ')} FROM ${quoteIdent(table.name)}`,
491
+ `INSERT INTO ${projection}(${projectionColumns})SELECT ${sourceId},${indexedColumns.join(', ')} FROM ${sourceTable}`,
500
492
  );
501
493
  }
502
494
  if (!mapped || !existed) {
503
495
  db.exec(`DELETE FROM ${mapping}`);
504
496
  db.exec(
505
- `INSERT INTO ${mapping}(id,source_id)SELECT rowid,${quoteIdent(FTS_SOURCE_ID_COLUMN)} FROM ${quoteIdent(index.name)}`,
497
+ `INSERT INTO ${mapping}(id,source_id)SELECT rowid,${quoteIdent(FTS_SOURCE_ID_COLUMN)} FROM ${projection}`,
506
498
  );
507
499
  }
508
500
  }
@@ -558,6 +550,14 @@ export function ensureLocalBookkeepingSchema(db: ClientDatabase): void {
558
550
  '_syncular_outbox_before_images',
559
551
  'client_commit_id TEXT NOT NULL,op_index INTEGER NOT NULL,existed INTEGER NOT NULL CHECK(existed IN (0,1)),sync_version INTEGER,values_json TEXT,PRIMARY KEY(client_commit_id,op_index)',
560
552
  );
553
+ if (
554
+ !db
555
+ .query('PRAGMA table_info(_syncular_outbox_before_images)')
556
+ .some((row) => row.name === 'delivery_seq')
557
+ )
558
+ db.exec(
559
+ 'ALTER TABLE _syncular_outbox_before_images ADD COLUMN delivery_seq INTEGER',
560
+ );
561
561
  createTable(
562
562
  '_syncular_commit_outcomes',
563
563
  "seq INTEGER PRIMARY KEY AUTOINCREMENT,client_commit_id TEXT NOT NULL UNIQUE,status TEXT NOT NULL CHECK(status IN ('applied','cached','conflict','rejected')),recorded_at_ms INTEGER NOT NULL,results TEXT NOT NULL,operations TEXT,resolution TEXT NOT NULL DEFAULT 'active' CHECK(resolution IN ('active','resolved_keep_server','superseded','dismissed')),resolved_at_ms INTEGER,replacement_client_commit_id TEXT",
@@ -665,15 +665,8 @@ export function fromSqlValue(column: RowColumn, value: SqlValue): RowValue {
665
665
  return value !== 0 && value !== false;
666
666
  case 'integer':
667
667
  return typeof value === 'bigint' ? Number(value) : (value as number);
668
- case 'float':
669
- return value as number;
670
- case 'bytes':
671
- case 'crdt':
672
- return value as Uint8Array;
673
- case 'string':
674
- case 'json':
675
- case 'blob_ref':
676
- return value as string;
668
+ default:
669
+ return value as RowValue;
677
670
  }
678
671
  }
679
672
 
@@ -695,17 +688,17 @@ export function normalizeRecordKeys(
695
688
  if (index === undefined) {
696
689
  if (key.startsWith('_sync_')) {
697
690
  throw invalidRequest(
698
- `table ${table.name}: ${JSON.stringify(key)} is an internal sync column and cannot appear in mutation values — did you build this record from a raw SELECT * row? (client.query() strips _sync_* columns; rows read via client.database keep them)`,
691
+ `table ${table.name}: ${JSON.stringify(key)} is an internal sync column; omit it from mutation values`,
699
692
  );
700
693
  }
701
694
  throw invalidRequest(
702
- `table ${table.name}: unknown column ${JSON.stringify(key)} in mutation values (snake_case and camelCase keys are accepted)`,
695
+ `table ${table.name}: unknown column ${JSON.stringify(key)} in mutation values`,
703
696
  );
704
697
  }
705
698
  const sqlName = (table.columns[index] as RowColumn).name;
706
699
  if (normalized.has(sqlName)) {
707
700
  throw invalidRequest(
708
- `table ${table.name}: column ${JSON.stringify(sqlName)} appears twice in mutation values (as both snake_case and camelCase) — pass it once`,
701
+ `table ${table.name}: column ${JSON.stringify(sqlName)} appears twice in mutation values`,
709
702
  );
710
703
  }
711
704
  normalized.set(sqlName, value);
@@ -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;