@dxos/echo-react 0.11.0 → 0.12.0-next-2be9f24c465d98f4bd186aa03ece788902e6c17a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/index.mjs +14 -10
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/usePagination.d.ts +2 -2
- package/dist/types/src/usePagination.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -6
- package/src/useObject.test.tsx +19 -19
- package/src/useObject.ts +2 -2
- package/src/usePagination.ts +2 -2
- package/dist/types/src/useQuery.__test.d.ts +0 -1
- package/dist/types/src/useQuery.__test.d.ts.map +0 -1
- package/src/useQuery.__test.tsx +0 -132
package/dist/lib/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
|
|
2
2
|
import { DXN, EID, Filter, Obj, Query, Ref, Scope, Type } from "@dxos/echo";
|
|
3
|
-
import { useAtomValue } from "@effect
|
|
4
|
-
import * as Atom from "
|
|
3
|
+
import { useAtomValue } from "@effect/atom-react/Hooks";
|
|
4
|
+
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
5
5
|
//#region src/useQuery.ts
|
|
6
6
|
var EMPTY_ARRAY$1 = [];
|
|
7
7
|
var noop = () => {};
|
|
@@ -270,33 +270,36 @@ var useObject = ((objOrRef, property) => {
|
|
|
270
270
|
* Hook for subscribing to an Echo object or Ref.
|
|
271
271
|
*/
|
|
272
272
|
var useObjectValue = (objOrRef) => {
|
|
273
|
-
|
|
273
|
+
const atom = useMemo(() => {
|
|
274
274
|
if (objOrRef == null) return Atom.make(() => void 0);
|
|
275
275
|
if (Ref.isRef(objOrRef)) return Obj.atom(objOrRef);
|
|
276
276
|
if (Obj.isSnapshot(objOrRef)) return Atom.make(() => objOrRef);
|
|
277
277
|
return Obj.atom(objOrRef);
|
|
278
|
-
}, [objOrRef])
|
|
278
|
+
}, [objOrRef]);
|
|
279
|
+
return useAtomValue(atom);
|
|
279
280
|
};
|
|
280
281
|
/**
|
|
281
282
|
* Resolves a Ref to its live target object, or returns the input when it is already an object.
|
|
282
283
|
* For refs, subscribes to load events only (not full mutation tracking).
|
|
283
284
|
*/
|
|
284
285
|
var useResolveRef = (objOrRef) => {
|
|
285
|
-
|
|
286
|
+
const atom = useMemo(() => {
|
|
286
287
|
if (objOrRef == null) return Atom.make(() => void 0);
|
|
287
288
|
if (!Ref.isRef(objOrRef)) return Atom.make(() => objOrRef);
|
|
288
289
|
return objOrRef.atom;
|
|
289
|
-
}, [objOrRef])
|
|
290
|
+
}, [objOrRef]);
|
|
291
|
+
return useAtomValue(atom);
|
|
290
292
|
};
|
|
291
293
|
/**
|
|
292
294
|
* Internal hook for subscribing to a specific property of an Echo object.
|
|
293
295
|
*/
|
|
294
296
|
var useObjectProperty = (obj, property) => {
|
|
295
|
-
|
|
297
|
+
const atom = useMemo(() => {
|
|
296
298
|
if (obj == null) return Atom.make(() => void 0);
|
|
297
299
|
if (Obj.isSnapshot(obj)) return Atom.make(() => obj[property]);
|
|
298
300
|
return Obj.atomProperty(obj, property);
|
|
299
|
-
}, [obj, property])
|
|
301
|
+
}, [obj, property]);
|
|
302
|
+
return useAtomValue(atom);
|
|
300
303
|
};
|
|
301
304
|
/**
|
|
302
305
|
* Hook to subscribe to multiple Refs' target objects.
|
|
@@ -314,14 +317,15 @@ var useObjectProperty = (obj, property) => {
|
|
|
314
317
|
* @returns Array of loaded target snapshots (excludes unloaded refs)
|
|
315
318
|
*/
|
|
316
319
|
var useObjects = (refs) => {
|
|
317
|
-
|
|
320
|
+
const atom = useMemo(() => Atom.make((get) => {
|
|
318
321
|
const results = [];
|
|
319
322
|
for (const ref of refs) {
|
|
320
323
|
const value = get(Obj.atom(ref));
|
|
321
324
|
if (value !== void 0) results.push(value);
|
|
322
325
|
}
|
|
323
326
|
return results;
|
|
324
|
-
}), [refs])
|
|
327
|
+
}), [refs]);
|
|
328
|
+
return useAtomValue(atom);
|
|
325
329
|
};
|
|
326
330
|
//#endregion
|
|
327
331
|
export { useObject, useObjectValue, useObjects, usePagination, useQuery, useResolveRef, useType };
|
package/dist/lib/index.mjs.map
CHANGED
|
@@ -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 // 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":"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"}
|
|
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 /** 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\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-react/Hooks';\nimport * as Atom from 'effect/unstable/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 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":"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"}
|
|
@@ -16,14 +16,14 @@ export type PaginationResult<T> = {
|
|
|
16
16
|
* while loading or already at the head.
|
|
17
17
|
*/
|
|
18
18
|
getPrevious: () => void;
|
|
19
|
+
/** Resets to the newest page and resumes live updates. */
|
|
20
|
+
jumpToHead: () => void;
|
|
19
21
|
/** Whether older items remain beyond what is currently loaded. Inferred from the last page's size. */
|
|
20
22
|
hasMore: boolean;
|
|
21
23
|
/** True until the current range delivers its first result, even an empty one. */
|
|
22
24
|
isLoading: boolean;
|
|
23
25
|
/** False once eviction has detached the window from the live head (i.e. skip > 0). */
|
|
24
26
|
atHead: boolean;
|
|
25
|
-
/** Resets to the newest page and resumes live updates. */
|
|
26
|
-
jumpToHead: () => void;
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* The element type a paginated query yields: the flat record for an aggregate query (branded with
|
|
@@ -1 +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;
|
|
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,0DAA0D;IAC1D,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,sGAAsG;IACtG,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,SAAS,EAAE,OAAO,CAAC;IACnB,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAC;CACjB,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"}
|