@dxos/echo-react 0.10.0 → 0.11.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,329 @@
1
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
2
+ import { DXN, EID, Filter, Obj, Query, Ref, Scope, Type } from "@dxos/echo";
3
+ import { useAtomValue } from "@effect-atom/atom-react";
4
+ import * as Atom from "@effect-atom/atom/Atom";
5
+ //#region src/useQuery.ts
6
+ var EMPTY_ARRAY$1 = [];
7
+ var noop = () => {};
8
+ /**
9
+ * Create subscription.
10
+ *
11
+ * @param queryOrFilter - The query or filter to apply. Query is memoized based on the AST. No need to call useMemo.
12
+ */
13
+ var useQuery = (resource, queryOrFilter) => {
14
+ const query = Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter;
15
+ const { getObjects, subscribe } = useMemo(() => {
16
+ let queryResult = void 0;
17
+ if (resource) queryResult = resource.query(query);
18
+ let subscribed = false;
19
+ return {
20
+ getObjects: () => subscribed && queryResult ? queryResult.results : EMPTY_ARRAY$1,
21
+ subscribe: (cb) => {
22
+ subscribed = true;
23
+ const unsubscribe = queryResult?.subscribe(cb) ?? noop;
24
+ return () => {
25
+ unsubscribe?.();
26
+ subscribed = false;
27
+ };
28
+ }
29
+ };
30
+ }, [resource, JSON.stringify(query.ast)]);
31
+ return useSyncExternalStore(subscribe, getObjects);
32
+ };
33
+ //#endregion
34
+ //#region src/usePagination.ts
35
+ var EMPTY_ARRAY = [];
36
+ /**
37
+ * Builds the external store backing `usePagination` for one query identity (filter/order/page
38
+ * size/resource). Mirrors `useQuery`'s own `useMemo`-built `{ getObjects, subscribe }` pair as
39
+ * closely as the added pagination state allows: nothing is queried and `.results` is never read
40
+ * until `subscribe()` is called (i.e. from `useSyncExternalStore`'s commit-safe effect, never
41
+ * during render). `getSnapshot` returns the last *delivered* results (`displayItems`), which are
42
+ * only replaced once a newly-pointed range actually produces a result -- so the previous page stays
43
+ * visible (rather than flashing empty) while an async range loads. `QueryResultImpl` still owns
44
+ * recompute/dedup; we only retain the most recent delivered array.
45
+ *
46
+ * `skip`/`limit` live as plain closure state rather than React state: pagination is internal
47
+ * bookkeeping for *which range this same identity is currently showing*, not a prop the rest of
48
+ * the component tree needs to read independently. `getNext`/`getPrevious` re-point the query at
49
+ * a new range by opening a fresh `QueryResult` for it, without discarding the previous one's
50
+ * results until the new one delivers its own -- that's what keeps the previous page visible while
51
+ * the new one loads (see the hook's own doc comment for why that matters).
52
+ */
53
+ var createPaginationStore = (resource, innerAst, pageSize, initialMaxWindowSize, initialItems) => {
54
+ let maxWindowSize = initialMaxWindowSize;
55
+ let skip = 0;
56
+ let limit = pageSize;
57
+ let rangeChangePending = false;
58
+ let innerUnsubscribe;
59
+ let displayItems = initialItems.length > 0 ? initialItems : EMPTY_ARRAY;
60
+ let isLoading = resource !== void 0 && displayItems.length === 0;
61
+ let snapshot = {
62
+ items: displayItems,
63
+ skip,
64
+ limit,
65
+ isLoading
66
+ };
67
+ const listeners = /* @__PURE__ */ new Set();
68
+ const notify = () => {
69
+ snapshot = {
70
+ items: displayItems,
71
+ skip,
72
+ limit,
73
+ isLoading
74
+ };
75
+ for (const listener of listeners) listener();
76
+ };
77
+ const pointAtRange = (markLoading) => {
78
+ innerUnsubscribe?.();
79
+ innerUnsubscribe = void 0;
80
+ if (!resource) {
81
+ displayItems = EMPTY_ARRAY;
82
+ isLoading = false;
83
+ notify();
84
+ return;
85
+ }
86
+ const effectiveQuery = Query.fromAst(innerAst).skip(skip).limit(limit);
87
+ const nextQueryResult = resource.query(effectiveQuery);
88
+ if (markLoading) isLoading = true;
89
+ innerUnsubscribe = nextQueryResult.subscribe(() => {
90
+ displayItems = nextQueryResult.results;
91
+ isLoading = false;
92
+ notify();
93
+ }, { fire: true });
94
+ notify();
95
+ queueMicrotask(() => {
96
+ rangeChangePending = false;
97
+ });
98
+ };
99
+ return {
100
+ subscribe: (onStoreChange) => {
101
+ listeners.add(onStoreChange);
102
+ if (listeners.size === 1) pointAtRange(displayItems.length === 0);
103
+ return () => {
104
+ listeners.delete(onStoreChange);
105
+ if (listeners.size === 0) {
106
+ innerUnsubscribe?.();
107
+ innerUnsubscribe = void 0;
108
+ }
109
+ };
110
+ },
111
+ getSnapshot: () => snapshot,
112
+ setMaxWindowSize: (value) => {
113
+ maxWindowSize = value;
114
+ },
115
+ getNext: () => {
116
+ if (isLoading || rangeChangePending || snapshot.items.length < limit) return;
117
+ rangeChangePending = true;
118
+ const nextLimit = limit + pageSize;
119
+ if (nextLimit <= maxWindowSize) limit = nextLimit;
120
+ else {
121
+ limit = maxWindowSize;
122
+ skip += pageSize;
123
+ }
124
+ pointAtRange(true);
125
+ },
126
+ getPrevious: () => {
127
+ if (isLoading || rangeChangePending || skip === 0) return;
128
+ rangeChangePending = true;
129
+ skip = Math.max(0, skip - pageSize);
130
+ pointAtRange(true);
131
+ },
132
+ jumpToHead: () => {
133
+ rangeChangePending = false;
134
+ skip = 0;
135
+ limit = pageSize;
136
+ pointAtRange(true);
137
+ }
138
+ };
139
+ };
140
+ /**
141
+ * Wraps a query with page management for paginated reads. The query's own `.limit(n)` clause is
142
+ * the page size -- the hook manages which page is loaded by rewriting `skip`/`limit` on top of
143
+ * it; the query must not carry its own `.skip()`.
144
+ *
145
+ * The query's `.orderBy(...)` is otherwise untouched, and applies to any query the underlying
146
+ * `Database.Queryable` supports -- e.g. `Order.natural('desc')` for feed insertion order.
147
+ *
148
+ * Keeps showing the previous page while a new one loads -- across both a `skip`/`limit` move and a
149
+ * query-identity change (filter/order/page-size/resource). The latter rebuilds the internal store;
150
+ * the last page is handed to the new store as its seed so the view refreshes in place instead of
151
+ * flashing empty, and `isLoading` stays put (a background refresh isn't a load the user asked for).
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * const { items, getNext, hasMore, isLoading, atHead, jumpToHead } = usePagination(
156
+ * db,
157
+ * Query.select(Filter.type(Message.Message)).from(feed).orderBy(Order.natural('desc')).limit(50),
158
+ * );
159
+ * ```
160
+ */
161
+ var usePagination = (resource, query, options) => {
162
+ if (query.ast.type !== "limit") throw new TypeError("usePagination requires the query to carry .limit(pageSize).");
163
+ if (query.ast.query.type === "skip") throw new TypeError("usePagination manages .skip() internally -- do not include it in the query.");
164
+ const pageSize = query.ast.limit;
165
+ const innerAst = query.ast.query;
166
+ const innerAstKey = JSON.stringify(innerAst);
167
+ const maxWindowSize = options?.maxWindowSize ?? pageSize * 10;
168
+ const previousItemsRef = useRef(EMPTY_ARRAY);
169
+ const store = useMemo(() => createPaginationStore(resource, innerAst, pageSize, maxWindowSize, previousItemsRef.current), [
170
+ resource,
171
+ innerAstKey,
172
+ pageSize
173
+ ]);
174
+ store.setMaxWindowSize(maxWindowSize);
175
+ const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot);
176
+ previousItemsRef.current = snapshot.items;
177
+ return useMemo(() => ({
178
+ items: snapshot.items,
179
+ getNext: store.getNext,
180
+ getPrevious: store.getPrevious,
181
+ hasMore: snapshot.items.length >= snapshot.limit,
182
+ isLoading: snapshot.isLoading,
183
+ atHead: snapshot.skip === 0,
184
+ jumpToHead: store.jumpToHead
185
+ }), [snapshot, store]);
186
+ };
187
+ //#endregion
188
+ //#region src/useType.ts
189
+ /**
190
+ * Subscribe to and retrieve a type by its URI from a space: a static schema's typename DXN, or a
191
+ * persisted (database) schema's `echo:` EID (what `Type.getURI` / `getTypeURIFromQuery` produce).
192
+ *
193
+ * Fans across the owning space db (persisted custom types) and the shared
194
+ * registry (static/runtime plugin types). Persisted types live only in the db,
195
+ * so a registry-only lookup misses them.
196
+ *
197
+ * DXN matching is version-agnostic: `dxn:com.example/Foo` matches `dxn:com.example/Foo:0.1.0`.
198
+ * This lets callers pass a bare typename DXN (no version) from e.g. a `ReferenceAnnotation`.
199
+ */
200
+ var useType = (db, typeUri) => {
201
+ const { subscribe, getType } = useMemo(() => {
202
+ if (!typeUri || !db) return {
203
+ subscribe: () => () => {},
204
+ getType: () => void 0
205
+ };
206
+ const searchEid = EID.isEID(typeUri) ? EID.tryParse(typeUri) : void 0;
207
+ const searchDxn = DXN.isDXN(typeUri) ? DXN.tryMake(typeUri) : void 0;
208
+ const queryResult = db.query(Query.select(Filter.type(Type.Type)).from(Scope.space(), Scope.registry()));
209
+ let subscribed = false;
210
+ const find = () => {
211
+ if (!subscribed) return;
212
+ return queryResult.results.find((type) => {
213
+ const uri = Type.getURI(type);
214
+ if (uri === typeUri) return true;
215
+ if (searchEid && EID.isEID(uri)) {
216
+ const typeEid = EID.tryParse(uri);
217
+ return typeEid != null && EID.getEntityId(typeEid) === EID.getEntityId(searchEid);
218
+ }
219
+ if (searchDxn && DXN.isDXN(uri)) {
220
+ const typeDxn = DXN.tryMake(uri);
221
+ return typeDxn != null && DXN.getName(typeDxn) === DXN.getName(searchDxn);
222
+ }
223
+ return false;
224
+ });
225
+ };
226
+ return {
227
+ subscribe: (onStoreChange) => {
228
+ subscribed = true;
229
+ const unsubscribe = queryResult.subscribe(onStoreChange);
230
+ return () => {
231
+ unsubscribe();
232
+ subscribed = false;
233
+ };
234
+ },
235
+ getType: find
236
+ };
237
+ }, [typeUri, db]);
238
+ return useSyncExternalStore(subscribe, getType);
239
+ };
240
+ //#endregion
241
+ //#region src/useObject.ts
242
+ var useObject = ((objOrRef, property) => {
243
+ const isRef = Ref.isRef(objOrRef);
244
+ const liveObj = useResolveRef(objOrRef);
245
+ const callback = useCallback((updateOrValue) => {
246
+ const obj = isRef ? objOrRef?.target : liveObj;
247
+ if (obj === void 0) return;
248
+ Obj.update(obj, (obj) => {
249
+ if (typeof updateOrValue === "function") {
250
+ const returnValue = updateOrValue(property !== void 0 ? obj[property] : obj);
251
+ if (returnValue !== void 0) {
252
+ if (property === void 0) throw new Error("Cannot re-assign the entire object");
253
+ obj[property] = returnValue;
254
+ }
255
+ } else {
256
+ if (property === void 0) throw new Error("Cannot re-assign the entire object");
257
+ obj[property] = updateOrValue;
258
+ }
259
+ });
260
+ }, [
261
+ objOrRef,
262
+ property,
263
+ isRef,
264
+ liveObj
265
+ ]);
266
+ if (property !== void 0) return [useObjectProperty(liveObj, property), callback];
267
+ else return [useObjectValue(objOrRef), callback];
268
+ });
269
+ /**
270
+ * Hook for subscribing to an Echo object or Ref.
271
+ */
272
+ var useObjectValue = (objOrRef) => {
273
+ return useAtomValue(useMemo(() => {
274
+ if (objOrRef == null) return Atom.make(() => void 0);
275
+ if (Ref.isRef(objOrRef)) return Obj.atom(objOrRef);
276
+ if (Obj.isSnapshot(objOrRef)) return Atom.make(() => objOrRef);
277
+ return Obj.atom(objOrRef);
278
+ }, [objOrRef]));
279
+ };
280
+ /**
281
+ * Resolves a Ref to its live target object, or returns the input when it is already an object.
282
+ * For refs, subscribes to load events only (not full mutation tracking).
283
+ */
284
+ var useResolveRef = (objOrRef) => {
285
+ return useAtomValue(useMemo(() => {
286
+ if (objOrRef == null) return Atom.make(() => void 0);
287
+ if (!Ref.isRef(objOrRef)) return Atom.make(() => objOrRef);
288
+ return objOrRef.atom;
289
+ }, [objOrRef]));
290
+ };
291
+ /**
292
+ * Internal hook for subscribing to a specific property of an Echo object.
293
+ */
294
+ var useObjectProperty = (obj, property) => {
295
+ return useAtomValue(useMemo(() => {
296
+ if (obj == null) return Atom.make(() => void 0);
297
+ if (Obj.isSnapshot(obj)) return Atom.make(() => obj[property]);
298
+ return Obj.atomProperty(obj, property);
299
+ }, [obj, property]));
300
+ };
301
+ /**
302
+ * Hook to subscribe to multiple Refs' target objects.
303
+ * Automatically dereferences each ref and handles async loading.
304
+ * Returns an array of loaded snapshots (filtering out undefined values).
305
+ *
306
+ * This hook is useful for aggregate computations like counts or filtering
307
+ * across multiple refs without using .target directly.
308
+ *
309
+ * @deprecated Subscribes the component to the whole list, re-rendering on any element's change. Prefer pushing
310
+ * the subscription down — derive the aggregate in an atom (reading each `Obj.atom(ref)`) and read it where it is
311
+ * needed, or subscribe to the individual ref/property closest to the consumer — to keep subscriptions granular.
312
+ *
313
+ * @param refs - Array of Refs to dereference and subscribe to
314
+ * @returns Array of loaded target snapshots (excludes unloaded refs)
315
+ */
316
+ var useObjects = (refs) => {
317
+ return useAtomValue(useMemo(() => Atom.make((get) => {
318
+ const results = [];
319
+ for (const ref of refs) {
320
+ const value = get(Obj.atom(ref));
321
+ if (value !== void 0) results.push(value);
322
+ }
323
+ return results;
324
+ }), [refs]));
325
+ };
326
+ //#endregion
327
+ export { useObject, useObjectValue, useObjects, usePagination, useQuery, useResolveRef, useType };
328
+
329
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/useQuery.ts","../../src/usePagination.ts","../../src/useType.ts","../../src/useObject.ts"],"sourcesContent":["//\n// Copyright 2022 DXOS.org\n//\n\nimport { useMemo, useSyncExternalStore } from 'react';\n\nimport { type Database, type Entity, Filter, Query } from '@dxos/echo';\n\nconst EMPTY_ARRAY: never[] = [];\n\nconst noop = () => {};\n\ninterface UseQueryFn {\n // Aggregate queries produce flat records branded with `Query.AggregateResult`; return them as-is,\n // never wrapped in `Entity.Entity` (they are not entities).\n <R extends Query.AggregateResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];\n\n <Q extends Query.Any, O extends Entity.Entity<Query.Type<Q>> = Entity.Entity<Query.Type<Q>>>(\n resource: Database.Queryable | undefined,\n query: Q,\n ): O[];\n\n <F extends Filter.Any, O extends Entity.Entity<Filter.Type<F>> = Entity.Entity<Filter.Type<F>>>(\n resource: Database.Queryable | undefined,\n filter: F,\n ): O[];\n}\n\n/**\n * Create subscription.\n *\n * @param queryOrFilter - The query or filter to apply. Query is memoized based on the AST. No need to call useMemo.\n */\n// The overloaded public signature returns `O[]` (entities) or the flat record `R[]` (aggregate\n// queries) depending on the query shape; the implementation forwards whatever the query result\n// produces, so — as with any implementation of a multi-signature overloaded function type — it is\n// typed as `any[]` here rather than picking one of the mutually-incompatible overload return shapes.\nexport const useQuery: UseQueryFn = (\n resource: Database.Queryable | undefined,\n queryOrFilter: Query.Any | Filter.Any,\n): any[] => {\n const query = Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter;\n\n const { getObjects, subscribe } = useMemo(() => {\n let queryResult = undefined;\n if (resource) {\n queryResult = resource.query(query);\n }\n\n let subscribed = false;\n return {\n getObjects: () => (subscribed && queryResult ? queryResult.results : EMPTY_ARRAY),\n subscribe: (cb: () => void) => {\n subscribed = true;\n const unsubscribe = queryResult?.subscribe(cb) ?? noop;\n return () => {\n unsubscribe?.();\n subscribed = false;\n };\n },\n };\n }, [resource, JSON.stringify(query.ast)]);\n\n // https://beta.reactjs.org/reference/react/useSyncExternalStore\n // NOTE: This hook will resubscribe whenever the callback passed to the first argument changes; make sure it is stable.\n const objects = useSyncExternalStore<any[]>(subscribe, getObjects);\n return objects;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { useMemo, useRef, useSyncExternalStore } from 'react';\n\nimport { type Database, type Entity, Query } from '@dxos/echo';\n\nconst EMPTY_ARRAY: never[] = [];\n\nexport type UsePaginationOptions = {\n /**\n * Max loaded items before the newest end is evicted (the window's start advances instead of\n * growing further). Defaults to 10x the query's page size.\n */\n maxWindowSize?: number;\n};\n\nexport type PaginationResult<T> = {\n /** Loaded window, in query order (newest first for `Order.natural('desc')`). */\n items: T[];\n /** Extends toward older items by one page. Single-flight; no-op while loading or exhausted. */\n getNext: () => void;\n /**\n * Extends back toward the head by one page (the inverse of `getNext`). Single-flight; no-op\n * while loading or already at the head.\n */\n getPrevious: () => void;\n /** Whether older items remain beyond what is currently loaded. Inferred from the last page's size. */\n hasMore: boolean;\n /** True until the current range delivers its first result, even an empty one. */\n isLoading: boolean;\n /** False once eviction has detached the window from the live head (i.e. skip > 0). */\n atHead: boolean;\n /** Resets to the newest page and resumes live updates. */\n jumpToHead: () => void;\n};\n\n/** Reactive snapshot handed to React by `useSyncExternalStore`; a fresh object only on real change. */\ntype Snapshot<O> = { items: O[]; skip: number; limit: number; isLoading: boolean };\n\n/**\n * The element type a paginated query yields: the flat record for an aggregate query (branded with\n * {@link Query.AggregateResult}), otherwise the query's entity type. Mirrors `useQuery`'s overloads.\n */\ntype PaginationElement<Q extends Query.Any> =\n Query.Type<Q> extends Query.AggregateResult ? Query.Type<Q> : Entity.Entity<Query.Type<Q>>;\n\n/**\n * Builds the external store backing `usePagination` for one query identity (filter/order/page\n * size/resource). Mirrors `useQuery`'s own `useMemo`-built `{ getObjects, subscribe }` pair as\n * closely as the added pagination state allows: nothing is queried and `.results` is never read\n * until `subscribe()` is called (i.e. from `useSyncExternalStore`'s commit-safe effect, never\n * during render). `getSnapshot` returns the last *delivered* results (`displayItems`), which are\n * only replaced once a newly-pointed range actually produces a result -- so the previous page stays\n * visible (rather than flashing empty) while an async range loads. `QueryResultImpl` still owns\n * recompute/dedup; we only retain the most recent delivered array.\n *\n * `skip`/`limit` live as plain closure state rather than React state: pagination is internal\n * bookkeeping for *which range this same identity is currently showing*, not a prop the rest of\n * the component tree needs to read independently. `getNext`/`getPrevious` re-point the query at\n * a new range by opening a fresh `QueryResult` for it, without discarding the previous one's\n * results until the new one delivers its own -- that's what keeps the previous page visible while\n * the new one loads (see the hook's own doc comment for why that matters).\n */\nconst createPaginationStore = <Q extends Query.Any, O>(\n resource: Database.Queryable | undefined,\n innerAst: Parameters<typeof Query.fromAst>[0],\n pageSize: number,\n initialMaxWindowSize: number,\n // Seed for the visible page, handed over when a query-identity change rebuilds the store, so the\n // view refreshes in place instead of flashing empty + loading (see the hook's `previousItemsRef`).\n initialItems: O[],\n) => {\n let maxWindowSize = initialMaxWindowSize;\n let skip = 0;\n let limit = pageSize;\n // Single-flight latch coalescing a burst of synchronous `getNext`/`getPrevious` calls (a\n // virtualizer's `onChange` fires once per rendered row) into one range change.\n let rangeChangePending = false;\n let innerUnsubscribe: (() => void) | undefined;\n // Held across a range change (and seeded across an identity change) until the new range delivers,\n // so the list never flickers to empty while a new (possibly async) range loads.\n let displayItems: O[] = initialItems.length > 0 ? initialItems : EMPTY_ARRAY;\n // Cleared on the range's first delivery, never on a timer, so it can't stick true on an empty feed.\n // False when seeded: a page is already shown, so this is a background refresh, not a first load.\n let isLoading = resource !== undefined && displayItems.length === 0;\n let snapshot: Snapshot<O> = { items: displayItems, skip, limit, isLoading };\n const listeners = new Set<() => void>();\n\n const notify = () => {\n snapshot = { items: displayItems, skip, limit, isLoading };\n for (const listener of listeners) {\n listener();\n }\n };\n\n // (Re)points the query at the current skip/limit. `markLoading` is true for a range the user\n // navigated to (initial load, `getNext`/`getPrevious`, `jumpToHead`); a seeded background refresh\n // passes false so the previous page stays visible and consumers don't blank on it.\n //\n // TODO(wittjosiah): For feeds, this re-fetches/decodes the whole feed on every range change --\n // only what's rendered is bounded, not what's fetched. Needs index-backed keyset pagination to\n // fix properly.\n const pointAtRange = (markLoading: boolean) => {\n innerUnsubscribe?.();\n innerUnsubscribe = undefined;\n\n if (!resource) {\n displayItems = EMPTY_ARRAY;\n isLoading = false;\n notify();\n return;\n }\n\n // `Query.fromAst` necessarily erases the entity type to `Query.Any`; the cast restores it,\n // which is sound because only `skip`/`limit` were layered on top of the caller's own query's\n // selection/filter/type -- the entity type this query selects is unchanged from `Q`.\n const effectiveQuery = Query.fromAst(innerAst).skip(skip).limit(limit) as Q;\n const nextQueryResult = resource.query(effectiveQuery);\n if (markLoading) {\n isLoading = true;\n }\n\n // Publish results and clear `isLoading` only on delivery -- deferred for an async source (a feed\n // served by the index) until its first response, which arrives even when that response is empty.\n innerUnsubscribe = nextQueryResult.subscribe(\n () => {\n displayItems = nextQueryResult.results as O[];\n isLoading = false;\n notify();\n },\n { fire: true },\n );\n\n notify();\n // The re-entrancy latch only needs to survive one tick, not the query's actual delivery.\n queueMicrotask(() => {\n rangeChangePending = false;\n });\n };\n\n return {\n subscribe: (onStoreChange: () => void) => {\n listeners.add(onStoreChange);\n if (listeners.size === 1) {\n // Seeded (background identity refresh) → don't surface loading; otherwise it's a first load.\n pointAtRange(displayItems.length === 0);\n }\n return () => {\n listeners.delete(onStoreChange);\n if (listeners.size === 0) {\n innerUnsubscribe?.();\n innerUnsubscribe = undefined;\n }\n };\n },\n getSnapshot: (): Snapshot<O> => snapshot,\n setMaxWindowSize: (value: number) => {\n maxWindowSize = value;\n },\n getNext: () => {\n if (isLoading || rangeChangePending || snapshot.items.length < limit) {\n return;\n }\n rangeChangePending = true;\n const nextLimit = limit + pageSize;\n if (nextLimit <= maxWindowSize) {\n limit = nextLimit;\n } else {\n limit = maxWindowSize;\n skip += pageSize;\n }\n pointAtRange(true);\n },\n getPrevious: () => {\n if (isLoading || rangeChangePending || skip === 0) {\n return;\n }\n rangeChangePending = true;\n // Slides the window toward the head at constant size (limit unchanged) -- decreasing skip\n // while holding limit fixed both reveals newer items at the front and drops an equal count\n // of the oldest loaded items, mirroring getNext's own \"slide\" branch (taken once it hits\n // maxWindowSize) in the opposite direction.\n skip = Math.max(0, skip - pageSize);\n pointAtRange(true);\n },\n jumpToHead: () => {\n rangeChangePending = false;\n skip = 0;\n limit = pageSize;\n pointAtRange(true);\n },\n };\n};\n\n/**\n * Wraps a query with page management for paginated reads. The query's own `.limit(n)` clause is\n * the page size -- the hook manages which page is loaded by rewriting `skip`/`limit` on top of\n * it; the query must not carry its own `.skip()`.\n *\n * The query's `.orderBy(...)` is otherwise untouched, and applies to any query the underlying\n * `Database.Queryable` supports -- e.g. `Order.natural('desc')` for feed insertion order.\n *\n * Keeps showing the previous page while a new one loads -- across both a `skip`/`limit` move and a\n * query-identity change (filter/order/page-size/resource). The latter rebuilds the internal store;\n * the last page is handed to the new store as its seed so the view refreshes in place instead of\n * flashing empty, and `isLoading` stays put (a background refresh isn't a load the user asked for).\n *\n * @example\n * ```tsx\n * const { items, getNext, hasMore, isLoading, atHead, jumpToHead } = usePagination(\n * db,\n * Query.select(Filter.type(Message.Message)).from(feed).orderBy(Order.natural('desc')).limit(50),\n * );\n * ```\n */\nexport const usePagination = <Q extends Query.Any>(\n resource: Database.Queryable | undefined,\n query: Q,\n options?: UsePaginationOptions,\n): PaginationResult<PaginationElement<Q>> => {\n if (query.ast.type !== 'limit') {\n throw new TypeError('usePagination requires the query to carry .limit(pageSize).');\n }\n if (query.ast.query.type === 'skip') {\n throw new TypeError('usePagination manages .skip() internally -- do not include it in the query.');\n }\n const pageSize = query.ast.limit;\n const innerAst = query.ast.query;\n const innerAstKey = JSON.stringify(innerAst);\n const maxWindowSize = options?.maxWindowSize ?? pageSize * 10;\n\n // The page shown right now, carried across a store rebuild to seed the new store. Written just\n // after the snapshot below, so the next render's `useMemo` sees the previously-displayed page.\n const previousItemsRef = useRef<PaginationElement<Q>[]>(EMPTY_ARRAY);\n\n // A new store is built when \"what\" is being shown changes: filter/order (`innerAstKey`), page\n // size, or `resource` (matching `useQuery`, which likewise keys its memo on `resource` directly\n // rather than just its truthiness). It is seeded from the previous store's page (see above).\n const store = useMemo(\n () =>\n createPaginationStore<Q, PaginationElement<Q>>(\n resource,\n innerAst,\n pageSize,\n maxWindowSize,\n previousItemsRef.current,\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [resource, innerAstKey, pageSize],\n );\n // `maxWindowSize` deliberately isn't part of the store's `useMemo` deps above (a caller passing\n // a literal default each render shouldn't reset the window), but a genuine change should still\n // take effect immediately rather than only on the next unrelated identity change.\n store.setMaxWindowSize(maxWindowSize);\n\n const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot);\n previousItemsRef.current = snapshot.items;\n\n // Stable across renders that don't change `snapshot` (`store`'s own methods are already stable\n // per store identity) -- callers can pass this straight through to a memoized child (e.g. a\n // virtualizer's pagination callbacks) without re-destructuring and re-bundling it themselves.\n return useMemo(\n () => ({\n items: snapshot.items,\n getNext: store.getNext,\n getPrevious: store.getPrevious,\n hasMore: snapshot.items.length >= snapshot.limit,\n isLoading: snapshot.isLoading,\n atHead: snapshot.skip === 0,\n jumpToHead: store.jumpToHead,\n }),\n [snapshot, store],\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useMemo, useSyncExternalStore } from 'react';\n\nimport { type Database, DXN, EID, Filter, Query, Scope, Type, URI } from '@dxos/echo';\n\n/**\n * Subscribe to and retrieve a type by its URI from a space: a static schema's typename DXN, or a\n * persisted (database) schema's `echo:` EID (what `Type.getURI` / `getTypeURIFromQuery` produce).\n *\n * Fans across the owning space db (persisted custom types) and the shared\n * registry (static/runtime plugin types). Persisted types live only in the db,\n * so a registry-only lookup misses them.\n *\n * DXN matching is version-agnostic: `dxn:com.example/Foo` matches `dxn:com.example/Foo:0.1.0`.\n * This lets callers pass a bare typename DXN (no version) from e.g. a `ReferenceAnnotation`.\n */\nexport const useType = <T extends Type.AnyEntity = Type.AnyEntity>(\n db?: Database.Database,\n typeUri?: URI.URI,\n): T | undefined => {\n const { subscribe, getType } = useMemo(() => {\n if (!typeUri || !db) {\n return {\n subscribe: () => () => {},\n getType: (): T | undefined => undefined,\n };\n }\n\n const searchEid = EID.isEID(typeUri) ? EID.tryParse(typeUri) : undefined;\n const searchDxn = DXN.isDXN(typeUri) ? DXN.tryMake(typeUri) : undefined;\n\n const queryResult = db.query(Query.select(Filter.type(Type.Type)).from(Scope.space(), Scope.registry()));\n let subscribed = false;\n const find = (): T | undefined => {\n if (!subscribed) {\n return undefined;\n }\n return queryResult.results.find((type) => {\n const uri = Type.getURI(type);\n if (uri === typeUri) {\n return true;\n }\n // EID matching is space-agnostic: echo:/<id> matches echo://<space>/<id>.\n if (searchEid && EID.isEID(uri)) {\n const typeEid = EID.tryParse(uri);\n return typeEid != null && EID.getEntityId(typeEid) === EID.getEntityId(searchEid);\n }\n // DXN matching is version-agnostic so callers may pass an unversioned DXN.\n if (searchDxn && DXN.isDXN(uri)) {\n const typeDxn = DXN.tryMake(uri);\n return typeDxn != null && DXN.getName(typeDxn) === DXN.getName(searchDxn);\n }\n return false;\n }) as T | undefined;\n };\n\n return {\n subscribe: (onStoreChange: () => void) => {\n subscribed = true;\n const unsubscribe = queryResult.subscribe(onStoreChange);\n return () => {\n unsubscribe();\n subscribed = false;\n };\n },\n getType: find,\n };\n }, [typeUri, db]);\n\n return useSyncExternalStore(subscribe, getType);\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect-atom/atom-react';\nimport * as Atom from '@effect-atom/atom/Atom';\nimport { useCallback, useMemo } from 'react';\n\nimport { Obj, Ref } from '@dxos/echo';\n\nexport interface ObjectUpdateCallback<T> {\n (update: (obj: Obj.Mutable<T>) => void): void;\n (update: (obj: Obj.Mutable<T>) => Obj.Mutable<T>): void;\n}\n\nexport interface ObjectPropUpdateCallback<T> {\n (update: (value: Obj.Mutable<T>) => void): void;\n (update: (value: Obj.Mutable<T>) => Obj.Mutable<T>): void;\n (newValue: T): void;\n}\n\nexport const useObject: {\n /**\n * Hook to subscribe to a Ref's target object.\n * Automatically dereferences the ref and handles async loading.\n * Returns a snapshot (undefined if the ref hasn't loaded yet).\n * Re-renders the component when the ref resolves or the target object changes.\n *\n * @param ref - The Ref to dereference and subscribe to\n * @returns The current target snapshot (or undefined if not loaded) and update callback\n *\n * @idiom org.dxos.echo-react.useObjectReactive\n * applies: Reading a ref's target (or a specific property) inside a React component — establishes a reactive subscription so the component re-renders when the ref resolves or the value changes\n * instead-of: `ref.target` — synchronous and not reactive; returns `undefined` when the target isn't loaded yet and never triggers a re-render when it becomes available\n * uses: {@link useObject}\n * related: org.dxos.echo.objAtomReactive, org.dxos.echo.refLoad\n */\n <T extends Obj.Unknown>(ref: Ref.Ref<T>): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];\n\n /**\n * Hook to subscribe to a Ref's target object that may be undefined.\n * Returns a snapshot (undefined if the ref is undefined or hasn't loaded yet).\n *\n * @param ref - The Ref to dereference and subscribe to (can be undefined)\n * @returns The current target snapshot (or undefined) and update callback\n */\n <T extends Obj.Unknown>(ref: Ref.Ref<T> | undefined): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];\n\n /**\n * Hook to subscribe to an entire Echo object.\n * Returns a snapshot of the current object value and automatically re-renders when the object changes.\n *\n * @param obj - The Echo object to subscribe to (objects only, not relations)\n * @returns The current object snapshot and update callback\n */\n <T extends Obj.Unknown>(obj: T): [Obj.Snapshot<T>, ObjectUpdateCallback<T>];\n\n /**\n * Hook to subscribe to an entire Echo object that may be undefined.\n * Returns a snapshot (undefined if the object is undefined).\n *\n * @param obj - The Echo object to subscribe to (can be undefined, objects only)\n * @returns The current object snapshot (or undefined) and update callback\n */\n <T extends Obj.Unknown>(obj: T | undefined): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];\n\n /**\n * Hook to subscribe to an Echo object or Ref.\n * Handles both cases - if passed a Ref, dereferences it and subscribes to the target.\n * Returns a snapshot (undefined if ref hasn't loaded).\n *\n * @param objOrRef - The Echo object or Ref to subscribe to\n * @returns The current object snapshot (or undefined) and update callback\n */\n <T extends Obj.Unknown>(objOrRef: T | Ref.Ref<T> | undefined): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];\n\n /**\n * Hook to subscribe to a specific property of an Echo object.\n * Returns the current property value and automatically re-renders when the property changes.\n *\n * @param obj - The Echo object to subscribe to (objects only, not relations)\n * @param property - Property key to subscribe to\n * @returns The current property value and update callback\n */\n <T extends Obj.Unknown, K extends keyof T>(obj: T, property: K): [T[K], ObjectPropUpdateCallback<T[K]>];\n\n /**\n * Hook to subscribe to a specific property of an Echo object that may be undefined.\n * Returns undefined if the object is undefined.\n *\n * @param obj - The Echo object to subscribe to (can be undefined, objects only)\n * @param property - Property key to subscribe to\n * @returns The current property value (or undefined) and update callback\n */\n <T extends Obj.Unknown, K extends keyof T>(\n obj: T | undefined,\n property: K,\n ): [T[K] | undefined, ObjectPropUpdateCallback<T[K]>];\n\n /**\n * Hook to subscribe to a specific property of a Ref's target object.\n * Automatically dereferences the ref and handles async loading.\n * Returns undefined if the ref hasn't loaded yet.\n *\n * @param ref - The Ref to dereference and subscribe to\n * @param property - Property key to subscribe to\n * @returns The current property value (or undefined if not loaded) and update callback\n */\n <T, K extends keyof T>(ref: Ref.Ref<T>, property: K): [T[K] | undefined, ObjectPropUpdateCallback<T[K]>];\n\n /**\n * Hook to subscribe to a specific property of a Ref's target object that may be undefined.\n * Returns undefined if the ref is undefined or hasn't loaded yet.\n *\n * @param ref - The Ref to dereference and subscribe to (can be undefined)\n * @param property - Property key to subscribe to\n * @returns The current property value (or undefined) and update callback\n */\n <T, K extends keyof T>(ref: Ref.Ref<T> | undefined, property: K): [T[K] | undefined, ObjectPropUpdateCallback<T[K]>];\n} = (<T extends Obj.Unknown, K extends keyof T>(objOrRef: T | Ref.Ref<T> | undefined, property?: K): any => {\n const isRef = Ref.isRef(objOrRef);\n const liveObj = useResolveRef(objOrRef);\n\n const callback: ObjectPropUpdateCallback<unknown> = useCallback(\n (updateOrValue: unknown | ((obj: unknown) => unknown)) => {\n // Get current target for refs (may have loaded since render).\n const obj = isRef ? (objOrRef as Ref.Ref<T>)?.target : liveObj;\n if (obj === undefined) {\n return;\n }\n Obj.update(obj, (obj: any) => {\n if (typeof updateOrValue === 'function') {\n const returnValue = updateOrValue(property !== undefined ? obj[property] : obj);\n if (returnValue !== undefined) {\n if (property === undefined) {\n throw new Error('Cannot re-assign the entire object');\n }\n obj[property] = returnValue;\n }\n } else {\n if (property === undefined) {\n throw new Error('Cannot re-assign the entire object');\n }\n obj[property] = updateOrValue;\n }\n });\n },\n [objOrRef, property, isRef, liveObj],\n );\n\n if (property !== undefined) {\n return [useObjectProperty(liveObj, property), callback];\n } else {\n return [useObjectValue(objOrRef), callback];\n }\n}) as any;\n\n/**\n * Hook for subscribing to an Echo object or Ref.\n */\nexport const useObjectValue = <T extends Obj.Unknown | Obj.Snapshot>(\n objOrRef: T | Ref.Ref<T> | undefined,\n): T extends Obj.Snapshot ? T : Obj.Snapshot<T & Obj.Unknown> | undefined => {\n const atom = useMemo(() => {\n if (objOrRef == null) {\n return Atom.make<Obj.Snapshot<T & Obj.Unknown> | undefined>(() => undefined);\n }\n if (Ref.isRef(objOrRef)) {\n return Obj.atom(objOrRef);\n }\n if (Obj.isSnapshot(objOrRef)) {\n return Atom.make<T>(() => objOrRef);\n }\n return Obj.atom(objOrRef as T & Obj.Unknown);\n }, [objOrRef]);\n return useAtomValue(atom as any);\n};\n\n/**\n * Resolves a Ref to its live target object, or returns the input when it is already an object.\n * For refs, subscribes to load events only (not full mutation tracking).\n */\nexport const useResolveRef = <T extends Obj.Unknown>(objOrRef: T | Ref.Ref<T> | undefined): T | undefined => {\n const atom = useMemo(() => {\n if (objOrRef == null) {\n return Atom.make<T | undefined>(() => undefined);\n }\n if (!Ref.isRef(objOrRef)) {\n return Atom.make<T | undefined>(() => objOrRef as T);\n }\n return objOrRef.atom;\n }, [objOrRef]);\n return useAtomValue(atom);\n};\n\n/**\n * Internal hook for subscribing to a specific property of an Echo object.\n */\nconst useObjectProperty = <T extends Obj.Unknown | Obj.Snapshot, K extends keyof T>(\n obj: T | undefined,\n property: K,\n): T[K] | undefined => {\n const atom = useMemo(() => {\n if (obj == null) {\n return Atom.make<T[K] | undefined>(() => undefined);\n }\n if (Obj.isSnapshot(obj)) {\n return Atom.make<T[K]>(() => obj[property]);\n }\n return Obj.atomProperty(obj as T & Obj.Unknown, property);\n }, [obj, property]);\n return useAtomValue(atom);\n};\n\n/**\n * Hook to subscribe to multiple Refs' target objects.\n * Automatically dereferences each ref and handles async loading.\n * Returns an array of loaded snapshots (filtering out undefined values).\n *\n * This hook is useful for aggregate computations like counts or filtering\n * across multiple refs without using .target directly.\n *\n * @deprecated Subscribes the component to the whole list, re-rendering on any element's change. Prefer pushing\n * the subscription down — derive the aggregate in an atom (reading each `Obj.atom(ref)`) and read it where it is\n * needed, or subscribe to the individual ref/property closest to the consumer — to keep subscriptions granular.\n *\n * @param refs - Array of Refs to dereference and subscribe to\n * @returns Array of loaded target snapshots (excludes unloaded refs)\n */\nexport const useObjects = <T extends Obj.Unknown>(refs: readonly Ref.Ref<T>[]): Obj.Snapshot<T>[] => {\n const atom = useMemo(\n () =>\n Atom.make((get) => {\n const results: Obj.Snapshot<T>[] = [];\n for (const ref of refs) {\n const value = get(Obj.atom(ref));\n if (value !== undefined) {\n results.push(value as Obj.Snapshot<T>);\n }\n }\n return results;\n }),\n [refs],\n );\n return useAtomValue(atom);\n};\n"],"mappings":";;;;;AAQA,IAAM,gBAAuB,CAAC;AAE9B,IAAM,aAAa,CAAC;;;;;;AA2BpB,IAAa,YACX,UACA,kBACU;CACV,MAAM,QAAQ,OAAO,GAAG,aAAa,IAAI,MAAM,OAAO,aAAa,IAAI;CAEvE,MAAM,EAAE,YAAY,cAAc,cAAc;EAC9C,IAAI,cAAc,KAAA;EAClB,IAAI,UACF,cAAc,SAAS,MAAM,KAAK;EAGpC,IAAI,aAAa;EACjB,OAAO;GACL,kBAAmB,cAAc,cAAc,YAAY,UAAU;GACrE,YAAY,OAAmB;IAC7B,aAAa;IACb,MAAM,cAAc,aAAa,UAAU,EAAE,KAAK;IAClD,aAAa;KACX,cAAc;KACd,aAAa;IACf;GACF;EACF;CACF,GAAG,CAAC,UAAU,KAAK,UAAU,MAAM,GAAG,CAAC,CAAC;CAKxC,OADgB,qBAA4B,WAAW,UAChD;AACT;;;AC3DA,IAAM,cAAuB,CAAC;;;;;;;;;;;;;;;;;;AAyD9B,IAAM,yBACJ,UACA,UACA,UACA,sBAGA,iBACG;CACH,IAAI,gBAAgB;CACpB,IAAI,OAAO;CACX,IAAI,QAAQ;CAGZ,IAAI,qBAAqB;CACzB,IAAI;CAGJ,IAAI,eAAoB,aAAa,SAAS,IAAI,eAAe;CAGjE,IAAI,YAAY,aAAa,KAAA,KAAa,aAAa,WAAW;CAClE,IAAI,WAAwB;EAAE,OAAO;EAAc;EAAM;EAAO;CAAU;CAC1E,MAAM,4BAAY,IAAI,IAAgB;CAEtC,MAAM,eAAe;EACnB,WAAW;GAAE,OAAO;GAAc;GAAM;GAAO;EAAU;EACzD,KAAK,MAAM,YAAY,WACrB,SAAS;CAEb;CASA,MAAM,gBAAgB,gBAAyB;EAC7C,mBAAmB;EACnB,mBAAmB,KAAA;EAEnB,IAAI,CAAC,UAAU;GACb,eAAe;GACf,YAAY;GACZ,OAAO;GACP;EACF;EAKA,MAAM,iBAAiB,MAAM,QAAQ,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,KAAK;EACrE,MAAM,kBAAkB,SAAS,MAAM,cAAc;EACrD,IAAI,aACF,YAAY;EAKd,mBAAmB,gBAAgB,gBAC3B;GACJ,eAAe,gBAAgB;GAC/B,YAAY;GACZ,OAAO;EACT,GACA,EAAE,MAAM,KAAK,CACf;EAEA,OAAO;EAEP,qBAAqB;GACnB,qBAAqB;EACvB,CAAC;CACH;CAEA,OAAO;EACL,YAAY,kBAA8B;GACxC,UAAU,IAAI,aAAa;GAC3B,IAAI,UAAU,SAAS,GAErB,aAAa,aAAa,WAAW,CAAC;GAExC,aAAa;IACX,UAAU,OAAO,aAAa;IAC9B,IAAI,UAAU,SAAS,GAAG;KACxB,mBAAmB;KACnB,mBAAmB,KAAA;IACrB;GACF;EACF;EACA,mBAAgC;EAChC,mBAAmB,UAAkB;GACnC,gBAAgB;EAClB;EACA,eAAe;GACb,IAAI,aAAa,sBAAsB,SAAS,MAAM,SAAS,OAC7D;GAEF,qBAAqB;GACrB,MAAM,YAAY,QAAQ;GAC1B,IAAI,aAAa,eACf,QAAQ;QACH;IACL,QAAQ;IACR,QAAQ;GACV;GACA,aAAa,IAAI;EACnB;EACA,mBAAmB;GACjB,IAAI,aAAa,sBAAsB,SAAS,GAC9C;GAEF,qBAAqB;GAKrB,OAAO,KAAK,IAAI,GAAG,OAAO,QAAQ;GAClC,aAAa,IAAI;EACnB;EACA,kBAAkB;GAChB,qBAAqB;GACrB,OAAO;GACP,QAAQ;GACR,aAAa,IAAI;EACnB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,iBACX,UACA,OACA,YAC2C;CAC3C,IAAI,MAAM,IAAI,SAAS,SACrB,MAAM,IAAI,UAAU,6DAA6D;CAEnF,IAAI,MAAM,IAAI,MAAM,SAAS,QAC3B,MAAM,IAAI,UAAU,6EAA6E;CAEnG,MAAM,WAAW,MAAM,IAAI;CAC3B,MAAM,WAAW,MAAM,IAAI;CAC3B,MAAM,cAAc,KAAK,UAAU,QAAQ;CAC3C,MAAM,gBAAgB,SAAS,iBAAiB,WAAW;CAI3D,MAAM,mBAAmB,OAA+B,WAAW;CAKnE,MAAM,QAAQ,cAEV,sBACE,UACA,UACA,UACA,eACA,iBAAiB,OACnB,GAEF;EAAC;EAAU;EAAa;CAAQ,CAClC;CAIA,MAAM,iBAAiB,aAAa;CAEpC,MAAM,WAAW,qBAAqB,MAAM,WAAW,MAAM,WAAW;CACxE,iBAAiB,UAAU,SAAS;CAKpC,OAAO,eACE;EACL,OAAO,SAAS;EAChB,SAAS,MAAM;EACf,aAAa,MAAM;EACnB,SAAS,SAAS,MAAM,UAAU,SAAS;EAC3C,WAAW,SAAS;EACpB,QAAQ,SAAS,SAAS;EAC1B,YAAY,MAAM;CACpB,IACA,CAAC,UAAU,KAAK,CAClB;AACF;;;;;;;;;;;;;;AChQA,IAAa,WACX,IACA,YACkB;CAClB,MAAM,EAAE,WAAW,YAAY,cAAc;EAC3C,IAAI,CAAC,WAAW,CAAC,IACf,OAAO;GACL,uBAAuB,CAAC;GACxB,eAA8B,KAAA;EAChC;EAGF,MAAM,YAAY,IAAI,MAAM,OAAO,IAAI,IAAI,SAAS,OAAO,IAAI,KAAA;EAC/D,MAAM,YAAY,IAAI,MAAM,OAAO,IAAI,IAAI,QAAQ,OAAO,IAAI,KAAA;EAE9D,MAAM,cAAc,GAAG,MAAM,MAAM,OAAO,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,CAAC;EACvG,IAAI,aAAa;EACjB,MAAM,aAA4B;GAChC,IAAI,CAAC,YACH;GAEF,OAAO,YAAY,QAAQ,MAAM,SAAS;IACxC,MAAM,MAAM,KAAK,OAAO,IAAI;IAC5B,IAAI,QAAQ,SACV,OAAO;IAGT,IAAI,aAAa,IAAI,MAAM,GAAG,GAAG;KAC/B,MAAM,UAAU,IAAI,SAAS,GAAG;KAChC,OAAO,WAAW,QAAQ,IAAI,YAAY,OAAO,MAAM,IAAI,YAAY,SAAS;IAClF;IAEA,IAAI,aAAa,IAAI,MAAM,GAAG,GAAG;KAC/B,MAAM,UAAU,IAAI,QAAQ,GAAG;KAC/B,OAAO,WAAW,QAAQ,IAAI,QAAQ,OAAO,MAAM,IAAI,QAAQ,SAAS;IAC1E;IACA,OAAO;GACT,CAAC;EACH;EAEA,OAAO;GACL,YAAY,kBAA8B;IACxC,aAAa;IACb,MAAM,cAAc,YAAY,UAAU,aAAa;IACvD,aAAa;KACX,YAAY;KACZ,aAAa;IACf;GACF;GACA,SAAS;EACX;CACF,GAAG,CAAC,SAAS,EAAE,CAAC;CAEhB,OAAO,qBAAqB,WAAW,OAAO;AAChD;;;ACpDA,IAAa,cAkGmC,UAAsC,aAAsB;CAC1G,MAAM,QAAQ,IAAI,MAAM,QAAQ;CAChC,MAAM,UAAU,cAAc,QAAQ;CAEtC,MAAM,WAA8C,aACjD,kBAAyD;EAExD,MAAM,MAAM,QAAS,UAAyB,SAAS;EACvD,IAAI,QAAQ,KAAA,GACV;EAEF,IAAI,OAAO,MAAM,QAAa;GAC5B,IAAI,OAAO,kBAAkB,YAAY;IACvC,MAAM,cAAc,cAAc,aAAa,KAAA,IAAY,IAAI,YAAY,GAAG;IAC9E,IAAI,gBAAgB,KAAA,GAAW;KAC7B,IAAI,aAAa,KAAA,GACf,MAAM,IAAI,MAAM,oCAAoC;KAEtD,IAAI,YAAY;IAClB;GACF,OAAO;IACL,IAAI,aAAa,KAAA,GACf,MAAM,IAAI,MAAM,oCAAoC;IAEtD,IAAI,YAAY;GAClB;EACF,CAAC;CACH,GACA;EAAC;EAAU;EAAU;EAAO;CAAO,CACrC;CAEA,IAAI,aAAa,KAAA,GACf,OAAO,CAAC,kBAAkB,SAAS,QAAQ,GAAG,QAAQ;MAEtD,OAAO,CAAC,eAAe,QAAQ,GAAG,QAAQ;AAE9C;;;;AAKA,IAAa,kBACX,aAC2E;CAa3E,OAAO,aAZM,cAAc;EACzB,IAAI,YAAY,MACd,OAAO,KAAK,WAAsD,KAAA,CAAS;EAE7E,IAAI,IAAI,MAAM,QAAQ,GACpB,OAAO,IAAI,KAAK,QAAQ;EAE1B,IAAI,IAAI,WAAW,QAAQ,GACzB,OAAO,KAAK,WAAc,QAAQ;EAEpC,OAAO,IAAI,KAAK,QAA2B;CAC7C,GAAG,CAAC,QAAQ,CACQ,CAAW;AACjC;;;;;AAMA,IAAa,iBAAwC,aAAwD;CAU3G,OAAO,aATM,cAAc;EACzB,IAAI,YAAY,MACd,OAAO,KAAK,WAA0B,KAAA,CAAS;EAEjD,IAAI,CAAC,IAAI,MAAM,QAAQ,GACrB,OAAO,KAAK,WAA0B,QAAa;EAErD,OAAO,SAAS;CAClB,GAAG,CAAC,QAAQ,CACQ,CAAI;AAC1B;;;;AAKA,IAAM,qBACJ,KACA,aACqB;CAUrB,OAAO,aATM,cAAc;EACzB,IAAI,OAAO,MACT,OAAO,KAAK,WAA6B,KAAA,CAAS;EAEpD,IAAI,IAAI,WAAW,GAAG,GACpB,OAAO,KAAK,WAAiB,IAAI,SAAS;EAE5C,OAAO,IAAI,aAAa,KAAwB,QAAQ;CAC1D,GAAG,CAAC,KAAK,QAAQ,CACG,CAAI;AAC1B;;;;;;;;;;;;;;;;AAiBA,IAAa,cAAqC,SAAmD;CAenG,OAAO,aAdM,cAET,KAAK,MAAM,QAAQ;EACjB,MAAM,UAA6B,CAAC;EACpC,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,QAAQ,IAAI,IAAI,KAAK,GAAG,CAAC;GAC/B,IAAI,UAAU,KAAA,GACZ,QAAQ,KAAK,KAAwB;EAEzC;EACA,OAAO;CACT,CAAC,GACH,CAAC,IAAI,CAEa,CAAI;AAC1B"}
@@ -1,4 +1,5 @@
1
1
  export * from './useQuery';
2
+ export * from './usePagination';
2
3
  export * from './useType';
3
4
  export * from './useObject';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
@@ -96,6 +96,15 @@ export declare const useObject: {
96
96
  */
97
97
  <T, K extends keyof T>(ref: Ref.Ref<T> | undefined, property: K): [T[K] | undefined, ObjectPropUpdateCallback<T[K]>];
98
98
  };
99
+ /**
100
+ * Hook for subscribing to an Echo object or Ref.
101
+ */
102
+ export declare const useObjectValue: <T extends Obj.Unknown | Obj.Snapshot>(objOrRef: T | Ref.Ref<T> | undefined) => T extends Obj.Snapshot ? T : Obj.Snapshot<T & Obj.Unknown> | undefined;
103
+ /**
104
+ * Resolves a Ref to its live target object, or returns the input when it is already an object.
105
+ * For refs, subscribes to load events only (not full mutation tracking).
106
+ */
107
+ export declare const useResolveRef: <T extends Obj.Unknown>(objOrRef: T | Ref.Ref<T> | undefined) => T | undefined;
99
108
  /**
100
109
  * Hook to subscribe to multiple Refs' target objects.
101
110
  * Automatically dereferences each ref and handles async loading.
@@ -1 +1 @@
1
- {"version":3,"file":"useObject.d.ts","sourceRoot":"","sources":["../../../src/useObject.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useObject.d.ts","sourceRoot":"","sources":["../../../src/useObject.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { type Database, type Entity, Query } from '@dxos/echo';
2
+ export type UsePaginationOptions = {
3
+ /**
4
+ * Max loaded items before the newest end is evicted (the window's start advances instead of
5
+ * growing further). Defaults to 10x the query's page size.
6
+ */
7
+ maxWindowSize?: number;
8
+ };
9
+ export type PaginationResult<T> = {
10
+ /** Loaded window, in query order (newest first for `Order.natural('desc')`). */
11
+ items: T[];
12
+ /** Extends toward older items by one page. Single-flight; no-op while loading or exhausted. */
13
+ getNext: () => void;
14
+ /**
15
+ * Extends back toward the head by one page (the inverse of `getNext`). Single-flight; no-op
16
+ * while loading or already at the head.
17
+ */
18
+ getPrevious: () => void;
19
+ /** Whether older items remain beyond what is currently loaded. Inferred from the last page's size. */
20
+ hasMore: boolean;
21
+ /** True until the current range delivers its first result, even an empty one. */
22
+ isLoading: boolean;
23
+ /** False once eviction has detached the window from the live head (i.e. skip > 0). */
24
+ atHead: boolean;
25
+ /** Resets to the newest page and resumes live updates. */
26
+ jumpToHead: () => void;
27
+ };
28
+ /**
29
+ * The element type a paginated query yields: the flat record for an aggregate query (branded with
30
+ * {@link Query.AggregateResult}), otherwise the query's entity type. Mirrors `useQuery`'s overloads.
31
+ */
32
+ type PaginationElement<Q extends Query.Any> = Query.Type<Q> extends Query.AggregateResult ? Query.Type<Q> : Entity.Entity<Query.Type<Q>>;
33
+ /**
34
+ * Wraps a query with page management for paginated reads. The query's own `.limit(n)` clause is
35
+ * the page size -- the hook manages which page is loaded by rewriting `skip`/`limit` on top of
36
+ * it; the query must not carry its own `.skip()`.
37
+ *
38
+ * The query's `.orderBy(...)` is otherwise untouched, and applies to any query the underlying
39
+ * `Database.Queryable` supports -- e.g. `Order.natural('desc')` for feed insertion order.
40
+ *
41
+ * Keeps showing the previous page while a new one loads -- across both a `skip`/`limit` move and a
42
+ * query-identity change (filter/order/page-size/resource). The latter rebuilds the internal store;
43
+ * the last page is handed to the new store as its seed so the view refreshes in place instead of
44
+ * flashing empty, and `isLoading` stays put (a background refresh isn't a load the user asked for).
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * const { items, getNext, hasMore, isLoading, atHead, jumpToHead } = usePagination(
49
+ * db,
50
+ * Query.select(Filter.type(Message.Message)).from(feed).orderBy(Order.natural('desc')).limit(50),
51
+ * );
52
+ * ```
53
+ */
54
+ export declare const usePagination: <Q extends Query.Any>(resource: Database.Queryable | undefined, query: Q, options?: UsePaginationOptions) => PaginationResult<PaginationElement<Q>>;
55
+ export {};
56
+ //# sourceMappingURL=usePagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePagination.d.ts","sourceRoot":"","sources":["../../../src/usePagination.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAI/D,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,gFAAgF;IAChF,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,+FAA+F;IAC/F,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,sGAAsG;IACtG,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,SAAS,EAAE,OAAO,CAAC;IACnB,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAC;IAChB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAKF;;;GAGG;AACH,KAAK,iBAAiB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,IACxC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAsJ7F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,KAAK,CAAC,GAAG,YACrC,QAAQ,CAAC,SAAS,GAAG,SAAS,SACjC,CAAC,YACE,oBAAoB,KAC7B,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAsDvC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=usePagination.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePagination.test.d.ts","sourceRoot":"","sources":["../../../src/usePagination.test.tsx"],"names":[],"mappings":""}
@@ -1,5 +1,6 @@
1
1
  import { type Database, type Entity, Filter, Query } from '@dxos/echo';
2
2
  interface UseQueryFn {
3
+ <R extends Query.AggregateResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];
3
4
  <Q extends Query.Any, O extends Entity.Entity<Query.Type<Q>> = Entity.Entity<Query.Type<Q>>>(resource: Database.Queryable | undefined, query: Q): O[];
4
5
  <F extends Filter.Any, O extends Entity.Entity<Filter.Type<F>> = Entity.Entity<Filter.Type<F>>>(resource: Database.Queryable | undefined, filter: F): O[];
5
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../../../src/useQuery.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMvE,UAAU,UAAU;IAClB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EACzF,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EACxC,KAAK,EAAE,CAAC,GACP,CAAC,EAAE,CAAC;IAEP,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAC5F,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EACxC,MAAM,EAAE,CAAC,GACR,CAAC,EAAE,CAAC;CACR;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,UA8BtB,CAAC"}
1
+ {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../../../src/useQuery.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMvE,UAAU,UAAU;IAGlB,CAAC,CAAC,SAAS,KAAK,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAExG,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EACzF,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EACxC,KAAK,EAAE,CAAC,GACP,CAAC,EAAE,CAAC;IAEP,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAC5F,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EACxC,MAAM,EAAE,CAAC,GACR,CAAC,EAAE,CAAC;CACR;AAED;;;;GAIG;AAKH,eAAO,MAAM,QAAQ,EAAE,UA8BtB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useQuery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQuery.test.d.ts","sourceRoot":"","sources":["../../../src/useQuery.test.tsx"],"names":[],"mappings":""}