@voltro/react-native 0.62.3 → 0.63.0

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/CHANGELOG.md CHANGED
@@ -39,6 +39,20 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.63.0] — 2026-09-03
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/client, @voltro/runtime, @voltro/cli, @voltro/web, @voltro/react-native** — Short transport gaps now have one bounded recovery contract. Raw diagnostics remain immediate, while `useConnectionStatus` exposes `recovering` during a configurable grace window and `useOnRpcError` emits exactly one visible transport failure only if the gap remains open. Same-subject runtime swaps keep subscription bases, revisions and optimistic patches; subject changes discard them. Interrupted mutations report `outcome: unknown` and replay once with the same key only after the deployed API proves that server idempotency is active. The codemod explains the new status member and the raw versus presentation error channels.
47
+
48
+ **`voltro update` carries you across this** — codemod `0.63.0/01_recovery-presentation`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.63.0).
49
+
50
+ ### Fixed
51
+
52
+ - **@voltro/database, @voltro/sql-turso, @voltro/sql-sqlite, @voltro/sql-mssql** — Framework-table evolution now converges under real SQLite and both local Turso transports, and has a live MSSQL fixture. The new Turso runs also fixed internal table protection, boolean/number introspection normalization, and case-insensitive SQLite-family index-name comparison. The MSSQL fixture exposed and fixes a second convergence bug: unbounded text that MSSQL auto-bounds for an index is now rendered and compared with the same indexed-column rule, so a second plan no longer tries to widen an indexed `NVARCHAR(450)` to `NVARCHAR(MAX)`.
53
+
54
+ ---
55
+
42
56
  ## [0.62.3] — 2026-09-03
43
57
 
44
58
  ### Added
package/dist/index.d.ts CHANGED
@@ -325,6 +325,7 @@ export declare interface MobileApiBinding {
325
325
  * {@link resolveDevWsUrl}. */
326
326
  readonly wsUrl: string;
327
327
  readonly headers?: ResolvableHeaders | undefined;
328
+ readonly recovery?: MountedApi['recovery'];
328
329
  }
329
330
 
330
331
  export declare interface MobileConnectionControls {
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as e, i as t, n, r, t as i } from "./devices-BjXAU-8I.js";
2
2
  import { useCallback as a, useEffect as o, useReducer as s, useRef as c, useState as l } from "react";
3
- import { buildApiRuntime as u, startApiSupervisor as d } from "@voltro/client";
3
+ import { buildApiRuntime as u, recoveryCoordinatorFor as d, startApiSupervisor as f } from "@voltro/client";
4
4
  //#region src/deepLink.ts
5
- var f = (e) => {
5
+ var p = (e) => {
6
6
  let t = e.pattern;
7
7
  if (t.length === 0 || t[0] !== "/") throw Error(`defineDeepLink: pattern must start with "/" (got "${t}")`);
8
8
  return {
@@ -10,7 +10,7 @@ var f = (e) => {
10
10
  pattern: t,
11
11
  handler: e.handler
12
12
  };
13
- }, p = (e) => {
13
+ }, m = (e) => {
14
14
  let t = e, n = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\//.exec(t);
15
15
  if (n) {
16
16
  let e = t.slice(n[0].length), r = n[1].toLowerCase();
@@ -27,8 +27,8 @@ var f = (e) => {
27
27
  return e;
28
28
  }
29
29
  });
30
- }, m = (e, t) => {
31
- let n = p(e), r = p(t);
30
+ }, h = (e, t) => {
31
+ let n = m(e), r = m(t);
32
32
  if (n.length !== r.length) return null;
33
33
  let i = {};
34
34
  for (let e = 0; e < n.length; e++) {
@@ -40,10 +40,10 @@ var f = (e) => {
40
40
  } else if (t !== "*" && t !== a) return null;
41
41
  }
42
42
  return i;
43
- }, h = (e, t) => {
44
- let n = m(e.pattern, t);
45
- return n === null ? null : (e.handler(n), n);
46
43
  }, g = (e, t) => {
44
+ let n = h(e.pattern, t);
45
+ return n === null ? null : (e.handler(n), n);
46
+ }, _ = (e, t) => {
47
47
  let n = e.split("/").map((n) => {
48
48
  if (!n.startsWith(":")) return n;
49
49
  let r = n.slice(1), i = t[r];
@@ -51,21 +51,21 @@ var f = (e) => {
51
51
  return encodeURIComponent(i);
52
52
  }).join("/");
53
53
  return n.length === 0 ? "/" : n;
54
- }, _ = (e, t) => {
54
+ }, v = (e, t) => {
55
55
  for (let n of e) {
56
- let e = m(n.pattern, t);
56
+ let e = h(n.pattern, t);
57
57
  if (e !== null) return {
58
58
  descriptor: n,
59
59
  params: e
60
60
  };
61
61
  }
62
62
  return null;
63
- }, v = (e, t) => {
64
- let n = _(e, t);
63
+ }, y = (e, t) => {
64
+ let n = v(e, t);
65
65
  if (n === null) return null;
66
66
  let r = n.descriptor.handler;
67
67
  return r(n.params), n.params;
68
- }, y = {
68
+ }, b = {
69
69
  status: "idle",
70
70
  isForeground: !0,
71
71
  lastSyncAt: void 0,
@@ -73,7 +73,7 @@ var f = (e) => {
73
73
  lastError: void 0,
74
74
  successCount: 0,
75
75
  failureCount: 0
76
- }, b = (e, t) => {
76
+ }, x = (e, t) => {
77
77
  switch (t.type) {
78
78
  case "foreground": return e.isForeground ? e : {
79
79
  ...e,
@@ -104,11 +104,11 @@ var f = (e) => {
104
104
  failureCount: e.failureCount + 1
105
105
  };
106
106
  case "reset": return {
107
- ...y,
107
+ ...b,
108
108
  isForeground: e.isForeground
109
109
  };
110
110
  }
111
- }, x = (e, t, n, r = !1) => !(!n.enabled || e.status === "syncing" || !r && !e.isForeground || !r && n.intervalMs > 0 && e.lastAttemptAt !== void 0 && t - e.lastAttemptAt < n.intervalMs), S = (e, t) => {
111
+ }, S = (e, t, n, r = !1) => !(!n.enabled || e.status === "syncing" || !r && !e.isForeground || !r && n.intervalMs > 0 && e.lastAttemptAt !== void 0 && t - e.lastAttemptAt < n.intervalMs), C = (e, t) => {
112
112
  if (typeof document > "u" || typeof window > "u") return () => {};
113
113
  let n = () => {
114
114
  document.visibilityState === "visible" ? e() : t();
@@ -116,13 +116,13 @@ var f = (e) => {
116
116
  return document.addEventListener("visibilitychange", n), window.addEventListener("focus", e), () => {
117
117
  document.removeEventListener("visibilitychange", n), window.removeEventListener("focus", e);
118
118
  };
119
- }, C = 3e5, w = (e, t = {}) => {
120
- let n = t.intervalMs ?? C, r = t.syncOnForeground ?? !0, i = t.enabled ?? !0, l = t.now ?? Date.now, u = t.subscribeForeground ?? S, [d, f] = s(b, y), p = c(e);
119
+ }, w = 3e5, T = (e, t = {}) => {
120
+ let n = t.intervalMs ?? w, r = t.syncOnForeground ?? !0, i = t.enabled ?? !0, l = t.now ?? Date.now, u = t.subscribeForeground ?? C, [d, f] = s(x, b), p = c(e);
121
121
  p.current = e;
122
122
  let m = c(d);
123
123
  m.current = d;
124
124
  let h = c(!0), g = a((e) => {
125
- x(m.current, l(), {
125
+ S(m.current, l(), {
126
126
  intervalMs: n,
127
127
  enabled: i
128
128
  }, e) && (f({
@@ -167,7 +167,7 @@ var f = (e) => {
167
167
  ...d,
168
168
  sync: _
169
169
  };
170
- }, T = {
170
+ }, E = {
171
171
  localFirst: !0,
172
172
  optimistic: !0,
173
173
  syncOnForeground: !0,
@@ -179,7 +179,7 @@ var f = (e) => {
179
179
  15e3,
180
180
  6e4
181
181
  ]
182
- }, E = (e, t) => e ? t > 0 ? "degraded" : "connected" : "offline", D = {
182
+ }, D = (e, t) => e ? t > 0 ? "degraded" : "connected" : "offline", O = {
183
183
  read: () => typeof navigator > "u" || navigator.onLine,
184
184
  subscribe: (e) => {
185
185
  if (typeof window > "u") return () => {};
@@ -188,7 +188,7 @@ var f = (e) => {
188
188
  window.removeEventListener("online", t), window.removeEventListener("offline", n);
189
189
  };
190
190
  }
191
- }, O = (e) => {
191
+ }, k = (e) => {
192
192
  let t = (e) => typeof e.isInternetReachable == "boolean" ? e.isInternetReachable : e.isConnected === !0, n = !0;
193
193
  return {
194
194
  read: () => n,
@@ -201,8 +201,8 @@ var f = (e) => {
201
201
  }).catch(() => {}), i;
202
202
  }
203
203
  };
204
- }, k = (e) => {
205
- let t = e?.onlineSource ?? D, [n, r] = l(() => t.read()), [i, s] = l(0), [u, d] = l(void 0), f = c(!0);
204
+ }, A = (e) => {
205
+ let t = e?.onlineSource ?? O, [n, r] = l(() => t.read()), [i, s] = l(0), [u, d] = l(void 0), f = c(!0);
206
206
  o(() => (f.current = !0, () => {
207
207
  f.current = !1;
208
208
  }), []), o(() => t.subscribe((e) => {
@@ -214,14 +214,14 @@ var f = (e) => {
214
214
  f.current && s(0);
215
215
  }, []);
216
216
  return {
217
- status: E(n, i),
217
+ status: D(n, i),
218
218
  online: n,
219
219
  failureCount: i,
220
220
  lastFailureAt: u,
221
221
  reportFailure: p,
222
222
  reportSuccess: m
223
223
  };
224
- }, A = (e) => {
224
+ }, j = (e) => {
225
225
  let { storage: t, keys: n } = e, r = e.onError ?? ((e, t) => {
226
226
  console.warn(`[voltro] persisting '${e}' failed`, t);
227
227
  }), i = /* @__PURE__ */ new Map(), a = !1, o = null, s = /* @__PURE__ */ new Map(), c = null, l = async () => {
@@ -280,38 +280,41 @@ var f = (e) => {
280
280
  },
281
281
  keys: () => [...i.keys()]
282
282
  };
283
- }, j = (e, t) => {
283
+ }, M = (e, t) => {
284
284
  let n = globalThis.WebSocket;
285
285
  if (n === void 0) throw Error("startMobileApis: no global WebSocket. React Native provides one; if you are running these bindings somewhere else, pass `webSocketConstructor`.");
286
286
  return t === void 0 ? new n(e) : new n(e, t);
287
- }, M = (e, t, n) => (r, i) => {
287
+ }, N = (e, t, n) => (r, i) => {
288
288
  let a = e(r, i);
289
289
  return a.addEventListener("open", () => t()), a.addEventListener("close", () => n("close")), a.addEventListener("error", () => n("error")), a;
290
- }, N = (e) => {
291
- let t = e.webSocketConstructor ?? j;
292
- return d({
290
+ }, P = (e) => {
291
+ let t = e.webSocketConstructor ?? M;
292
+ return f({
293
293
  apis: e.apis.map((e) => ({
294
294
  name: e.name,
295
295
  group: e.group,
296
296
  descriptors: e.descriptors,
297
297
  wsUrl: e.wsUrl,
298
- ...e.headers === void 0 ? {} : { headers: e.headers }
298
+ ...e.headers === void 0 ? {} : { headers: e.headers },
299
+ ...e.recovery === void 0 ? {} : { recovery: e.recovery }
299
300
  })),
300
301
  ...e.retryDelayMs === void 0 ? {} : { retryDelayMs: e.retryDelayMs },
301
302
  onChange: e.onChange,
302
- buildClient: async (e, n, r, i, a) => await u({
303
+ buildClient: async (e, n, r, i, a, o) => await u({
303
304
  name: e.name,
305
+ generation: n,
304
306
  wsUrl: e.wsUrl,
305
307
  group: e.group,
306
308
  ...e.headers === void 0 ? {} : { headers: e.headers },
307
309
  ...a === void 0 ? {} : { errorBus: a },
308
- webSocketConstructor: M(t, r, i)
310
+ ...o === void 0 ? {} : { recovery: o },
311
+ webSocketConstructor: N(t, r, i)
309
312
  })
310
313
  });
311
- }, P = (e, t, n = "/ws") => {
314
+ }, F = (e, t, n = "/ws") => {
312
315
  let r = (e ?? "").split(":")[0];
313
316
  return r === void 0 || r === "" || r === "localhost" || r === "127.0.0.1" ? `ws://localhost:${t}${n}` : `ws://${r}:${t}${n}`;
314
- }, F = (e, t) => {
317
+ }, I = (e, t) => {
315
318
  let n = /* @__PURE__ */ new Map();
316
319
  for (let r of e) {
317
320
  let e = t.get(r.name);
@@ -321,10 +324,11 @@ var f = (e) => {
321
324
  client: e.client,
322
325
  descriptors: r.descriptors,
323
326
  inspectBaseUrl: r.wsUrl.replace(/^ws/, "http").replace(/\/ws$/, ""),
324
- errorBus: e.errorBus
327
+ errorBus: e.errorBus,
328
+ recovery: e.recovery ?? d(e.errorBus, r.name, r.recovery)
325
329
  });
326
330
  }
327
331
  return n;
328
332
  };
329
333
  //#endregion
330
- export { i as DEVICES_TABLE, n as DEVICE_PLATFORMS, b as backgroundSyncReducer, D as browserOnlineSource, A as createAsyncStoragePersistence, g as deepLinkHref, j as defaultWebSocketConstructor, f as defineDeepLink, E as deriveConnectionStatus, v as dispatchDeepLink, y as initialBackgroundSyncState, r as isDevicePlatform, m as matchDeepLink, _ as matchFirstDeepLink, O as netInfoOnlineSource, T as offlineFirstDefaults, t as registerDevice, P as resolveDevWsUrl, e as resolveDeviceRegistration, h as runDeepLink, x as shouldSync, N as startMobileApis, F as toApiHandles, w as useBackgroundSync, k as useMobileConnectionStatus, M as wireSocket };
334
+ export { i as DEVICES_TABLE, n as DEVICE_PLATFORMS, x as backgroundSyncReducer, O as browserOnlineSource, j as createAsyncStoragePersistence, _ as deepLinkHref, M as defaultWebSocketConstructor, p as defineDeepLink, D as deriveConnectionStatus, y as dispatchDeepLink, b as initialBackgroundSyncState, r as isDevicePlatform, h as matchDeepLink, v as matchFirstDeepLink, k as netInfoOnlineSource, E as offlineFirstDefaults, t as registerDevice, F as resolveDevWsUrl, e as resolveDeviceRegistration, g as runDeepLink, S as shouldSync, P as startMobileApis, I as toApiHandles, T as useBackgroundSync, A as useMobileConnectionStatus, N as wireSocket };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/react-native",
3
- "version": "0.62.3",
3
+ "version": "0.63.0",
4
4
  "description": "React Native bindings for the framework: device-registration primitive, background-sync state machine, offline-first client defaults + connection-status surface, and the deep-link declaration shape — the credential-free mobile plumbing on top of the RN-safe React client.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -38,8 +38,8 @@
38
38
  "node": ">=24.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@voltro/client": "0.62.3",
42
- "@voltro/database": "0.62.3"
41
+ "@voltro/client": "0.63.0",
42
+ "@voltro/database": "0.63.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^19.0.0"