syncorejs 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/_vendor/cli/app.d.mts.map +1 -1
- package/dist/_vendor/cli/app.mjs +330 -46
- package/dist/_vendor/cli/app.mjs.map +1 -1
- package/dist/_vendor/cli/context.mjs +27 -9
- package/dist/_vendor/cli/context.mjs.map +1 -1
- package/dist/_vendor/cli/dev-session.mjs.map +1 -1
- package/dist/_vendor/cli/doctor.mjs +513 -46
- package/dist/_vendor/cli/doctor.mjs.map +1 -1
- package/dist/_vendor/cli/errors.mjs.map +1 -1
- package/dist/_vendor/cli/help.mjs.map +1 -1
- package/dist/_vendor/cli/index.mjs +9 -2
- package/dist/_vendor/cli/index.mjs.map +1 -1
- package/dist/_vendor/cli/messages.mjs +5 -4
- package/dist/_vendor/cli/messages.mjs.map +1 -1
- package/dist/_vendor/cli/preflight.mjs.map +1 -1
- package/dist/_vendor/cli/project.mjs +125 -27
- package/dist/_vendor/cli/project.mjs.map +1 -1
- package/dist/_vendor/cli/render.mjs +57 -9
- package/dist/_vendor/cli/render.mjs.map +1 -1
- package/dist/_vendor/cli/targets.mjs +4 -3
- package/dist/_vendor/cli/targets.mjs.map +1 -1
- package/dist/_vendor/core/cli.d.mts +20 -4
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +458 -133
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/devtools-auth.mjs +60 -0
- package/dist/_vendor/core/devtools-auth.mjs.map +1 -0
- package/dist/_vendor/core/index.d.mts +5 -3
- package/dist/_vendor/core/index.mjs +22 -2
- package/dist/_vendor/core/index.mjs.map +1 -1
- package/dist/_vendor/core/runtime/components.d.mts +111 -0
- package/dist/_vendor/core/runtime/components.d.mts.map +1 -0
- package/dist/_vendor/core/runtime/components.mjs +186 -0
- package/dist/_vendor/core/runtime/components.mjs.map +1 -0
- package/dist/_vendor/core/runtime/devtools.d.mts +4 -4
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +178 -60
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +398 -16
- package/dist/_vendor/core/runtime/functions.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/functions.mjs +74 -3
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/id.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/id.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +83 -0
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +720 -0
- package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +234 -0
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs +255 -0
- package/dist/_vendor/core/runtime/internal/engines/schedulerEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs +200 -0
- package/dist/_vendor/core/runtime/internal/engines/schemaEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +252 -0
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +145 -0
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +221 -0
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs +32 -0
- package/dist/_vendor/core/runtime/internal/runtimeStatus.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs +61 -0
- package/dist/_vendor/core/runtime/internal/systemMeta.mjs.map +1 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs +41 -0
- package/dist/_vendor/core/runtime/internal/transactionCoordinator.mjs.map +1 -0
- package/dist/_vendor/core/runtime/runtime.d.mts +1187 -202
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs +73 -1365
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts +113 -0
- package/dist/_vendor/core/transport.d.mts.map +1 -0
- package/dist/_vendor/core/transport.mjs +428 -0
- package/dist/_vendor/core/transport.mjs.map +1 -0
- package/dist/_vendor/devtools-protocol/index.d.ts +187 -4
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js +25 -9
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/config.d.ts +3 -4
- package/dist/_vendor/next/config.d.ts.map +1 -1
- package/dist/_vendor/next/config.js +37 -19
- package/dist/_vendor/next/config.js.map +1 -1
- package/dist/_vendor/next/index.d.ts +109 -29
- package/dist/_vendor/next/index.d.ts.map +1 -1
- package/dist/_vendor/next/index.js +104 -26
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +156 -37
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +80 -12
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-expo/react.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/react.js +11 -10
- package/dist/_vendor/platform-expo/react.js.map +1 -1
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js +16 -0
- package/dist/_vendor/platform-expo/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-node/index.d.mts +192 -24
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +236 -97
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs +15 -2
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc.d.mts +11 -35
- package/dist/_vendor/platform-node/ipc.d.mts.map +1 -1
- package/dist/_vendor/platform-node/ipc.mjs +3 -273
- package/dist/_vendor/platform-node/ipc.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +43 -1
- package/dist/_vendor/platform-web/external-change.d.ts.map +1 -1
- package/dist/_vendor/platform-web/external-change.js +32 -1
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +323 -51
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +233 -30
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.d.ts +12 -0
- package/dist/_vendor/platform-web/indexeddb.d.ts.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.js +10 -0
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.d.ts +13 -0
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
- package/dist/_vendor/platform-web/opfs.js +12 -0
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.d.ts +54 -0
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
- package/dist/_vendor/platform-web/persistence.js +15 -0
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts +1 -2
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +27 -13
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/platform-web/sqljs.js +10 -1
- package/dist/_vendor/platform-web/sqljs.js.map +1 -1
- package/dist/_vendor/platform-web/web-sqljs-wasm.js +8 -0
- package/dist/_vendor/platform-web/web-sqljs-wasm.js.map +1 -0
- package/dist/_vendor/platform-web/worker.d.ts +71 -44
- package/dist/_vendor/platform-web/worker.d.ts.map +1 -1
- package/dist/_vendor/platform-web/worker.js +40 -271
- package/dist/_vendor/platform-web/worker.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +222 -23
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +476 -63
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/schema/definition.d.ts +151 -37
- package/dist/_vendor/schema/definition.d.ts.map +1 -1
- package/dist/_vendor/schema/definition.js +102 -20
- package/dist/_vendor/schema/definition.js.map +1 -1
- package/dist/_vendor/schema/index.d.ts +4 -4
- package/dist/_vendor/schema/index.js +2 -2
- package/dist/_vendor/schema/planner.d.ts +19 -2
- package/dist/_vendor/schema/planner.d.ts.map +1 -1
- package/dist/_vendor/schema/planner.js +79 -3
- package/dist/_vendor/schema/planner.js.map +1 -1
- package/dist/_vendor/schema/validators.d.ts +279 -83
- package/dist/_vendor/schema/validators.d.ts.map +1 -1
- package/dist/_vendor/schema/validators.js +330 -38
- package/dist/_vendor/schema/validators.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +245 -19
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +443 -20
- package/dist/_vendor/svelte/index.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/package.json +29 -21
|
@@ -2,16 +2,50 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
//#region src/index.tsx
|
|
4
4
|
/**
|
|
5
|
-
* Pass `
|
|
6
|
-
*
|
|
5
|
+
* Pass `skip` as the `args` argument to any Syncore React hook to suppress
|
|
6
|
+
* that subscription entirely.
|
|
7
|
+
*
|
|
8
|
+
* Useful when the query arguments depend on state that is not yet available
|
|
9
|
+
* (e.g. a selected item ID) — instead of conditionally calling the hook
|
|
10
|
+
* (which violates the Rules of Hooks), pass `skip` to deactivate it:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const task = useQuery(api.tasks.get, selectedId ? { id: selectedId } : skip);
|
|
14
|
+
* // task is `undefined` while selectedId is null/undefined
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Skipped queries return `undefined` for `data`, `"skipped"` for `status`,
|
|
18
|
+
* and `false` for `isLoading`.
|
|
7
19
|
*/
|
|
8
20
|
const skip = "skip";
|
|
21
|
+
const defaultRuntimeStatus = {
|
|
22
|
+
kind: "starting",
|
|
23
|
+
reason: "booting"
|
|
24
|
+
};
|
|
9
25
|
const SyncoreContext = createContext(null);
|
|
10
26
|
/**
|
|
11
|
-
*
|
|
27
|
+
* Provides a Syncore client to all React descendants via context.
|
|
28
|
+
*
|
|
29
|
+
* Wrap your app (or any subtree that uses Syncore hooks) with
|
|
30
|
+
* `SyncoreProvider`. All `useQuery`, `useMutation`, `useAction`, and
|
|
31
|
+
* `useQueries` calls inside the tree will automatically use the client you
|
|
32
|
+
* supply.
|
|
33
|
+
*
|
|
34
|
+
* ```tsx
|
|
35
|
+
* // For a browser worker setup
|
|
36
|
+
* const client = createBrowserWorkerClient();
|
|
37
|
+
*
|
|
38
|
+
* function App() {
|
|
39
|
+
* return (
|
|
40
|
+
* <SyncoreProvider client={client}>
|
|
41
|
+
* <TaskList />
|
|
42
|
+
* </SyncoreProvider>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
12
46
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
47
|
+
* For Next.js apps use `SyncoreNextProvider` which also handles service worker
|
|
48
|
+
* and worker URL configuration.
|
|
15
49
|
*/
|
|
16
50
|
function SyncoreProvider({ client, children }) {
|
|
17
51
|
return /* @__PURE__ */ jsx(SyncoreContext.Provider, {
|
|
@@ -20,9 +54,17 @@ function SyncoreProvider({ client, children }) {
|
|
|
20
54
|
});
|
|
21
55
|
}
|
|
22
56
|
/**
|
|
23
|
-
*
|
|
57
|
+
* Returns the active `SyncoreClient` from the nearest {@link SyncoreProvider}
|
|
58
|
+
* in the React tree.
|
|
24
59
|
*
|
|
25
|
-
* Throws if
|
|
60
|
+
* Throws if called outside of a `SyncoreProvider`. Prefer the higher-level
|
|
61
|
+
* hooks (`useQuery`, `useMutation`, etc.) for common operations — use
|
|
62
|
+
* `useSyncore` only when you need direct access to the client object.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* const client = useSyncore();
|
|
66
|
+
* const tasks = await client.query(api.tasks.list);
|
|
67
|
+
* ```
|
|
26
68
|
*/
|
|
27
69
|
function useSyncore() {
|
|
28
70
|
const client = useContext(SyncoreContext);
|
|
@@ -30,15 +72,90 @@ function useSyncore() {
|
|
|
30
72
|
return client;
|
|
31
73
|
}
|
|
32
74
|
/**
|
|
33
|
-
*
|
|
75
|
+
* Subscribe to the runtime’s lifecycle status.
|
|
76
|
+
*
|
|
77
|
+
* Returns a {@link SyncoreRuntimeStatus} that updates whenever the underlying
|
|
78
|
+
* runtime changes state (e.g. starting, ready, error). Use it to gate your UI
|
|
79
|
+
* on the runtime being ready or to display an error boundary:
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* function TaskList() {
|
|
83
|
+
* const status = useSyncoreStatus();
|
|
84
|
+
* if (status.kind === "starting") return <Spinner />;
|
|
85
|
+
* if (status.kind === "error") return <ErrorScreen error={status.error} />;
|
|
86
|
+
* return <Tasks />;
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Most components do not need this — `useQuery` already incorporates runtime
|
|
91
|
+
* status into the `SyncoreQueryState.runtimeStatus` field.
|
|
92
|
+
*/
|
|
93
|
+
function useSyncoreStatus() {
|
|
94
|
+
const client = useSyncore();
|
|
95
|
+
const watch = useMemo(() => client.watchRuntimeStatus(), [client]);
|
|
96
|
+
const [status, setStatus] = useState(() => readRuntimeStatusSnapshot(watch));
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const sync = () => {
|
|
99
|
+
setStatus(readRuntimeStatusSnapshot(watch));
|
|
100
|
+
};
|
|
101
|
+
sync();
|
|
102
|
+
return watch.onUpdate(sync);
|
|
103
|
+
}, [watch]);
|
|
104
|
+
useEffect(() => () => {
|
|
105
|
+
watch.dispose?.();
|
|
106
|
+
}, [watch]);
|
|
107
|
+
return status;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to a reactive Syncore query and return the current data.
|
|
111
|
+
*
|
|
112
|
+
* The component re-renders automatically whenever the query result changes.
|
|
113
|
+
* If the query throws, `useQuery` re-throws the error so a React error
|
|
114
|
+
* boundary can catch it — use {@link useQueryState} if you need to handle
|
|
115
|
+
* errors inline.
|
|
116
|
+
*
|
|
117
|
+
* ```tsx
|
|
118
|
+
* // Basic usage
|
|
119
|
+
* const tasks = useQuery(api.tasks.list);
|
|
120
|
+
*
|
|
121
|
+
* // With arguments
|
|
122
|
+
* const task = useQuery(api.tasks.get, { id: taskId });
|
|
123
|
+
*
|
|
124
|
+
* // Conditionally skip when arguments are not yet available
|
|
125
|
+
* const task = useQuery(api.tasks.get, taskId ? { id: taskId } : skip);
|
|
126
|
+
* ```
|
|
34
127
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
128
|
+
* @param reference - A typed function reference (from the generated `api` object).
|
|
129
|
+
* @param args - The query’s arguments, or `skip` to suppress the subscription.
|
|
130
|
+
* @returns The current query result, or `undefined` while loading.
|
|
38
131
|
*/
|
|
39
132
|
function useQuery(reference, ...args) {
|
|
133
|
+
const state = useQueryState(reference, ...args);
|
|
134
|
+
if (state.error) throw state.error;
|
|
135
|
+
return state.data;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe to a reactive Syncore query and return the full
|
|
139
|
+
* {@link SyncoreQueryState} including loading, error, and runtime status.
|
|
140
|
+
*
|
|
141
|
+
* Use this instead of {@link useQuery} when you need to:
|
|
142
|
+
* - Differentiate between `undefined` data and an error.
|
|
143
|
+
* - React to `isLoading` / `isError` without relying on error boundaries.
|
|
144
|
+
* - Inspect `runtimeStatus` for the underlying runtime’s health.
|
|
145
|
+
*
|
|
146
|
+
* ```tsx
|
|
147
|
+
* const { data, isLoading, isError, error } = useQueryState(api.tasks.list);
|
|
148
|
+
*
|
|
149
|
+
* if (isLoading) return <Spinner />;
|
|
150
|
+
* if (isError) return <ErrorBanner message={error.message} />;
|
|
151
|
+
* return <TaskList tasks={data} />;
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
function useQueryState(reference, ...args) {
|
|
40
155
|
const isSkipped = args[0] === skip;
|
|
41
|
-
const
|
|
156
|
+
const client = useSyncore();
|
|
157
|
+
const runtimeStatus = useSyncoreStatus();
|
|
158
|
+
const watch = useManagedQueryWatch(client, reference, isSkipped ? void 0 : normalizeOptionalArgs(args), isSkipped);
|
|
42
159
|
const [snapshot, setSnapshot] = useState(() => isSkipped ? noOpSnapshot : readWatchSnapshot(watch));
|
|
43
160
|
useEffect(() => {
|
|
44
161
|
if (isSkipped) {
|
|
@@ -51,78 +168,277 @@ function useQuery(reference, ...args) {
|
|
|
51
168
|
sync();
|
|
52
169
|
return watch.onUpdate(sync);
|
|
53
170
|
}, [watch, isSkipped]);
|
|
54
|
-
|
|
55
|
-
return snapshot.result;
|
|
171
|
+
return toQueryState(snapshot, runtimeStatus, isSkipped);
|
|
56
172
|
}
|
|
57
|
-
const noOpSnapshot = {
|
|
58
|
-
result: void 0,
|
|
59
|
-
error: void 0
|
|
60
|
-
};
|
|
61
|
-
const noOpWatch = {
|
|
62
|
-
onUpdate: () => () => {},
|
|
63
|
-
localQueryResult: () => void 0,
|
|
64
|
-
localQueryError: () => void 0
|
|
65
|
-
};
|
|
66
173
|
/**
|
|
67
|
-
*
|
|
174
|
+
* Returns a stable callback for executing a Syncore mutation.
|
|
175
|
+
*
|
|
176
|
+
* The returned function is type-safe: its parameter types are inferred from
|
|
177
|
+
* the mutation definition and remain stable across re-renders (no need to
|
|
178
|
+
* wrap in `useCallback`).
|
|
179
|
+
*
|
|
180
|
+
* ```tsx
|
|
181
|
+
* const createTask = useMutation(api.tasks.create);
|
|
182
|
+
*
|
|
183
|
+
* return (
|
|
184
|
+
* <button onClick={() => createTask({ title: "New task" })}>
|
|
185
|
+
* Add task
|
|
186
|
+
* </button>
|
|
187
|
+
* );
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @param reference - A typed mutation reference from the generated `api` object.
|
|
191
|
+
* @returns A function that, when called, executes the mutation and returns a
|
|
192
|
+
* promise that resolves to the mutation’s return value.
|
|
68
193
|
*/
|
|
69
194
|
function useMutation(reference) {
|
|
70
195
|
const client = useSyncore();
|
|
71
196
|
return (...args) => client.mutation(reference, normalizeOptionalArgs(args));
|
|
72
197
|
}
|
|
73
198
|
/**
|
|
74
|
-
*
|
|
199
|
+
* Returns a stable callback for executing a Syncore action.
|
|
200
|
+
*
|
|
201
|
+
* Identical to {@link useMutation} but for actions. Use this when the work you
|
|
202
|
+
* need to do cannot run inside a transaction (external API calls, long-running
|
|
203
|
+
* tasks, etc.).
|
|
204
|
+
*
|
|
205
|
+
* ```tsx
|
|
206
|
+
* const importTasks = useAction(api.tasks.importFromCsv);
|
|
207
|
+
*
|
|
208
|
+
* return (
|
|
209
|
+
* <button onClick={() => importTasks({ url: csvUrl })}>
|
|
210
|
+
* Import
|
|
211
|
+
* </button>
|
|
212
|
+
* );
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* @param reference - A typed action reference from the generated `api` object.
|
|
216
|
+
* @returns A function that, when called, executes the action and returns a
|
|
217
|
+
* promise that resolves to the action’s return value.
|
|
75
218
|
*/
|
|
76
219
|
function useAction(reference) {
|
|
77
220
|
const client = useSyncore();
|
|
78
221
|
return (...args) => client.action(reference, normalizeOptionalArgs(args));
|
|
79
222
|
}
|
|
80
223
|
/**
|
|
81
|
-
*
|
|
224
|
+
* Subscribe to multiple Syncore queries simultaneously and receive per-entry
|
|
225
|
+
* state objects in a single hook call.
|
|
226
|
+
*
|
|
227
|
+
* More efficient than calling `useQuery` in a loop when the set of queries is
|
|
228
|
+
* known at component render time. The hook maintains only one subscription per
|
|
229
|
+
* unique `(reference, args)` combination even if entries are duplicated.
|
|
230
|
+
*
|
|
231
|
+
* ```tsx
|
|
232
|
+
* const { header, sidebar } = useQueries({
|
|
233
|
+
* header: { query: api.layout.header },
|
|
234
|
+
* sidebar: { query: api.layout.sidebar, args: { userId } },
|
|
235
|
+
* });
|
|
236
|
+
*
|
|
237
|
+
* if (header.isLoading || sidebar.isLoading) return <Spinner />;
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @param entries - A record of named query requests. Each entry can include
|
|
241
|
+
* `args: skip` to suppress that specific subscription.
|
|
242
|
+
* @returns A record with the same keys, each holding a {@link SyncoreQueryState}.
|
|
82
243
|
*/
|
|
83
244
|
function useQueries(entries) {
|
|
84
245
|
const client = useSyncore();
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
246
|
+
const runtimeStatus = useSyncoreStatus();
|
|
247
|
+
const entriesKey = stableStringify(Object.entries(entries).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => ({
|
|
248
|
+
key,
|
|
249
|
+
referenceName: entry.query.name,
|
|
250
|
+
skipped: entry.args === skip,
|
|
251
|
+
args: entry.args === "skip" ? {} : normalizeOptionalArgs([entry.args ?? {}])
|
|
89
252
|
})));
|
|
90
253
|
const normalizedEntries = useMemo(() => JSON.parse(entriesKey), [entriesKey]);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
name: entry.referenceName
|
|
96
|
-
}, entry.args)
|
|
97
|
-
})), [client, normalizedEntries]);
|
|
98
|
-
const [snapshot, setSnapshot] = useState(() => readQueriesSnapshot(watches));
|
|
99
|
-
useEffect(() => () => {
|
|
100
|
-
for (const entry of watches) entry.watch.dispose?.();
|
|
101
|
-
}, [watches]);
|
|
254
|
+
const [observer] = useState(() => new ReactQueriesObserver(client));
|
|
255
|
+
const [, setVersion] = useState(0);
|
|
256
|
+
if (observer.client !== client) observer.replaceClient(client);
|
|
257
|
+
useEffect(() => () => observer.destroy(), [observer]);
|
|
102
258
|
useEffect(() => {
|
|
103
|
-
|
|
104
|
-
|
|
259
|
+
observer.setEntries(normalizedEntries);
|
|
260
|
+
setVersion((value) => value + 1);
|
|
261
|
+
return observer.subscribe(() => {
|
|
262
|
+
setVersion((value) => value + 1);
|
|
263
|
+
});
|
|
264
|
+
}, [normalizedEntries, observer]);
|
|
265
|
+
const snapshot = observer.getSnapshot(normalizedEntries);
|
|
266
|
+
return useMemo(() => {
|
|
267
|
+
return Object.fromEntries(normalizedEntries.map((entry) => [entry.key, toQueryState(snapshot[entry.key] ?? noOpSnapshot, runtimeStatus, entry.skipped)]));
|
|
268
|
+
}, [
|
|
269
|
+
normalizedEntries,
|
|
270
|
+
runtimeStatus,
|
|
271
|
+
snapshot
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Subscribe to a paginated Syncore query, incrementally loading more pages.
|
|
276
|
+
*
|
|
277
|
+
* The query must accept a `paginationOpts` argument and return a
|
|
278
|
+
* `PaginationResult`. The hook manages cursors automatically — call the
|
|
279
|
+
* returned `loadMore` function to append the next page to the results.
|
|
280
|
+
*
|
|
281
|
+
* ```tsx
|
|
282
|
+
* const { results, status, loadMore, hasMore } = usePaginatedQuery(
|
|
283
|
+
* api.tasks.list,
|
|
284
|
+
* { projectId },
|
|
285
|
+
* { initialNumItems: 20 },
|
|
286
|
+
* );
|
|
287
|
+
*
|
|
288
|
+
* return (
|
|
289
|
+
* <>
|
|
290
|
+
* {results.map((t) => <TaskRow key={t._id} task={t} />)}
|
|
291
|
+
* {hasMore && (
|
|
292
|
+
* <button
|
|
293
|
+
* onClick={() => loadMore(20)}
|
|
294
|
+
* disabled={status === "loadingMore"}
|
|
295
|
+
* >
|
|
296
|
+
* Load more
|
|
297
|
+
* </button>
|
|
298
|
+
* )}
|
|
299
|
+
* </>
|
|
300
|
+
* );
|
|
301
|
+
* ```
|
|
302
|
+
*
|
|
303
|
+
* Pass `skip` as `args` to suppress the subscription until arguments are
|
|
304
|
+
* ready.
|
|
305
|
+
*
|
|
306
|
+
* @param reference - A typed query reference whose handler calls
|
|
307
|
+
* `ctx.db.query(…).paginate(paginationOpts)`.
|
|
308
|
+
* @param args - Arguments for the query (excluding
|
|
309
|
+
* `paginationOpts`, which is managed internally), or `skip`.
|
|
310
|
+
* @param options.initialNumItems - Number of items to load on the first page.
|
|
311
|
+
* @returns A {@link UsePaginatedQueryResult} with the accumulated results and
|
|
312
|
+
* a `loadMore` callback.
|
|
313
|
+
*/
|
|
314
|
+
function usePaginatedQuery(reference, args, options) {
|
|
315
|
+
if (typeof options.initialNumItems !== "number" || options.initialNumItems <= 0) throw new Error(`options.initialNumItems must be a positive number. Received ${String(options.initialNumItems)}.`);
|
|
316
|
+
const runtimeStatus = useSyncoreStatus();
|
|
317
|
+
const isSkipped = args === skip;
|
|
318
|
+
const normalizedArgs = isSkipped ? {} : args ?? {};
|
|
319
|
+
const requestKey = stableStringify({
|
|
320
|
+
referenceName: reference.name,
|
|
321
|
+
args: normalizedArgs,
|
|
322
|
+
initialNumItems: options.initialNumItems,
|
|
323
|
+
skipped: isSkipped
|
|
324
|
+
});
|
|
325
|
+
const createInitialState = useMemo(() => () => ({
|
|
326
|
+
requestKey,
|
|
327
|
+
nextPageKey: 1,
|
|
328
|
+
pages: isSkipped ? [] : [{
|
|
329
|
+
key: "0",
|
|
330
|
+
cursor: null,
|
|
331
|
+
numItems: options.initialNumItems
|
|
332
|
+
}]
|
|
333
|
+
}), [
|
|
334
|
+
isSkipped,
|
|
335
|
+
options.initialNumItems,
|
|
336
|
+
requestKey
|
|
337
|
+
]);
|
|
338
|
+
const [state, setState] = useState(createInitialState);
|
|
339
|
+
let currentState = state;
|
|
340
|
+
if (currentState.requestKey !== requestKey) {
|
|
341
|
+
currentState = createInitialState();
|
|
342
|
+
setState(currentState);
|
|
343
|
+
}
|
|
344
|
+
const pageStates = useQueries(useMemo(() => {
|
|
345
|
+
const requests = {};
|
|
346
|
+
for (const page of currentState.pages) requests[page.key] = {
|
|
347
|
+
query: reference,
|
|
348
|
+
args: {
|
|
349
|
+
...normalizedArgs,
|
|
350
|
+
paginationOpts: {
|
|
351
|
+
cursor: page.cursor,
|
|
352
|
+
numItems: page.numItems
|
|
353
|
+
}
|
|
354
|
+
}
|
|
105
355
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
356
|
+
return requests;
|
|
357
|
+
}, [
|
|
358
|
+
currentState.pages,
|
|
359
|
+
normalizedArgs,
|
|
360
|
+
reference
|
|
361
|
+
]));
|
|
362
|
+
const derived = useMemo(() => {
|
|
363
|
+
const pages = [];
|
|
364
|
+
let error;
|
|
365
|
+
for (const page of currentState.pages) {
|
|
366
|
+
const pageState = pageStates[page.key];
|
|
367
|
+
if (!pageState || pageState.status === "loading") break;
|
|
368
|
+
if (pageState.status === "error") {
|
|
369
|
+
error = pageState.error;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
if (pageState.data) pages.push(pageState.data);
|
|
373
|
+
}
|
|
374
|
+
const results = pages.flatMap((page) => page.page);
|
|
375
|
+
const lastLoadedPage = pages.at(-1);
|
|
376
|
+
const lastRequestedKey = currentState.pages.at(-1)?.key;
|
|
377
|
+
const lastRequestedState = lastRequestedKey ? pageStates[lastRequestedKey] : void 0;
|
|
378
|
+
const isLoading = !isSkipped && pages.length === 0 && !error;
|
|
379
|
+
const isLoadingMore = currentState.pages.length > pages.length || !!lastRequestedState && lastRequestedState.status === "loading" && pages.length > 0;
|
|
380
|
+
const hasMore = !!lastLoadedPage && !lastLoadedPage.isDone;
|
|
381
|
+
const status = error ? "error" : isSkipped ? "ready" : isLoading ? "loading" : isLoadingMore ? "loadingMore" : hasMore ? "ready" : "exhausted";
|
|
382
|
+
return {
|
|
383
|
+
pages,
|
|
384
|
+
results,
|
|
385
|
+
error,
|
|
386
|
+
isLoading,
|
|
387
|
+
isLoadingMore,
|
|
388
|
+
hasMore,
|
|
389
|
+
cursor: lastLoadedPage?.cursor ?? null,
|
|
390
|
+
status
|
|
110
391
|
};
|
|
111
|
-
}, [
|
|
112
|
-
|
|
392
|
+
}, [
|
|
393
|
+
currentState.pages,
|
|
394
|
+
isSkipped,
|
|
395
|
+
pageStates
|
|
396
|
+
]);
|
|
397
|
+
return {
|
|
398
|
+
...derived,
|
|
399
|
+
runtimeStatus,
|
|
400
|
+
loadMore(numItems = options.initialNumItems) {
|
|
401
|
+
if (isSkipped || derived.error || derived.isLoadingMore || !derived.hasMore || !derived.cursor) return;
|
|
402
|
+
setState((previous) => ({
|
|
403
|
+
...previous,
|
|
404
|
+
nextPageKey: previous.nextPageKey + 1,
|
|
405
|
+
pages: [...previous.pages, {
|
|
406
|
+
key: String(previous.nextPageKey),
|
|
407
|
+
cursor: derived.cursor,
|
|
408
|
+
numItems
|
|
409
|
+
}]
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
};
|
|
113
413
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
414
|
+
const noOpSnapshot = {
|
|
415
|
+
data: void 0,
|
|
416
|
+
error: void 0
|
|
417
|
+
};
|
|
418
|
+
const noOpWatch = {
|
|
419
|
+
onUpdate: () => () => void 0,
|
|
420
|
+
localQueryResult: () => void 0,
|
|
421
|
+
localQueryError: () => void 0
|
|
422
|
+
};
|
|
423
|
+
function useManagedQueryWatch(client, reference, args, isSkipped = false) {
|
|
424
|
+
const argsKey = isSkipped ? skip : stableStringify(args ?? {});
|
|
425
|
+
const [watch, setWatch] = useState(() => noOpWatch);
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
if (isSkipped) {
|
|
428
|
+
setWatch(noOpWatch);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const nextWatch = client.watchQuery(reference, JSON.parse(argsKey));
|
|
432
|
+
setWatch(nextWatch);
|
|
433
|
+
return () => {
|
|
434
|
+
nextWatch.dispose?.();
|
|
435
|
+
};
|
|
436
|
+
}, [
|
|
437
|
+
argsKey,
|
|
118
438
|
client,
|
|
119
|
-
|
|
120
|
-
reference
|
|
121
|
-
isSkipped
|
|
439
|
+
isSkipped,
|
|
440
|
+
reference
|
|
122
441
|
]);
|
|
123
|
-
useEffect(() => () => {
|
|
124
|
-
if (!isSkipped) watch.dispose?.();
|
|
125
|
-
}, [watch, isSkipped]);
|
|
126
442
|
return watch;
|
|
127
443
|
}
|
|
128
444
|
function normalizeOptionalArgs(args) {
|
|
@@ -130,12 +446,36 @@ function normalizeOptionalArgs(args) {
|
|
|
130
446
|
}
|
|
131
447
|
function readWatchSnapshot(watch) {
|
|
132
448
|
return {
|
|
133
|
-
|
|
449
|
+
data: watch.localQueryResult(),
|
|
134
450
|
error: watch.localQueryError()
|
|
135
451
|
};
|
|
136
452
|
}
|
|
137
|
-
function readQueriesSnapshot(
|
|
138
|
-
return Object.fromEntries(
|
|
453
|
+
function readQueriesSnapshot(records) {
|
|
454
|
+
return Object.fromEntries(records.map((entry) => [entry.key, entry.snapshot]));
|
|
455
|
+
}
|
|
456
|
+
function readRuntimeStatusSnapshot(watch) {
|
|
457
|
+
return watch.localQueryResult() ?? defaultRuntimeStatus;
|
|
458
|
+
}
|
|
459
|
+
function toQueryState(snapshot, runtimeStatus, isSkipped) {
|
|
460
|
+
if (isSkipped) return {
|
|
461
|
+
data: void 0,
|
|
462
|
+
error: void 0,
|
|
463
|
+
status: "skipped",
|
|
464
|
+
runtimeStatus,
|
|
465
|
+
isLoading: false,
|
|
466
|
+
isError: false,
|
|
467
|
+
isReady: false
|
|
468
|
+
};
|
|
469
|
+
const status = snapshot.error !== void 0 ? "error" : snapshot.data === void 0 ? "loading" : "success";
|
|
470
|
+
return {
|
|
471
|
+
data: snapshot.data,
|
|
472
|
+
error: snapshot.error,
|
|
473
|
+
status,
|
|
474
|
+
runtimeStatus,
|
|
475
|
+
isLoading: status === "loading",
|
|
476
|
+
isError: status === "error",
|
|
477
|
+
isReady: status === "success"
|
|
478
|
+
};
|
|
139
479
|
}
|
|
140
480
|
function stableStringify(value) {
|
|
141
481
|
return JSON.stringify(sortValue(value));
|
|
@@ -145,7 +485,80 @@ function sortValue(value) {
|
|
|
145
485
|
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortValue(nested)]));
|
|
146
486
|
return value;
|
|
147
487
|
}
|
|
488
|
+
var ReactQueriesObserver = class {
|
|
489
|
+
client;
|
|
490
|
+
listeners = /* @__PURE__ */ new Set();
|
|
491
|
+
records = /* @__PURE__ */ new Map();
|
|
492
|
+
constructor(client) {
|
|
493
|
+
this.client = client;
|
|
494
|
+
}
|
|
495
|
+
replaceClient(client) {
|
|
496
|
+
this.destroy();
|
|
497
|
+
this.client = client;
|
|
498
|
+
}
|
|
499
|
+
setEntries(entries) {
|
|
500
|
+
const activeKeys = new Set(entries.map((entry) => entry.key));
|
|
501
|
+
for (const entry of entries) {
|
|
502
|
+
const requestKey = `${entry.referenceName}:${stableStringify(entry.args)}:${String(entry.skipped)}`;
|
|
503
|
+
const current = this.records.get(entry.key);
|
|
504
|
+
if (current?.requestKey === requestKey) continue;
|
|
505
|
+
current?.unsubscribe();
|
|
506
|
+
current?.watch?.dispose?.();
|
|
507
|
+
if (entry.skipped) {
|
|
508
|
+
this.records.set(entry.key, {
|
|
509
|
+
requestKey,
|
|
510
|
+
snapshot: noOpSnapshot,
|
|
511
|
+
unsubscribe: () => void 0
|
|
512
|
+
});
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const watch = this.client.watchQuery({
|
|
516
|
+
kind: "query",
|
|
517
|
+
name: entry.referenceName
|
|
518
|
+
}, entry.args);
|
|
519
|
+
const record = {
|
|
520
|
+
requestKey,
|
|
521
|
+
snapshot: readWatchSnapshot(watch),
|
|
522
|
+
unsubscribe: () => void 0,
|
|
523
|
+
watch
|
|
524
|
+
};
|
|
525
|
+
record.unsubscribe = watch.onUpdate(() => {
|
|
526
|
+
record.snapshot = readWatchSnapshot(watch);
|
|
527
|
+
this.notify();
|
|
528
|
+
});
|
|
529
|
+
this.records.set(entry.key, record);
|
|
530
|
+
}
|
|
531
|
+
for (const [key, record] of this.records.entries()) {
|
|
532
|
+
if (activeKeys.has(key)) continue;
|
|
533
|
+
record.unsubscribe();
|
|
534
|
+
record.watch?.dispose?.();
|
|
535
|
+
this.records.delete(key);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
getSnapshot(entries) {
|
|
539
|
+
return readQueriesSnapshot(entries.map((entry) => ({
|
|
540
|
+
key: entry.key,
|
|
541
|
+
snapshot: this.records.get(entry.key)?.snapshot ?? noOpSnapshot
|
|
542
|
+
})));
|
|
543
|
+
}
|
|
544
|
+
subscribe(listener) {
|
|
545
|
+
this.listeners.add(listener);
|
|
546
|
+
return () => {
|
|
547
|
+
this.listeners.delete(listener);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
destroy() {
|
|
551
|
+
for (const record of this.records.values()) {
|
|
552
|
+
record.unsubscribe();
|
|
553
|
+
record.watch?.dispose?.();
|
|
554
|
+
}
|
|
555
|
+
this.records.clear();
|
|
556
|
+
}
|
|
557
|
+
notify() {
|
|
558
|
+
for (const listener of this.listeners) listener();
|
|
559
|
+
}
|
|
560
|
+
};
|
|
148
561
|
//#endregion
|
|
149
|
-
export { SyncoreProvider, skip, useAction, useMutation, useQueries, useQuery, useSyncore };
|
|
562
|
+
export { SyncoreProvider, skip, useAction, useMutation, usePaginatedQuery, useQueries, useQuery, useQueryState, useSyncore, useSyncoreStatus };
|
|
150
563
|
|
|
151
564
|
//# sourceMappingURL=index.js.map
|