@voltro/react-native 0.29.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.
@@ -0,0 +1,341 @@
1
+ export declare type BackgroundSyncAction = {
2
+ readonly type: 'foreground';
3
+ } | {
4
+ readonly type: 'background';
5
+ } | {
6
+ readonly type: 'syncStarted';
7
+ readonly at: number;
8
+ } | {
9
+ readonly type: 'syncSucceeded';
10
+ readonly at: number;
11
+ } | {
12
+ readonly type: 'syncFailed';
13
+ readonly at: number;
14
+ readonly error: string;
15
+ } | {
16
+ readonly type: 'reset';
17
+ };
18
+
19
+ export declare const backgroundSyncReducer: (state: BackgroundSyncState, action: BackgroundSyncAction) => BackgroundSyncState;
20
+
21
+ export declare interface BackgroundSyncState {
22
+ readonly status: BackgroundSyncStatus;
23
+ /** Whether the app is currently foregrounded (drives interval + focus sync). */
24
+ readonly isForeground: boolean;
25
+ /** When the last SUCCESSFUL sync completed, epoch ms. `undefined` until one does. */
26
+ readonly lastSyncAt: number | undefined;
27
+ /** When the last sync ATTEMPT started, epoch ms — the interval clock reads this. */
28
+ readonly lastAttemptAt: number | undefined;
29
+ /** The last error message, set on failure, cleared on the next success/start. */
30
+ readonly lastError: string | undefined;
31
+ /** Successful syncs so far. */
32
+ readonly successCount: number;
33
+ /** Consecutive failures with no success since — 0 when healthy. */
34
+ readonly failureCount: number;
35
+ }
36
+
37
+ export declare type BackgroundSyncStatus = 'idle' | 'syncing' | 'success' | 'error';
38
+
39
+ /**
40
+ * The descriptor a `*.deepLink.ts` file default-exports. `_tag` marks it for
41
+ * the (future) discovery walk without relying on structural guessing.
42
+ */
43
+ export declare interface DeepLinkDescriptor<Pattern extends string = string> {
44
+ readonly _tag: 'VoltroDeepLink';
45
+ readonly pattern: Pattern;
46
+ readonly handler: DeepLinkHandler<Pattern>;
47
+ }
48
+
49
+ /** A handler for a matched deep link. May be async (navigation can await). */
50
+ export declare type DeepLinkHandler<Pattern extends string> = (params: DeepLinkParams<Pattern>) => void | Promise<void>;
51
+
52
+ /**
53
+ * The params a pattern yields, inferred from its `:name` segments.
54
+ *
55
+ * `'/orders/:id'` → `{ id: string }`, `'/t/:tenant/u/:user'` →
56
+ * `{ tenant: string; user: string }`, a param-less pattern →
57
+ * `Record<string, never>`. This is what makes `handler: ({ id }) => …`
58
+ * type-check against exactly the params the pattern declares.
59
+ */
60
+ export declare type DeepLinkParams<Pattern extends string> = Pattern extends `${string}:${infer Param}/${infer Rest}` ? {
61
+ readonly [K in Param | keyof DeepLinkParams<`/${Rest}`>]: string;
62
+ } : Pattern extends `${string}:${infer Param}` ? {
63
+ readonly [K in Param]: string;
64
+ } : Record<string, never>;
65
+
66
+ /**
67
+ * Declare a deep link.
68
+ *
69
+ * ```ts
70
+ * export default defineDeepLink({
71
+ * pattern: '/orders/:id',
72
+ * handler: ({ id }) => navigateTo(`/orders/${id}`),
73
+ * })
74
+ * ```
75
+ */
76
+ export declare const defineDeepLink: <const Pattern extends string>(config: DefineDeepLinkConfig<Pattern>) => DeepLinkDescriptor<Pattern>;
77
+
78
+ export declare interface DefineDeepLinkConfig<Pattern extends string> {
79
+ readonly pattern: Pattern;
80
+ readonly handler: DeepLinkHandler<Pattern>;
81
+ }
82
+
83
+ /**
84
+ * The status mapping, pure and standalone (mirrors `@voltro/client`'s):
85
+ * - browser says offline → `offline` (a real signal for "no", weak for "yes"),
86
+ * - otherwise any outstanding failure → `degraded`,
87
+ * - else `connected`. No fake "connecting" ping.
88
+ */
89
+ export declare const deriveConnectionStatus: (online: boolean, failureCount: number) => MobileConnectionStatus;
90
+
91
+ /** The set of platforms, as a value (for validation / `<Picker>` lists). */
92
+ export declare const DEVICE_PLATFORMS: ReadonlyArray<DevicePlatform>;
93
+
94
+ /** The push transport a device is reachable on. */
95
+ export declare type DevicePlatform = 'ios' | 'android' | 'web';
96
+
97
+ /**
98
+ * The normalised row, ready to upsert into `_voltro_devices`. `locale` and
99
+ * `timezone` are resolved (never `undefined`); `registeredAt` is the moment we
100
+ * built this. `userId` / `tenantId` are NOT here on purpose — they are the
101
+ * server's to stamp from the authenticated request, never trusted from the
102
+ * client (mirrors how API-key metadata is merged UNDER the framework's claims).
103
+ */
104
+ export declare interface DeviceRegistration {
105
+ readonly deviceToken: string;
106
+ readonly platform: DevicePlatform;
107
+ readonly locale: string;
108
+ readonly timezone: string;
109
+ readonly appVersion: string | null;
110
+ readonly metadata: Readonly<Record<string, unknown>> | null;
111
+ readonly registeredAt: number;
112
+ }
113
+
114
+ /**
115
+ * Ambient facts the resolver reads for the locale/timezone defaults. Injectable
116
+ * so it is pure + testable — in a real RN/web runtime the defaults come from
117
+ * the device, in a test you pass them explicitly.
118
+ */
119
+ export declare interface DeviceRegistrationEnv {
120
+ readonly locale?: string;
121
+ readonly timezone?: string;
122
+ readonly now?: () => number;
123
+ }
124
+
125
+ /**
126
+ * What the app hands us at registration time. `deviceToken` and `platform` are
127
+ * the only two the caller MUST supply — the OS gives it both. `locale` and
128
+ * `timezone` are filled from the running environment when omitted (see
129
+ * {@link resolveDeviceRegistration}); `appVersion` and `metadata` are optional
130
+ * tags the app may attach.
131
+ */
132
+ export declare interface DeviceRegistrationInput {
133
+ readonly deviceToken: string;
134
+ readonly platform: DevicePlatform;
135
+ readonly locale?: string;
136
+ readonly timezone?: string;
137
+ readonly appVersion?: string;
138
+ readonly metadata?: Readonly<Record<string, unknown>>;
139
+ }
140
+
141
+ /** The framework table name devices are stored in — shared with `./schema`. */
142
+ export declare const DEVICES_TABLE = "_voltro_devices";
143
+
144
+ /**
145
+ * The transport that actually persists a device. The app supplies it — usually
146
+ * a generated mutation caller (`api.registerDevice`) or a plain `fetch` — so
147
+ * this package stays free of any transport/codegen coupling. It receives the
148
+ * fully-resolved row and returns whatever the server sends back.
149
+ */
150
+ export declare type DeviceUpsert<Result = unknown> = (registration: DeviceRegistration) => Promise<Result>;
151
+
152
+ /**
153
+ * Subscribe to "app returned to the foreground". Returns an unsubscribe.
154
+ *
155
+ * The app supplies this — on RN, wrap `AppState.addEventListener('change', …)`
156
+ * and call `onForeground` when it flips to `'active'`; on web the default below
157
+ * uses `visibilitychange` + `focus`. Injectable so the hook is testable without
158
+ * a real app lifecycle.
159
+ */
160
+ export declare type ForegroundSubscribe = (onForeground: () => void, onBackground: () => void) => () => void;
161
+
162
+ export declare const initialBackgroundSyncState: BackgroundSyncState;
163
+
164
+ /** Narrow an untrusted string to a {@link DevicePlatform}. */
165
+ export declare const isDevicePlatform: (value: unknown) => value is DevicePlatform;
166
+
167
+ /**
168
+ * Match a `pattern` against a `path`, returning the captured params or `null`
169
+ * when it does not match. Pure — no navigation, no side effects.
170
+ *
171
+ * ```ts
172
+ * matchDeepLink('/orders/:id', '/orders/42') // → { id: '42' }
173
+ * matchDeepLink('/orders/:id', '/orders/42/edit') // → null
174
+ * matchDeepLink('/health', '/health') // → {}
175
+ * ```
176
+ *
177
+ * A `*` segment matches exactly one segment without capturing; a trailing `*`
178
+ * is not a catch-all (keep the model boringly predictable — one pattern
179
+ * segment matches one path segment).
180
+ */
181
+ export declare const matchDeepLink: <Pattern extends string>(pattern: Pattern, path: string) => DeepLinkParams<Pattern> | null;
182
+
183
+ /**
184
+ * First descriptor whose pattern matches `path`, with its captured params —
185
+ * the hand-registration entry point until file discovery lands. Declaration
186
+ * order wins, so list more-specific patterns first.
187
+ */
188
+ export declare const matchFirstDeepLink: (descriptors: ReadonlyArray<DeepLinkDescriptor>, path: string) => {
189
+ readonly descriptor: DeepLinkDescriptor;
190
+ readonly params: Record<string, string>;
191
+ } | null;
192
+
193
+ export declare interface MobileConnectionControls {
194
+ /** Report that a call failed — bumps `failureCount`, flips to `degraded`. */
195
+ readonly reportFailure: () => void;
196
+ /** Report a known-good round trip — clears `degraded`. */
197
+ readonly reportSuccess: () => void;
198
+ }
199
+
200
+ export declare interface MobileConnectionState {
201
+ readonly status: MobileConnectionStatus;
202
+ /** `navigator.onLine` (true when unknowable, e.g. SSR / headless RN). */
203
+ readonly online: boolean;
204
+ /** Consecutive reported failures with no success since. 0 when healthy. */
205
+ readonly failureCount: number;
206
+ readonly lastFailureAt: number | undefined;
207
+ }
208
+
209
+ export declare type MobileConnectionStatus = 'connected' | 'degraded' | 'offline';
210
+
211
+ /**
212
+ * The mobile client posture. Spread into the client config on RN:
213
+ *
214
+ * ```ts
215
+ * createClient({ ...offlineFirstDefaults, url })
216
+ * ```
217
+ */
218
+ export declare interface OfflineFirstDefaults {
219
+ /** Local-first is ON by default on mobile (opt-out), OFF on web (opt-in). */
220
+ readonly localFirst: boolean;
221
+ /** Apply mutations optimistically before the server confirms. */
222
+ readonly optimistic: boolean;
223
+ /** Sync when the app returns to the foreground. */
224
+ readonly syncOnForeground: boolean;
225
+ /** Background sync cadence while foregrounded, ms. */
226
+ readonly syncIntervalMs: number;
227
+ /** Surface sync/connection status in the UI (mobile shows it prominently). */
228
+ readonly showSyncStatus: boolean;
229
+ /** Retry backoff schedule for a failed sync, ms. */
230
+ readonly retryBackoffMs: ReadonlyArray<number>;
231
+ }
232
+
233
+ export declare const offlineFirstDefaults: OfflineFirstDefaults;
234
+
235
+ /**
236
+ * Register (or re-register, on token rotation) the current device.
237
+ *
238
+ * ```ts
239
+ * await registerDevice(
240
+ * (row) => api.mutate('registerDevice', row),
241
+ * { deviceToken, platform: 'ios' },
242
+ * )
243
+ * ```
244
+ *
245
+ * Idempotent by construction: the `_voltro_devices` unique key is
246
+ * `(platform, token)`, so calling this again with the same token updates the
247
+ * existing row (locale/timezone/lastSeen) instead of inserting a duplicate. A
248
+ * ROTATED token is a new registration; reaping the stale one is the sender
249
+ * adapter's job (a seam), not the client's.
250
+ */
251
+ export declare const registerDevice: <Result = unknown>(upsert: DeviceUpsert<Result>, input: DeviceRegistrationInput, env?: DeviceRegistrationEnv) => Promise<Result>;
252
+
253
+ /**
254
+ * Turn a raw {@link DeviceRegistrationInput} into a {@link DeviceRegistration}.
255
+ *
256
+ * Locale/timezone precedence: explicit input → injected env → ambient runtime →
257
+ * the `en` / `UTC` floor. Pure given an `env` (including `now`), so the same
258
+ * input always produces the same row in a test.
259
+ */
260
+ export declare const resolveDeviceRegistration: (input: DeviceRegistrationInput, env?: DeviceRegistrationEnv) => DeviceRegistration;
261
+
262
+ /**
263
+ * Match `path` against a descriptor and, on a hit, invoke its handler with the
264
+ * captured params. Returns the params (so callers know it matched) or `null`.
265
+ */
266
+ export declare const runDeepLink: <Pattern extends string>(descriptor: DeepLinkDescriptor<Pattern>, path: string) => DeepLinkParams<Pattern> | null;
267
+
268
+ /**
269
+ * Should a trigger run a sync right now? Pure — the single place the timing
270
+ * policy lives, so the hook and any test agree:
271
+ *
272
+ * - not while one is already in flight (single-flight),
273
+ * - not while backgrounded (a background tick is the OS's job, not ours),
274
+ * - not before `intervalMs` has elapsed since the last ATTEMPT,
275
+ * - not when disabled.
276
+ *
277
+ * A `force` trigger (a manual `sync()` call) bypasses only the interval gate —
278
+ * it still respects single-flight and enabled, because running two syncs at
279
+ * once or after teardown is never what the caller meant.
280
+ */
281
+ export declare const shouldSync: (state: BackgroundSyncState, now: number, options: ShouldSyncOptions, force?: boolean) => boolean;
282
+
283
+ export declare interface ShouldSyncOptions {
284
+ /** Minimum ms between attempts; `0` disables the interval gate. */
285
+ readonly intervalMs: number;
286
+ /** Whether syncing is enabled at all. */
287
+ readonly enabled: boolean;
288
+ }
289
+
290
+ /**
291
+ * Register a periodic + foreground-triggered sync callback.
292
+ *
293
+ * ```tsx
294
+ * const { status, lastSyncAt, sync } = useBackgroundSync(
295
+ * () => api.refetchAll(),
296
+ * { intervalMs: 60_000, syncOnForeground: true },
297
+ * )
298
+ * ```
299
+ *
300
+ * `onSync` may be async — a rejection is captured into `status: 'error'` +
301
+ * `lastError`, a resolution into `status: 'success'` + `lastSyncAt`. The
302
+ * callback is read through a ref, so passing a fresh closure each render does
303
+ * not reset the timer.
304
+ */
305
+ export declare const useBackgroundSync: (onSync: () => void | Promise<void>, options?: UseBackgroundSyncOptions) => UseBackgroundSyncResult;
306
+
307
+ export declare interface UseBackgroundSyncOptions {
308
+ /** Run the sync on this cadence while foregrounded. `0` disables the interval. */
309
+ readonly intervalMs?: number;
310
+ /** Run the sync when the app returns to the foreground. Default `true`. */
311
+ readonly syncOnForeground?: boolean;
312
+ /** Master switch — when `false`, no trigger runs. Default `true`. */
313
+ readonly enabled?: boolean;
314
+ /** Override the foreground signal source (RN passes an `AppState` wrapper). */
315
+ readonly subscribeForeground?: ForegroundSubscribe;
316
+ /** Injectable clock, for tests. Default `Date.now`. */
317
+ readonly now?: () => number;
318
+ }
319
+
320
+ export declare interface UseBackgroundSyncResult extends BackgroundSyncState {
321
+ /** Trigger a sync by hand. Bypasses the interval gate; still single-flight. */
322
+ readonly sync: () => void;
323
+ }
324
+
325
+ /**
326
+ * Observe mobile connection health.
327
+ *
328
+ * ```tsx
329
+ * const { status, reportFailure, reportSuccess } = useMobileConnectionStatus()
330
+ * {status !== 'connected' && <OfflineBanner status={status} />}
331
+ * ```
332
+ *
333
+ * Standalone by design (see the file header): it derives `offline` from
334
+ * `navigator.onLine` + its events, and `degraded` from failures the app reports
335
+ * — it does not reach into any transport. Coming back online clears the failure
336
+ * count, on the same reasoning as the web hook: those failures were the offline
337
+ * window itself.
338
+ */
339
+ export declare const useMobileConnectionStatus: () => MobileConnectionState & MobileConnectionControls;
340
+
341
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,196 @@
1
+ import { a as e, i as t, n, r, t as i } from "./devices-BjXAU-8I.js";
2
+ import { useCallback as a, useEffect as o, useReducer as s, useRef as c, useState as l } from "react";
3
+ //#region src/deepLink.ts
4
+ var u = (e) => {
5
+ let t = e.pattern;
6
+ if (t.length === 0 || t[0] !== "/") throw Error(`defineDeepLink: pattern must start with "/" (got "${t}")`);
7
+ return {
8
+ _tag: "VoltroDeepLink",
9
+ pattern: t,
10
+ handler: e.handler
11
+ };
12
+ }, d = (e) => {
13
+ let t = e, n = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\//.exec(t);
14
+ if (n) {
15
+ let e = t.slice(n[0].length), r = n[1].toLowerCase();
16
+ if (r === "http" || r === "https") {
17
+ let n = e.indexOf("/");
18
+ t = n === -1 ? "/" : e.slice(n);
19
+ } else t = `/${e}`;
20
+ }
21
+ let r = t.search(/[?#]/);
22
+ return r !== -1 && (t = t.slice(0, r)), t.split("/").filter((e) => e.length > 0).map((e) => {
23
+ try {
24
+ return decodeURIComponent(e);
25
+ } catch {
26
+ return e;
27
+ }
28
+ });
29
+ }, f = (e, t) => {
30
+ let n = d(e), r = d(t);
31
+ if (n.length !== r.length) return null;
32
+ let i = {};
33
+ for (let e = 0; e < n.length; e++) {
34
+ let t = n[e], a = r[e];
35
+ if (t.startsWith(":")) {
36
+ let e = t.slice(1);
37
+ if (e.length === 0) return null;
38
+ i[e] = a;
39
+ } else if (t !== "*" && t !== a) return null;
40
+ }
41
+ return i;
42
+ }, p = (e, t) => {
43
+ let n = f(e.pattern, t);
44
+ return n === null ? null : (e.handler(n), n);
45
+ }, m = (e, t) => {
46
+ for (let n of e) {
47
+ let e = f(n.pattern, t);
48
+ if (e !== null) return {
49
+ descriptor: n,
50
+ params: e
51
+ };
52
+ }
53
+ return null;
54
+ }, h = {
55
+ status: "idle",
56
+ isForeground: !0,
57
+ lastSyncAt: void 0,
58
+ lastAttemptAt: void 0,
59
+ lastError: void 0,
60
+ successCount: 0,
61
+ failureCount: 0
62
+ }, g = (e, t) => {
63
+ switch (t.type) {
64
+ case "foreground": return e.isForeground ? e : {
65
+ ...e,
66
+ isForeground: !0
67
+ };
68
+ case "background": return e.isForeground ? {
69
+ ...e,
70
+ isForeground: !1
71
+ } : e;
72
+ case "syncStarted": return e.status === "syncing" ? e : {
73
+ ...e,
74
+ status: "syncing",
75
+ lastAttemptAt: t.at,
76
+ lastError: void 0
77
+ };
78
+ case "syncSucceeded": return {
79
+ ...e,
80
+ status: "success",
81
+ lastSyncAt: t.at,
82
+ lastError: void 0,
83
+ successCount: e.successCount + 1,
84
+ failureCount: 0
85
+ };
86
+ case "syncFailed": return {
87
+ ...e,
88
+ status: "error",
89
+ lastError: t.error,
90
+ failureCount: e.failureCount + 1
91
+ };
92
+ case "reset": return {
93
+ ...h,
94
+ isForeground: e.isForeground
95
+ };
96
+ }
97
+ }, _ = (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), v = (e, t) => {
98
+ if (typeof document > "u" || typeof window > "u") return () => {};
99
+ let n = () => {
100
+ document.visibilityState === "visible" ? e() : t();
101
+ };
102
+ return document.addEventListener("visibilitychange", n), window.addEventListener("focus", e), () => {
103
+ document.removeEventListener("visibilitychange", n), window.removeEventListener("focus", e);
104
+ };
105
+ }, y = 5 * 6e4, b = (e, t = {}) => {
106
+ let n = t.intervalMs ?? y, r = t.syncOnForeground ?? !0, i = t.enabled ?? !0, l = t.now ?? Date.now, u = t.subscribeForeground ?? v, [d, f] = s(g, h), p = c(e);
107
+ p.current = e;
108
+ let m = c(d);
109
+ m.current = d;
110
+ let b = c(!0), x = a((e) => {
111
+ _(m.current, l(), {
112
+ intervalMs: n,
113
+ enabled: i
114
+ }, e) && (f({
115
+ type: "syncStarted",
116
+ at: l()
117
+ }), Promise.resolve().then(() => p.current()).then(() => {
118
+ b.current && f({
119
+ type: "syncSucceeded",
120
+ at: l()
121
+ });
122
+ }).catch((e) => {
123
+ b.current && f({
124
+ type: "syncFailed",
125
+ at: l(),
126
+ error: e instanceof Error ? e.message : String(e)
127
+ });
128
+ }));
129
+ }, [
130
+ i,
131
+ n,
132
+ l
133
+ ]);
134
+ o(() => (b.current = !0, () => {
135
+ b.current = !1;
136
+ }), []), o(() => u(() => {
137
+ f({ type: "foreground" }), r && x(!0);
138
+ }, () => f({ type: "background" })), [
139
+ u,
140
+ r,
141
+ x
142
+ ]), o(() => {
143
+ if (!i || n <= 0) return;
144
+ let e = setInterval(() => x(!1), n);
145
+ return () => clearInterval(e);
146
+ }, [
147
+ i,
148
+ n,
149
+ x
150
+ ]);
151
+ let S = a(() => x(!0), [x]);
152
+ return {
153
+ ...d,
154
+ sync: S
155
+ };
156
+ }, x = {
157
+ localFirst: !0,
158
+ optimistic: !0,
159
+ syncOnForeground: !0,
160
+ syncIntervalMs: 5 * 6e4,
161
+ showSyncStatus: !0,
162
+ retryBackoffMs: [
163
+ 1e3,
164
+ 5e3,
165
+ 15e3,
166
+ 6e4
167
+ ]
168
+ }, S = (e, t) => e ? t > 0 ? "degraded" : "connected" : "offline", C = () => typeof navigator > "u" || navigator.onLine, w = () => {
169
+ let [e, t] = l(C), [n, r] = l(0), [i, s] = l(void 0), u = c(!0);
170
+ o(() => (u.current = !0, () => {
171
+ u.current = !1;
172
+ }), []), o(() => {
173
+ if (typeof window > "u") return;
174
+ let e = () => {
175
+ t(!0), r(0);
176
+ }, n = () => t(!1);
177
+ return window.addEventListener("online", e), window.addEventListener("offline", n), () => {
178
+ window.removeEventListener("online", e), window.removeEventListener("offline", n);
179
+ };
180
+ }, []);
181
+ let d = a(() => {
182
+ u.current && (r((e) => e + 1), s(Date.now()));
183
+ }, []), f = a(() => {
184
+ u.current && r(0);
185
+ }, []);
186
+ return {
187
+ status: S(e, n),
188
+ online: e,
189
+ failureCount: n,
190
+ lastFailureAt: i,
191
+ reportFailure: d,
192
+ reportSuccess: f
193
+ };
194
+ };
195
+ //#endregion
196
+ export { i as DEVICES_TABLE, n as DEVICE_PLATFORMS, g as backgroundSyncReducer, u as defineDeepLink, S as deriveConnectionStatus, h as initialBackgroundSyncState, r as isDevicePlatform, f as matchDeepLink, m as matchFirstDeepLink, x as offlineFirstDefaults, t as registerDevice, e as resolveDeviceRegistration, p as runDeepLink, _ as shouldSync, b as useBackgroundSync, w as useMobileConnectionStatus };
@@ -0,0 +1,22 @@
1
+ import { ColumnBuilder } from '@voltro/database';
2
+ import { FieldDefinitions } from '@voltro/database';
3
+ import { Table } from '@voltro/database';
4
+
5
+ /** The framework table name devices are stored in — shared with `./schema`. */
6
+ export declare const DEVICES_TABLE = "_voltro_devices";
7
+
8
+ export declare const devicesTable: Table<"_voltro_devices", FieldDefinitions<{
9
+ readonly id: ColumnBuilder<string, "id", boolean>;
10
+ readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
11
+ readonly userId: ColumnBuilder<string, "text", boolean>;
12
+ readonly platform: ColumnBuilder<string, "text", boolean>;
13
+ readonly token: ColumnBuilder<string, "text", boolean>;
14
+ readonly locale: ColumnBuilder<string | null, "text", boolean>;
15
+ readonly timezone: ColumnBuilder<string | null, "text", boolean>;
16
+ readonly appVersion: ColumnBuilder<string | null, "text", boolean>;
17
+ readonly metadata: ColumnBuilder<string | null, "text", boolean>;
18
+ readonly lastSeenAt: ColumnBuilder<Date, "timestamp", true>;
19
+ readonly createdAt: ColumnBuilder<Date, "timestamp", true>;
20
+ }>, true, "byDeviceUser" | "byDeviceTenant">;
21
+
22
+ export { }
package/dist/schema.js ADDED
@@ -0,0 +1,18 @@
1
+ import { t as e } from "./devices-BjXAU-8I.js";
2
+ import { id as t, table as n, text as r, timestamp as i } from "@voltro/database";
3
+ //#region src/schema.ts
4
+ var a = n(e, {
5
+ id: t({ prefix: "device" }),
6
+ tenantId: r().nullable(),
7
+ userId: r(),
8
+ platform: r(),
9
+ token: r(),
10
+ locale: r().nullable(),
11
+ timezone: r().nullable(),
12
+ appVersion: r().nullable(),
13
+ metadata: r().nullable(),
14
+ lastSeenAt: i().default("now"),
15
+ createdAt: i().default("now")
16
+ }).unique("byDeviceToken", ["platform", "token"]).index("byDeviceUser", ["userId"]).index("byDeviceTenant", ["tenantId"]);
17
+ //#endregion
18
+ export { e as DEVICES_TABLE, a as devicesTable };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@voltro/react-native",
3
+ "version": "0.29.0",
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
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./schema": {
27
+ "types": "./dist/schema.d.ts",
28
+ "import": "./dist/schema.js",
29
+ "default": "./dist/schema.js"
30
+ }
31
+ },
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=24.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@voltro/database": "0.29.0"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^19.0.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "react": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }