@doync/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vitor Buzinaro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # `@doync/react`
2
+
3
+ Thin React hooks over the doync client call surface (`subscribe` / `once` / `local` / `mutate`). The hooks consume any `DoyncClient` — construct one with `@doync/web` or `@doync/mobile` and pass it to the Provider. Environment packages are never re-exported from here.
4
+
5
+ Apps import client types (`DoyncClient`, `View`, …) from this package; `@doync/client` is for adapter authors.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add @doync/react @doync/client @doync/core
11
+ # plus one environment package:
12
+ pnpm add @doync/web # browser
13
+ # or
14
+ pnpm add @doync/mobile # React Native
15
+ ```
16
+
17
+ Peer: `react` ≥ 18.
18
+
19
+ ## Provider
20
+
21
+ Boot the environment client once, then provide it to the tree. Keep the same client object for the life of the app (or tab); drive login/logout through `client.setAuth`, not by reconstructing the client.
22
+
23
+ ```tsx
24
+ import { DoyncProvider } from '@doync/react'
25
+ import { createWebClient } from '@doync/web'
26
+ // or: import { createMobileClient } from '@doync/mobile'
27
+
28
+ const client = createWebClient({/* worker, userId, … — see @doync/web */})
29
+
30
+ export function App() {
31
+ return (
32
+ <DoyncProvider client={client}>
33
+ <TaskList />
34
+ </DoyncProvider>
35
+ )
36
+ }
37
+ ```
38
+
39
+ `useDoyncClient()` returns the same client (throws outside a Provider) when you need the imperative surface from a component.
40
+
41
+ ## Shared definition module
42
+
43
+ Hooks take the same `queries` / `mutations` trees every client uses — define them once (`defineSchema` / `defineQueries` / `defineMutations` from `@doync/core`, or the Drizzle flavor) and import that module from web, mobile, and the DB-worker entry:
44
+
45
+ ```ts
46
+ // shared/data.ts — one data layer for every client
47
+ export { schema, queries, mutations }
48
+ ```
49
+
50
+ Query-taking hooks accept a **Bound query**: call the registered leaf with its args, `queries.issues.open(args)`. Binding is pure; a fresh Bound query object per render does not bust memoization (key is name + args). Falsy (`cond && queries.issues.open(args)`) means "no query" and keeps stable hook order.
51
+
52
+ ## `useQuery`
53
+
54
+ Subscribe to a registered query. Mount retains the shared View handle; unmount releases it. Rows update live as pokes and optimistic writes move them. The second tuple element is the View status.
55
+
56
+ ```tsx
57
+ import { useQuery } from '@doync/react'
58
+ import { queries } from './shared/data'
59
+
60
+ function IssueList({ projectId }: { projectId: string }) {
61
+ const [issues, status] = useQuery(queries.issues.open({ projectId }))
62
+ // multi-row: issues is readonly Row[]
63
+ // a sql.one / findFirst query types as Row | undefined instead
64
+
65
+ if (status.status !== 'complete' && issues.length === 0) {
66
+ return <Spinner />
67
+ }
68
+ return (
69
+ <ul>
70
+ {issues.map((issue) => (
71
+ <li key={issue.id}>{issue.title}</li>
72
+ ))}
73
+ </ul>
74
+ )
75
+ }
76
+ ```
77
+
78
+ Options (second argument):
79
+
80
+ - `ttl` — connected-clock grace for this Subscription.
81
+ - `skip: true` — short-circuit the desire while keeping shape-preserving empties (`[]` multi-row / `undefined` one-query). Distinct from a falsy query argument, which yields `undefined` rows and status `'unknown'` for both shapes.
82
+
83
+ One-ness is inferred from the bound query (`sql.one` / Drizzle `findFirst`) — never passed as an option.
84
+
85
+ ## `useQueryOnce`
86
+
87
+ Cache-and-network Once read. Rows render from the local Replica immediately, then update when the server answer lands. `status` tracks the network half.
88
+
89
+ ```tsx
90
+ const [rows, { status }] = useQueryOnce(queries.issueById({ id }))
91
+ // status: 'loading' | 'success' | 'error' | 'skipped' (falsy argument)
92
+ ```
93
+
94
+ ## `useLocalQuery`
95
+
96
+ Arbitrary SQL over the synced Replica — aggregates, joins, window functions. Re-runs on local commits; never registered upstream; offline-capable and free to the server.
97
+
98
+ ```tsx
99
+ const [rows, status] = useLocalQuery<{ n: number }>(
100
+ 'select count(*) as n from issue where open = 1',
101
+ )
102
+ // or: useLocalQuery({ sql: 'select … where id = ?', params: [id] })
103
+ // or: useLocalQuery(() => ({ sql, params }))
104
+ ```
105
+
106
+ A falsy source skips (`undefined` rows, status `'unknown'`).
107
+
108
+ ## `useMutation`
109
+
110
+ A registered mutation as a callable. Apply optimistically and push; render off `client`, await authoritative confirmation off `server` when it matters.
111
+
112
+ ```tsx
113
+ import { useMutation } from '@doync/react'
114
+ import { mutations } from './shared/data'
115
+
116
+ function CreateIssue() {
117
+ const createIssue = useMutation(mutations.issue.create)
118
+
119
+ async function onSubmit(input: { id: string; title: string }) {
120
+ const { client, server } = createIssue(input)
121
+ await client // local apply settled (throws if the local body rejects)
122
+ // optional: await server // Mirror confirmed (throws if rejected)
123
+ }
124
+
125
+ return /* … */
126
+ }
127
+ ```
128
+
129
+ ## Connection and schema status
130
+
131
+ ```tsx
132
+ import { useConnectionStatus, useSchemaStatus } from '@doync/react'
133
+
134
+ function StatusPill() {
135
+ const connection = useConnectionStatus()
136
+ // 'connecting' | 'connected' | 'disconnected' | 'error' | 'needs-auth'
137
+
138
+ const schema = useSchemaStatus()
139
+ // null when nominal; else { kind: 'reload' | 'server-behind' | 'resync' | 'forget', message }
140
+ // Re-reads on both entry AND silent clear — a banner driven only by a
141
+ // one-shot callback would stick after recovery.
142
+
143
+ return (
144
+ <>
145
+ <span>{connection}</span>
146
+ {schema ? <Banner>{schema.message}</Banner> : null}
147
+ </>
148
+ )
149
+ }
150
+ ```
151
+
152
+ ## Public surface
153
+
154
+ | Export | Role |
155
+ | --- | --- |
156
+ | `DoyncProvider` / `DoyncProviderProps` | Provide the client |
157
+ | `useDoyncClient` | Imperative client from context |
158
+ | `useQuery` / `UseQueryOptions` | Live Subscription |
159
+ | `useQueryOnce` / `OnceStatus` | Cache-and-network Once |
160
+ | `useLocalQuery` / `LocalSource` | Local SQL over the Replica |
161
+ | `useMutation` | Optimistic mutate + push |
162
+ | `useConnectionStatus` | Mirror socket health |
163
+ | `useSchemaStatus` | Schema-handling state (or `null`) |
164
+ | `BoundQuery` | Re-exported from `@doync/core` |
165
+ | `DoyncClient` / `View` / `OnceView` / `ViewStatus` / `QueryStatus` / `ConnectionStatus` / `SchemaEvent` / `SchemaEventKind` / `FalsyQuery` / `MutationOptions` / `MutationResult` / `LogoutBehavior` / `SubscribeOptions` / `PreloadOptions` / `PreloadHandle` | Client call-surface family (re-exported from `@doync/client`) |
166
+
167
+ ## Internal API
168
+
169
+ The main entry (`.`) is the semver-governed public surface documented here. Anything imported from `@doync/react/internal` may change in any release, including patches, without notice — use it only if you accept that risk.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-client-D1I3o0t5.cjs");let t=require("react"),n=require("react/jsx-runtime"),r=require("@doync/core/internal");const i=(0,t.createContext)(null);function a({client:e,children:t}){return(0,n.jsx)(i.Provider,{value:e,children:t})}function o(){let e=(0,t.useContext)(i);if(e===null)throw Error(`doync: a doync hook was used outside a <DoyncProvider>`);return e}function s(e,n){let r=o(),i=n?.skip??!1,a=x(e),s=a?`|ttl=${n?.ttl??``}|skip=${i}|falsy=1`:`${b(e)}|ttl=${n?.ttl??``}|skip=${i}|falsy=0`,l=(0,t.useMemo)(()=>{if(a)return g;let t=S(e,`useQuery`);return r.subscribe(t,{ttl:n?.ttl,skip:i})},[r,s]);(0,t.useEffect)(()=>(l.retain(),()=>l.release()),[l]);let u=v(l),d=y(l),f=(0,t.useMemo)(()=>{if(a)return!1;let t=e;return c(t.query,t.args)},[s]);if(a)return[void 0,d];if(l.one!==void 0&&l.one!==f)throw Error(l.one?`doync: the subscribed statement is a one-query (sql.one / findFirst) but this client resolved it as multi-row — the query definition disagrees across the resolve boundary`:`doync: the subscribed statement is multi-row but this client resolved it as a one-query — the query definition disagrees across the resolve boundary`);return[l.one??f?u.length>0?u[0]:void 0:u,d]}function c(e,t){try{return e.resolve({args:t})?.one??!1}catch{return!1}}function l(e){let n=o(),r=x(e),i=(0,t.useMemo)(()=>{if(r)return _;let t=S(e,`useQueryOnce`);return n.once(t)},[n,r?`falsy=1`:`${b(e)}|falsy=0`]),[a,s]=(0,t.useState)(`loading`);(0,t.useEffect)(()=>{if(r)return;let e=!0;return s(`loading`),i.server.then(()=>{e&&s(`success`)},()=>{e&&s(`error`)}),()=>{e=!1,i.dispose()}},[i,r]);let c=r?`skipped`:a,l=v(i);return[r?void 0:l,{status:c}]}function u(e,n=[]){let r=o(),i=x(e),a=i?null:typeof e==`function`?e():e,s=a===null?``:typeof a==`string`?a:a.sql,c=a===null||typeof a==`string`?n:a.params??[],l=(0,t.useMemo)(()=>i?g:r.local(s,...c),[r,i?`falsy=1`:`${s}|${JSON.stringify(c)}|falsy=0`]);(0,t.useEffect)(()=>(l.retain(),()=>l.release()),[l]);let u=v(l),d=y(l);return i?[void 0,d]:[u,d]}function d(){let e=o();return(0,t.useSyncExternalStore)((0,t.useCallback)(t=>e.onConnectionChange(t),[e]),()=>e.connectionStatus,()=>e.connectionStatus)}function f(e){let n=o();return(0,t.useCallback)((t,r)=>n.mutate(e,t,r),[n,e])}function p(){let e=o();return(0,t.useSyncExternalStore)((0,t.useCallback)(t=>e.onSchemaChange(t),[e]),()=>e.schemaStatus,()=>e.schemaStatus)}const m=Object.freeze([]),h=Object.freeze({status:`unknown`}),g={current:()=>m,status:()=>h,onChange:()=>()=>{},retain:()=>{},release:()=>{}},_={current:()=>m,onChange:()=>()=>{},dispose:()=>{},server:new Promise(()=>{})};function v(e){let n=(0,t.useCallback)(t=>e.onChange(t),[e]),r=(0,t.useCallback)(()=>e.current(),[e]);return(0,t.useSyncExternalStore)(n,r,r)}function y(e){let n=(0,t.useCallback)(t=>e.onChange(t),[e]),r=(0,t.useCallback)(()=>e.status(),[e]);return(0,t.useSyncExternalStore)(n,r,r)}function b(e){return`${e.query.name??``}|${JSON.stringify(e.args??null)}`}function x(e){return e===!1||e==null}function S(e,t){if((0,r.isBoundQuery)(e))return e;throw Error(typeof e==`function`?`doync: ${t} expected a BoundQuery — received a function; did you forget to call it?`:`doync: ${t} expected a BoundQuery (produced by queries.…(args)), got ${typeof e==`object`&&e?`an object`:String(e)}`)}exports.DoyncProvider=a,exports.useClient=e.t,exports.useConnectionStatus=d,exports.useDoyncClient=o,exports.useLocalQuery=u,exports.useMutation=f,exports.useQuery=s,exports.useQueryOnce=l,exports.useSchemaStatus=p;
@@ -0,0 +1,146 @@
1
+ import { ConnectionStatus, ConnectionStatus as ConnectionStatus$1, DoyncClient, DoyncClient as DoyncClient$1, FalsyQuery, FalsyQuery as FalsyQuery$1, LogoutBehavior, LogoutBehavior as LogoutBehavior$1, MutationOptions, MutationOptions as MutationOptions$1, MutationResult, MutationResult as MutationResult$1, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEvent as SchemaEvent$1, SchemaEventKind, SubscribeOptions, SubscribeOptions as SubscribeOptions$1, View, ViewStatus, ViewStatus as ViewStatus$1 } from "@doync/client";
2
+ import { ReactElement, ReactNode } from "react";
3
+ import { BoundQuery, BoundQuery as BoundQuery$1, MutationDefinition, SqlValue } from "@doync/core";
4
+
5
+ //#region src/provider.d.ts
6
+ /** Props for {@link DoyncProvider}. */
7
+ interface DoyncProviderProps {
8
+ /** Client from `createWebClient` / `createMobileClient` (or a test double). */
9
+ readonly client: DoyncClient$1;
10
+ readonly children: ReactNode;
11
+ }
12
+ /** Provide a {@link DoyncClient} to `useQuery`, `useMutation`, and siblings. */
13
+ declare function DoyncProvider({
14
+ client,
15
+ children
16
+ }: DoyncProviderProps): ReactElement;
17
+ /**
18
+ * {@link DoyncClient} from the nearest {@link DoyncProvider}. Throws if used
19
+ * outside a provider.
20
+ */
21
+ declare function useDoyncClient(): DoyncClient$1;
22
+ //#endregion
23
+ //#region src/hooks.d.ts
24
+ /**
25
+ * Options for {@link useQuery}: `ttl` (server warm-grace after unmount) and
26
+ * `skip`. One-ness is inferred from the bound query — there is no `one`
27
+ * option.
28
+ */
29
+ type UseQueryOptions = SubscribeOptions$1;
30
+ /**
31
+ * Live subscription to a bound query. Pass `queries.issues.open(args)`. Returns
32
+ * `[rows, status]` and updates as local writes and server sync move the rows.
33
+ *
34
+ * - Multi-row queries → `[readonly Row[], status]`
35
+ * - One-row (`` sql.one`…` `` / `findFirst`) → `[Row | undefined, status]`
36
+ * - Falsy (`cond && bound`) → `[undefined, { status: 'unknown' }]` (stable hooks)
37
+ * - `skip: true` → empty shaped like the query (`[]` or `undefined`), status
38
+ * `unknown`
39
+ *
40
+ * Status runs `unknown` → `complete` on first confirmation, or `error` if the
41
+ * subscribe fails. Mount retains the shared handle; unmount releases it.
42
+ */
43
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, true>, options?: UseQueryOptions): [Row | undefined, ViewStatus$1];
44
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, false>, options?: UseQueryOptions): [readonly Row[], ViewStatus$1];
45
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, true> | FalsyQuery$1, options?: UseQueryOptions): [Row | undefined, ViewStatus$1];
46
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, false> | FalsyQuery$1, options?: UseQueryOptions): [readonly Row[] | undefined, ViewStatus$1];
47
+ /**
48
+ * Network half of a {@link useQueryOnce} read: `loading` → `success` / `error`,
49
+ * or `skipped` when the argument is falsy (no request started).
50
+ */
51
+ type OnceStatus = "loading" | "success" | "error" | "skipped";
52
+ /**
53
+ * One-shot cache-and-network read. Rows come from the local replica
54
+ * immediately, then update when the server answer lands. `status` tracks the
55
+ * network half. Falsy argument → `[undefined, { status: 'skipped' }]` with
56
+ * stable hooks. StrictMode-safe (remount keeps the in-flight request).
57
+ */
58
+ declare function useQueryOnce<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, boolean>): [readonly Row[], {
59
+ readonly status: OnceStatus;
60
+ }];
61
+ declare function useQueryOnce<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, boolean> | FalsyQuery$1): [readonly Row[] | undefined, {
62
+ readonly status: OnceStatus;
63
+ }];
64
+ /**
65
+ * SQL source for {@link useLocalQuery}: a string, `{ sql, params }`, a callback
66
+ * returning either, or falsy ("no local read").
67
+ */
68
+ type LocalSource = string | {
69
+ readonly sql: string;
70
+ readonly params?: readonly SqlValue[];
71
+ } | (() => string | {
72
+ readonly sql: string;
73
+ readonly params?: readonly SqlValue[];
74
+ });
75
+ /**
76
+ * Run arbitrary SQL over the local replica reactively (aggregates, joins, …).
77
+ * Re-runs on local commits; never registered with the server — offline-capable
78
+ * and free to the Mirror. Falsy source → `[undefined, { status: 'unknown' }]`.
79
+ */
80
+ declare function useLocalQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(source: LocalSource, params?: readonly SqlValue[]): [readonly Row[], ViewStatus$1];
81
+ declare function useLocalQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(source: LocalSource | FalsyQuery$1, params?: readonly SqlValue[]): [readonly Row[] | undefined, ViewStatus$1];
82
+ /**
83
+ * Live {@link ConnectionStatus} to the Mirror: `connecting` / `connected` /
84
+ * `disconnected` / `error` / `needs-auth`. Re-renders on every transition — use
85
+ * for a status pill, offline banner, or needs-auth → logout flow.
86
+ */
87
+ declare function useConnectionStatus(): ConnectionStatus$1;
88
+ /**
89
+ * Registered mutation as a callable. Call with `args` to apply optimistically
90
+ * and push; returns `{ client, server }` — paint from `client`, await `server`
91
+ * for Origin confirmation.
92
+ */
93
+ declare function useMutation<Input = unknown>(mutation: MutationDefinition<Input>): (args: Input, options?: MutationOptions$1) => MutationResult$1;
94
+ /**
95
+ * Current schema/recovery state, or `null` when nominal. Use for a deploy-skew
96
+ * / recovery banner — re-renders when the state clears too (a one-shot
97
+ * `onSchemaEvent` callback alone would stick after recovery).
98
+ */
99
+ declare function useSchemaStatus(): SchemaEvent$1 | null;
100
+ //#endregion
101
+ //#region src/use-client.d.ts
102
+ /**
103
+ * Minimum call surface the hook routes onto. Platform clients (`MobileClient`,
104
+ * `WebClient`) both satisfy this; tests inject a fake.
105
+ */
106
+ interface ClientLifecycle {
107
+ close(): void;
108
+ setAuth(auth: {
109
+ token: string | null;
110
+ ctx?: unknown;
111
+ userId: string | null;
112
+ }): void;
113
+ setLogoutBehavior(behavior: LogoutBehavior$1): void;
114
+ }
115
+ /**
116
+ * How a single options-bag key is handled when its value changes between
117
+ * renders. Keys absent from the policy are not compared (construction-only
118
+ * values like `schema` / `queries` — module-level constants in real apps).
119
+ *
120
+ * - `identity` — close the old client and create a new one (e.g. a different
121
+ * database `name` is a different client; no in-place path).
122
+ * - `auth` — `token` / `userId` / `ctx` feed a single `setAuth` call (`ctx` is
123
+ * compared by JSON).
124
+ * - `logoutBehavior` — call `setLogoutBehavior`.
125
+ * - `throw` — value change throws (e.g. `url`). Checked before `identity`, so a
126
+ * same-render name+url swap cannot silently reconnect elsewhere.
127
+ * - `ignore` — captured at creation; later changes are ignored (e.g. web's
128
+ * `worker` factory, whose required inline-literal form makes the reference
129
+ * unstable across renders).
130
+ */
131
+ type OptionDisposition = "identity" | "auth" | "logoutBehavior" | "throw" | "ignore";
132
+ /**
133
+ * Per-platform table mapping option keys to {@link OptionDisposition}. Keys not
134
+ * listed are not compared; unknown keys on the options bag are ignored.
135
+ */
136
+ type ClientOptionPolicy<TOptions extends object> = { readonly [K in keyof TOptions]?: OptionDisposition };
137
+ /**
138
+ * Hold a client for as long as `options` is non-null. Pass `null` until the
139
+ * database id is known (e.g. after a create-session mutation acks). Option
140
+ * changes route onto client verbs per `policy` — see
141
+ * {@link OptionDisposition}.
142
+ */
143
+ declare function useClient<TOptions extends object, TClient extends ClientLifecycle>(options: TOptions | null, createClient: (options: TOptions) => TClient, policy: ClientOptionPolicy<TOptions>): TClient | null;
144
+ //#endregion
145
+ export { type BoundQuery, type ClientLifecycle, type ClientOptionPolicy, type ConnectionStatus, type DoyncClient, DoyncProvider, type DoyncProviderProps, type FalsyQuery, type LocalSource, type LogoutBehavior, type MutationOptions, type MutationResult, type OnceStatus, type OnceView, type OptionDisposition, type PreloadHandle, type PreloadOptions, type QueryStatus, type SchemaEvent, type SchemaEventKind, type SubscribeOptions, type UseQueryOptions, type View, type ViewStatus, useClient, useConnectionStatus, useDoyncClient, useLocalQuery, useMutation, useQuery, useQueryOnce, useSchemaStatus };
146
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/provider.tsx","../src/hooks.tsx","../src/use-client.ts"],"mappings":";;;;;;UAQiB,kBAAA;;WAEN,MAAA,EAAQ,aAAA;EAAA,SACR,QAAA,EAAU,SAAA;AAAA;;iBAIL,aAAA;EACd,MAAA;EACA;AAAA,GACC,kBAAA,GAAqB,YAAA;;;;;iBAUR,cAAA,IAAkB,aAAA;;;;;;AApBlC;;KC4BY,eAAA,GAAkB,kBAAA;;;;;;;ADzBT;AAIrB;;;;;;iBCsCgB,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,SAClB,OAAA,GAAU,eAAA,IACR,GAAA,cAAiB,YAAA;AAAA,iBAEL,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,UAClB,OAAA,GAAU,eAAA,aACC,GAAA,IAAO,YAAA;AAAA,iBAEJ,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,UAAa,YAAA,EAC/B,OAAA,GAAU,eAAA,IACR,GAAA,cAAiB,YAAA;AAAA,iBAEL,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,WAAc,YAAA,EAChC,OAAA,GAAU,eAAA,aACC,GAAA,gBAAmB,YAAA;;;;;KAoFpB,UAAA;;ADjJY;AAUxB;;;;iBCgJgB,YAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,uBACP,GAAA;EAAA,SAAkB,MAAA,EAAQ,UAAA;AAAA;AAAA,iBAEvB,YAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,aAAgB,YAAA,aACvB,GAAA;EAAA,SAA8B,MAAA,EAAQ,UAAA;AAAA;;;AAlJrB;AAiB9B;KAmLY,WAAA;EAAA,SAEG,GAAA;EAAA,SAAsB,MAAA,YAAkB,QAAA;AAAA;EAAA,SAGpC,GAAA;EAAA,SAAsB,MAAA,YAAkB,QAAA;AAAA;;;;;;iBAO3C,aAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,MAAA,EAAQ,WAAA,EACR,MAAA,YAAkB,QAAA,eACP,GAAA,IAAO,YAAA;AAAA,iBACJ,aAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,MAAA,EAAQ,WAAA,GAAc,YAAA,EACtB,MAAA,YAAkB,QAAA,eACP,GAAA,gBAAmB,YAAA;;;;;;iBAiDhB,mBAAA,IAAuB,kBAAA;;;;;AAtPlB;iBAyQL,WAAA,kBACd,QAAA,EAAU,kBAAA,CAAmB,KAAA,KAC3B,IAAA,EAAM,KAAA,EAAO,OAAA,GAAU,iBAAA,KAAoB,gBAAA;;;;;;iBAgB/B,eAAA,IAAmB,aAAA;;;;AD1Ud;AAIrB;;UEMiB,eAAA;EACf,KAAA;EACA,OAAA,CAAQ,IAAA;IACN,KAAA;IACA,GAAA;IACA,MAAA;EAAA;EAEF,iBAAA,CAAkB,QAAA,EAAU,gBAAA;AAAA;;;;;;AFVN;AAUxB;;;;AAAkC;;;;ACQlC;;KCWY,iBAAA;;ADXkB;AAiB9B;;KCKY,kBAAA,mDACW,QAAA,IAAY,iBAAA;;;;;;;iBAiBnB,SAAA,0CAEE,eAAA,EAEhB,OAAA,EAAS,QAAA,SACT,YAAA,GAAe,OAAA,EAAS,QAAA,KAAa,OAAA,EACrC,MAAA,EAAQ,kBAAA,CAAmB,QAAA,IAC1B,OAAA"}
@@ -0,0 +1,146 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ import { ConnectionStatus, ConnectionStatus as ConnectionStatus$1, DoyncClient, DoyncClient as DoyncClient$1, FalsyQuery, FalsyQuery as FalsyQuery$1, LogoutBehavior, LogoutBehavior as LogoutBehavior$1, MutationOptions, MutationOptions as MutationOptions$1, MutationResult, MutationResult as MutationResult$1, OnceView, PreloadHandle, PreloadOptions, QueryStatus, SchemaEvent, SchemaEvent as SchemaEvent$1, SchemaEventKind, SubscribeOptions, SubscribeOptions as SubscribeOptions$1, View, ViewStatus, ViewStatus as ViewStatus$1 } from "@doync/client";
3
+ import { BoundQuery, BoundQuery as BoundQuery$1, MutationDefinition, SqlValue } from "@doync/core";
4
+
5
+ //#region src/provider.d.ts
6
+ /** Props for {@link DoyncProvider}. */
7
+ interface DoyncProviderProps {
8
+ /** Client from `createWebClient` / `createMobileClient` (or a test double). */
9
+ readonly client: DoyncClient$1;
10
+ readonly children: ReactNode;
11
+ }
12
+ /** Provide a {@link DoyncClient} to `useQuery`, `useMutation`, and siblings. */
13
+ declare function DoyncProvider({
14
+ client,
15
+ children
16
+ }: DoyncProviderProps): ReactElement;
17
+ /**
18
+ * {@link DoyncClient} from the nearest {@link DoyncProvider}. Throws if used
19
+ * outside a provider.
20
+ */
21
+ declare function useDoyncClient(): DoyncClient$1;
22
+ //#endregion
23
+ //#region src/hooks.d.ts
24
+ /**
25
+ * Options for {@link useQuery}: `ttl` (server warm-grace after unmount) and
26
+ * `skip`. One-ness is inferred from the bound query — there is no `one`
27
+ * option.
28
+ */
29
+ type UseQueryOptions = SubscribeOptions$1;
30
+ /**
31
+ * Live subscription to a bound query. Pass `queries.issues.open(args)`. Returns
32
+ * `[rows, status]` and updates as local writes and server sync move the rows.
33
+ *
34
+ * - Multi-row queries → `[readonly Row[], status]`
35
+ * - One-row (`` sql.one`…` `` / `findFirst`) → `[Row | undefined, status]`
36
+ * - Falsy (`cond && bound`) → `[undefined, { status: 'unknown' }]` (stable hooks)
37
+ * - `skip: true` → empty shaped like the query (`[]` or `undefined`), status
38
+ * `unknown`
39
+ *
40
+ * Status runs `unknown` → `complete` on first confirmation, or `error` if the
41
+ * subscribe fails. Mount retains the shared handle; unmount releases it.
42
+ */
43
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, true>, options?: UseQueryOptions): [Row | undefined, ViewStatus$1];
44
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, false>, options?: UseQueryOptions): [readonly Row[], ViewStatus$1];
45
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, true> | FalsyQuery$1, options?: UseQueryOptions): [Row | undefined, ViewStatus$1];
46
+ declare function useQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, false> | FalsyQuery$1, options?: UseQueryOptions): [readonly Row[] | undefined, ViewStatus$1];
47
+ /**
48
+ * Network half of a {@link useQueryOnce} read: `loading` → `success` / `error`,
49
+ * or `skipped` when the argument is falsy (no request started).
50
+ */
51
+ type OnceStatus = "loading" | "success" | "error" | "skipped";
52
+ /**
53
+ * One-shot cache-and-network read. Rows come from the local replica
54
+ * immediately, then update when the server answer lands. `status` tracks the
55
+ * network half. Falsy argument → `[undefined, { status: 'skipped' }]` with
56
+ * stable hooks. StrictMode-safe (remount keeps the in-flight request).
57
+ */
58
+ declare function useQueryOnce<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, boolean>): [readonly Row[], {
59
+ readonly status: OnceStatus;
60
+ }];
61
+ declare function useQueryOnce<Row extends Record<string, unknown> = Record<string, SqlValue>>(query: BoundQuery$1<Row, boolean> | FalsyQuery$1): [readonly Row[] | undefined, {
62
+ readonly status: OnceStatus;
63
+ }];
64
+ /**
65
+ * SQL source for {@link useLocalQuery}: a string, `{ sql, params }`, a callback
66
+ * returning either, or falsy ("no local read").
67
+ */
68
+ type LocalSource = string | {
69
+ readonly sql: string;
70
+ readonly params?: readonly SqlValue[];
71
+ } | (() => string | {
72
+ readonly sql: string;
73
+ readonly params?: readonly SqlValue[];
74
+ });
75
+ /**
76
+ * Run arbitrary SQL over the local replica reactively (aggregates, joins, …).
77
+ * Re-runs on local commits; never registered with the server — offline-capable
78
+ * and free to the Mirror. Falsy source → `[undefined, { status: 'unknown' }]`.
79
+ */
80
+ declare function useLocalQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(source: LocalSource, params?: readonly SqlValue[]): [readonly Row[], ViewStatus$1];
81
+ declare function useLocalQuery<Row extends Record<string, unknown> = Record<string, SqlValue>>(source: LocalSource | FalsyQuery$1, params?: readonly SqlValue[]): [readonly Row[] | undefined, ViewStatus$1];
82
+ /**
83
+ * Live {@link ConnectionStatus} to the Mirror: `connecting` / `connected` /
84
+ * `disconnected` / `error` / `needs-auth`. Re-renders on every transition — use
85
+ * for a status pill, offline banner, or needs-auth → logout flow.
86
+ */
87
+ declare function useConnectionStatus(): ConnectionStatus$1;
88
+ /**
89
+ * Registered mutation as a callable. Call with `args` to apply optimistically
90
+ * and push; returns `{ client, server }` — paint from `client`, await `server`
91
+ * for Origin confirmation.
92
+ */
93
+ declare function useMutation<Input = unknown>(mutation: MutationDefinition<Input>): (args: Input, options?: MutationOptions$1) => MutationResult$1;
94
+ /**
95
+ * Current schema/recovery state, or `null` when nominal. Use for a deploy-skew
96
+ * / recovery banner — re-renders when the state clears too (a one-shot
97
+ * `onSchemaEvent` callback alone would stick after recovery).
98
+ */
99
+ declare function useSchemaStatus(): SchemaEvent$1 | null;
100
+ //#endregion
101
+ //#region src/use-client.d.ts
102
+ /**
103
+ * Minimum call surface the hook routes onto. Platform clients (`MobileClient`,
104
+ * `WebClient`) both satisfy this; tests inject a fake.
105
+ */
106
+ interface ClientLifecycle {
107
+ close(): void;
108
+ setAuth(auth: {
109
+ token: string | null;
110
+ ctx?: unknown;
111
+ userId: string | null;
112
+ }): void;
113
+ setLogoutBehavior(behavior: LogoutBehavior$1): void;
114
+ }
115
+ /**
116
+ * How a single options-bag key is handled when its value changes between
117
+ * renders. Keys absent from the policy are not compared (construction-only
118
+ * values like `schema` / `queries` — module-level constants in real apps).
119
+ *
120
+ * - `identity` — close the old client and create a new one (e.g. a different
121
+ * database `name` is a different client; no in-place path).
122
+ * - `auth` — `token` / `userId` / `ctx` feed a single `setAuth` call (`ctx` is
123
+ * compared by JSON).
124
+ * - `logoutBehavior` — call `setLogoutBehavior`.
125
+ * - `throw` — value change throws (e.g. `url`). Checked before `identity`, so a
126
+ * same-render name+url swap cannot silently reconnect elsewhere.
127
+ * - `ignore` — captured at creation; later changes are ignored (e.g. web's
128
+ * `worker` factory, whose required inline-literal form makes the reference
129
+ * unstable across renders).
130
+ */
131
+ type OptionDisposition = "identity" | "auth" | "logoutBehavior" | "throw" | "ignore";
132
+ /**
133
+ * Per-platform table mapping option keys to {@link OptionDisposition}. Keys not
134
+ * listed are not compared; unknown keys on the options bag are ignored.
135
+ */
136
+ type ClientOptionPolicy<TOptions extends object> = { readonly [K in keyof TOptions]?: OptionDisposition };
137
+ /**
138
+ * Hold a client for as long as `options` is non-null. Pass `null` until the
139
+ * database id is known (e.g. after a create-session mutation acks). Option
140
+ * changes route onto client verbs per `policy` — see
141
+ * {@link OptionDisposition}.
142
+ */
143
+ declare function useClient<TOptions extends object, TClient extends ClientLifecycle>(options: TOptions | null, createClient: (options: TOptions) => TClient, policy: ClientOptionPolicy<TOptions>): TClient | null;
144
+ //#endregion
145
+ export { type BoundQuery, type ClientLifecycle, type ClientOptionPolicy, type ConnectionStatus, type DoyncClient, DoyncProvider, type DoyncProviderProps, type FalsyQuery, type LocalSource, type LogoutBehavior, type MutationOptions, type MutationResult, type OnceStatus, type OnceView, type OptionDisposition, type PreloadHandle, type PreloadOptions, type QueryStatus, type SchemaEvent, type SchemaEventKind, type SubscribeOptions, type UseQueryOptions, type View, type ViewStatus, useClient, useConnectionStatus, useDoyncClient, useLocalQuery, useMutation, useQuery, useQueryOnce, useSchemaStatus };
146
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/provider.tsx","../src/hooks.tsx","../src/use-client.ts"],"mappings":";;;;;;UAQiB,kBAAA;;WAEN,MAAA,EAAQ,aAAA;EAAA,SACR,QAAA,EAAU,SAAA;AAAA;;iBAIL,aAAA;EACd,MAAA;EACA;AAAA,GACC,kBAAA,GAAqB,YAAA;;;;;iBAUR,cAAA,IAAkB,aAAA;;;;;;AApBlC;;KC4BY,eAAA,GAAkB,kBAAA;;;;;;;ADzBT;AAIrB;;;;;;iBCsCgB,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,SAClB,OAAA,GAAU,eAAA,IACR,GAAA,cAAiB,YAAA;AAAA,iBAEL,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,UAClB,OAAA,GAAU,eAAA,aACC,GAAA,IAAO,YAAA;AAAA,iBAEJ,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,UAAa,YAAA,EAC/B,OAAA,GAAU,eAAA,IACR,GAAA,cAAiB,YAAA;AAAA,iBAEL,QAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,WAAc,YAAA,EAChC,OAAA,GAAU,eAAA,aACC,GAAA,gBAAmB,YAAA;;;;;KAoFpB,UAAA;;ADjJY;AAUxB;;;;iBCgJgB,YAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,uBACP,GAAA;EAAA,SAAkB,MAAA,EAAQ,UAAA;AAAA;AAAA,iBAEvB,YAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,KAAA,EAAO,YAAA,CAAW,GAAA,aAAgB,YAAA,aACvB,GAAA;EAAA,SAA8B,MAAA,EAAQ,UAAA;AAAA;;;AAlJrB;AAiB9B;KAmLY,WAAA;EAAA,SAEG,GAAA;EAAA,SAAsB,MAAA,YAAkB,QAAA;AAAA;EAAA,SAGpC,GAAA;EAAA,SAAsB,MAAA,YAAkB,QAAA;AAAA;;;;;;iBAO3C,aAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,MAAA,EAAQ,WAAA,EACR,MAAA,YAAkB,QAAA,eACP,GAAA,IAAO,YAAA;AAAA,iBACJ,aAAA,aACF,MAAA,oBAA0B,MAAA,SAAe,QAAA,GAErD,MAAA,EAAQ,WAAA,GAAc,YAAA,EACtB,MAAA,YAAkB,QAAA,eACP,GAAA,gBAAmB,YAAA;;;;;;iBAiDhB,mBAAA,IAAuB,kBAAA;;;;;AAtPlB;iBAyQL,WAAA,kBACd,QAAA,EAAU,kBAAA,CAAmB,KAAA,KAC3B,IAAA,EAAM,KAAA,EAAO,OAAA,GAAU,iBAAA,KAAoB,gBAAA;;;;;;iBAgB/B,eAAA,IAAmB,aAAA;;;;AD1Ud;AAIrB;;UEMiB,eAAA;EACf,KAAA;EACA,OAAA,CAAQ,IAAA;IACN,KAAA;IACA,GAAA;IACA,MAAA;EAAA;EAEF,iBAAA,CAAkB,QAAA,EAAU,gBAAA;AAAA;;;;;;AFVN;AAUxB;;;;AAAkC;;;;ACQlC;;KCWY,iBAAA;;ADXkB;AAiB9B;;KCKY,kBAAA,mDACW,QAAA,IAAY,iBAAA;;;;;;;iBAiBnB,SAAA,0CAEE,eAAA,EAEhB,OAAA,EAAS,QAAA,SACT,YAAA,GAAe,OAAA,EAAS,QAAA,KAAa,OAAA,EACrC,MAAA,EAAQ,kBAAA,CAAmB,QAAA,IAC1B,OAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./use-client-DanwzoBF.js";import{createContext as t,useCallback as n,useContext as r,useEffect as i,useMemo as a,useState as o,useSyncExternalStore as s}from"react";import{jsx as c}from"react/jsx-runtime";import{isBoundQuery as l}from"@doync/core/internal";const u=t(null);function d({client:e,children:t}){return c(u.Provider,{value:e,children:t})}function f(){let e=r(u);if(e===null)throw Error(`doync: a doync hook was used outside a <DoyncProvider>`);return e}function p(e,t){let n=f(),r=t?.skip??!1,o=D(e),s=o?`|ttl=${t?.ttl??``}|skip=${r}|falsy=1`:`${E(e)}|ttl=${t?.ttl??``}|skip=${r}|falsy=0`,c=a(()=>{if(o)return S;let i=O(e,`useQuery`);return n.subscribe(i,{ttl:t?.ttl,skip:r})},[n,s]);i(()=>(c.retain(),()=>c.release()),[c]);let l=w(c),u=T(c),d=a(()=>{if(o)return!1;let t=e;return m(t.query,t.args)},[s]);if(o)return[void 0,u];if(c.one!==void 0&&c.one!==d)throw Error(c.one?`doync: the subscribed statement is a one-query (sql.one / findFirst) but this client resolved it as multi-row — the query definition disagrees across the resolve boundary`:`doync: the subscribed statement is multi-row but this client resolved it as a one-query — the query definition disagrees across the resolve boundary`);return[c.one??d?l.length>0?l[0]:void 0:l,u]}function m(e,t){try{return e.resolve({args:t})?.one??!1}catch{return!1}}function h(e){let t=f(),n=D(e),r=a(()=>{if(n)return C;let r=O(e,`useQueryOnce`);return t.once(r)},[t,n?`falsy=1`:`${E(e)}|falsy=0`]),[s,c]=o(`loading`);i(()=>{if(n)return;let e=!0;return c(`loading`),r.server.then(()=>{e&&c(`success`)},()=>{e&&c(`error`)}),()=>{e=!1,r.dispose()}},[r,n]);let l=n?`skipped`:s,u=w(r);return[n?void 0:u,{status:l}]}function g(e,t=[]){let n=f(),r=D(e),o=r?null:typeof e==`function`?e():e,s=o===null?``:typeof o==`string`?o:o.sql,c=o===null||typeof o==`string`?t:o.params??[],l=a(()=>r?S:n.local(s,...c),[n,r?`falsy=1`:`${s}|${JSON.stringify(c)}|falsy=0`]);i(()=>(l.retain(),()=>l.release()),[l]);let u=w(l),d=T(l);return r?[void 0,d]:[u,d]}function _(){let e=f();return s(n(t=>e.onConnectionChange(t),[e]),()=>e.connectionStatus,()=>e.connectionStatus)}function v(e){let t=f();return n((n,r)=>t.mutate(e,n,r),[t,e])}function y(){let e=f();return s(n(t=>e.onSchemaChange(t),[e]),()=>e.schemaStatus,()=>e.schemaStatus)}const b=Object.freeze([]),x=Object.freeze({status:`unknown`}),S={current:()=>b,status:()=>x,onChange:()=>()=>{},retain:()=>{},release:()=>{}},C={current:()=>b,onChange:()=>()=>{},dispose:()=>{},server:new Promise(()=>{})};function w(e){let t=n(t=>e.onChange(t),[e]),r=n(()=>e.current(),[e]);return s(t,r,r)}function T(e){let t=n(t=>e.onChange(t),[e]),r=n(()=>e.status(),[e]);return s(t,r,r)}function E(e){return`${e.query.name??``}|${JSON.stringify(e.args??null)}`}function D(e){return e===!1||e==null}function O(e,t){if(l(e))return e;throw Error(typeof e==`function`?`doync: ${t} expected a BoundQuery — received a function; did you forget to call it?`:`doync: ${t} expected a BoundQuery (produced by queries.…(args)), got ${typeof e==`object`&&e?`an object`:String(e)}`)}export{d as DoyncProvider,e as useClient,_ as useConnectionStatus,f as useDoyncClient,g as useLocalQuery,v as useMutation,p as useQuery,h as useQueryOnce,y as useSchemaStatus};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/provider.tsx","../src/hooks.tsx"],"sourcesContent":["import type { DoyncClient } from '@doync/client'\nimport type { ReactElement, ReactNode } from 'react'\n\nimport { createContext, useContext } from 'react'\n\nconst DoyncContext = createContext<DoyncClient | null>(null)\n\n/** Props for {@link DoyncProvider}. */\nexport interface DoyncProviderProps {\n /** Client from `createWebClient` / `createMobileClient` (or a test double). */\n readonly client: DoyncClient\n readonly children: ReactNode\n}\n\n/** Provide a {@link DoyncClient} to `useQuery`, `useMutation`, and siblings. */\nexport function DoyncProvider({\n client,\n children,\n}: DoyncProviderProps): ReactElement {\n return (\n <DoyncContext.Provider value={client}>{children}</DoyncContext.Provider>\n )\n}\n\n/**\n * {@link DoyncClient} from the nearest {@link DoyncProvider}. Throws if used\n * outside a provider.\n */\nexport function useDoyncClient(): DoyncClient {\n const client = useContext(DoyncContext)\n if (client === null) {\n throw new Error('doync: a doync hook was used outside a <DoyncProvider>')\n }\n return client\n}\n","import type {\n ConnectionStatus,\n FalsyQuery,\n MutationOptions,\n MutationResult,\n OnceView,\n SchemaEvent,\n SubscribeOptions,\n View,\n ViewStatus,\n} from '@doync/client'\nimport type {\n BoundQuery,\n MutationDefinition,\n QueryDefinition,\n SqlValue,\n} from '@doync/core'\n\nimport { isBoundQuery } from '@doync/core/internal'\nimport {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react'\n\nimport { useDoyncClient } from './provider'\n\n// ── useQuery (subscribe — the norm) ───────────────────────────────────────\n\n/**\n * Options for {@link useQuery}: `ttl` (server warm-grace after unmount) and\n * `skip`. One-ness is inferred from the bound query — there is no `one`\n * option.\n */\nexport type UseQueryOptions = SubscribeOptions\n\n/**\n * Live subscription to a bound query. Pass `queries.issues.open(args)`. Returns\n * `[rows, status]` and updates as local writes and server sync move the rows.\n *\n * - Multi-row queries → `[readonly Row[], status]`\n * - One-row (`` sql.one`…` `` / `findFirst`) → `[Row | undefined, status]`\n * - Falsy (`cond && bound`) → `[undefined, { status: 'unknown' }]` (stable hooks)\n * - `skip: true` → empty shaped like the query (`[]` or `undefined`), status\n * `unknown`\n *\n * Status runs `unknown` → `complete` on first confirmation, or `error` if the\n * subscribe fails. Mount retains the shared handle; unmount releases it.\n */\n\n// Non-falsy one-query.\nexport function useQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, true>,\n options?: UseQueryOptions,\n): [Row | undefined, ViewStatus]\n// Non-falsy multi-row.\nexport function useQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, false>,\n options?: UseQueryOptions,\n): [readonly Row[], ViewStatus]\n// Maybe one-query (argument can be falsy) — `| undefined` only here.\nexport function useQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, true> | FalsyQuery,\n options?: UseQueryOptions,\n): [Row | undefined, ViewStatus]\n// Maybe multi-row — rows themselves gain `| undefined` when no query.\nexport function useQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, false> | FalsyQuery,\n options?: UseQueryOptions,\n): [readonly Row[] | undefined, ViewStatus]\n// Implementation (boolean One + maybe).\nexport function useQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, boolean> | FalsyQuery,\n options?: UseQueryOptions,\n): [readonly Row[] | (Row | undefined) | undefined, ViewStatus] {\n const client = useDoyncClient()\n const skip = options?.skip ?? false\n const falsy = isFalsyQuery(query)\n // Falsy+skip in the key so toggles swap Views without reordering hooks.\n // Creation pure (ADR-0023/Q3); bind pure (ADR-0027) — key name+JSON args.\n const key = falsy\n ? `|ttl=${options?.ttl ?? ''}|skip=${skip}|falsy=1`\n : `${boundQueryKey(query)}|ttl=${options?.ttl ?? ''}|skip=${skip}|falsy=0`\n const view = useMemo((): View<Row> => {\n if (falsy) return INERT_VIEW as unknown as View<Row>\n // BoundQuery through (ADR-0027 / #200); reject truthy impostors.\n const bound = requireBoundQuery(query, 'useQuery')\n return client.subscribe(bound as BoundQuery<Row, boolean>, {\n ttl: options?.ttl,\n skip,\n }) as View<Row>\n // identity is `key`, not object identity\n }, [client, key])\n useEffect(() => {\n view.retain()\n return () => view.release()\n }, [view])\n const rows = useViewRows(view)\n const status = useViewStatus(view)\n // One-ness inferred (#115): resolve locally for immediate shape (topology\n // view.one is late; skipped never resolves). Falsy keep slot only.\n const resolvedOne = useMemo(() => {\n if (falsy) return false\n const bound = query as BoundQuery<Row, boolean>\n return resolvedOneness(bound.query, bound.args)\n }, [key])\n\n // Falsy (ADR-0027): rows undefined for both shapes; after hooks for order.\n // Distinct from skip (shape-preserving empties with a bound query).\n if (falsy) {\n return [undefined, status]\n }\n\n // view.one is authoritative once known — must agree with local resolve or the\n // resolve boundary disagrees (throw). LIMITATION: mixed resolvers\n // (`args.x ? sql.one : sql`) have type-level One=false but runtime agreement,\n // so this guard cannot fire; prefer pure sql.one/findFirst.\n if (view.one !== undefined && view.one !== resolvedOne) {\n throw new Error(\n view.one\n ? 'doync: the subscribed statement is a one-query (sql.one / findFirst) but this client resolved it as multi-row — the query definition disagrees across the resolve boundary'\n : 'doync: the subscribed statement is multi-row but this client resolved it as a one-query — the query definition disagrees across the resolve boundary',\n )\n }\n const one = view.one ?? resolvedOne\n const value = one ? (rows.length > 0 ? (rows[0] as Row) : undefined) : rows\n return [value, status]\n}\n\n/**\n * Local one-ness (#115): same resolve the engine does at subscribe so shape is\n * known before topology `view.one`. Unresolvable args ⇒ false (defer to\n * view.one).\n */\nfunction resolvedOneness(\n query: QueryDefinition<unknown, unknown, boolean>,\n args: unknown,\n): boolean {\n try {\n return query.resolve({ args })?.one ?? false\n } catch {\n return false\n }\n}\n\n// ── useQueryOnce (cache-and-network Once) ─────────────────────────────\n\n/**\n * Network half of a {@link useQueryOnce} read: `loading` → `success` / `error`,\n * or `skipped` when the argument is falsy (no request started).\n */\nexport type OnceStatus = 'loading' | 'success' | 'error' | 'skipped'\n\n/**\n * One-shot cache-and-network read. Rows come from the local replica\n * immediately, then update when the server answer lands. `status` tracks the\n * network half. Falsy argument → `[undefined, { status: 'skipped' }]` with\n * stable hooks. StrictMode-safe (remount keeps the in-flight request).\n */\n// Non-falsy: today's exact rows type.\nexport function useQueryOnce<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, boolean>,\n): [readonly Row[], { readonly status: OnceStatus }]\n// Maybe (argument can be falsy) — rows gain `| undefined` only here.\nexport function useQueryOnce<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, boolean> | FalsyQuery,\n): [readonly Row[] | undefined, { readonly status: OnceStatus }]\nexport function useQueryOnce<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n query: BoundQuery<Row, boolean> | FalsyQuery,\n): [readonly Row[] | undefined, { readonly status: OnceStatus }] {\n const client = useDoyncClient()\n const falsy = isFalsyQuery(query)\n const key = falsy ? 'falsy=1' : `${boundQueryKey(query)}|falsy=0`\n const view = useMemo((): OnceView<Row> => {\n if (falsy) return INERT_ONCE as unknown as OnceView<Row>\n // BoundQuery through (ADR-0027 / #200).\n const bound = requireBoundQuery(query, 'useQueryOnce')\n return client.once(bound as BoundQuery<Row, boolean>) as OnceView<Row>\n }, [client, key])\n // Live network status; falsy returns skipped sync so bound→falsy never flash\n // stale success/error (#197).\n const [liveStatus, setLiveStatus] =\n useState<Exclude<OnceStatus, 'skipped'>>('loading')\n useEffect(() => {\n // Commit owns Once (Q7). Falsy → nothing. Live starts network (cancels\n // StrictMode dispose arm). Cleanup = Warm-tick dispose.\n if (falsy) return\n let active = true\n setLiveStatus('loading')\n view.server.then(\n () => {\n if (active) setLiveStatus('success')\n },\n () => {\n if (active) setLiveStatus('error')\n },\n )\n return () => {\n active = false\n view.dispose()\n }\n }, [view, falsy])\n const status: OnceStatus = falsy ? 'skipped' : liveStatus\n // Always read store (hook order); falsy → undefined.\n const rows = useViewRows(view)\n return [falsy ? undefined : rows, { status }]\n}\n\n// ── useLocalQuery (arbitrary SQL over the replica) ─────────────────────────\n\n/**\n * SQL source for {@link useLocalQuery}: a string, `{ sql, params }`, a callback\n * returning either, or falsy (\"no local read\").\n */\nexport type LocalSource =\n | string\n | { readonly sql: string; readonly params?: readonly SqlValue[] }\n | (() =>\n | string\n | { readonly sql: string; readonly params?: readonly SqlValue[] })\n\n/**\n * Run arbitrary SQL over the local replica reactively (aggregates, joins, …).\n * Re-runs on local commits; never registered with the server — offline-capable\n * and free to the Mirror. Falsy source → `[undefined, { status: 'unknown' }]`.\n */\nexport function useLocalQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n source: LocalSource,\n params?: readonly SqlValue[],\n): [readonly Row[], ViewStatus]\nexport function useLocalQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n source: LocalSource | FalsyQuery,\n params?: readonly SqlValue[],\n): [readonly Row[] | undefined, ViewStatus]\nexport function useLocalQuery<\n Row extends Record<string, unknown> = Record<string, SqlValue>,\n>(\n source: LocalSource | FalsyQuery,\n params: readonly SqlValue[] = [],\n): [readonly Row[] | undefined, ViewStatus] {\n const client = useDoyncClient()\n const falsy = isFalsyQuery(source)\n const resolved = falsy\n ? null\n : typeof source === 'function'\n ? source()\n : source\n const sql =\n resolved === null\n ? ''\n : typeof resolved === 'string'\n ? resolved\n : resolved.sql\n const sqlParams =\n resolved === null\n ? params\n : typeof resolved === 'string'\n ? params\n : (resolved.params ?? [])\n const key = falsy ? 'falsy=1' : `${sql}|${JSON.stringify(sqlParams)}|falsy=0`\n const view = useMemo((): View<Row> => {\n if (falsy) return INERT_VIEW as unknown as View<Row>\n return client.local<Row>(sql, ...sqlParams)\n }, [client, key])\n useEffect(() => {\n view.retain()\n return () => view.release()\n }, [view])\n // Both stores always (hook order). Live Local is always complete (#104).\n const rows = useViewRows(view)\n const status = useViewStatus(view)\n if (falsy) return [undefined, status]\n return [rows, status]\n}\n\n// ── useConnectionStatus (topology socket health) ──────────────────────────\n\n/**\n * Live {@link ConnectionStatus} to the Mirror: `connecting` / `connected` /\n * `disconnected` / `error` / `needs-auth`. Re-renders on every transition — use\n * for a status pill, offline banner, or needs-auth → logout flow.\n */\nexport function useConnectionStatus(): ConnectionStatus {\n const client = useDoyncClient()\n return useSyncExternalStore(\n useCallback(\n (onChange: () => void) => client.onConnectionChange(onChange),\n [client],\n ),\n () => client.connectionStatus,\n () => client.connectionStatus,\n )\n}\n\n// ── useMutation ────────────────────────────────────────────────────────────\n\n/**\n * Registered mutation as a callable. Call with `args` to apply optimistically\n * and push; returns `{ client, server }` — paint from `client`, await `server`\n * for Origin confirmation.\n */\nexport function useMutation<Input = unknown>(\n mutation: MutationDefinition<Input>,\n): (args: Input, options?: MutationOptions) => MutationResult {\n const client = useDoyncClient()\n return useCallback(\n (args: Input, options?: MutationOptions) =>\n client.mutate(mutation, args, options),\n [client, mutation],\n )\n}\n\n// ── useSchemaStatus ───────────────────────────────────────────────────────\n\n/**\n * Current schema/recovery state, or `null` when nominal. Use for a deploy-skew\n * / recovery banner — re-renders when the state clears too (a one-shot\n * `onSchemaEvent` callback alone would stick after recovery).\n */\nexport function useSchemaStatus(): SchemaEvent | null {\n const client = useDoyncClient()\n return useSyncExternalStore(\n useCallback(\n (onChange: () => void) => client.onSchemaChange(onChange),\n [client],\n ),\n () => client.schemaStatus,\n () => client.schemaStatus,\n )\n}\n\n// ── shared internals ───────────────────────────────────────────────────────\n\n/** Stable empty snapshots (fresh ones tear the store). */\nconst EMPTY_ROWS: readonly Record<string, unknown>[] = Object.freeze([])\nconst UNKNOWN_STATUS: ViewStatus = Object.freeze({ status: 'unknown' })\n\n/**\n * Inert View for falsy useQuery/useLocalQuery (ADR-0027): no desire, empty,\n * unknown, no-op retain. Hook adds undefined-rows (vs skip's shape-preserving\n * empty). Module-stable.\n */\nconst INERT_VIEW: View<Record<string, unknown>> = {\n current: () => EMPTY_ROWS,\n status: () => UNKNOWN_STATUS,\n onChange: () => () => {},\n retain: () => {},\n release: () => {},\n}\n\n/**\n * Inert Once for falsy useQueryOnce (ADR-0027): no network. Hook reports\n * skipped; never awaits server (does not settle).\n */\nconst INERT_ONCE: OnceView<Record<string, unknown>> = {\n current: () => EMPTY_ROWS,\n onChange: () => () => {},\n dispose: () => {},\n // Never settles — unused for the skipped status path.\n server: new Promise(() => {}),\n}\n\n/** Track View rows via useSyncExternalStore (stable current() until change). */\nfunction useViewRows<Row extends Record<string, unknown>>(\n view: View<Row> | OnceView<Row>,\n): readonly Row[] {\n const subscribe = useCallback(\n (onStoreChange: () => void) => view.onChange(onStoreChange),\n [view],\n )\n const getSnapshot = useCallback(() => view.current(), [view])\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/**\n * Track {@link ViewStatus} via useSyncExternalStore (#105): same onChange as\n * rows; stable until visible status changes.\n */\nfunction useViewStatus<Row extends Record<string, unknown>>(\n view: View<Row>,\n): ViewStatus {\n const subscribe = useCallback(\n (onStoreChange: () => void) => view.onChange(onStoreChange),\n [view],\n )\n const getSnapshot = useCallback(() => view.status(), [view])\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n\n/** Dedup key for a bound-query call (name + args). */\nfunction boundQueryKey(bound: {\n readonly query: { readonly name?: string }\n readonly args: unknown\n}): string {\n return `${bound.query.name ?? ''}|${JSON.stringify(bound.args ?? null)}`\n}\n\n/** `false | null | undefined` — the \"no query\" sentinel (ADR-0027). */\nfunction isFalsyQuery(value: unknown): value is FalsyQuery {\n return value === false || value === null || value === undefined\n}\n\n/**\n * BoundQuery check at the react surface (ADR-0027). Bare function → forget-\n * to-call wording.\n */\nfunction requireBoundQuery(\n value: unknown,\n surface: 'useQuery' | 'useQueryOnce',\n): BoundQuery {\n if (isBoundQuery(value)) return value\n if (typeof value === 'function') {\n throw new Error(\n `doync: ${surface} expected a BoundQuery — received a function; did you forget to call it?`,\n )\n }\n throw new Error(\n `doync: ${surface} expected a BoundQuery (produced by queries.…(args)), got ${typeof value === 'object' && value !== null ? 'an object' : String(value)}`,\n )\n}\n"],"mappings":"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"}
File without changes
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
File without changes
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./use-client-D1I3o0t5.cjs");let t=require("@doync/mobile");const n={name:`identity`,url:`throw`,token:`auth`,userId:`auth`,ctx:`auth`,logoutBehavior:`logoutBehavior`};function r(r){return e.t(r,t.createMobileClient,n)}exports.useMobileClient=r;
@@ -0,0 +1,13 @@
1
+ import { CreateMobileClientOptions, MobileClient } from "@doync/mobile";
2
+
3
+ //#region src/mobile.d.ts
4
+ /**
5
+ * Hold a {@link MobileClient} for as long as `options` is non-null. Auth and
6
+ * logout-behavior changes apply in place; a `name` change recreates the client;
7
+ * a `url` change throws. Pass `null` until the database id is known, then hand
8
+ * the result to `<DoyncProvider>`.
9
+ */
10
+ declare function useMobileClient<TAuthData = unknown>(options: CreateMobileClientOptions<TAuthData> | null): MobileClient | null;
11
+ //#endregion
12
+ export { type CreateMobileClientOptions, type MobileClient, useMobileClient };
13
+ //# sourceMappingURL=mobile.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile.d.cts","names":[],"sources":["../src/mobile.ts"],"mappings":";;;;;;;;;iBAyDgB,eAAA,sBACd,OAAA,EAAS,yBAAA,CAA0B,SAAA,WAClC,YAAA"}
@@ -0,0 +1,13 @@
1
+ import { CreateMobileClientOptions, MobileClient } from "@doync/mobile";
2
+
3
+ //#region src/mobile.d.ts
4
+ /**
5
+ * Hold a {@link MobileClient} for as long as `options` is non-null. Auth and
6
+ * logout-behavior changes apply in place; a `name` change recreates the client;
7
+ * a `url` change throws. Pass `null` until the database id is known, then hand
8
+ * the result to `<DoyncProvider>`.
9
+ */
10
+ declare function useMobileClient<TAuthData = unknown>(options: CreateMobileClientOptions<TAuthData> | null): MobileClient | null;
11
+ //#endregion
12
+ export { type CreateMobileClientOptions, type MobileClient, useMobileClient };
13
+ //# sourceMappingURL=mobile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile.d.ts","names":[],"sources":["../src/mobile.ts"],"mappings":";;;;;;;;;iBAyDgB,eAAA,sBACd,OAAA,EAAS,yBAAA,CAA0B,SAAA,WAClC,YAAA"}
package/dist/mobile.js ADDED
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./use-client-DanwzoBF.js";import{createMobileClient as t}from"@doync/mobile";const n={name:`identity`,url:`throw`,token:`auth`,userId:`auth`,ctx:`auth`,logoutBehavior:`logoutBehavior`};function r(r){return e(r,t,n)}export{r as useMobileClient};
2
+ //# sourceMappingURL=mobile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile.js","names":[],"sources":["../src/mobile.ts"],"sourcesContent":["/**\n * `@doync/react/mobile` — {@link useMobileClient}, a thin wrapper around\n * {@link useClient} that supplies `createMobileClient` and the mobile option\n * policy. Imports `@doync/mobile` as an optional peer of `@doync/react`, so a\n * web-only app never pulls it in.\n *\n * ```ts\n * import { useMobileClient } from '@doync/react/mobile'\n *\n * const client = useMobileClient(\n * id\n * ? {\n * name: id,\n * schema,\n * queries,\n * mutations,\n * url: 'wss://example.com/sync',\n * token,\n * userId,\n * ctx,\n * }\n * : null,\n * )\n * // pass client to <DoyncProvider> once non-null\n * ```\n */\n\nimport type { CreateMobileClientOptions, MobileClient } from '@doync/mobile'\n\nimport { createMobileClient } from '@doync/mobile'\n\nimport { useClient, type ClientOptionPolicy } from './use-client'\n\n/**\n * Mobile option dispositions (ADR-0034). Schema / queries / mutations (and any\n * monorepo-only injects on `CreateMobileClientOptionsForTest`) are\n * construction- time constants — the hook does not police them (absent from the\n * policy = not compared).\n *\n * `url` is throw-on-change: a config bug cannot silently reconnect elsewhere.\n * There is no `worker` on mobile (single process).\n */\nconst MOBILE_CLIENT_POLICY: ClientOptionPolicy<CreateMobileClientOptions> = {\n name: 'identity',\n url: 'throw',\n token: 'auth',\n userId: 'auth',\n ctx: 'auth',\n logoutBehavior: 'logoutBehavior',\n}\n\n/**\n * Hold a {@link MobileClient} for as long as `options` is non-null. Auth and\n * logout-behavior changes apply in place; a `name` change recreates the client;\n * a `url` change throws. Pass `null` until the database id is known, then hand\n * the result to `<DoyncProvider>`.\n */\nexport function useMobileClient<TAuthData = unknown>(\n options: CreateMobileClientOptions<TAuthData> | null,\n): MobileClient | null {\n return useClient(\n options as CreateMobileClientOptions | null,\n createMobileClient,\n MOBILE_CLIENT_POLICY as ClientOptionPolicy<CreateMobileClientOptions>,\n )\n}\n\nexport type { CreateMobileClientOptions, MobileClient }\n"],"mappings":"gGA0CA,MAAM,EAAsE,CAC1E,KAAM,WACN,IAAK,QACL,MAAO,OACP,OAAQ,OACR,IAAK,OACL,eAAgB,gBAClB,EAQA,SAAgB,EACd,EACqB,CACrB,OAAO,EACL,EACA,EACA,CACF,CACF"}