@docstack/react 0.0.7 → 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.
@@ -0,0 +1,423 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { useContext, useCallback, useEffect, useRef, useState } from "react";
11
+ import { DocStackContext } from "../components/StackProvider/index.js";
12
+ import { Domain } from "@docstack/client";
13
+ /**
14
+ * Hook to create a new Domain in a specific stack.
15
+ *
16
+ * @param stack - The name of the stack to create the domain in.
17
+ * @returns A callback function to create the domain.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const CreateDomain = () => {
22
+ * const createDomain = useDomainCreate('my-stack');
23
+ * // Assume sourceClass and targetClass are available Class instances
24
+ *
25
+ * const handleCreate = async () => {
26
+ * const newDomain = await createDomain(
27
+ * 'UserProjects',
28
+ * '1:N',
29
+ * userClass,
30
+ * projectClass,
31
+ * 'User has many projects'
32
+ * );
33
+ * };
34
+ *
35
+ * return <button onClick={handleCreate}>Create Domain</button>;
36
+ * };
37
+ * ```
38
+ */
39
+ export const useDomainCreate = (stack) => {
40
+ const docStack = useContext(DocStackContext);
41
+ return useCallback((domainName, cardinality, sourceClass, targetClass, domainDesc) => __awaiter(void 0, void 0, void 0, function* () {
42
+ try {
43
+ if (!docStack) {
44
+ // Handle the case where the provider is not yet initialized or missing
45
+ // You could throw an error or return an empty state.
46
+ // Null until the provider's `ready` event; that is startup,
47
+ // not a missing provider. See ADR-0022.
48
+ console.warn('useDomainCreate - stack not ready yet; the call was ignored.');
49
+ // setLoading(false);
50
+ return Promise.resolve(null);
51
+ }
52
+ // Run the initial query
53
+ const stackInstance = docStack.getStack(stack);
54
+ if (stackInstance) {
55
+ const domain = yield Domain.create(stackInstance, null, domainName, "domain", cardinality, sourceClass, targetClass, domainDesc);
56
+ return domain;
57
+ }
58
+ return null;
59
+ }
60
+ catch (err) {
61
+ // setError(err);
62
+ console.error(err);
63
+ return null;
64
+ }
65
+ }), [docStack, stack]);
66
+ };
67
+ /**
68
+ * Hook to retrieve a list of domains from a stack based on a selector.
69
+ * Maintains a real-time list of domains matching the selector.
70
+ *
71
+ * @param stack - The name of the stack to query.
72
+ * @param selector - Mango selector to filter domains.
73
+ * @returns Object containing the list of domains, loading state, and error.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * const DomainList = () => {
78
+ * const { domainList, loading } = useDomainList('my-stack', {
79
+ * relation: { $eq: '1:N' }
80
+ * });
81
+ *
82
+ * if (loading) return <div>Loading...</div>;
83
+ *
84
+ * return (
85
+ * <ul>
86
+ * {domainList.map(d => <li key={d.id}>{d.name} ({d.relation})</li>)}
87
+ * </ul>
88
+ * );
89
+ * };
90
+ * ```
91
+ */
92
+ export const useDomainList = (stack, selector) => {
93
+ const docStack = useContext(DocStackContext);
94
+ const [originClass, setOriginClass] = useState();
95
+ const [domainList, setDomainList] = useState([]);
96
+ const domainListRef = useRef([]);
97
+ const [loading, setLoading] = useState(true);
98
+ const [error, setError] = useState(null);
99
+ useEffect(() => {
100
+ // Only run if the docStack is available and a className is provided
101
+ if (!docStack) {
102
+ // Null until the provider's `ready` event: startup, not a missing
103
+ // provider. Reporting "loaded" here is indistinguishable from a genuinely
104
+ // empty result. See ADR-0022.
105
+ setLoading(true);
106
+ return;
107
+ }
108
+ const fetchClass = () => __awaiter(void 0, void 0, void 0, function* () {
109
+ setLoading(true);
110
+ setError(null);
111
+ try {
112
+ const stackInstance = docStack.getStack(stack);
113
+ if (stackInstance) {
114
+ const retrievedClass = yield stackInstance.getClass('domain');
115
+ if (retrievedClass) {
116
+ setOriginClass(retrievedClass);
117
+ }
118
+ }
119
+ }
120
+ catch (err) {
121
+ setError(err);
122
+ setLoading(false);
123
+ }
124
+ });
125
+ fetchClass();
126
+ return () => {
127
+ // clean what?
128
+ };
129
+ }, [docStack, stack]); // Dependency on docStack and stack
130
+ useEffect(() => {
131
+ if (!originClass) {
132
+ return;
133
+ }
134
+ let cancelled = false;
135
+ let attached = null;
136
+ const runQueryAndListen = () => __awaiter(void 0, void 0, void 0, function* () {
137
+ setLoading(true);
138
+ try {
139
+ const stackInstance = docStack.getStack(stack);
140
+ const initialDomainModelList = yield originClass.getCards(selector);
141
+ const initDomainList = yield Promise.all(initialDomainModelList.map((dm) => __awaiter(void 0, void 0, void 0, function* () { return yield Domain.buildFromModel(stackInstance, dm); })));
142
+ if (cancelled) {
143
+ // Torn down mid-query; these were built anyway and each holds a live
144
+ // subscription until it is closed.
145
+ for (const domain of initDomainList)
146
+ domain.close();
147
+ return;
148
+ }
149
+ domainListRef.current = initDomainList;
150
+ setDomainList(domainListRef.current);
151
+ }
152
+ catch (err) {
153
+ if (!cancelled)
154
+ setError(err);
155
+ }
156
+ finally {
157
+ if (!cancelled)
158
+ setLoading(false);
159
+ }
160
+ if (cancelled)
161
+ return;
162
+ const changeListener = (change) => {
163
+ const doc = change.detail.doc;
164
+ if (!doc.active) {
165
+ // A doc was deleted
166
+ const docIndex = domainListRef.current.findIndex((d) => d.id == doc._id);
167
+ if (docIndex != -1) {
168
+ domainListRef.current = [
169
+ ...domainListRef.current.slice(0, docIndex),
170
+ ...domainListRef.current.slice(docIndex + 1, domainListRef.current.length)
171
+ ];
172
+ }
173
+ }
174
+ else {
175
+ // A doc was changed or added
176
+ const docIndex = domainListRef.current.findIndex((d) => d.id == doc._id);
177
+ if (docIndex != -1) {
178
+ // A doc was changed
179
+ domainListRef.current = [
180
+ ...domainListRef.current.slice(0, docIndex),
181
+ doc,
182
+ ...domainListRef.current.slice(docIndex + 1, domainListRef.current.length)
183
+ ];
184
+ }
185
+ else {
186
+ // A doc was added
187
+ domainListRef.current.push(doc);
188
+ }
189
+ }
190
+ setDomainList([...domainListRef.current]);
191
+ };
192
+ attached = changeListener;
193
+ originClass.addEventListener('doc', attached);
194
+ });
195
+ runQueryAndListen();
196
+ // The cleanup used to be returned from `runQueryAndListen`, where React never saw
197
+ // it: the listener stayed attached and the built domains stayed subscribed.
198
+ return () => {
199
+ var _a;
200
+ cancelled = true;
201
+ if (attached)
202
+ originClass.removeEventListener('doc', attached);
203
+ // Guarded: the change handler above pushes the raw document for a domain it
204
+ // has not seen before, so the list is not uniformly Domain instances.
205
+ for (const domain of domainListRef.current)
206
+ (_a = domain === null || domain === void 0 ? void 0 : domain.close) === null || _a === void 0 ? void 0 : _a.call(domain);
207
+ domainListRef.current = [];
208
+ };
209
+ }, [originClass, JSON.stringify(selector)]); // Dependency on classObj and query
210
+ return { domainList, loading, error };
211
+ };
212
+ /**
213
+ * Hook to retrieve a single Domain instance by name.
214
+ *
215
+ * @param stack - The name of the stack.
216
+ * @param domainName - The name of the domain to retrieve.
217
+ * @returns Object containing the Domain instance, loading state, and error.
218
+ *
219
+ * @example
220
+ * ```tsx
221
+ * const DomainDetails = () => {
222
+ * const { domain, loading } = useDomain('my-stack', 'UserProjects');
223
+ *
224
+ * if (loading) return <div>Loading...</div>;
225
+ * if (!domain) return <div>Domain not found</div>;
226
+ *
227
+ * return <div>Relation Type: {domain.relation}</div>;
228
+ * };
229
+ * ```
230
+ */
231
+ export const useDomain = (stack, domainName) => {
232
+ const docStack = useContext(DocStackContext);
233
+ const [loading, setLoading] = useState(false);
234
+ const [error, setError] = useState();
235
+ const [domain, setDomain] = useState();
236
+ const reqRef = useRef(false);
237
+ useEffect(() => {
238
+ if (!docStack) {
239
+ // Handle the case where the provider is not yet initialized or missing
240
+ // You could throw an error or return an empty state.
241
+ // The provider publishes `null` into the context until its `ready`
242
+ // event fires, so this is the normal startup window, not a missing
243
+ // provider. Reporting it as one sends the reader hunting for a bug
244
+ // that is not there - and `setLoading(false)` was worse than the
245
+ // message: it tells a consumer "loaded, and empty" during startup,
246
+ // which is indistinguishable from a genuinely empty result. See
247
+ // ADR-0022.
248
+ setLoading(true);
249
+ return;
250
+ }
251
+ const fetchClass = () => __awaiter(void 0, void 0, void 0, function* () {
252
+ try {
253
+ const stackInstance = docStack.getStack(stack);
254
+ if (stackInstance) {
255
+ const res = yield stackInstance.getDomain(domainName);
256
+ // TODO: manage class model (schema!) updates
257
+ if (res) {
258
+ setDomain(res);
259
+ }
260
+ }
261
+ }
262
+ catch (e) {
263
+ setError(e);
264
+ }
265
+ finally {
266
+ setLoading(false);
267
+ }
268
+ });
269
+ if (!reqRef.current) {
270
+ reqRef.current = true;
271
+ setLoading(true);
272
+ fetchClass();
273
+ }
274
+ return () => {
275
+ // reqRef.current = false;
276
+ };
277
+ }, [docStack, stack, domainName]);
278
+ return { loading, error, domain };
279
+ };
280
+ /**
281
+ * Hook to retrieve relation documents for a specific domain.
282
+ * Maintains a real-time list of relations matching the query.
283
+ *
284
+ * @param stack - The name of the stack.
285
+ * @param domainName - The domain name to fetch relations for.
286
+ * @param query - Optional Mango selector to filter relations.
287
+ * @returns Object containing the list of relation documents, loading state, and error.
288
+ *
289
+ * @example
290
+ * ```tsx
291
+ * const ProjectTasks = () => {
292
+ * const { docs, loading } = useDomainRelations('my-stack', 'ProjectTasks', {
293
+ * sourceId: { $eq: 'Project-123' }
294
+ * });
295
+ *
296
+ * if (loading) return <div>Loading...</div>;
297
+ *
298
+ * return (
299
+ * <ul>
300
+ * {docs.map(rel => (
301
+ * <li key={rel._id}>Linked Task: {rel.targetId}</li>
302
+ * ))}
303
+ * </ul>
304
+ * );
305
+ * };
306
+ * ```
307
+ */
308
+ export const useDomainRelations = (stack, domainName, query = {}) => {
309
+ const docStack = useContext(DocStackContext);
310
+ const [domain, setDomain] = useState();
311
+ const [docs, setDocs] = useState([]);
312
+ const docsRef = useRef([]);
313
+ const [loading, setLoading] = useState(true);
314
+ const [error, setError] = useState(null);
315
+ useEffect(() => {
316
+ // Only run if the docStack is available and a className is provided
317
+ if (!docStack) {
318
+ // Null until the provider's `ready` event: startup, not a missing
319
+ // provider. Reporting "loaded" here is indistinguishable from a genuinely
320
+ // empty result. See ADR-0022.
321
+ setLoading(true);
322
+ return;
323
+ }
324
+ if (!domainName) {
325
+ // A genuinely absent domainName is "nothing to load", which is a settled state -
326
+ // unlike the pre-ready window above.
327
+ setLoading(false);
328
+ return;
329
+ }
330
+ const fetchClass = () => __awaiter(void 0, void 0, void 0, function* () {
331
+ setLoading(true);
332
+ setError(null);
333
+ try {
334
+ const stackInstance = docStack.getStack(stack);
335
+ if (stackInstance) {
336
+ const retrievedDomain = yield stackInstance.getDomain(domainName);
337
+ if (retrievedDomain) {
338
+ setDomain(retrievedDomain);
339
+ }
340
+ }
341
+ }
342
+ catch (err) {
343
+ setError(err);
344
+ setLoading(false);
345
+ }
346
+ });
347
+ fetchClass();
348
+ return () => {
349
+ // clean what?
350
+ };
351
+ }, [docStack, stack, domainName]); // Dependency on docStack and className
352
+ useEffect(() => {
353
+ if (!domain) {
354
+ return;
355
+ }
356
+ let cancelled = false;
357
+ let attached = null;
358
+ const runQueryAndListen = () => __awaiter(void 0, void 0, void 0, function* () {
359
+ setLoading(true);
360
+ try {
361
+ const initialDocs = yield domain.getRelations(query);
362
+ if (cancelled)
363
+ return;
364
+ docsRef.current = initialDocs;
365
+ setDocs(docsRef.current);
366
+ }
367
+ catch (err) {
368
+ if (!cancelled)
369
+ setError(err);
370
+ }
371
+ finally {
372
+ if (!cancelled)
373
+ setLoading(false);
374
+ }
375
+ if (cancelled)
376
+ return;
377
+ const changeListener = (change) => {
378
+ const doc = change.detail.doc;
379
+ if (!doc.active) {
380
+ // A doc was deleted
381
+ const docIndex = docsRef.current.findIndex((d) => d._id == doc._id);
382
+ if (docIndex != -1) {
383
+ docsRef.current = [
384
+ ...docsRef.current.slice(0, docIndex),
385
+ ...docsRef.current.slice(docIndex + 1, docsRef.current.length)
386
+ ];
387
+ }
388
+ }
389
+ else {
390
+ // A doc was changed or added
391
+ console.log("useDomainRelations - a doc was changed or added", { doc });
392
+ const docIndex = docsRef.current.findIndex((d) => d._id == doc._id);
393
+ if (docIndex != -1) {
394
+ // A doc was changed
395
+ console.log("useDomainRelations - a doc was changed", { doc });
396
+ docsRef.current = [
397
+ ...docsRef.current.slice(0, docIndex),
398
+ doc,
399
+ ...docsRef.current.slice(docIndex + 1, docsRef.current.length)
400
+ ];
401
+ }
402
+ else {
403
+ // A doc was added
404
+ console.log("useDomainRelations - a doc was added", { doc });
405
+ docsRef.current.push(doc);
406
+ }
407
+ }
408
+ setDocs([...docsRef.current]);
409
+ };
410
+ attached = changeListener;
411
+ domain.addEventListener('doc', attached);
412
+ });
413
+ runQueryAndListen();
414
+ // The cleanup used to be returned from `runQueryAndListen`, so React never
415
+ // received it and each query change left another listener on the domain.
416
+ return () => {
417
+ cancelled = true;
418
+ if (attached)
419
+ domain.removeEventListener('doc', attached);
420
+ };
421
+ }, [domain, JSON.stringify(query)]); // Dependency on classObj and query
422
+ return { docs, loading, error };
423
+ };
@@ -1,11 +1,17 @@
1
- import { Document, SelectAST, UnionAST } from '@docstack/shared';
1
+ import { Document, SelectAST, UnionAST } from '@docstack/client';
2
2
  /**
3
3
  * Hook to execute a SQL query against a specific stack.
4
4
  *
5
+ * Live by default: the query re-runs when a document changes in a class it actually
6
+ * reads, derived from the `ast` the query itself returns. It used to run once and never
7
+ * again, which was invisible beside `useClassDocs` in the same import - one list
8
+ * refreshing next to one that did not. See ADR-0025.
9
+ *
5
10
  * @param stack - The name of the stack to query.
6
11
  * @param sql - The SQL query string.
7
- * @param params - Optional parameters for the SQL query.
8
- * @returns Object containing the query result (rows and AST), loading state, and error.
12
+ * @param params - Values for the query's `?` placeholders.
13
+ * @param options - See {@link QuerySQLOptions}; `{ live: false }` for a snapshot.
14
+ * @returns The query result (rows and AST), loading state, error, and `refetch`.
9
15
  *
10
16
  * @example
11
17
  * ```tsx
@@ -22,13 +28,25 @@ import { Document, SelectAST, UnionAST } from '@docstack/shared';
22
28
  * };
23
29
  * ```
24
30
  */
25
- export declare const useQuerySQL: (stack: string, sql: string, ...params: any[]) => {
31
+ export type QuerySQLOptions = {
32
+ /**
33
+ * Re-run when a document changes in a class this query reads. Defaults to `true`.
34
+ *
35
+ * Pass `false` for a deliberate snapshot - and say so at the call site, which the
36
+ * previous behaviour never did.
37
+ */
38
+ live?: boolean;
39
+ /** Coalesce a burst of changes into one re-run, in milliseconds. Defaults to 150. */
40
+ coalesceMs?: number;
41
+ };
42
+ export declare const useQuerySQL: (stack: string, sql: string, params?: any[], options?: QuerySQLOptions) => {
26
43
  loading: boolean;
27
44
  result: {
28
45
  rows: any[];
29
46
  ast: (SelectAST | UnionAST)[] | null;
30
47
  };
31
- error: null;
48
+ error: any;
49
+ refetch: () => Promise<void>;
32
50
  };
33
51
  /**
34
52
  * Hook to find documents in a stack using a Mango selector.
@@ -0,0 +1,206 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ // src/hooks/useFind.js
11
+ import { useCallback, useContext, useEffect, useRef, useState } from 'react';
12
+ import { DocStackContext } from '../components/StackProvider/index.js';
13
+ import { collectQueryClasses } from '@docstack/client';
14
+ export const useQuerySQL = (stack, sql, params = [], options = {}) => {
15
+ const { live = true, coalesceMs = 150 } = options;
16
+ const docStack = useContext(DocStackContext);
17
+ const [result, setResult] = useState({ rows: [], ast: [] });
18
+ const [loading, setLoading] = useState(true);
19
+ const [error, setError] = useState(null);
20
+ // Which classes to watch. Held in state because it falls out of the first result and
21
+ // drives the subscription effect below.
22
+ const [watched, setWatched] = useState(undefined);
23
+ // Stable identities, so the effects key on the query rather than on the render count.
24
+ // The old `queryRef` latch was standing in for this: `params` arrived as a rest
25
+ // parameter, a fresh array every render, so the effect re-ran every render and the
26
+ // latch was the only thing preventing a query storm - at the cost of never re-running
27
+ // at all, including when `sql` changed. See ADR-0025.
28
+ const paramsKey = JSON.stringify(params);
29
+ const paramsRef = useRef(params);
30
+ paramsRef.current = params;
31
+ // Guards against a slow earlier run overwriting a fast later one.
32
+ const runId = useRef(0);
33
+ const runQuery = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
34
+ const stackInstance = docStack === null || docStack === void 0 ? void 0 : docStack.getStack(stack);
35
+ if (!stackInstance)
36
+ return;
37
+ const id = ++runId.current;
38
+ try {
39
+ const queryResult = yield stackInstance.query(sql, ...paramsRef.current);
40
+ if (id !== runId.current)
41
+ return;
42
+ setResult(queryResult);
43
+ setWatched(collectQueryClasses(queryResult.ast));
44
+ setError(null);
45
+ }
46
+ catch (err) {
47
+ if (id === runId.current)
48
+ setError(err);
49
+ }
50
+ finally {
51
+ if (id === runId.current)
52
+ setLoading(false);
53
+ }
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }), [docStack, stack, sql, paramsKey]);
56
+ useEffect(() => {
57
+ if (!docStack) {
58
+ // Null until the provider's `ready` event: startup, not a missing provider.
59
+ // See ADR-0022.
60
+ setLoading(true);
61
+ return;
62
+ }
63
+ setLoading(true);
64
+ runQuery();
65
+ }, [docStack, runQuery]);
66
+ const watchedKey = JSON.stringify(watched !== null && watched !== void 0 ? watched : null);
67
+ useEffect(() => {
68
+ const stackInstance = docStack === null || docStack === void 0 ? void 0 : docStack.getStack(stack);
69
+ // `undefined` is "no result yet"; `null` is "the AST could not be accounted for".
70
+ if (!live || !stackInstance || watched === undefined)
71
+ return;
72
+ let cancelled = false;
73
+ let timer;
74
+ let subscriptions = [];
75
+ const target = new EventTarget();
76
+ const onDoc = () => {
77
+ clearTimeout(timer);
78
+ timer = setTimeout(runQuery, coalesceMs);
79
+ };
80
+ target.addEventListener("doc", onDoc);
81
+ // Fail open. Watching every class is wasteful; watching none is silently wrong,
82
+ // and silence is the failure this hook exists to end. Subscriptions share one
83
+ // database listener, so the wasteful branch costs little. See ADR-0025.
84
+ const resolveClasses = () => __awaiter(void 0, void 0, void 0, function* () {
85
+ if (watched === null)
86
+ return stackInstance.getClassNames();
87
+ return watched;
88
+ });
89
+ void resolveClasses().then(classes => {
90
+ if (cancelled)
91
+ return;
92
+ subscriptions = classes.map(name => stackInstance.subscribeClassDocs(name, target));
93
+ });
94
+ return () => {
95
+ cancelled = true;
96
+ clearTimeout(timer);
97
+ target.removeEventListener("doc", onDoc);
98
+ for (const subscription of subscriptions)
99
+ stackInstance.releaseListener(subscription);
100
+ };
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [docStack, stack, live, coalesceMs, watchedKey, runQuery]);
103
+ return { loading, result, error, refetch: runQuery };
104
+ };
105
+ /**
106
+ * Hook to find documents in a stack using a Mango selector.
107
+ *
108
+ * @param stack - The name of the stack to query.
109
+ * @param query - Object containing the selector and optional fields projection.
110
+ * @param sort - Optional sort criteria.
111
+ * @param limit - Maximum number of documents to return (default: 50).
112
+ * @returns Object containing the list of documents, loading state, and error.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * const ActiveTasks = () => {
117
+ * const { docs, loading } = useFind('my-stack', {
118
+ * selector: {
119
+ * "~class": "Task",
120
+ * active: true
121
+ * },
122
+ * fields: ['_id', 'title']
123
+ * });
124
+ *
125
+ * if (loading) return <div>Loading...</div>;
126
+ *
127
+ * return (
128
+ * <ul>
129
+ * {docs.map(doc => <li key={doc._id}>{doc.title}</li>)}
130
+ * </ul>
131
+ * );
132
+ * };
133
+ * ```
134
+ */
135
+ export const useFind = (stack, query, sort, limit = 50) => {
136
+ const docStack = useContext(DocStackContext);
137
+ const [docs, setDocs] = useState([]);
138
+ const [loading, setLoading] = useState(true);
139
+ const [error, setError] = useState(null);
140
+ useEffect(() => {
141
+ var _a;
142
+ // Check if the docStack instance is available
143
+ if (!docStack) {
144
+ // Handle the case where the provider is not yet initialized or missing
145
+ // You could throw an error or return an empty state.
146
+ // The provider publishes `null` into the context until its `ready`
147
+ // event fires, so this is the normal startup window, not a missing
148
+ // provider. Reporting it as one sends the reader hunting for a bug
149
+ // that is not there - and `setLoading(false)` was worse than the
150
+ // message: it tells a consumer "loaded, and empty" during startup,
151
+ // which is indistinguishable from a genuinely empty result. See
152
+ // ADR-0022.
153
+ setLoading(true);
154
+ return;
155
+ }
156
+ setLoading(true);
157
+ const runQuery = () => __awaiter(void 0, void 0, void 0, function* () {
158
+ try {
159
+ const stackInstance = docStack.getStack(stack);
160
+ if (stackInstance) {
161
+ // Run the initial query
162
+ const initialDocs = yield stackInstance.findDocuments(query.selector, query.fields);
163
+ if (initialDocs.docs.length) {
164
+ let docs = initialDocs.docs; // [TODO] Check types
165
+ setDocs(docs);
166
+ }
167
+ }
168
+ }
169
+ catch (err) {
170
+ setError(err);
171
+ }
172
+ finally {
173
+ setLoading(false);
174
+ }
175
+ });
176
+ runQuery();
177
+ // A selector names its class directly, so there is no AST to consult - but it has
178
+ // to be subscribed the same way. This used to listen for `docStack`'s `change`,
179
+ // which is dispatched from the replication path and carries a `direction`: a
180
+ // document written locally never produces one, so an implementation built on it
181
+ // would appear to work while syncing and do nothing on the machine where the user
182
+ // is typing. See ADR-0025.
183
+ const className = (_a = query.selector) === null || _a === void 0 ? void 0 : _a["~class"];
184
+ const stackInstance = docStack.getStack(stack);
185
+ let timer;
186
+ let subscription;
187
+ const target = new EventTarget();
188
+ const onDoc = () => {
189
+ clearTimeout(timer);
190
+ timer = setTimeout(runQuery, 150);
191
+ };
192
+ if (stackInstance && typeof className === "string" && className) {
193
+ target.addEventListener("doc", onDoc);
194
+ subscription = stackInstance.subscribeClassDocs(className, target);
195
+ }
196
+ return () => {
197
+ clearTimeout(timer);
198
+ target.removeEventListener("doc", onDoc);
199
+ if (stackInstance && subscription)
200
+ stackInstance.releaseListener(subscription);
201
+ };
202
+ }, [docStack, stack, JSON.stringify(query)]); // Re-run if docStack or query changes
203
+ return { docs, loading, error };
204
+ };
205
+ export const useClassCreate = () => {
206
+ };