@dxos/echo-react 0.14.0 → 0.16.0-next-60712ab191a99ba90316cc4be753e9e11b17100a

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.
@@ -1,38 +1,118 @@
1
- import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
2
- import { DXN, EID, Filter, Obj, Query, Ref, Scope, Type } from "@dxos/echo";
3
1
  import { useAtomValue } from "@effect/atom-react/Hooks";
4
2
  import * as Atom from "effect/reactivity/Atom";
5
- //#region src/useQuery.ts
6
- var EMPTY_ARRAY$1 = [];
7
- var noop = () => {};
3
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
4
+ import { DXN, EID, Entity, Filter, Obj, Query, Ref, Scope, Type } from "@dxos/echo";
5
+ //#region src/useLabel.ts
8
6
  /**
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.
7
+ * The label of an ECHO entity, re-rendering only when the label string changes.
8
+ * A snapshot is read as is; a live entity subscribes through `Entity.labelAtom`.
12
9
  */
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
- };
10
+ var useLabel = (entity, options) => {
11
+ const atom = useMemo(() => entity && Entity.isEntity(entity) ? Entity.labelAtom(entity) : Atom.make(() => entity ? Entity.getLabel(entity) : void 0), [entity]);
12
+ return useAtomValue(atom) ?? (options?.fallback === "typename" && entity ? Entity.getTypename(entity) : void 0);
13
+ };
14
+ /**
15
+ * The labels of a list of live ECHO entities, keyed by entity.
16
+ * Re-renders when the list or any label string changes.
17
+ */
18
+ var useLabels = (entities) => useAtomValue(useMemo(() => Atom.make((get) => new Map(entities.map((entity) => [entity, get(Entity.labelAtom(entity))]))), [entities]));
19
+ //#endregion
20
+ //#region src/useObject.ts
21
+ var useObject = ((objOrRef, propertyOrOptions) => {
22
+ const isOptions = typeof propertyOrOptions === "object" && propertyOrOptions !== null;
23
+ const property = isOptions ? void 0 : propertyOrOptions;
24
+ const options = isOptions ? propertyOrOptions : void 0;
25
+ const isRef = Ref.isRef(objOrRef);
26
+ const liveObj = useResolveRef(objOrRef);
27
+ const callback = useCallback((updateOrValue) => {
28
+ const obj = isRef ? objOrRef?.target : liveObj;
29
+ if (obj === void 0) return;
30
+ Obj.update(obj, (obj) => {
31
+ if (typeof updateOrValue === "function") {
32
+ const returnValue = updateOrValue(property !== void 0 ? obj[property] : obj);
33
+ if (returnValue !== void 0) {
34
+ if (property === void 0) throw new Error("Cannot re-assign the entire object");
35
+ obj[property] = returnValue;
36
+ }
37
+ } else {
38
+ if (property === void 0) throw new Error("Cannot re-assign the entire object");
39
+ obj[property] = updateOrValue;
28
40
  }
29
- };
30
- }, [resource, JSON.stringify(query.ast)]);
31
- return useSyncExternalStore(subscribe, getObjects);
41
+ });
42
+ }, [
43
+ objOrRef,
44
+ property,
45
+ isRef,
46
+ liveObj
47
+ ]);
48
+ if (property !== void 0) return [useObjectProperty(liveObj, property), callback];
49
+ else return [useObjectValue(objOrRef, options), callback];
50
+ });
51
+ /**
52
+ * Hook for subscribing to an Echo object or Ref.
53
+ */
54
+ var useObjectValue = (objOrRef, options) => {
55
+ const deleted = options?.deleted;
56
+ const atom = useMemo(() => {
57
+ if (objOrRef == null) return Atom.make(() => void 0);
58
+ if (Ref.isRef(objOrRef)) return Obj.atom(objOrRef, { deleted });
59
+ if (Obj.isSnapshot(objOrRef)) return Atom.make(() => objOrRef);
60
+ return Obj.atom(objOrRef);
61
+ }, [objOrRef, deleted]);
62
+ return useAtomValue(atom);
63
+ };
64
+ /**
65
+ * Resolves a Ref to its live target object, or returns the input when it is already an object.
66
+ * For refs, subscribes to load events only (not full mutation tracking).
67
+ */
68
+ var useResolveRef = (objOrRef) => {
69
+ const atom = useMemo(() => {
70
+ if (objOrRef == null) return Atom.make(() => void 0);
71
+ if (!Ref.isRef(objOrRef)) return Atom.make(() => objOrRef);
72
+ return objOrRef.atom;
73
+ }, [objOrRef]);
74
+ return useAtomValue(atom);
75
+ };
76
+ /**
77
+ * Internal hook for subscribing to a specific property of an Echo object.
78
+ */
79
+ var useObjectProperty = (obj, property) => {
80
+ const atom = useMemo(() => {
81
+ if (obj == null) return Atom.make(() => void 0);
82
+ if (Obj.isSnapshot(obj)) return Atom.make(() => obj[property]);
83
+ return Obj.atomProperty(obj, property);
84
+ }, [obj, property]);
85
+ return useAtomValue(atom);
86
+ };
87
+ /**
88
+ * Hook to subscribe to multiple Refs' target objects.
89
+ * Automatically dereferences each ref and handles async loading.
90
+ * Returns an array of loaded snapshots (filtering out undefined values).
91
+ *
92
+ * This hook is useful for aggregate computations like counts or filtering
93
+ * across multiple refs without using .target directly.
94
+ *
95
+ * @deprecated Subscribes the component to the whole list, re-rendering on any element's change. Prefer pushing
96
+ * the subscription down — derive the aggregate in an atom (reading each `Obj.atom(ref)`) and read it where it is
97
+ * needed, or subscribe to the individual ref/property closest to the consumer — to keep subscriptions granular.
98
+ *
99
+ * @param refs - Array of Refs to dereference and subscribe to
100
+ * @returns Array of loaded target snapshots (excludes unloaded refs)
101
+ */
102
+ var useObjects = (refs) => {
103
+ const atom = useMemo(() => Atom.make((get) => {
104
+ const results = [];
105
+ for (const ref of refs) {
106
+ const value = get(Obj.atom(ref));
107
+ if (value !== void 0) results.push(value);
108
+ }
109
+ return results;
110
+ }), [refs]);
111
+ return useAtomValue(atom);
32
112
  };
33
113
  //#endregion
34
114
  //#region src/usePagination.ts
35
- var EMPTY_ARRAY = [];
115
+ var EMPTY_ARRAY$1 = [];
36
116
  /**
37
117
  * Builds the external store backing `usePagination` for one query identity (filter/order/page
38
118
  * size/resource). Mirrors `useQuery`'s own `useMemo`-built `{ getObjects, subscribe }` pair as
@@ -56,7 +136,7 @@ var createPaginationStore = (resource, innerAst, pageSize, initialMaxWindowSize,
56
136
  let limit = pageSize;
57
137
  let rangeChangePending = false;
58
138
  let innerUnsubscribe;
59
- let displayItems = initialItems.length > 0 ? initialItems : EMPTY_ARRAY;
139
+ let displayItems = initialItems.length > 0 ? initialItems : EMPTY_ARRAY$1;
60
140
  let isLoading = resource !== void 0 && displayItems.length === 0;
61
141
  let snapshot = {
62
142
  items: displayItems,
@@ -78,7 +158,7 @@ var createPaginationStore = (resource, innerAst, pageSize, initialMaxWindowSize,
78
158
  innerUnsubscribe?.();
79
159
  innerUnsubscribe = void 0;
80
160
  if (!resource) {
81
- displayItems = EMPTY_ARRAY;
161
+ displayItems = EMPTY_ARRAY$1;
82
162
  isLoading = false;
83
163
  notify();
84
164
  return;
@@ -165,7 +245,7 @@ var usePagination = (resource, query, options) => {
165
245
  const innerAst = query.ast.query;
166
246
  const innerAstKey = JSON.stringify(innerAst);
167
247
  const maxWindowSize = options?.maxWindowSize ?? pageSize * 10;
168
- const previousItemsRef = useRef(EMPTY_ARRAY);
248
+ const previousItemsRef = useRef(EMPTY_ARRAY$1);
169
249
  const store = useMemo(() => createPaginationStore(resource, innerAst, pageSize, maxWindowSize, previousItemsRef.current), [
170
250
  resource,
171
251
  innerAstKey,
@@ -185,6 +265,35 @@ var usePagination = (resource, query, options) => {
185
265
  }), [snapshot, store]);
186
266
  };
187
267
  //#endregion
268
+ //#region src/useQuery.ts
269
+ var EMPTY_ARRAY = [];
270
+ var noop = () => {};
271
+ /**
272
+ * Create subscription.
273
+ *
274
+ * @param queryOrFilter - The query or filter to apply. Query is memoized based on the AST. No need to call useMemo.
275
+ */
276
+ var useQuery = (resource, queryOrFilter) => {
277
+ const astKey = JSON.stringify((Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter).ast);
278
+ const { getObjects, subscribe } = useMemo(() => {
279
+ let queryResult = void 0;
280
+ if (resource) queryResult = resource.query(Query.fromAst(JSON.parse(astKey)));
281
+ let subscribed = false;
282
+ return {
283
+ getObjects: () => subscribed && queryResult ? queryResult.results : EMPTY_ARRAY,
284
+ subscribe: (cb) => {
285
+ subscribed = true;
286
+ const unsubscribe = queryResult?.subscribe(cb) ?? noop;
287
+ return () => {
288
+ unsubscribe?.();
289
+ subscribed = false;
290
+ };
291
+ }
292
+ };
293
+ }, [resource, astKey]);
294
+ return useSyncExternalStore(subscribe, getObjects);
295
+ };
296
+ //#endregion
188
297
  //#region src/useType.ts
189
298
  /**
190
299
  * Subscribe to and retrieve a type by its URI from a space: a static schema's typename DXN, or a
@@ -238,100 +347,6 @@ var useType = (db, typeUri) => {
238
347
  return useSyncExternalStore(subscribe, getType);
239
348
  };
240
349
  //#endregion
241
- //#region src/useObject.ts
242
- var useObject = ((objOrRef, propertyOrOptions) => {
243
- const isOptions = typeof propertyOrOptions === "object" && propertyOrOptions !== null;
244
- const property = isOptions ? void 0 : propertyOrOptions;
245
- const options = isOptions ? propertyOrOptions : void 0;
246
- const isRef = Ref.isRef(objOrRef);
247
- const liveObj = useResolveRef(objOrRef);
248
- const callback = useCallback((updateOrValue) => {
249
- const obj = isRef ? objOrRef?.target : liveObj;
250
- if (obj === void 0) return;
251
- Obj.update(obj, (obj) => {
252
- if (typeof updateOrValue === "function") {
253
- const returnValue = updateOrValue(property !== void 0 ? obj[property] : obj);
254
- if (returnValue !== void 0) {
255
- if (property === void 0) throw new Error("Cannot re-assign the entire object");
256
- obj[property] = returnValue;
257
- }
258
- } else {
259
- if (property === void 0) throw new Error("Cannot re-assign the entire object");
260
- obj[property] = updateOrValue;
261
- }
262
- });
263
- }, [
264
- objOrRef,
265
- property,
266
- isRef,
267
- liveObj
268
- ]);
269
- if (property !== void 0) return [useObjectProperty(liveObj, property), callback];
270
- else return [useObjectValue(objOrRef, options), callback];
271
- });
272
- /**
273
- * Hook for subscribing to an Echo object or Ref.
274
- */
275
- var useObjectValue = (objOrRef, options) => {
276
- const deleted = options?.deleted;
277
- const atom = useMemo(() => {
278
- if (objOrRef == null) return Atom.make(() => void 0);
279
- if (Ref.isRef(objOrRef)) return Obj.atom(objOrRef, { deleted });
280
- if (Obj.isSnapshot(objOrRef)) return Atom.make(() => objOrRef);
281
- return Obj.atom(objOrRef);
282
- }, [objOrRef, deleted]);
283
- return useAtomValue(atom);
284
- };
285
- /**
286
- * Resolves a Ref to its live target object, or returns the input when it is already an object.
287
- * For refs, subscribes to load events only (not full mutation tracking).
288
- */
289
- var useResolveRef = (objOrRef) => {
290
- const atom = useMemo(() => {
291
- if (objOrRef == null) return Atom.make(() => void 0);
292
- if (!Ref.isRef(objOrRef)) return Atom.make(() => objOrRef);
293
- return objOrRef.atom;
294
- }, [objOrRef]);
295
- return useAtomValue(atom);
296
- };
297
- /**
298
- * Internal hook for subscribing to a specific property of an Echo object.
299
- */
300
- var useObjectProperty = (obj, property) => {
301
- const atom = useMemo(() => {
302
- if (obj == null) return Atom.make(() => void 0);
303
- if (Obj.isSnapshot(obj)) return Atom.make(() => obj[property]);
304
- return Obj.atomProperty(obj, property);
305
- }, [obj, property]);
306
- return useAtomValue(atom);
307
- };
308
- /**
309
- * Hook to subscribe to multiple Refs' target objects.
310
- * Automatically dereferences each ref and handles async loading.
311
- * Returns an array of loaded snapshots (filtering out undefined values).
312
- *
313
- * This hook is useful for aggregate computations like counts or filtering
314
- * across multiple refs without using .target directly.
315
- *
316
- * @deprecated Subscribes the component to the whole list, re-rendering on any element's change. Prefer pushing
317
- * the subscription down — derive the aggregate in an atom (reading each `Obj.atom(ref)`) and read it where it is
318
- * needed, or subscribe to the individual ref/property closest to the consumer — to keep subscriptions granular.
319
- *
320
- * @param refs - Array of Refs to dereference and subscribe to
321
- * @returns Array of loaded target snapshots (excludes unloaded refs)
322
- */
323
- var useObjects = (refs) => {
324
- const atom = useMemo(() => Atom.make((get) => {
325
- const results = [];
326
- for (const ref of refs) {
327
- const value = get(Obj.atom(ref));
328
- if (value !== void 0) results.push(value);
329
- }
330
- return results;
331
- }), [refs]);
332
- return useAtomValue(atom);
333
- };
334
- //#endregion
335
- export { useObject, useObjectValue, useObjects, usePagination, useQuery, useResolveRef, useType };
350
+ export { useLabel, useLabels, useObject, useObjectValue, useObjects, usePagination, useQuery, useResolveRef, useType };
336
351
 
337
352
  //# sourceMappingURL=index.mjs.map
@@ -1 +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 <R extends Query.RecordResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];\n <R extends Query.RecordResult>(resource: Database.Queryable | undefined, filter: Filter.Filter<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// rows and changes) 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 /** Resets to the newest page and resumes live updates. */\n jumpToHead: () => 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};\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\ntype PaginationElement<Q extends Query.Any> =\n Query.Type<Q> extends Query.RecordResult ? 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-react/Hooks';\nimport * as Atom from 'effect/reactivity/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 a Ref's target object, resolving a target that has been deleted.\n *\n * @param ref - The Ref to dereference and subscribe to\n * @param options - `{ deleted: 'include' }` keeps a tombstoned target instead of reading it as undefined\n * @returns The current target snapshot (or undefined if not loaded) and update callback\n */\n <T extends Obj.Unknown>(\n ref: Ref.Ref<T> | undefined,\n options: Ref.LoadOptions,\n ): [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>(\n objOrRef: T | Ref.Ref<T> | undefined,\n propertyOrOptions?: K | Ref.LoadOptions,\n): any => {\n const isOptions = typeof propertyOrOptions === 'object' && propertyOrOptions !== null;\n const property = isOptions ? undefined : propertyOrOptions;\n const options = isOptions ? propertyOrOptions : undefined;\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, options), 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 options?: Ref.LoadOptions,\n): T extends Obj.Snapshot ? T : Obj.Snapshot<T & Obj.Unknown> | undefined => {\n const deleted = options?.deleted;\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, { deleted });\n }\n if (Obj.isSnapshot(objOrRef)) {\n return Atom.make<T>(() => objOrRef);\n }\n return Obj.atom(objOrRef as T & Obj.Unknown);\n // Keyed on the option's value: an inline options object is a new identity on every render.\n }, [objOrRef, deleted]);\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":"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"}
1
+ {"version":3,"file":"index.mjs","names":["EMPTY_ARRAY"],"sources":["../../src/useLabel.ts","../../src/useObject.ts","../../src/usePagination.ts","../../src/useQuery.ts","../../src/useType.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Atom from 'effect/reactivity/Atom';\nimport { useMemo } from 'react';\n\nimport { Entity } from '@dxos/echo';\n\nexport type UseLabelOptions = {\n /** Fall back to the entity's typename when it has no label. */\n fallback?: 'typename';\n};\n\n/**\n * The label of an ECHO entity, re-rendering only when the label string changes.\n * A snapshot is read as is; a live entity subscribes through `Entity.labelAtom`.\n */\nexport const useLabel = (\n entity: Entity.Unknown | Entity.Snapshot | undefined,\n options?: UseLabelOptions,\n): string | undefined => {\n const atom = useMemo(\n () =>\n entity && Entity.isEntity(entity)\n ? Entity.labelAtom(entity)\n : Atom.make<string | undefined>(() => (entity ? Entity.getLabel(entity) : undefined)),\n [entity],\n );\n const label = useAtomValue(atom);\n return label ?? (options?.fallback === 'typename' && entity ? Entity.getTypename(entity) : undefined);\n};\n\n/**\n * The labels of a list of live ECHO entities, keyed by entity.\n * Re-renders when the list or any label string changes.\n */\nexport const useLabels = <T extends Entity.Unknown>(entities: readonly T[]): ReadonlyMap<T, string | undefined> =>\n useAtomValue(\n useMemo(\n () => Atom.make((get) => new Map(entities.map((entity) => [entity, get(Entity.labelAtom(entity))] as const))),\n [entities],\n ),\n );\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Atom from 'effect/reactivity/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 a Ref's target object, resolving a target that has been deleted.\n *\n * @param ref - The Ref to dereference and subscribe to\n * @param options - `{ deleted: 'include' }` keeps a tombstoned target instead of reading it as undefined\n * @returns The current target snapshot (or undefined if not loaded) and update callback\n */\n <T extends Obj.Unknown>(\n ref: Ref.Ref<T> | undefined,\n options: Ref.LoadOptions,\n ): [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>(\n objOrRef: T | Ref.Ref<T> | undefined,\n propertyOrOptions?: K | Ref.LoadOptions,\n): any => {\n const isOptions = typeof propertyOrOptions === 'object' && propertyOrOptions !== null;\n const property = isOptions ? undefined : propertyOrOptions;\n const options = isOptions ? propertyOrOptions : undefined;\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, options), 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 options?: Ref.LoadOptions,\n): T extends Obj.Snapshot ? T : Obj.Snapshot<T & Obj.Unknown> | undefined => {\n const deleted = options?.deleted;\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, { deleted });\n }\n if (Obj.isSnapshot(objOrRef)) {\n return Atom.make<T>(() => objOrRef);\n }\n return Obj.atom(objOrRef as T & Obj.Unknown);\n // Keyed on the option's value: an inline options object is a new identity on every render.\n }, [objOrRef, deleted]);\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","//\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 /** Resets to the newest page and resumes live updates. */\n jumpToHead: () => 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};\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\ntype PaginationElement<Q extends Query.Any> =\n Query.Type<Q> extends Query.RecordResult ? 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 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 <R extends Query.RecordResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];\n <R extends Query.RecordResult>(resource: Database.Queryable | undefined, filter: Filter.Filter<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// rows and changes) 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 // Keyed by the AST and rebuilt from it, so a caller's inline filter (a new object every render) reuses\n // the query; the memo reads only its key, which the React Compiler requires to honor it.\n const astKey = JSON.stringify((Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter).ast);\n\n const { getObjects, subscribe } = useMemo(() => {\n let queryResult = undefined;\n if (resource) {\n queryResult = resource.query(Query.fromAst(JSON.parse(astKey)));\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, astKey]);\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 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"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- export * from './useQuery.ts';
1
+ export { type UseLabelOptions, useLabel, useLabels } from './useLabel.ts';
2
+ export * from './useObject.ts';
2
3
  export * from './usePagination.ts';
4
+ export * from './useQuery.ts';
3
5
  export * from './useType.ts';
4
- export * from './useObject.ts';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Entity } from '@dxos/echo';
2
+ export type UseLabelOptions = {
3
+ /** Fall back to the entity's typename when it has no label. */
4
+ fallback?: 'typename';
5
+ };
6
+ /**
7
+ * The label of an ECHO entity, re-rendering only when the label string changes.
8
+ * A snapshot is read as is; a live entity subscribes through `Entity.labelAtom`.
9
+ */
10
+ export declare const useLabel: (entity: Entity.Unknown | Entity.Snapshot | undefined, options?: UseLabelOptions) => string | undefined;
11
+ /**
12
+ * The labels of a list of live ECHO entities, keyed by entity.
13
+ * Re-renders when the list or any label string changes.
14
+ */
15
+ export declare const useLabels: <T extends Entity.Unknown>(entities: readonly T[]) => ReadonlyMap<T, string | undefined>;
16
+ //# sourceMappingURL=useLabel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLabel.d.ts","sourceRoot":"","sources":["../../../src/useLabel.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,MAAM,MAAM,eAAe,GAAG;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,WACX,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,GAAG,SAAS,YAC1C,eAAe,KACxB,MAAM,GAAG,SAUX,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC,EAAE,KAAG,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAM3G,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useLabel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLabel.test.d.ts","sourceRoot":"","sources":["../../../src/useLabel.test.tsx"],"names":[],"mappings":""}
@@ -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,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACrG,CAAC,CAAC,SAAS,KAAK,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,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"}
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,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACrG,CAAC,CAAC,SAAS,KAAK,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,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,UAgCtB,CAAC"}