@dashai/sdk 0.7.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/dist/auth/client.cjs +185 -0
  4. package/dist/auth/client.cjs.map +1 -0
  5. package/dist/auth/client.d.cts +137 -0
  6. package/dist/auth/client.d.ts +137 -0
  7. package/dist/auth/client.js +175 -0
  8. package/dist/auth/client.js.map +1 -0
  9. package/dist/auth/middleware.cjs +352 -0
  10. package/dist/auth/middleware.cjs.map +1 -0
  11. package/dist/auth/middleware.d.cts +90 -0
  12. package/dist/auth/middleware.d.ts +90 -0
  13. package/dist/auth/middleware.js +343 -0
  14. package/dist/auth/middleware.js.map +1 -0
  15. package/dist/auth/server.cjs +445 -0
  16. package/dist/auth/server.cjs.map +1 -0
  17. package/dist/auth/server.d.cts +170 -0
  18. package/dist/auth/server.d.ts +170 -0
  19. package/dist/auth/server.js +432 -0
  20. package/dist/auth/server.js.map +1 -0
  21. package/dist/auth/types.cjs +31 -0
  22. package/dist/auth/types.cjs.map +1 -0
  23. package/dist/auth/types.d.cts +163 -0
  24. package/dist/auth/types.d.ts +163 -0
  25. package/dist/auth/types.js +29 -0
  26. package/dist/auth/types.js.map +1 -0
  27. package/dist/deps/index.cjs +117 -0
  28. package/dist/deps/index.cjs.map +1 -0
  29. package/dist/deps/index.d.cts +93 -0
  30. package/dist/deps/index.d.ts +93 -0
  31. package/dist/deps/index.js +112 -0
  32. package/dist/deps/index.js.map +1 -0
  33. package/dist/errors-BV75u7b9.d.cts +207 -0
  34. package/dist/errors-BV75u7b9.d.ts +207 -0
  35. package/dist/index.cjs +721 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +171 -0
  38. package/dist/index.d.ts +171 -0
  39. package/dist/index.js +703 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/query/index.cjs +621 -0
  42. package/dist/query/index.cjs.map +1 -0
  43. package/dist/query/index.d.cts +800 -0
  44. package/dist/query/index.d.ts +800 -0
  45. package/dist/query/index.js +617 -0
  46. package/dist/query/index.js.map +1 -0
  47. package/dist/react/index.cjs +199 -0
  48. package/dist/react/index.cjs.map +1 -0
  49. package/dist/react/index.d.cts +117 -0
  50. package/dist/react/index.d.ts +117 -0
  51. package/dist/react/index.js +195 -0
  52. package/dist/react/index.js.map +1 -0
  53. package/dist/types-BLNQ1S1C.d.cts +396 -0
  54. package/dist/types-BLNQ1S1C.d.ts +396 -0
  55. package/package.json +109 -0
@@ -0,0 +1,117 @@
1
+ import { L as ListOpts, P as Page, b as BaseRow, B as BaseInsert, c as BaseUpdate, i as TableClient } from '../types-BLNQ1S1C.js';
2
+ import { Query, AggQuery, QueryResult } from '../query/index.js';
3
+
4
+ /**
5
+ * Hook options. Mirrors {@link ListOpts} fields the caller most often
6
+ * wants reactive — `filter`, `sort`, `limit`, `offset` — and a
7
+ * convenience `enabled` switch to skip the fetch entirely (useful when
8
+ * the caller's filter depends on other not-yet-loaded state).
9
+ *
10
+ * The hook RE-FETCHES whenever any of these values change (compared
11
+ * by JSON.stringify — sufficient for the typical filter object;
12
+ * callers with non-serialisable filters can memoise their `opts` or
13
+ * call `refetch()` imperatively).
14
+ */
15
+ interface UseTableOpts<Row> extends ListOpts<Row> {
16
+ /**
17
+ * When `false`, the hook returns the idle state (`isLoading: false`,
18
+ * `data: null`) and skips the fetch. Default `true`.
19
+ */
20
+ enabled?: boolean;
21
+ }
22
+ interface UseTableResult<Row> {
23
+ /** Page rows. `null` until first fetch resolves. */
24
+ data: Row[] | null;
25
+ /** Full page envelope (rows + cursor + total). `null` until first fetch. */
26
+ page: Page<Row> | null;
27
+ /** True during the in-flight fetch (including refetches). */
28
+ isLoading: boolean;
29
+ /**
30
+ * Set when the most recent fetch threw. Cleared on the next
31
+ * successful refetch.
32
+ */
33
+ error: Error | null;
34
+ /** Re-runs the list query with the current opts. */
35
+ refetch: () => Promise<void>;
36
+ /**
37
+ * Optimistically replace the local `data` without hitting the
38
+ * server. Useful for instant UI after a mutation; the next
39
+ * `refetch()` reconciles with the server.
40
+ */
41
+ mutate: (next: Row[]) => void;
42
+ }
43
+ /**
44
+ * Fetch a paginated list from a DashWise table. Re-runs when any of
45
+ * the `opts` change.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * const { data, isLoading, error, refetch } = useTable(
50
+ * db.db<Task>("tasks"),
51
+ * { filter: { done: false }, sort: [{ field: "createdAt", desc: true }] }
52
+ * );
53
+ * ```
54
+ */
55
+ declare function useTable<Row extends BaseRow, Insert extends BaseInsert = BaseInsert, Update extends BaseUpdate = BaseUpdate>(table: TableClient<Row, Insert, Update>, opts?: UseTableOpts<Row>): UseTableResult<Row>;
56
+
57
+ interface UseRecordResult<Row, Update> {
58
+ /** The record. `null` until first fetch resolves or after `remove()`. */
59
+ data: Row | null;
60
+ /** True during the in-flight fetch (initial load OR refetch). */
61
+ isLoading: boolean;
62
+ /** True while a save / remove call is in flight. */
63
+ isSaving: boolean;
64
+ error: Error | null;
65
+ /** Re-run the GET. */
66
+ refetch: () => Promise<void>;
67
+ /**
68
+ * Patch the record. The hook PATCHes via `table.update(id, updates)`
69
+ * and refreshes `data` from the server's response. Throws on
70
+ * validation failure; callers can wrap in try/catch.
71
+ */
72
+ save: (updates: Update) => Promise<Row>;
73
+ /**
74
+ * Delete the record. After success, `data` becomes `null` and any
75
+ * subsequent `refetch()` will see the 404. The hook does NOT
76
+ * navigate; callers handle post-delete UX (toast, redirect, etc.).
77
+ */
78
+ remove: () => Promise<void>;
79
+ }
80
+ /**
81
+ * Fetch a single record by id from a DashWise table; expose save +
82
+ * remove handles so a detail page never needs to write
83
+ * `useEffect`/`useState`/`fetch` boilerplate.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const { data: task, isLoading, save } = useRecord(db.db<Task>("tasks"), id);
88
+ * if (!task) return <LoadingState />;
89
+ * return (
90
+ * <Button onClick={() => save({ done: !task.done })}>
91
+ * Toggle done
92
+ * </Button>
93
+ * );
94
+ * ```
95
+ *
96
+ * Skips the fetch when `id` is null / undefined / "" — handy for
97
+ * conditional rendering ("show form only if there's a selected id").
98
+ */
99
+ declare function useRecord<Row extends BaseRow, Insert extends BaseInsert = BaseInsert, Update extends BaseUpdate = BaseUpdate>(table: TableClient<Row, Insert, Update>, id: string | number | null | undefined): UseRecordResult<Row, Update>;
100
+
101
+ interface UseQueryResult<T> {
102
+ /** Query result envelope. `null` until first fetch resolves. */
103
+ data: T | null;
104
+ /** True during the in-flight fetch (initial + refetches). */
105
+ isLoading: boolean;
106
+ /**
107
+ * Set when the most recent fetch threw. Cleared on the next
108
+ * successful refetch.
109
+ */
110
+ error: Error | null;
111
+ /** Re-run the query (uses the builder's current AST). */
112
+ refetch: () => Promise<void>;
113
+ }
114
+ declare function useQuery<Row extends BaseRow>(query: Query<Row>): UseQueryResult<Page<Row>>;
115
+ declare function useQuery(query: AggQuery): UseQueryResult<QueryResult>;
116
+
117
+ export { type UseQueryResult, type UseRecordResult, type UseTableOpts, type UseTableResult, useQuery, useRecord, useTable };
@@ -0,0 +1,195 @@
1
+ 'use client';
2
+ import { useState, useRef, useCallback, useEffect } from 'react';
3
+
4
+ // src/react/use-table.ts
5
+ function useTable(table, opts = {}) {
6
+ const { enabled = true, ...listOpts } = opts;
7
+ const [page, setPage] = useState(null);
8
+ const [isLoading, setIsLoading] = useState(enabled);
9
+ const [error, setError] = useState(null);
10
+ const fingerprint = JSON.stringify(listOpts);
11
+ const tableRef = useRef(table);
12
+ tableRef.current = table;
13
+ const refetch = useCallback(async () => {
14
+ if (!enabled) return;
15
+ setIsLoading(true);
16
+ setError(null);
17
+ try {
18
+ const result = await tableRef.current.list(JSON.parse(fingerprint));
19
+ setPage(result);
20
+ } catch (err) {
21
+ setError(err instanceof Error ? err : new Error(String(err)));
22
+ } finally {
23
+ setIsLoading(false);
24
+ }
25
+ }, [enabled, fingerprint]);
26
+ useEffect(() => {
27
+ if (!enabled) {
28
+ setIsLoading(false);
29
+ return;
30
+ }
31
+ let cancelled = false;
32
+ setIsLoading(true);
33
+ setError(null);
34
+ void (async () => {
35
+ try {
36
+ const result = await tableRef.current.list(JSON.parse(fingerprint));
37
+ if (cancelled) return;
38
+ setPage(result);
39
+ } catch (err) {
40
+ if (cancelled) return;
41
+ setError(err instanceof Error ? err : new Error(String(err)));
42
+ } finally {
43
+ if (cancelled) return;
44
+ setIsLoading(false);
45
+ }
46
+ })();
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [enabled, fingerprint]);
51
+ const mutate = useCallback((next) => {
52
+ setPage(
53
+ (prev) => prev ? { ...prev, rows: next } : { rows: next, total: next.length, limit: next.length, offset: 0 }
54
+ );
55
+ }, []);
56
+ return {
57
+ data: page ? page.rows : null,
58
+ page,
59
+ isLoading,
60
+ error,
61
+ refetch,
62
+ mutate
63
+ };
64
+ }
65
+ function useRecord(table, id) {
66
+ const enabled = id !== null && id !== void 0 && id !== "";
67
+ const [data, setData] = useState(null);
68
+ const [isLoading, setIsLoading] = useState(enabled);
69
+ const [isSaving, setIsSaving] = useState(false);
70
+ const [error, setError] = useState(null);
71
+ const tableRef = useRef(table);
72
+ tableRef.current = table;
73
+ const refetch = useCallback(async () => {
74
+ if (!enabled) return;
75
+ setIsLoading(true);
76
+ setError(null);
77
+ try {
78
+ const row = await tableRef.current.get(Number(id));
79
+ setData(row);
80
+ } catch (err) {
81
+ setError(err instanceof Error ? err : new Error(String(err)));
82
+ } finally {
83
+ setIsLoading(false);
84
+ }
85
+ }, [enabled, id]);
86
+ useEffect(() => {
87
+ if (!enabled) {
88
+ setData(null);
89
+ setIsLoading(false);
90
+ return;
91
+ }
92
+ let cancelled = false;
93
+ setIsLoading(true);
94
+ setError(null);
95
+ void (async () => {
96
+ try {
97
+ const row = await tableRef.current.get(Number(id));
98
+ if (cancelled) return;
99
+ setData(row);
100
+ } catch (err) {
101
+ if (cancelled) return;
102
+ setError(err instanceof Error ? err : new Error(String(err)));
103
+ } finally {
104
+ if (cancelled) return;
105
+ setIsLoading(false);
106
+ }
107
+ })();
108
+ return () => {
109
+ cancelled = true;
110
+ };
111
+ }, [enabled, id]);
112
+ const save = useCallback(
113
+ async (updates) => {
114
+ if (!enabled) {
115
+ throw new Error("useRecord.save: id is null/empty");
116
+ }
117
+ setIsSaving(true);
118
+ setError(null);
119
+ try {
120
+ const row = await tableRef.current.update(Number(id), updates);
121
+ setData(row);
122
+ return row;
123
+ } catch (err) {
124
+ const e = err instanceof Error ? err : new Error(String(err));
125
+ setError(e);
126
+ throw e;
127
+ } finally {
128
+ setIsSaving(false);
129
+ }
130
+ },
131
+ [enabled, id]
132
+ );
133
+ const remove = useCallback(async () => {
134
+ if (!enabled) return;
135
+ setIsSaving(true);
136
+ setError(null);
137
+ try {
138
+ await tableRef.current.delete(Number(id));
139
+ setData(null);
140
+ } catch (err) {
141
+ const e = err instanceof Error ? err : new Error(String(err));
142
+ setError(e);
143
+ throw e;
144
+ } finally {
145
+ setIsSaving(false);
146
+ }
147
+ }, [enabled, id]);
148
+ return { data, isLoading, isSaving, error, refetch, save, remove };
149
+ }
150
+ function useQuery(query) {
151
+ const [data, setData] = useState(null);
152
+ const [isLoading, setIsLoading] = useState(true);
153
+ const [error, setError] = useState(null);
154
+ const fingerprint = JSON.stringify(query.toAst());
155
+ const queryRef = useRef(query);
156
+ queryRef.current = query;
157
+ const refetch = useCallback(async () => {
158
+ setIsLoading(true);
159
+ setError(null);
160
+ try {
161
+ const result = await queryRef.current.execute();
162
+ setData(result);
163
+ } catch (err) {
164
+ setError(err instanceof Error ? err : new Error(String(err)));
165
+ } finally {
166
+ setIsLoading(false);
167
+ }
168
+ }, [fingerprint]);
169
+ useEffect(() => {
170
+ let cancelled = false;
171
+ setIsLoading(true);
172
+ setError(null);
173
+ void (async () => {
174
+ try {
175
+ const result = await queryRef.current.execute();
176
+ if (cancelled) return;
177
+ setData(result);
178
+ } catch (err) {
179
+ if (cancelled) return;
180
+ setError(err instanceof Error ? err : new Error(String(err)));
181
+ } finally {
182
+ if (cancelled) return;
183
+ setIsLoading(false);
184
+ }
185
+ })();
186
+ return () => {
187
+ cancelled = true;
188
+ };
189
+ }, [fingerprint]);
190
+ return { data, isLoading, error, refetch };
191
+ }
192
+
193
+ export { useQuery, useRecord, useTable };
194
+ //# sourceMappingURL=index.js.map
195
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/use-table.ts","../../src/react/use-record.ts","../../src/react/use-query.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type {\n BaseInsert,\n BaseRow,\n BaseUpdate,\n ListOpts,\n Page,\n TableClient,\n} from '../data/types.js';\n\n/**\n * Hook options. Mirrors {@link ListOpts} fields the caller most often\n * wants reactive — `filter`, `sort`, `limit`, `offset` — and a\n * convenience `enabled` switch to skip the fetch entirely (useful when\n * the caller's filter depends on other not-yet-loaded state).\n *\n * The hook RE-FETCHES whenever any of these values change (compared\n * by JSON.stringify — sufficient for the typical filter object;\n * callers with non-serialisable filters can memoise their `opts` or\n * call `refetch()` imperatively).\n */\nexport interface UseTableOpts<Row> extends ListOpts<Row> {\n /**\n * When `false`, the hook returns the idle state (`isLoading: false`,\n * `data: null`) and skips the fetch. Default `true`.\n */\n enabled?: boolean;\n}\n\nexport interface UseTableResult<Row> {\n /** Page rows. `null` until first fetch resolves. */\n data: Row[] | null;\n /** Full page envelope (rows + cursor + total). `null` until first fetch. */\n page: Page<Row> | null;\n /** True during the in-flight fetch (including refetches). */\n isLoading: boolean;\n /**\n * Set when the most recent fetch threw. Cleared on the next\n * successful refetch.\n */\n error: Error | null;\n /** Re-runs the list query with the current opts. */\n refetch: () => Promise<void>;\n /**\n * Optimistically replace the local `data` without hitting the\n * server. Useful for instant UI after a mutation; the next\n * `refetch()` reconciles with the server.\n */\n mutate: (next: Row[]) => void;\n}\n\n/**\n * Fetch a paginated list from a DashWise table. Re-runs when any of\n * the `opts` change.\n *\n * @example\n * ```tsx\n * const { data, isLoading, error, refetch } = useTable(\n * db.db<Task>(\"tasks\"),\n * { filter: { done: false }, sort: [{ field: \"createdAt\", desc: true }] }\n * );\n * ```\n */\nexport function useTable<\n Row extends BaseRow,\n Insert extends BaseInsert = BaseInsert,\n Update extends BaseUpdate = BaseUpdate,\n>(\n table: TableClient<Row, Insert, Update>,\n opts: UseTableOpts<Row> = {},\n): UseTableResult<Row> {\n const { enabled = true, ...listOpts } = opts;\n const [page, setPage] = useState<Page<Row> | null>(null);\n const [isLoading, setIsLoading] = useState<boolean>(enabled);\n const [error, setError] = useState<Error | null>(null);\n\n // Memoise the listOpts fingerprint so we don't refetch on every\n // render. JSON.stringify is good enough for plain-object filters;\n // callers with non-serialisable opts can pre-memoise + pass a\n // stable reference.\n const fingerprint = JSON.stringify(listOpts);\n\n // Stable ref to the table client so the effect doesn't refire when\n // a fresh client instance is constructed each render (a common\n // anti-pattern that would otherwise trash the hook's behaviour).\n const tableRef = useRef(table);\n tableRef.current = table;\n\n const refetch = useCallback(async () => {\n if (!enabled) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await tableRef.current.list(JSON.parse(fingerprint));\n setPage(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, fingerprint]);\n\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n void (async () => {\n try {\n const result = await tableRef.current.list(JSON.parse(fingerprint));\n if (cancelled) return;\n setPage(result);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (cancelled) return;\n setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [enabled, fingerprint]);\n\n const mutate = useCallback((next: Row[]) => {\n setPage((prev) =>\n prev\n ? { ...prev, rows: next }\n : { rows: next, total: next.length, limit: next.length, offset: 0 },\n );\n }, []);\n\n return {\n data: page ? page.rows : null,\n page,\n isLoading,\n error,\n refetch,\n mutate,\n };\n}\n","'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type {\n BaseInsert,\n BaseRow,\n BaseUpdate,\n TableClient,\n} from '../data/types.js';\n\nexport interface UseRecordResult<Row, Update> {\n /** The record. `null` until first fetch resolves or after `remove()`. */\n data: Row | null;\n /** True during the in-flight fetch (initial load OR refetch). */\n isLoading: boolean;\n /** True while a save / remove call is in flight. */\n isSaving: boolean;\n error: Error | null;\n /** Re-run the GET. */\n refetch: () => Promise<void>;\n /**\n * Patch the record. The hook PATCHes via `table.update(id, updates)`\n * and refreshes `data` from the server's response. Throws on\n * validation failure; callers can wrap in try/catch.\n */\n save: (updates: Update) => Promise<Row>;\n /**\n * Delete the record. After success, `data` becomes `null` and any\n * subsequent `refetch()` will see the 404. The hook does NOT\n * navigate; callers handle post-delete UX (toast, redirect, etc.).\n */\n remove: () => Promise<void>;\n}\n\n/**\n * Fetch a single record by id from a DashWise table; expose save +\n * remove handles so a detail page never needs to write\n * `useEffect`/`useState`/`fetch` boilerplate.\n *\n * @example\n * ```tsx\n * const { data: task, isLoading, save } = useRecord(db.db<Task>(\"tasks\"), id);\n * if (!task) return <LoadingState />;\n * return (\n * <Button onClick={() => save({ done: !task.done })}>\n * Toggle done\n * </Button>\n * );\n * ```\n *\n * Skips the fetch when `id` is null / undefined / \"\" — handy for\n * conditional rendering (\"show form only if there's a selected id\").\n */\nexport function useRecord<\n Row extends BaseRow,\n Insert extends BaseInsert = BaseInsert,\n Update extends BaseUpdate = BaseUpdate,\n>(\n table: TableClient<Row, Insert, Update>,\n id: string | number | null | undefined,\n): UseRecordResult<Row, Update> {\n const enabled = id !== null && id !== undefined && id !== '';\n const [data, setData] = useState<Row | null>(null);\n const [isLoading, setIsLoading] = useState<boolean>(enabled);\n const [isSaving, setIsSaving] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Stable ref to the table client (see use-table.ts for rationale).\n const tableRef = useRef(table);\n tableRef.current = table;\n\n const refetch = useCallback(async () => {\n if (!enabled) return;\n setIsLoading(true);\n setError(null);\n try {\n const row = await tableRef.current.get(Number(id));\n setData(row);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, id]);\n\n useEffect(() => {\n if (!enabled) {\n setData(null);\n setIsLoading(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n void (async () => {\n try {\n const row = await tableRef.current.get(Number(id));\n if (cancelled) return;\n setData(row);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (cancelled) return;\n setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [enabled, id]);\n\n const save = useCallback(\n async (updates: Update): Promise<Row> => {\n if (!enabled) {\n throw new Error('useRecord.save: id is null/empty');\n }\n setIsSaving(true);\n setError(null);\n try {\n const row = await tableRef.current.update(Number(id), updates);\n setData(row);\n return row;\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n throw e;\n } finally {\n setIsSaving(false);\n }\n },\n [enabled, id],\n );\n\n const remove = useCallback(async (): Promise<void> => {\n if (!enabled) return;\n setIsSaving(true);\n setError(null);\n try {\n await tableRef.current.delete(Number(id));\n setData(null);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n throw e;\n } finally {\n setIsSaving(false);\n }\n }, [enabled, id]);\n\n return { data, isLoading, isSaving, error, refetch, save, remove };\n}\n","'use client';\n\n/**\n * React hook for the typed query plane — Phase 2 slice 5.\n *\n * Mirrors {@link useTable}'s cancellation + AST-hash-based refetch\n * pattern, but operates on the typed builder ({@link Query} or\n * {@link AggQuery}) rather than a raw table client + opts. The\n * builder's `.toAst()` output is the cache key — when the AST hash\n * changes, the hook refetches.\n *\n * Two overloads:\n * - `useQuery(query)` where `query: Query<Row>` → result.data is\n * `Page<Row>` (the standard list envelope).\n * - `useQuery(aggQuery)` where `aggQuery: AggQuery` → result.data\n * is `QueryResult` (aggregation envelope).\n *\n * Returns `{ data, isLoading, error, refetch }`. No `.mutate()` (the\n * builder is the source of truth — to refresh, change the builder\n * inputs; to optimistically tweak, callers can wrap with their own\n * state).\n *\n * @example\n * ```tsx\n * import { qb } from '@dashai/generated';\n * import { useQuery } from '@dashai/sdk/react';\n *\n * function OpenTasks() {\n * const { data, isLoading, error, refetch } = useQuery(\n * qb.tasks.where({ done: false }).orderBy([{ field: 'priority', dir: 'desc' }])\n * );\n * if (isLoading) return <Spinner />;\n * if (error) return <Err err={error} />;\n * return <ul>{data?.rows.map((t) => <li key={t.id}>{t.title}</li>)}</ul>;\n * }\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type { BaseRow, Page } from '../data/types.js';\nimport type { AggQuery, Query, QueryResult } from '../query/builder.js';\n\n// ── Result shapes ──────────────────────────────────────────────────────\n\nexport interface UseQueryResult<T> {\n /** Query result envelope. `null` until first fetch resolves. */\n data: T | null;\n /** True during the in-flight fetch (initial + refetches). */\n isLoading: boolean;\n /**\n * Set when the most recent fetch threw. Cleared on the next\n * successful refetch.\n */\n error: Error | null;\n /** Re-run the query (uses the builder's current AST). */\n refetch: () => Promise<void>;\n}\n\n// ── Overloads ──────────────────────────────────────────────────────────\n\nexport function useQuery<Row extends BaseRow>(\n query: Query<Row>,\n): UseQueryResult<Page<Row>>;\nexport function useQuery(query: AggQuery): UseQueryResult<QueryResult>;\n\n// ── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Shared implementation for both overloads. Both `Query<Row>` and\n * `AggQuery` expose `.toAst()` + `.execute()`; the result type is\n * what the overload narrows.\n */\nexport function useQuery(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n query: { toAst: () => unknown; execute: () => Promise<any> },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): UseQueryResult<any> {\n const [data, setData] = useState<unknown | null>(null);\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Cache key = JSON-stringified AST. Same trade-off as `useTable`'s\n // fingerprint: cheap, good enough for the common case, callers\n // with non-serialisable inputs can memoise their builder upstream.\n // Computed every render but only the string identity changes\n // trigger the effect.\n const fingerprint = JSON.stringify(query.toAst());\n\n // Stable ref to the builder so the effect's call site doesn't\n // re-fire when the caller passes a fresh builder instance every\n // render (the chainable methods return new instances each time,\n // so this is the common case).\n const queryRef = useRef(query);\n queryRef.current = query;\n\n const refetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await queryRef.current.execute();\n setData(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [fingerprint]);\n\n useEffect(() => {\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n void (async () => {\n try {\n const result = await queryRef.current.execute();\n if (cancelled) return;\n setData(result);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (cancelled) return;\n setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [fingerprint]);\n\n return { data, isLoading, error, refetch };\n}\n"]}