@dxos/echo-react 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-react",
3
- "version": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264",
3
+ "version": "0.13.0",
4
4
  "description": "React integration for ECHO.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -25,8 +25,8 @@
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
- "@effect/atom-react": "4.0.0-rc.108",
29
- "@dxos/echo": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
28
+ "@effect/atom-react": "4.0.0",
29
+ "@dxos/echo": "0.13.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@testing-library/react": "^16.3.2",
@@ -34,7 +34,7 @@
34
34
  "@types/react-dom": "~19.2.3",
35
35
  "react": "~19.2.7",
36
36
  "react-dom": "~19.2.7",
37
- "@dxos/echo-client": "0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264"
37
+ "@dxos/echo-client": "0.13.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "~19.2.7"
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export * from './useQuery';
6
- export * from './usePagination';
7
- export * from './useType';
8
- export * from './useObject';
5
+ export * from './useQuery.ts';
6
+ export * from './usePagination.ts';
7
+ export * from './useType.ts';
8
+ export * from './useObject.ts';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { RegistryContext } from '@effect/atom-react/RegistryContext';
6
6
  import { renderHook, waitFor } from '@testing-library/react';
7
- import * as AtomRegistry from 'effect/unstable/reactivity/AtomRegistry';
7
+ import * as AtomRegistry from 'effect/reactivity/AtomRegistry';
8
8
  import React, { type PropsWithChildren } from 'react';
9
9
  import { describe, expect, test } from 'vitest';
10
10
 
@@ -12,7 +12,7 @@ import { Obj, Ref } from '@dxos/echo';
12
12
  import { createObject } from '@dxos/echo-client';
13
13
  import { TestSchema } from '@dxos/echo/testing';
14
14
 
15
- import { useObject } from './useObject';
15
+ import { useObject } from './useObject.ts';
16
16
 
17
17
  const createWrapper = (registry: AtomRegistry.AtomRegistry) => {
18
18
  return ({ children }: PropsWithChildren) => (
package/src/useObject.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { useAtomValue } from '@effect/atom-react/Hooks';
6
- import * as Atom from 'effect/unstable/reactivity/Atom';
6
+ import * as Atom from 'effect/reactivity/Atom';
7
7
  import { useCallback, useMemo } from 'react';
8
8
 
9
9
  import { Obj, Ref } from '@dxos/echo';
@@ -46,6 +46,18 @@ export const useObject: {
46
46
  */
47
47
  <T extends Obj.Unknown>(ref: Ref.Ref<T> | undefined): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];
48
48
 
49
+ /**
50
+ * Hook to subscribe to a Ref's target object, resolving a target that has been deleted.
51
+ *
52
+ * @param ref - The Ref to dereference and subscribe to
53
+ * @param options - `{ deleted: 'include' }` keeps a tombstoned target instead of reading it as undefined
54
+ * @returns The current target snapshot (or undefined if not loaded) and update callback
55
+ */
56
+ <T extends Obj.Unknown>(
57
+ ref: Ref.Ref<T> | undefined,
58
+ options: Ref.LoadOptions,
59
+ ): [Obj.Snapshot<T> | undefined, ObjectUpdateCallback<T>];
60
+
49
61
  /**
50
62
  * Hook to subscribe to an entire Echo object.
51
63
  * Returns a snapshot of the current object value and automatically re-renders when the object changes.
@@ -117,7 +129,13 @@ export const useObject: {
117
129
  * @returns The current property value (or undefined) and update callback
118
130
  */
119
131
  <T, K extends keyof T>(ref: Ref.Ref<T> | undefined, property: K): [T[K] | undefined, ObjectPropUpdateCallback<T[K]>];
120
- } = (<T extends Obj.Unknown, K extends keyof T>(objOrRef: T | Ref.Ref<T> | undefined, property?: K): any => {
132
+ } = (<T extends Obj.Unknown, K extends keyof T>(
133
+ objOrRef: T | Ref.Ref<T> | undefined,
134
+ propertyOrOptions?: K | Ref.LoadOptions,
135
+ ): any => {
136
+ const isOptions = typeof propertyOrOptions === 'object' && propertyOrOptions !== null;
137
+ const property = isOptions ? undefined : propertyOrOptions;
138
+ const options = isOptions ? propertyOrOptions : undefined;
121
139
  const isRef = Ref.isRef(objOrRef);
122
140
  const liveObj = useResolveRef(objOrRef);
123
141
 
@@ -151,7 +169,7 @@ export const useObject: {
151
169
  if (property !== undefined) {
152
170
  return [useObjectProperty(liveObj, property), callback];
153
171
  } else {
154
- return [useObjectValue(objOrRef), callback];
172
+ return [useObjectValue(objOrRef, options), callback];
155
173
  }
156
174
  }) as any;
157
175
 
@@ -160,19 +178,22 @@ export const useObject: {
160
178
  */
161
179
  export const useObjectValue = <T extends Obj.Unknown | Obj.Snapshot>(
162
180
  objOrRef: T | Ref.Ref<T> | undefined,
181
+ options?: Ref.LoadOptions,
163
182
  ): T extends Obj.Snapshot ? T : Obj.Snapshot<T & Obj.Unknown> | undefined => {
183
+ const deleted = options?.deleted;
164
184
  const atom = useMemo(() => {
165
185
  if (objOrRef == null) {
166
186
  return Atom.make<Obj.Snapshot<T & Obj.Unknown> | undefined>(() => undefined);
167
187
  }
168
188
  if (Ref.isRef(objOrRef)) {
169
- return Obj.atom(objOrRef);
189
+ return Obj.atom(objOrRef, { deleted });
170
190
  }
171
191
  if (Obj.isSnapshot(objOrRef)) {
172
192
  return Atom.make<T>(() => objOrRef);
173
193
  }
174
194
  return Obj.atom(objOrRef as T & Obj.Unknown);
175
- }, [objOrRef]);
195
+ // Keyed on the option's value: an inline options object is a new identity on every render.
196
+ }, [objOrRef, deleted]);
176
197
  return useAtomValue(atom as any);
177
198
  };
178
199
 
@@ -9,7 +9,7 @@ import { Aggregate, Database, Feed, Filter, Obj, Order, Query } from '@dxos/echo
9
9
  import { EchoTestBuilder } from '@dxos/echo-client/testing';
10
10
  import { TestSchema } from '@dxos/echo/testing';
11
11
 
12
- import { usePagination } from './usePagination';
12
+ import { usePagination } from './usePagination.ts';
13
13
 
14
14
  describe('usePagination', () => {
15
15
  let builder: EchoTestBuilder;
@@ -39,12 +39,8 @@ export type PaginationResult<T> = {
39
39
  /** Reactive snapshot handed to React by `useSyncExternalStore`; a fresh object only on real change. */
40
40
  type Snapshot<O> = { items: O[]; skip: number; limit: number; isLoading: boolean };
41
41
 
42
- /**
43
- * The element type a paginated query yields: the flat record for an aggregate query (branded with
44
- * {@link Query.AggregateResult}), otherwise the query's entity type. Mirrors `useQuery`'s overloads.
45
- */
46
42
  type PaginationElement<Q extends Query.Any> =
47
- Query.Type<Q> extends Query.AggregateResult ? Query.Type<Q> : Entity.Entity<Query.Type<Q>>;
43
+ Query.Type<Q> extends Query.RecordResult ? Query.Type<Q> : Entity.Entity<Query.Type<Q>>;
48
44
 
49
45
  /**
50
46
  * Builds the external store backing `usePagination` for one query identity (filter/order/page
@@ -9,7 +9,7 @@ import { Aggregate, Filter, Obj, Query } from '@dxos/echo';
9
9
  import { EchoTestBuilder } from '@dxos/echo-client/testing';
10
10
  import { TestSchema } from '@dxos/echo/testing';
11
11
 
12
- import { useQuery } from './useQuery';
12
+ import { useQuery } from './useQuery.ts';
13
13
 
14
14
  describe('useQuery', () => {
15
15
  let builder: EchoTestBuilder;
package/src/useQuery.ts CHANGED
@@ -11,9 +11,8 @@ const EMPTY_ARRAY: never[] = [];
11
11
  const noop = () => {};
12
12
 
13
13
  interface UseQueryFn {
14
- // Aggregate queries produce flat records branded with `Query.AggregateResult`; return them as-is,
15
- // never wrapped in `Entity.Entity` (they are not entities).
16
- <R extends Query.AggregateResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];
14
+ <R extends Query.RecordResult>(resource: Database.Queryable | undefined, query: Query.Query<R>): R[];
15
+ <R extends Query.RecordResult>(resource: Database.Queryable | undefined, filter: Filter.Filter<R>): R[];
17
16
 
18
17
  <Q extends Query.Any, O extends Entity.Entity<Query.Type<Q>> = Entity.Entity<Query.Type<Q>>>(
19
18
  resource: Database.Queryable | undefined,
@@ -32,7 +31,7 @@ interface UseQueryFn {
32
31
  * @param queryOrFilter - The query or filter to apply. Query is memoized based on the AST. No need to call useMemo.
33
32
  */
34
33
  // The overloaded public signature returns `O[]` (entities) or the flat record `R[]` (aggregate
35
- // queries) depending on the query shape; the implementation forwards whatever the query result
34
+ // rows and changes) depending on the query shape; the implementation forwards whatever the query result
36
35
  // produces, so — as with any implementation of a multi-signature overloaded function type — it is
37
36
  // typed as `any[]` here rather than picking one of the mutually-incompatible overload return shapes.
38
37
  export const useQuery: UseQueryFn = (