@syncular/react 0.15.14 → 0.15.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
@@ -101,6 +101,27 @@ const clientResource = createSyncClientResource(() => createClient());
101
101
  Call `await clientResource.dispose()` from the application's real lifecycle
102
102
  owner, not from a StrictMode-sensitive child effect.
103
103
 
104
+ If the application requires authentication or signed quarantine processing
105
+ before protected data is visible, complete the concrete client's security
106
+ preflight before returning it from the resource factory. Do not mount the
107
+ ordinary provider tree while `securityLifecycle` is `preflight`: reactive store
108
+ startup intentionally touches protected status/outcome/query surfaces.
109
+
110
+ ```ts
111
+ const clientResource = createSyncClientResource(async () => {
112
+ const client = await createClient({ securityPreflight: true });
113
+ await applyValidatedLocalPurge(client);
114
+ await client.activateSecurity({ encryption: acceptedKeyring });
115
+ return client;
116
+ });
117
+ ```
118
+
119
+ The normalized client exposes `securityLifecycle()`,
120
+ `beginSecurityPreflight()`, and keyless `activateSecurity()` for host-agnostic
121
+ coordination. Key-bearing activation stays on each concrete client type because
122
+ direct TypeScript uses an `EncryptionConfig`, while Worker/Tauri/React Native
123
+ use the portable keyring.
124
+
104
125
  Use `renderBoundary` as the canonical application guard across browser and
105
126
  native hosts. It covers resource startup/errors, migration, client upgrade,
106
127
  server-behind, incompatible-schema, and unreachable-leader states. The
package/dist/client.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * value → read it) and wraps every result in `Promise.resolve`, so a hook
12
12
  * never has to care which core it holds.
13
13
  */
14
- import type { ClientChangeListener, CommitOutcome, CommitOutcomeQuery, ConflictRecord, InvalidationListener, LeadershipState, LeaseState, LocalDataPurgeInput, LocalDataPurgeResult, MutationInput, PresencePeer, QueryReadSpec, QuerySnapshot, RejectionRecord, ResolveCommitOutcomeInput, SchemaFloor, SqlRow, SqlValue, SyncStatusSnapshot, WindowBase, WindowState } from '@syncular/client';
14
+ import type { ClientChangeListener, CommitOutcome, CommitOutcomeQuery, ConflictRecord, InvalidationListener, LeadershipState, LeaseState, LocalDataPurgeInput, LocalDataPurgeResult, MutationInput, PresencePeer, QueryReadSpec, QuerySnapshot, RejectionRecord, ResolveCommitOutcomeInput, SchemaFloor, SecurityLifecycle, SqlRow, SqlValue, SyncStatusSnapshot, WindowBase, WindowState } from '@syncular/client';
15
15
  /**
16
16
  * The structural union of `SyncClient` and `SyncClientHandle`. Members that
17
17
  * diverge are typed as "value or method, sync or promise"; {@link normalizeClient}
@@ -25,6 +25,10 @@ export interface SyncClientLike {
25
25
  onPresence(listener: (scopeKey: string) => void): () => void;
26
26
  onLeadershipChange?(listener: (state: LeadershipState) => void): () => void;
27
27
  leadershipSnapshot?(): LeadershipState | undefined;
28
+ securityLifecycle: SecurityLifecycle | (() => SecurityLifecycle | Promise<SecurityLifecycle>);
29
+ beginSecurityPreflight(): void | Promise<void>;
30
+ /** Key-bearing activation remains available on each concrete host type. */
31
+ activateSecurity(): void | Promise<void>;
28
32
  query(sql: string, params?: readonly SqlValue[]): SqlRow[] | Promise<SqlRow[]>;
29
33
  mutate(mutations: readonly MutationInput[]): string | Promise<string>;
30
34
  patch(table: string, rowId: string, partial: Readonly<Record<string, unknown>>, options?: {
@@ -58,6 +62,10 @@ export interface NormalizedClient {
58
62
  onPresence(listener: (scopeKey: string) => void): () => void;
59
63
  onLeadershipChange(listener: (state: LeadershipState) => void): () => void;
60
64
  leadershipSnapshot(): LeadershipState | undefined;
65
+ securityLifecycle(): Promise<SecurityLifecycle>;
66
+ beginSecurityPreflight(): Promise<void>;
67
+ /** Key-bearing activation remains available on each concrete host type. */
68
+ activateSecurity(): Promise<void>;
61
69
  query(sql: string, params?: readonly SqlValue[]): Promise<SqlRow[]>;
62
70
  mutate(mutations: readonly MutationInput[]): Promise<string>;
63
71
  patch(table: string, rowId: string, partial: Readonly<Record<string, unknown>>, options?: {
package/dist/client.js CHANGED
@@ -20,6 +20,9 @@ export function normalizeClient(client) {
20
20
  onPresence: (listener) => client.onPresence(listener),
21
21
  onLeadershipChange: (listener) => client.onLeadershipChange?.(listener) ?? (() => { }),
22
22
  leadershipSnapshot: () => client.leadershipSnapshot?.(),
23
+ securityLifecycle: () => resolveMember(client, 'securityLifecycle'),
24
+ beginSecurityPreflight: () => Promise.resolve(client.beginSecurityPreflight()),
25
+ activateSecurity: () => Promise.resolve(client.activateSecurity()),
23
26
  query: (sql, params) => Promise.resolve(client.query(sql, params)),
24
27
  mutate: (mutations) => Promise.resolve(client.mutate(mutations)),
25
28
  patch: (table, rowId, partial, options) => Promise.resolve(client.patch(table, rowId, partial, options)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/react",
3
- "version": "0.15.14",
3
+ "version": "0.15.16",
4
4
  "description": "React hooks for Syncular offline-first sync",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -48,15 +48,15 @@
48
48
  "test": "bun test --preload ./test/setup.ts"
49
49
  },
50
50
  "dependencies": {
51
- "@syncular/client": "0.15.14"
51
+ "@syncular/client": "0.15.16"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "react": ">=18.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@happy-dom/global-registrator": "^15.11.0",
58
- "@syncular/core": "0.15.14",
59
- "@syncular/server": "0.15.14",
57
+ "@happy-dom/global-registrator": "^20.0.0",
58
+ "@syncular/core": "0.15.16",
59
+ "@syncular/server": "0.15.16",
60
60
  "@testing-library/react": "^16.1.0",
61
61
  "@types/react": "^18.3.0",
62
62
  "react": "^18.3.1",
package/src/client.ts CHANGED
@@ -28,6 +28,7 @@ import type {
28
28
  RejectionRecord,
29
29
  ResolveCommitOutcomeInput,
30
30
  SchemaFloor,
31
+ SecurityLifecycle,
31
32
  SqlRow,
32
33
  SqlValue,
33
34
  SyncStatusSnapshot,
@@ -48,6 +49,12 @@ export interface SyncClientLike {
48
49
  onPresence(listener: (scopeKey: string) => void): () => void;
49
50
  onLeadershipChange?(listener: (state: LeadershipState) => void): () => void;
50
51
  leadershipSnapshot?(): LeadershipState | undefined;
52
+ securityLifecycle:
53
+ | SecurityLifecycle
54
+ | (() => SecurityLifecycle | Promise<SecurityLifecycle>);
55
+ beginSecurityPreflight(): void | Promise<void>;
56
+ /** Key-bearing activation remains available on each concrete host type. */
57
+ activateSecurity(): void | Promise<void>;
51
58
  query(
52
59
  sql: string,
53
60
  params?: readonly SqlValue[],
@@ -130,6 +137,10 @@ export interface NormalizedClient {
130
137
  onPresence(listener: (scopeKey: string) => void): () => void;
131
138
  onLeadershipChange(listener: (state: LeadershipState) => void): () => void;
132
139
  leadershipSnapshot(): LeadershipState | undefined;
140
+ securityLifecycle(): Promise<SecurityLifecycle>;
141
+ beginSecurityPreflight(): Promise<void>;
142
+ /** Key-bearing activation remains available on each concrete host type. */
143
+ activateSecurity(): Promise<void>;
133
144
  query(sql: string, params?: readonly SqlValue[]): Promise<SqlRow[]>;
134
145
  mutate(mutations: readonly MutationInput[]): Promise<string>;
135
146
  patch(
@@ -173,6 +184,10 @@ export function normalizeClient(client: SyncClientLike): NormalizedClient {
173
184
  onLeadershipChange: (listener) =>
174
185
  client.onLeadershipChange?.(listener) ?? (() => {}),
175
186
  leadershipSnapshot: () => client.leadershipSnapshot?.(),
187
+ securityLifecycle: () => resolveMember(client, 'securityLifecycle'),
188
+ beginSecurityPreflight: () =>
189
+ Promise.resolve(client.beginSecurityPreflight()),
190
+ activateSecurity: () => Promise.resolve(client.activateSecurity()),
176
191
  query: (sql, params) => Promise.resolve(client.query(sql, params)),
177
192
  mutate: (mutations) => Promise.resolve(client.mutate(mutations)),
178
193
  patch: (table, rowId, partial, options) =>