@syncular/tauri 0.30.14 → 0.30.16

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
@@ -153,7 +153,9 @@ quarantine, or crash-resumed cleanup must finish before clinical data is
153
153
  available. The native database opens and migrates, but query/snapshot, mutation,
154
154
  subscription, sync, realtime, presence, blob, and automatic retry work fails
155
155
  with `client.security_preflight_required`. Status, local revision, lifecycle,
156
- and `purgeLocalData` remain available.
156
+ and `purgeLocalData` remain available. A creation-time `defineAuthorityReads`
157
+ policy on `createTauriAuthoritySyncClient` enables the read-only
158
+ `authoritySnapshot()` described below.
157
159
 
158
160
  ```ts
159
161
  const client = await createTauriSyncClient({
@@ -180,6 +182,82 @@ with `setHeaders()` only after successful activation. Closed clients reject data
180
182
  and control calls with `client.closed` before testing preflight; listener
181
183
  registration and local progress reads throw that code. `close()` is idempotent.
182
184
 
185
+ ## Authority evidence before activation
186
+
187
+ Declare authority reads at client creation with `defineAuthorityReads` from
188
+ `@syncular/client/authority`. Each table declares plain columns, including
189
+ its primary key and scope columns, and concrete scope selectors. These selectors remain
190
+ fixed for that client. A declaration cannot include encrypted, bytes, blob,
191
+ CRDT or internal columns. Choose only authority fields; clinical fields and
192
+ credentials do not belong in this policy.
193
+
194
+ ```ts
195
+ import { defineAuthorityReads } from '@syncular/client/authority';
196
+ import { createTauriAuthoritySyncClient } from '@syncular/tauri/authority';
197
+
198
+ const client = await createTauriAuthoritySyncClient({
199
+ schema,
200
+ securityPreflight: true,
201
+ transportEnabled: false,
202
+ authorityReads: defineAuthorityReads([{
203
+ table: 'memberships',
204
+ columns: ['id', 'user_id', 'facility_id', 'status', 'version'],
205
+ scopes: { membership_id: acceptedMembershipIds },
206
+ }]),
207
+ });
208
+ const evidence = await client.authoritySnapshot();
209
+ ```
210
+
211
+ Use `createTauriAuthoritySyncClient` for the bridge that exposes
212
+ `authoritySnapshot()`. The ordinary `createTauriSyncClient` class does not
213
+ contain that method or its reply decoder. Upgrade 0.30.15 callers to these
214
+ explicit subpaths; the snapshot and native security checks keep their semantics.
215
+
216
+ The native application must also set an independent ceiling at plugin creation:
217
+
218
+ ```rust
219
+ let config = SyncularConfig {
220
+ authority_columns: [("memberships".into(), vec![
221
+ "id".into(), "user_id".into(), "facility_id".into(),
222
+ "status".into(), "version".into(),
223
+ ])].into(),
224
+ ..Default::default()
225
+ };
226
+ ```
227
+
228
+ Rust rejects any webview declaration outside that ceiling. It also validates
229
+ columns against the schema. `authoritySnapshot()` accepts zero arguments;
230
+ forged IPC carrying SQL, replacement tables or columns fails with
231
+ `client.authority_read_forbidden`. Ordinary `query`, `querySnapshot` and writes
232
+ still fail with `client.security_preflight_required`.
233
+
234
+ The result contains `revision: bigint`, `complete`, and `tables`. Every table
235
+ contains accepted `rows` (`values`, server `version`, `hasLocalIntent`), scoped
236
+ `localIntentRowIds`, the declared `scopes`, `coverage`, and sanitized
237
+ `persisted` subscription evidence (`requestedScopes`, `effectiveScopes`,
238
+ `cursor`, `status`, `complete`). It exposes no intended values, bootstrap
239
+ tokens, subscription parameters or keys. Both cores read rows, revision and
240
+ coverage in one SQLite snapshot. The native authority read runs on the mutable
241
+ owner; it does not use the ordinary query sidecar or its latency contract.
242
+
243
+ `coverage` is `complete`, `pending` or `missing`. Complete coverage requires
244
+ completed unfiltered subscriptions for every requested scope tuple. Several
245
+ subscriptions can cover the selection together. Scope loss and an unfinished
246
+ bootstrap invalidate completeness. Completed empty coverage has an empty row
247
+ set. The application must reject admission when an expected authority row is
248
+ absent, even when the set is complete.
249
+
250
+ Accepted bases remain separate from pending, failed and protected ACK intent.
251
+ A local-only creation appears only in `localIntentRowIds`. The application
252
+ validates the complete chain against independently accepted authority evidence,
253
+ actor/device identity, signed lease and trusted time before installing keys.
254
+ The SDK does not authorize the application. The read changes no lifecycle,
255
+ transport, keyring, subscriptions or rows.
256
+
257
+ The direct Bun/SQLite client and worker handle expose the same policy and
258
+ snapshot shape. Importing the policy is opt-in; ordinary clients do not ship
259
+ its reader. Update npm packages and native crates together and rebuild the app.
260
+
183
261
  ## Privacy-safe diagnostics
184
262
 
185
263
  `diagnosticsSnapshot({ expectedSubscriptions })` and `onDiagnostics(listener)`
@@ -0,0 +1,10 @@
1
+ import type { AuthorityReadPolicy, AuthoritySnapshot } from '@syncular/client/authority';
2
+ import { TauriSyncClient, type TauriSyncClientConfig } from './index.js';
3
+ /** Explicit native bridge with the zero-argument preflight authority read. */
4
+ export declare class TauriAuthoritySyncClient extends TauriSyncClient {
5
+ authoritySnapshot(): Promise<AuthoritySnapshot>;
6
+ }
7
+ /** Construct an authority-enabled bridge; Rust still enforces its independent ceiling. */
8
+ export declare function createTauriAuthoritySyncClient(config: TauriSyncClientConfig & {
9
+ readonly authorityReads: AuthorityReadPolicy;
10
+ }): Promise<TauriAuthoritySyncClient>;
@@ -0,0 +1,26 @@
1
+ import { createTauriSyncClient, decodeRow, TauriSyncClient, TauriSyncError, } from './index.js';
2
+ /** Explicit native bridge with the zero-argument preflight authority read. */
3
+ export class TauriAuthoritySyncClient extends TauriSyncClient {
4
+ async authoritySnapshot() {
5
+ this.requireOpen();
6
+ if (arguments.length)
7
+ throw new TauriSyncError('client.authority_read_forbidden', 'authoritySnapshot accepts no arguments');
8
+ const result = (await this.command('authoritySnapshot', {}));
9
+ this.requireOpen();
10
+ return {
11
+ ...result,
12
+ revision: BigInt(result.revision),
13
+ tables: result.tables.map((table) => ({
14
+ ...table,
15
+ rows: table.rows.map((row) => ({
16
+ ...row,
17
+ values: decodeRow(row.values),
18
+ })),
19
+ })),
20
+ };
21
+ }
22
+ }
23
+ /** Construct an authority-enabled bridge; Rust still enforces its independent ceiling. */
24
+ export function createTauriAuthoritySyncClient(config) {
25
+ return createTauriSyncClient(config, (...args) => new TauriAuthoritySyncClient(...args));
26
+ }
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ import type { ClientSnapshotMethods, PromiseMethods } from '@syncular/client';
28
28
  * `invoke`/`listen` either from its ESM entry points, from the ambient
29
29
  * `window.__TAURI__`, or via injected doubles (tests).
30
30
  */
31
- import type { ClientChangeListener, ClientDiagnosticsListener, ClientDiagnosticsRequest, ClientDiagnosticsSnapshot, CommitOutcome, CommitOutcomeQuery, ConflictRecord, EncryptionKeyringConfig, InvalidationListener, LocalDataPurgeInput, LocalDataPurgeResult, LocalDataRebootstrapInput, LocalDataRebootstrapResult, MutationInput, PresencePeer, PreviousVersionAudit, PreviousVersionReadSpec, PreviousVersionSnapshot, QueryReadSpec, QuerySnapshot, RejectionRecord, ResolveCommitOutcomeInput, SecurityLifecycle, SqlRow, SqlValue, SyncClientConfig, SyncStatusSnapshot, WindowBase, WindowState } from '@syncular/client';
31
+ import type { AuthorityReadPolicy, ClientChangeListener, ClientDiagnosticsListener, ClientDiagnosticsRequest, ClientDiagnosticsSnapshot, CommitOutcome, CommitOutcomeQuery, ConflictRecord, EncryptionKeyringConfig, InvalidationListener, LocalDataPurgeInput, LocalDataPurgeResult, LocalDataRebootstrapInput, LocalDataRebootstrapResult, MutationInput, PresencePeer, PreviousVersionAudit, PreviousVersionReadSpec, PreviousVersionSnapshot, QueryReadSpec, QuerySnapshot, RejectionRecord, ResolveCommitOutcomeInput, SecurityLifecycle, SqlRow, SqlValue, SyncClientConfig, SyncStatusSnapshot, WindowBase, WindowState } from '@syncular/client';
32
32
  /** One event pushed on `syncular://event` (the derived client-observable set). */
33
33
  interface SyncularEvent {
34
34
  readonly type: string;
@@ -74,6 +74,8 @@ export interface TauriSyncClientConfig {
74
74
  * encoded into the native command envelope and never sent to the server.
75
75
  */
76
76
  readonly encryption?: EncryptionKeyringConfig;
77
+ /** Fixed plain authority reads permitted before security activation. */
78
+ readonly authorityReads?: AuthorityReadPolicy;
77
79
  /** Open the native replica behind the fail-closed security gate. */
78
80
  readonly securityPreflight?: boolean;
79
81
  /** Open local storage with network transport closed until explicitly resumed. */
@@ -95,6 +97,8 @@ export interface TauriSyncClientConfig {
95
97
  export type BytesEnvelope = {
96
98
  readonly $bytes: string;
97
99
  };
100
+ /** @internal Shared native row codec. */
101
+ export declare function decodeRow(row: Record<string, unknown>): SqlRow;
98
102
  /**
99
103
  * The webview-side proxy implementing `SyncClientLike` over the plugin. Every
100
104
  * snapshot method returns a promise from an IPC round trip. React hooks accept
@@ -104,6 +108,10 @@ export declare class TauriSyncClient implements PromiseMethods<ClientSnapshotMet
104
108
  #private;
105
109
  /** @internal — use {@link createTauriSyncClient}. */
106
110
  constructor(tauri: TauriApi, unlisten: () => void, securityLifecycle?: SecurityLifecycle);
111
+ /** @internal Shared command gate for explicit bridge extensions. */
112
+ protected command(method: string, params: Record<string, unknown>): Promise<unknown>;
113
+ /** @internal Recheck local close after an extension's asynchronous reply. */
114
+ protected requireOpen(): void;
107
115
  /** @internal — fan an incoming plugin event out to the local listeners. */
108
116
  __dispatchEvent(event: SyncularEvent): void;
109
117
  securityLifecycle(): Promise<SecurityLifecycle>;
@@ -201,4 +209,6 @@ export declare class TauriSyncError extends Error {
201
209
  * React `<SyncProvider client={…}>`.
202
210
  */
203
211
  export declare function createTauriSyncClient(config: TauriSyncClientConfig): Promise<TauriSyncClient>;
212
+ /** @internal Explicit bridge extension used by the authority entry. */
213
+ export declare function createTauriSyncClient<Client extends TauriSyncClient>(config: TauriSyncClientConfig, createClient: (...args: ConstructorParameters<typeof TauriSyncClient>) => Client): Promise<Client>;
204
214
  export {};
package/dist/index.js CHANGED
@@ -82,7 +82,8 @@ function decodeCell(value) {
82
82
  // string (SQLite json columns arrive as text already; this is defensive).
83
83
  return JSON.stringify(value);
84
84
  }
85
- function decodeRow(row) {
85
+ /** @internal Shared native row codec. */
86
+ export function decodeRow(row) {
86
87
  const out = {};
87
88
  for (const [key, value] of Object.entries(row)) {
88
89
  // Reserved `_sync_*` columns stay engine-internal (parity with the
@@ -163,6 +164,7 @@ export class TauriSyncClient {
163
164
  this.#requireOpen();
164
165
  if (this.#securityLifecycle === 'preflight' &&
165
166
  ![
167
+ 'authoritySnapshot',
166
168
  'securityLifecycle',
167
169
  'beginSecurityPreflight',
168
170
  'activateSecurity',
@@ -189,6 +191,14 @@ export class TauriSyncClient {
189
191
  throw new TauriSyncError('client.closed', 'the Tauri sync client is closed');
190
192
  }
191
193
  }
194
+ /** @internal Shared command gate for explicit bridge extensions. */
195
+ command(method, params) {
196
+ return this.#command(method, params);
197
+ }
198
+ /** @internal Recheck local close after an extension's asynchronous reply. */
199
+ requireOpen() {
200
+ this.#requireOpen();
201
+ }
192
202
  #requireActive() {
193
203
  this.#requireOpen();
194
204
  if (this.#securityLifecycle === 'preflight')
@@ -578,19 +588,20 @@ export class TauriSyncClient {
578
588
  async close() {
579
589
  if (this.#closed)
580
590
  return;
591
+ const shutdown = this.#command('shutdown', {});
592
+ this.#closed = true;
581
593
  try {
582
- await this.#command('shutdown', {});
594
+ await shutdown;
583
595
  }
584
596
  finally {
585
- this.#closed = true;
597
+ this.#unlisten?.();
598
+ this.#unlisten = undefined;
599
+ this.#invalidationListeners.clear();
600
+ this.#progress.clear();
601
+ this.#changeListeners.clear();
602
+ this.#diagnosticsListeners.clear();
603
+ this.#presenceListeners.clear();
586
604
  }
587
- this.#unlisten?.();
588
- this.#unlisten = undefined;
589
- this.#invalidationListeners.clear();
590
- this.#progress.clear();
591
- this.#changeListeners.clear();
592
- this.#diagnosticsListeners.clear();
593
- this.#presenceListeners.clear();
594
605
  }
595
606
  }
596
607
  function decodeDiagnosticsSnapshot(value, kind) {
@@ -700,13 +711,7 @@ function encodeMutation(mutation) {
700
711
  }
701
712
  return { ...mutation, values };
702
713
  }
703
- /**
704
- * Construct the bridge and issue the native `create` (opening/attaching the
705
- * file DB on the Rust side per the plugin config). Returns a ready
706
- * `TauriSyncClient` that satisfies `SyncClientLike` — pass it straight to the
707
- * React `<SyncProvider client={…}>`.
708
- */
709
- export async function createTauriSyncClient(config) {
714
+ export async function createTauriSyncClient(config, createClient = (...args) => new TauriSyncClient(...args)) {
710
715
  const tauri = await resolveTauri(config.tauri);
711
716
  // Wire the event stream BEFORE create, so no early change batch is missed.
712
717
  const clientRef = {
@@ -719,7 +724,7 @@ export async function createTauriSyncClient(config) {
719
724
  unlisten();
720
725
  throw new TauriSyncError('sync.invalid_request', 'securityPreflight and encryption are mutually exclusive; install keys with activateSecurity after preflight');
721
726
  }
722
- const client = new TauriSyncClient(tauri, unlisten, config.securityPreflight === true ? 'preflight' : 'active');
727
+ const client = createClient(tauri, unlisten, config.securityPreflight === true ? 'preflight' : 'active');
723
728
  clientRef.client = client;
724
729
  // The native side owns every path (plugin config); the JS side names a
725
730
  // database and supplies the schema, clientId, and limits.
@@ -743,6 +748,9 @@ export async function createTauriSyncClient(config) {
743
748
  ...(config.transportEnabled !== undefined
744
749
  ? { transportEnabled: config.transportEnabled }
745
750
  : {}),
751
+ ...(config.authorityReads !== undefined
752
+ ? { authorityReads: config.authorityReads.declarations }
753
+ : {}),
746
754
  ...(config.securityPreflight !== undefined
747
755
  ? { securityPreflight: config.securityPreflight }
748
756
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/tauri",
3
- "version": "0.30.14",
3
+ "version": "0.30.16",
4
4
  "description": "Tauri integration for the Syncular client",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -33,6 +33,14 @@
33
33
  "types": "./dist/index.d.ts",
34
34
  "default": "./dist/index.js"
35
35
  }
36
+ },
37
+ "./authority": {
38
+ "bun": "./src/authority.ts",
39
+ "browser": "./dist/authority.js",
40
+ "import": {
41
+ "types": "./dist/authority.d.ts",
42
+ "default": "./dist/authority.js"
43
+ }
36
44
  }
37
45
  },
38
46
  "files": [
@@ -48,13 +56,13 @@
48
56
  "test": "bun test"
49
57
  },
50
58
  "dependencies": {
51
- "@syncular/client": "0.30.14"
59
+ "@syncular/client": "0.30.16"
52
60
  },
53
61
  "peerDependencies": {
54
62
  "@tauri-apps/api": ">=2.0.0"
55
63
  },
56
64
  "devDependencies": {
57
- "@syncular/react": "0.30.14",
58
- "@syncular/server": "0.30.14"
65
+ "@syncular/react": "0.30.16",
66
+ "@syncular/server": "0.30.16"
59
67
  }
60
68
  }
@@ -0,0 +1,51 @@
1
+ import type {
2
+ AuthorityReadPolicy,
3
+ AuthoritySnapshot,
4
+ } from '@syncular/client/authority';
5
+ import {
6
+ createTauriSyncClient,
7
+ decodeRow,
8
+ TauriSyncClient,
9
+ TauriSyncError,
10
+ type TauriSyncClientConfig,
11
+ } from './index';
12
+
13
+ /** Explicit native bridge with the zero-argument preflight authority read. */
14
+ export class TauriAuthoritySyncClient extends TauriSyncClient {
15
+ async authoritySnapshot(): Promise<AuthoritySnapshot> {
16
+ this.requireOpen();
17
+ if (arguments.length)
18
+ throw new TauriSyncError(
19
+ 'client.authority_read_forbidden',
20
+ 'authoritySnapshot accepts no arguments',
21
+ );
22
+ const result = (await this.command('authoritySnapshot', {})) as Omit<
23
+ AuthoritySnapshot,
24
+ 'revision'
25
+ > & { revision: string };
26
+ this.requireOpen();
27
+ return {
28
+ ...result,
29
+ revision: BigInt(result.revision),
30
+ tables: result.tables.map((table) => ({
31
+ ...table,
32
+ rows: table.rows.map((row) => ({
33
+ ...row,
34
+ values: decodeRow(row.values),
35
+ })),
36
+ })),
37
+ };
38
+ }
39
+ }
40
+
41
+ /** Construct an authority-enabled bridge; Rust still enforces its independent ceiling. */
42
+ export function createTauriAuthoritySyncClient(
43
+ config: TauriSyncClientConfig & {
44
+ readonly authorityReads: AuthorityReadPolicy;
45
+ },
46
+ ): Promise<TauriAuthoritySyncClient> {
47
+ return createTauriSyncClient(
48
+ config,
49
+ (...args) => new TauriAuthoritySyncClient(...args),
50
+ );
51
+ }
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ import type { ClientSnapshotMethods, PromiseMethods } from '@syncular/client';
34
34
  */
35
35
 
36
36
  import type {
37
+ AuthorityReadPolicy,
37
38
  ClientChangeBatch,
38
39
  ClientChangeListener,
39
40
  ClientDiagnosticsListener,
@@ -139,6 +140,8 @@ export interface TauriSyncClientConfig {
139
140
  * encoded into the native command envelope and never sent to the server.
140
141
  */
141
142
  readonly encryption?: EncryptionKeyringConfig;
143
+ /** Fixed plain authority reads permitted before security activation. */
144
+ readonly authorityReads?: AuthorityReadPolicy;
142
145
  /** Open the native replica behind the fail-closed security gate. */
143
146
  readonly securityPreflight?: boolean;
144
147
  /** Open local storage with network transport closed until explicitly resumed. */
@@ -246,7 +249,8 @@ function decodeCell(value: unknown): SqlValue {
246
249
  return JSON.stringify(value);
247
250
  }
248
251
 
249
- function decodeRow(row: Record<string, unknown>): SqlRow {
252
+ /** @internal Shared native row codec. */
253
+ export function decodeRow(row: Record<string, unknown>): SqlRow {
250
254
  const out: SqlRow = {};
251
255
  for (const [key, value] of Object.entries(row)) {
252
256
  // Reserved `_sync_*` columns stay engine-internal (parity with the
@@ -346,6 +350,7 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
346
350
  if (
347
351
  this.#securityLifecycle === 'preflight' &&
348
352
  ![
353
+ 'authoritySnapshot',
349
354
  'securityLifecycle',
350
355
  'beginSecurityPreflight',
351
356
  'activateSecurity',
@@ -389,6 +394,19 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
389
394
  }
390
395
  }
391
396
 
397
+ /** @internal Shared command gate for explicit bridge extensions. */
398
+ protected command(
399
+ method: string,
400
+ params: Record<string, unknown>,
401
+ ): Promise<unknown> {
402
+ return this.#command(method, params);
403
+ }
404
+
405
+ /** @internal Recheck local close after an extension's asynchronous reply. */
406
+ protected requireOpen(): void {
407
+ this.#requireOpen();
408
+ }
409
+
392
410
  #requireActive(): void {
393
411
  this.#requireOpen();
394
412
  if (this.#securityLifecycle === 'preflight') this.#throwSecurityPreflight();
@@ -932,18 +950,19 @@ export class TauriSyncClient implements PromiseMethods<ClientSnapshotMethods> {
932
950
  /** Shut down the native core, release its keyring, then detach listeners. */
933
951
  async close(): Promise<void> {
934
952
  if (this.#closed) return;
953
+ const shutdown = this.#command('shutdown', {});
954
+ this.#closed = true;
935
955
  try {
936
- await this.#command('shutdown', {});
956
+ await shutdown;
937
957
  } finally {
938
- this.#closed = true;
958
+ this.#unlisten?.();
959
+ this.#unlisten = undefined;
960
+ this.#invalidationListeners.clear();
961
+ this.#progress.clear();
962
+ this.#changeListeners.clear();
963
+ this.#diagnosticsListeners.clear();
964
+ this.#presenceListeners.clear();
939
965
  }
940
- this.#unlisten?.();
941
- this.#unlisten = undefined;
942
- this.#invalidationListeners.clear();
943
- this.#progress.clear();
944
- this.#changeListeners.clear();
945
- this.#diagnosticsListeners.clear();
946
- this.#presenceListeners.clear();
947
966
  }
948
967
  }
949
968
 
@@ -1066,8 +1085,20 @@ function encodeMutation(mutation: MutationInput): unknown {
1066
1085
  * `TauriSyncClient` that satisfies `SyncClientLike` — pass it straight to the
1067
1086
  * React `<SyncProvider client={…}>`.
1068
1087
  */
1088
+ export function createTauriSyncClient(
1089
+ config: TauriSyncClientConfig,
1090
+ ): Promise<TauriSyncClient>;
1091
+ /** @internal Explicit bridge extension used by the authority entry. */
1092
+ export function createTauriSyncClient<Client extends TauriSyncClient>(
1093
+ config: TauriSyncClientConfig,
1094
+ createClient: (
1095
+ ...args: ConstructorParameters<typeof TauriSyncClient>
1096
+ ) => Client,
1097
+ ): Promise<Client>;
1069
1098
  export async function createTauriSyncClient(
1070
1099
  config: TauriSyncClientConfig,
1100
+ createClient = (...args: ConstructorParameters<typeof TauriSyncClient>) =>
1101
+ new TauriSyncClient(...args),
1071
1102
  ): Promise<TauriSyncClient> {
1072
1103
  const tauri = await resolveTauri(config.tauri);
1073
1104
 
@@ -1090,7 +1121,7 @@ export async function createTauriSyncClient(
1090
1121
  );
1091
1122
  }
1092
1123
 
1093
- const client = new TauriSyncClient(
1124
+ const client = createClient(
1094
1125
  tauri,
1095
1126
  unlisten,
1096
1127
  config.securityPreflight === true ? 'preflight' : 'active',
@@ -1119,6 +1150,9 @@ export async function createTauriSyncClient(
1119
1150
  ...(config.transportEnabled !== undefined
1120
1151
  ? { transportEnabled: config.transportEnabled }
1121
1152
  : {}),
1153
+ ...(config.authorityReads !== undefined
1154
+ ? { authorityReads: config.authorityReads.declarations }
1155
+ : {}),
1122
1156
  ...(config.securityPreflight !== undefined
1123
1157
  ? { securityPreflight: config.securityPreflight }
1124
1158
  : {}),