@evolu/react 8.2.1 → 9.0.0-preview.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.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/EvoluContext.d.ts +3 -0
  3. package/dist/EvoluContext.d.ts.map +1 -0
  4. package/dist/EvoluContext.js +2 -0
  5. package/dist/EvoluProvider.d.ts +7 -0
  6. package/dist/EvoluProvider.d.ts.map +1 -0
  7. package/dist/EvoluProvider.js +4 -0
  8. package/dist/createUseEvolu.d.ts +13 -0
  9. package/dist/createUseEvolu.d.ts.map +1 -0
  10. package/dist/createUseEvolu.js +14 -0
  11. package/dist/index.d.ts +10 -33
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +10 -42
  14. package/dist/useAppOwner.d.ts +4 -0
  15. package/dist/useAppOwner.d.ts.map +1 -0
  16. package/dist/useAppOwner.js +8 -0
  17. package/dist/useEvolu.d.ts +9 -0
  18. package/dist/useEvolu.d.ts.map +1 -0
  19. package/dist/useEvolu.js +15 -0
  20. package/dist/useEvoluError.d.ts +4 -0
  21. package/dist/useEvoluError.d.ts.map +1 -0
  22. package/dist/useEvoluError.js +8 -0
  23. package/dist/useQueries.d.ts +9 -0
  24. package/dist/useQueries.d.ts.map +1 -0
  25. package/dist/useQueries.js +25 -0
  26. package/dist/useQuery.d.ts +37 -0
  27. package/dist/useQuery.d.ts.map +1 -0
  28. package/dist/useQuery.js +45 -0
  29. package/dist/useQuerySubscription.d.ts +10 -0
  30. package/dist/useQuerySubscription.d.ts.map +1 -0
  31. package/dist/useQuerySubscription.js +18 -0
  32. package/dist/useSyncState.d.ts +4 -0
  33. package/dist/useSyncState.d.ts.map +1 -0
  34. package/dist/useSyncState.js +8 -0
  35. package/dist/useWasSSR.d.ts +6 -0
  36. package/dist/useWasSSR.d.ts.map +1 -0
  37. package/dist/useWasSSR.js +11 -0
  38. package/package.json +14 -20
  39. package/src/EvoluContext.ts +4 -0
  40. package/src/EvoluProvider.tsx +13 -0
  41. package/src/createUseEvolu.ts +17 -0
  42. package/src/index.ts +10 -49
  43. package/src/useAppOwner.ts +13 -0
  44. package/src/useEvolu.ts +20 -0
  45. package/src/useEvoluError.ts +9 -0
  46. package/src/useQueries.ts +46 -0
  47. package/src/useQuery.ts +55 -0
  48. package/src/useQuerySubscription.ts +38 -0
  49. package/src/useSyncState.ts +13 -0
  50. package/src/useWasSSR.ts +17 -0
package/README.md CHANGED
@@ -12,4 +12,4 @@ The Evolu community is on [GitHub Discussions](https://github.com/evoluhq/evolu/
12
12
 
13
13
  To chat with other community members, you can join the [Evolu Discord](https://discord.gg/2J8yyyyxtZ).
14
14
 
15
- [![Twitter URL](https://img.shields.io/twitter/url/https/x.com/evoluhq.svg?style=social&label=Follow%20%40evoluhq)](https://x.com/evoluhq)
15
+ [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/evoluhq.svg?style=social&label=Follow%20%40evoluhq)](https://twitter.com/evoluhq)
@@ -0,0 +1,3 @@
1
+ import { Evolu } from "@evolu/common/evolu";
2
+ export declare const EvoluContext: import("react").Context<Evolu<any> | null>;
3
+ //# sourceMappingURL=EvoluContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EvoluContext.d.ts","sourceRoot":"","sources":["../src/EvoluContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAG5C,eAAO,MAAM,YAAY,4CAAyC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { createContext } from "react";
2
+ export const EvoluContext = createContext(null);
@@ -0,0 +1,7 @@
1
+ import { Evolu } from "@evolu/common/evolu";
2
+ import { ReactNode } from "react";
3
+ export declare const EvoluProvider: ({ children, value, }: {
4
+ readonly children?: ReactNode | undefined;
5
+ readonly value: Evolu<any>;
6
+ }) => React.ReactElement;
7
+ //# sourceMappingURL=EvoluProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EvoluProvider.d.ts","sourceRoot":"","sources":["../src/EvoluProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlC,eAAO,MAAM,aAAa,GAAI,sBAG3B;IACD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CAC5B,KAAG,KAAK,CAAC,YAAqE,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { EvoluContext } from "./EvoluContext.js";
4
+ export const EvoluProvider = ({ children, value, }) => _jsx(EvoluContext, { value: value, children: children });
@@ -0,0 +1,13 @@
1
+ import { Evolu, EvoluSchema } from "@evolu/common";
2
+ /**
3
+ * Creates a typed React Hook returning an instance of {@link Evolu}.
4
+ *
5
+ * ### Example
6
+ *
7
+ * ```ts
8
+ * const useEvolu = createUseEvolu(evolu);
9
+ * const { insert, update } = useEvolu();
10
+ * ```
11
+ */
12
+ export declare const createUseEvolu: <S extends EvoluSchema>(evolu: Evolu<S>) => (() => Evolu<S>);
13
+ //# sourceMappingURL=createUseEvolu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createUseEvolu.d.ts","sourceRoot":"","sources":["../src/createUseEvolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGnD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,WAAW,EAElD,OAAO,KAAK,CAAC,CAAC,CAAC,KACd,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAA+B,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { useEvolu } from "./useEvolu.js";
2
+ /**
3
+ * Creates a typed React Hook returning an instance of {@link Evolu}.
4
+ *
5
+ * ### Example
6
+ *
7
+ * ```ts
8
+ * const useEvolu = createUseEvolu(evolu);
9
+ * const { insert, update } = useEvolu();
10
+ * ```
11
+ */
12
+ export const createUseEvolu = (
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ evolu) => useEvolu;
package/dist/index.d.ts CHANGED
@@ -1,34 +1,11 @@
1
- export * from "@evolu/common/public";
2
- export declare const
3
- /**
4
- * Create Evolu from the database schema.
5
- *
6
- * Tables with a name prefixed with `_` are local-only, which means they are
7
- * never synced. It's useful for device-specific or temporal data.
8
- *
9
- * @example
10
- * import * as S from "@effect/schema/Schema";
11
- * import * as E from "@evolu/react";
12
- *
13
- * const TodoId = E.id("Todo");
14
- * type TodoId = typeof TodoId.Type;
15
- *
16
- * const TodoTable = E.table({
17
- * id: TodoId,
18
- * title: E.NonEmptyString1000,
19
- * });
20
- * type TodoTable = typeof TodoTable.Type;
21
- *
22
- * const Database = E.database({
23
- * todo: TodoTable,
24
- *
25
- * // Prefix `_` makes the table local-only (it will not sync)
26
- * _todo: TodoTable,
27
- * });
28
- * type Database = typeof Database.Type;
29
- *
30
- * const evolu = E.createEvolu(Database);
31
- */
32
- createEvolu: <T extends import("@evolu/common").EvoluSchema, I>(schema: import("@effect/schema/Schema").Schema<T, I>, config?: Partial<import("@evolu/common").EvoluConfig<T>>) => import("@evolu/common").Evolu<T>;
33
- export * from "@evolu/common-react";
1
+ export * from "./createUseEvolu.js";
2
+ export * from "./EvoluContext.js";
3
+ export * from "./EvoluProvider.js";
4
+ export * from "./useAppOwner.js";
5
+ export * from "./useEvolu.js";
6
+ export * from "./useEvoluError.js";
7
+ export * from "./useQueries.js";
8
+ export * from "./useQuery.js";
9
+ export * from "./useQuerySubscription.js";
10
+ export * from "./useSyncState.js";
34
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,sBAAsB,CAAC;AASrC,eAAO;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,WAAW,wMAC8D,CAAC;AAE5E,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,42 +1,10 @@
1
- import { EvoluFactory, FlushSync } from "@evolu/common";
2
- import { EvoluFactoryWeb } from "@evolu/common-web";
3
- import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
- import { flushSync } from "react-dom";
6
- export * from "@evolu/common/public";
7
- const EvoluFactoryWebReact = EvoluFactoryWeb.pipe(Layer.provide(Layer.succeed(FlushSync, flushSync)));
8
- // JSDoc doesn't support destructured parameters, so we must copy-paste
9
- // createEvolu docs from `evolu-common/src/Evolu.ts`.
10
- // https://github.com/microsoft/TypeScript/issues/11859
11
- export const {
12
- /**
13
- * Create Evolu from the database schema.
14
- *
15
- * Tables with a name prefixed with `_` are local-only, which means they are
16
- * never synced. It's useful for device-specific or temporal data.
17
- *
18
- * @example
19
- * import * as S from "@effect/schema/Schema";
20
- * import * as E from "@evolu/react";
21
- *
22
- * const TodoId = E.id("Todo");
23
- * type TodoId = typeof TodoId.Type;
24
- *
25
- * const TodoTable = E.table({
26
- * id: TodoId,
27
- * title: E.NonEmptyString1000,
28
- * });
29
- * type TodoTable = typeof TodoTable.Type;
30
- *
31
- * const Database = E.database({
32
- * todo: TodoTable,
33
- *
34
- * // Prefix `_` makes the table local-only (it will not sync)
35
- * _todo: TodoTable,
36
- * });
37
- * type Database = typeof Database.Type;
38
- *
39
- * const evolu = E.createEvolu(Database);
40
- */
41
- createEvolu, } = EvoluFactory.pipe(Effect.provide(EvoluFactoryWebReact), Effect.runSync);
42
- export * from "@evolu/common-react";
1
+ export * from "./createUseEvolu.js";
2
+ export * from "./EvoluContext.js";
3
+ export * from "./EvoluProvider.js";
4
+ export * from "./useAppOwner.js";
5
+ export * from "./useEvolu.js";
6
+ export * from "./useEvoluError.js";
7
+ export * from "./useQueries.js";
8
+ export * from "./useQuery.js";
9
+ export * from "./useQuerySubscription.js";
10
+ export * from "./useSyncState.js";
@@ -0,0 +1,4 @@
1
+ import { AppOwner } from "@evolu/common";
2
+ /** Subscribe to {@link AppOwner} changes. */
3
+ export declare const useAppOwner: () => AppOwner | null;
4
+ //# sourceMappingURL=useAppOwner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAppOwner.d.ts","sourceRoot":"","sources":["../src/useAppOwner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,eAAe,CAAC;AAIpD,6CAA6C;AAC7C,eAAO,MAAM,WAAW,QAAO,QAAQ,GAAG,IAOzC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { constNull } from "@evolu/common";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+ /** Subscribe to {@link AppOwner} changes. */
5
+ export const useAppOwner = () => {
6
+ const evolu = useEvolu();
7
+ return useSyncExternalStore(evolu.subscribeAppOwner, evolu.getAppOwner, constNull);
8
+ };
@@ -0,0 +1,9 @@
1
+ import { Evolu } from "@evolu/common/evolu";
2
+ /**
3
+ * React Hook returning a generic instance of {@link Evolu}.
4
+ *
5
+ * This is intended for internal usage. Applications should use
6
+ * {@link createUseEvolu}, which provides a correctly typed instance.
7
+ */
8
+ export declare const useEvolu: () => Evolu;
9
+ //# sourceMappingURL=useEvolu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEvolu.d.ts","sourceRoot":"","sources":["../src/useEvolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAK5C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QAAO,KAQ3B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { useContext } from "react";
2
+ import { EvoluContext } from "./EvoluContext.js";
3
+ /**
4
+ * React Hook returning a generic instance of {@link Evolu}.
5
+ *
6
+ * This is intended for internal usage. Applications should use
7
+ * {@link createUseEvolu}, which provides a correctly typed instance.
8
+ */
9
+ export const useEvolu = () => {
10
+ const evolu = useContext(EvoluContext);
11
+ if (evolu == null) {
12
+ throw new Error("Could not find Evolu context value. Ensure the component is wrapped in an <EvoluProvider>.");
13
+ }
14
+ return evolu;
15
+ };
@@ -0,0 +1,4 @@
1
+ import { EvoluError } from "@evolu/common";
2
+ /** Subscribe to {@link EvoluError} changes. */
3
+ export declare const useEvoluError: () => EvoluError | null;
4
+ //# sourceMappingURL=useEvoluError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEvoluError.d.ts","sourceRoot":"","sources":["../src/useEvoluError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAE,MAAM,eAAe,CAAC;AAItD,+CAA+C;AAC/C,eAAO,MAAM,aAAa,QAAO,UAAU,GAAG,IAG7C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { constNull } from "@evolu/common";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+ /** Subscribe to {@link EvoluError} changes. */
5
+ export const useEvoluError = () => {
6
+ const evolu = useEvolu();
7
+ return useSyncExternalStore(evolu.subscribeError, evolu.getError, constNull);
8
+ };
@@ -0,0 +1,9 @@
1
+ import { Queries, QueriesToQueryRows, QueriesToQueryRowsPromises, Row } from "@evolu/common/evolu";
2
+ /** The same as {@link useQuery}, but for many queries. */
3
+ export declare const useQueries: <R extends Row, Q extends Queries<R>, OQ extends Queries<R>>(queries: [...Q], options?: Partial<{
4
+ /** Queries that should be only loaded, not subscribed to. */
5
+ readonly once: [...OQ];
6
+ /** Reuse existing promises instead of loading so query will not suspense. */
7
+ readonly promises: [...QueriesToQueryRowsPromises<Q>, ...QueriesToQueryRowsPromises<OQ>];
8
+ }>) => [...QueriesToQueryRows<Q>, ...QueriesToQueryRows<OQ>];
9
+ //# sourceMappingURL=useQueries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQueries.d.ts","sourceRoot":"","sources":["../src/useQueries.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,kBAAkB,EAClB,0BAA0B,EAC1B,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAO7B,0DAA0D;AAC1D,eAAO,MAAM,UAAU,GACrB,CAAC,SAAS,GAAG,EACb,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EACpB,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,EAErB,SAAS,CAAC,GAAG,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAEvB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,CACjB,GAAG,0BAA0B,CAAC,CAAC,CAAC,EAChC,GAAG,0BAA0B,CAAC,EAAE,CAAC,CAClC,CAAC;CACH,CAAM,KACN,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAgBtD,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { use, useRef } from "react";
2
+ import { useEvolu } from "./useEvolu.js";
3
+ import { useQuerySubscription } from "./useQuerySubscription.js";
4
+ import { useWasSSR } from "./useWasSSR.js";
5
+ /** The same as {@link useQuery}, but for many queries. */
6
+ export const useQueries = (queries, options = {}) => {
7
+ const evolu = useEvolu();
8
+ const once = useRef(options).current.once;
9
+ const allQueries = once ? queries.concat(once) : queries;
10
+ const wasSSR = useWasSSR();
11
+ if (wasSSR) {
12
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
13
+ if (!options.promises)
14
+ evolu.loadQueries(allQueries);
15
+ }
16
+ else {
17
+ if (options.promises)
18
+ options.promises.map(use);
19
+ else
20
+ evolu.loadQueries(allQueries).map(use);
21
+ }
22
+ return allQueries.map((query, i) =>
23
+ // eslint-disable-next-line react-hooks/rules-of-hooks
24
+ useQuerySubscription(query, { once: i > queries.length - 1 }));
25
+ };
@@ -0,0 +1,37 @@
1
+ import { Query, QueryRows, Row } from "@evolu/common/evolu";
2
+ /**
3
+ * Load and subscribe to the Query, and return an object with `rows` and `row`
4
+ * properties that are automatically updated when data changes.
5
+ *
6
+ * Note that {@link useQuery} uses React Suspense. It means every usage of
7
+ * {@link useQuery} blocks rendering until loading is completed. To avoid loading
8
+ * waterfall with more queries, use {@link useQueries}.
9
+ *
10
+ * ### Example
11
+ *
12
+ * ```ts
13
+ * // Get all rows.
14
+ * const rows = useQuery(allTodos);
15
+ *
16
+ * // Get the first row (it can be null).
17
+ * const row = useQuery(todoById(1));
18
+ *
19
+ * // Get all rows, but without subscribing to changes.
20
+ * const rows = useQuery(allTodos, { once: true });
21
+ *
22
+ * // Prefetch rows.
23
+ * const allTodos = evolu.createQuery((db) =>
24
+ * db.selectFrom("todo").selectAll(),
25
+ * );
26
+ * const allTodosPromise = evolu.loadQuery(allTodos);
27
+ * // Use prefetched rows.
28
+ * const rows = useQuery(allTodos, { promise: allTodosPromise });
29
+ * ```
30
+ */
31
+ export declare const useQuery: <R extends Row>(query: Query<R>, options?: Partial<{
32
+ /** Without subscribing to changes. */
33
+ readonly once: boolean;
34
+ /** Reuse existing promise instead of loading so query will not suspense. */
35
+ readonly promise: Promise<QueryRows<R>>;
36
+ }>) => QueryRows<R>;
37
+ //# sourceMappingURL=useQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../src/useQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAO5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,GAAG,EACpC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC,CAAM,KACN,SAAS,CAAC,CAAC,CASb,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { use } from "react";
2
+ import { useEvolu } from "./useEvolu.js";
3
+ import { useQuerySubscription } from "./useQuerySubscription.js";
4
+ import { useWasSSR } from "./useWasSSR.js";
5
+ /**
6
+ * Load and subscribe to the Query, and return an object with `rows` and `row`
7
+ * properties that are automatically updated when data changes.
8
+ *
9
+ * Note that {@link useQuery} uses React Suspense. It means every usage of
10
+ * {@link useQuery} blocks rendering until loading is completed. To avoid loading
11
+ * waterfall with more queries, use {@link useQueries}.
12
+ *
13
+ * ### Example
14
+ *
15
+ * ```ts
16
+ * // Get all rows.
17
+ * const rows = useQuery(allTodos);
18
+ *
19
+ * // Get the first row (it can be null).
20
+ * const row = useQuery(todoById(1));
21
+ *
22
+ * // Get all rows, but without subscribing to changes.
23
+ * const rows = useQuery(allTodos, { once: true });
24
+ *
25
+ * // Prefetch rows.
26
+ * const allTodos = evolu.createQuery((db) =>
27
+ * db.selectFrom("todo").selectAll(),
28
+ * );
29
+ * const allTodosPromise = evolu.loadQuery(allTodos);
30
+ * // Use prefetched rows.
31
+ * const rows = useQuery(allTodos, { promise: allTodosPromise });
32
+ * ```
33
+ */
34
+ export const useQuery = (query, options = {}) => {
35
+ const evolu = useEvolu();
36
+ const wasSSR = useWasSSR();
37
+ if (wasSSR) {
38
+ if (!options.promise)
39
+ void evolu.loadQuery(query);
40
+ }
41
+ else {
42
+ use(options.promise ?? evolu.loadQuery(query));
43
+ }
44
+ return useQuerySubscription(query, options);
45
+ };
@@ -0,0 +1,10 @@
1
+ import { Query, QueryRows, Row } from "@evolu/common/evolu";
2
+ /** Subscribe to {@link Query} {@link QueryRows} changes. */
3
+ export declare const useQuerySubscription: <R extends Row>(query: Query<R>, options?: Partial<{
4
+ /**
5
+ * Only subscribe and get the current value once. Subscribed query will not
6
+ * invoke React Suspense after a mutation.
7
+ */
8
+ readonly once: boolean;
9
+ }>) => QueryRows<R>;
10
+ //# sourceMappingURL=useQuerySubscription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQuerySubscription.d.ts","sourceRoot":"","sources":["../src/useQuerySubscription.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAa,MAAM,qBAAqB,CAAC;AAIvE,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,GAAG,EAChD,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,UAAS,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAM,KACN,SAAS,CAAC,CAAC,CAsBb,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { constVoid } from "@evolu/common";
2
+ import { emptyRows } from "@evolu/common/evolu";
3
+ import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
4
+ import { useEvolu } from "./useEvolu.js";
5
+ /** Subscribe to {@link Query} {@link QueryRows} changes. */
6
+ export const useQuerySubscription = (query, options = {}) => {
7
+ const evolu = useEvolu();
8
+ // useRef to not break "rules-of-hooks"
9
+ const { once } = useRef(options).current;
10
+ if (once) {
11
+ /* eslint-disable react-hooks/rules-of-hooks */
12
+ useEffect(
13
+ // No useSyncExternalStore, no unnecessary updates.
14
+ () => evolu.subscribeQuery(query)(constVoid), [evolu, query]);
15
+ return evolu.getQueryRows(query);
16
+ }
17
+ return useSyncExternalStore(useMemo(() => evolu.subscribeQuery(query), [evolu, query]), useMemo(() => () => evolu.getQueryRows(query), [evolu, query]), () => emptyRows);
18
+ };
@@ -0,0 +1,4 @@
1
+ import { SyncState } from "@evolu/common/evolu";
2
+ /** Subscribe to {@link SyncState} changes. */
3
+ export declare const useSyncState: () => SyncState;
4
+ //# sourceMappingURL=useSyncState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSyncState.d.ts","sourceRoot":"","sources":["../src/useSyncState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,qBAAqB,CAAC;AAIlE,8CAA8C;AAC9C,eAAO,MAAM,YAAY,QAAO,SAO/B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { initialSyncState } from "@evolu/common/evolu";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+ /** Subscribe to {@link SyncState} changes. */
5
+ export const useSyncState = () => {
6
+ const evolu = useEvolu();
7
+ return useSyncExternalStore(evolu.subscribeSyncState, evolu.getSyncState, () => initialSyncState);
8
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * "Unlike the typeof window hack, this ensures that the server and hydration
3
+ * sees the same thing."
4
+ */
5
+ export declare const useWasSSR: () => boolean;
6
+ //# sourceMappingURL=useWasSSR.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWasSSR.d.ts","sourceRoot":"","sources":["../src/useWasSSR.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,SAAS,QAAO,OAO5B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { useRef, useSyncExternalStore } from "react";
2
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
3
+ const emptySubscribe = () => () => { };
4
+ /**
5
+ * "Unlike the typeof window hack, this ensures that the server and hydration
6
+ * sees the same thing."
7
+ */
8
+ export const useWasSSR = () => {
9
+ const ref = useRef(false);
10
+ return useSyncExternalStore(emptySubscribe, () => ref.current, () => (ref.current = true));
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/react",
3
- "version": "8.2.1",
3
+ "version": "9.0.0-preview.0",
4
4
  "description": "Evolu for React",
5
5
  "keywords": [
6
6
  "evolu",
@@ -20,7 +20,8 @@
20
20
  ".": {
21
21
  "types": "./dist/index.d.ts",
22
22
  "import": "./dist/index.js",
23
- "browser": "./dist/index.js"
23
+ "browser": "./dist/index.js",
24
+ "react-native": "./dist/index.js"
24
25
  }
25
26
  },
26
27
  "files": [
@@ -29,35 +30,28 @@
29
30
  "README.md"
30
31
  ],
31
32
  "devDependencies": {
32
- "@types/react-dom": "~18.3.0",
33
- "eslint": "^8.57.0",
34
- "react-dom": "^18.3.1",
35
- "typescript": "^5.4.5",
36
- "vitest": "^2.0.0",
37
- "@evolu/common": "5.4.5",
38
- "@evolu/common-react": "8.1.1",
39
- "@evolu/common-web": "8.2.2",
40
- "@evolu/tsconfig": "0.0.2",
41
- "eslint-config-evolu": "1.0.0"
33
+ "@types/react-dom": "^19.1.6",
34
+ "react": "^19.1.0",
35
+ "typescript": "^5.8.3",
36
+ "vitest": "^3.2.2",
37
+ "@evolu/common": "6.0.0-preview.0",
38
+ "@evolu/tsconfig": "0.0.2"
42
39
  },
43
40
  "peerDependencies": {
44
- "@evolu/common": "^5.4.5",
45
- "@evolu/common-react": "^8.1.1",
46
- "@evolu/common-web": "^8.2.2",
47
- "react-dom": "^18.2.0"
41
+ "@evolu/common": "^6.0.0-preview.0",
42
+ "react": "^19.0.0"
48
43
  },
49
44
  "publishConfig": {
50
45
  "access": "public"
51
46
  },
52
47
  "engines": {
53
- "node": ">=20.0.0"
48
+ "node": ">=22.0.0"
54
49
  },
55
50
  "sideEffects": [],
56
51
  "scripts": {
57
52
  "dev": "tsc --watch",
58
- "build": "rm -rf dist && tsc",
59
- "lint": "eslint src --ext .ts,.tsx",
60
- "clean": "rm -rf .turbo node_modules dist",
53
+ "build": "shx rm -rf dist && tsc",
54
+ "clean": "shx rm -rf .turbo node_modules dist",
61
55
  "format": "prettier --write \"src/*.{ts,tsx,md}\""
62
56
  }
63
57
  }
@@ -0,0 +1,4 @@
1
+ import { Evolu } from "@evolu/common/evolu";
2
+ import { createContext } from "react";
3
+
4
+ export const EvoluContext = createContext<Evolu<any> | null>(null);
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import { Evolu } from "@evolu/common/evolu";
4
+ import { ReactNode } from "react";
5
+ import { EvoluContext } from "./EvoluContext.js";
6
+
7
+ export const EvoluProvider = ({
8
+ children,
9
+ value,
10
+ }: {
11
+ readonly children?: ReactNode | undefined;
12
+ readonly value: Evolu<any>;
13
+ }): React.ReactElement => <EvoluContext value={value}>{children}</EvoluContext>;
@@ -0,0 +1,17 @@
1
+ import { Evolu, EvoluSchema } from "@evolu/common";
2
+ import { useEvolu } from "./useEvolu.js";
3
+
4
+ /**
5
+ * Creates a typed React Hook returning an instance of {@link Evolu}.
6
+ *
7
+ * ### Example
8
+ *
9
+ * ```ts
10
+ * const useEvolu = createUseEvolu(evolu);
11
+ * const { insert, update } = useEvolu();
12
+ * ```
13
+ */
14
+ export const createUseEvolu = <S extends EvoluSchema>(
15
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
+ evolu: Evolu<S>,
17
+ ): (() => Evolu<S>) => useEvolu as () => Evolu<S>;
package/src/index.ts CHANGED
@@ -1,49 +1,10 @@
1
- import { EvoluFactory, FlushSync } from "@evolu/common";
2
- import { EvoluFactoryWeb } from "@evolu/common-web";
3
- import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
- import { flushSync } from "react-dom";
6
-
7
- export * from "@evolu/common/public";
8
-
9
- const EvoluFactoryWebReact = EvoluFactoryWeb.pipe(
10
- Layer.provide(Layer.succeed(FlushSync, flushSync)),
11
- );
12
-
13
- // JSDoc doesn't support destructured parameters, so we must copy-paste
14
- // createEvolu docs from `evolu-common/src/Evolu.ts`.
15
- // https://github.com/microsoft/TypeScript/issues/11859
16
- export const {
17
- /**
18
- * Create Evolu from the database schema.
19
- *
20
- * Tables with a name prefixed with `_` are local-only, which means they are
21
- * never synced. It's useful for device-specific or temporal data.
22
- *
23
- * @example
24
- * import * as S from "@effect/schema/Schema";
25
- * import * as E from "@evolu/react";
26
- *
27
- * const TodoId = E.id("Todo");
28
- * type TodoId = typeof TodoId.Type;
29
- *
30
- * const TodoTable = E.table({
31
- * id: TodoId,
32
- * title: E.NonEmptyString1000,
33
- * });
34
- * type TodoTable = typeof TodoTable.Type;
35
- *
36
- * const Database = E.database({
37
- * todo: TodoTable,
38
- *
39
- * // Prefix `_` makes the table local-only (it will not sync)
40
- * _todo: TodoTable,
41
- * });
42
- * type Database = typeof Database.Type;
43
- *
44
- * const evolu = E.createEvolu(Database);
45
- */
46
- createEvolu,
47
- } = EvoluFactory.pipe(Effect.provide(EvoluFactoryWebReact), Effect.runSync);
48
-
49
- export * from "@evolu/common-react";
1
+ export * from "./createUseEvolu.js";
2
+ export * from "./EvoluContext.js";
3
+ export * from "./EvoluProvider.js";
4
+ export * from "./useAppOwner.js";
5
+ export * from "./useEvolu.js";
6
+ export * from "./useEvoluError.js";
7
+ export * from "./useQueries.js";
8
+ export * from "./useQuery.js";
9
+ export * from "./useQuerySubscription.js";
10
+ export * from "./useSyncState.js";
@@ -0,0 +1,13 @@
1
+ import { AppOwner, constNull } from "@evolu/common";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+
5
+ /** Subscribe to {@link AppOwner} changes. */
6
+ export const useAppOwner = (): AppOwner | null => {
7
+ const evolu = useEvolu();
8
+ return useSyncExternalStore(
9
+ evolu.subscribeAppOwner,
10
+ evolu.getAppOwner,
11
+ constNull,
12
+ );
13
+ };
@@ -0,0 +1,20 @@
1
+ import { Evolu } from "@evolu/common/evolu";
2
+ import { useContext } from "react";
3
+ import { EvoluContext } from "./EvoluContext.js";
4
+ import type { createUseEvolu } from "./createUseEvolu.js";
5
+
6
+ /**
7
+ * React Hook returning a generic instance of {@link Evolu}.
8
+ *
9
+ * This is intended for internal usage. Applications should use
10
+ * {@link createUseEvolu}, which provides a correctly typed instance.
11
+ */
12
+ export const useEvolu = (): Evolu => {
13
+ const evolu = useContext(EvoluContext);
14
+ if (evolu == null) {
15
+ throw new Error(
16
+ "Could not find Evolu context value. Ensure the component is wrapped in an <EvoluProvider>.",
17
+ );
18
+ }
19
+ return evolu as Evolu;
20
+ };
@@ -0,0 +1,9 @@
1
+ import { constNull, EvoluError } from "@evolu/common";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+
5
+ /** Subscribe to {@link EvoluError} changes. */
6
+ export const useEvoluError = (): EvoluError | null => {
7
+ const evolu = useEvolu();
8
+ return useSyncExternalStore(evolu.subscribeError, evolu.getError, constNull);
9
+ };
@@ -0,0 +1,46 @@
1
+ import {
2
+ Queries,
3
+ QueriesToQueryRows,
4
+ QueriesToQueryRowsPromises,
5
+ Row,
6
+ } from "@evolu/common/evolu";
7
+ import { use, useRef } from "react";
8
+ import { useEvolu } from "./useEvolu.js";
9
+ import type { useQuery } from "./useQuery.js";
10
+ import { useQuerySubscription } from "./useQuerySubscription.js";
11
+ import { useWasSSR } from "./useWasSSR.js";
12
+
13
+ /** The same as {@link useQuery}, but for many queries. */
14
+ export const useQueries = <
15
+ R extends Row,
16
+ Q extends Queries<R>,
17
+ OQ extends Queries<R>,
18
+ >(
19
+ queries: [...Q],
20
+ options: Partial<{
21
+ /** Queries that should be only loaded, not subscribed to. */
22
+ readonly once: [...OQ];
23
+
24
+ /** Reuse existing promises instead of loading so query will not suspense. */
25
+ readonly promises: [
26
+ ...QueriesToQueryRowsPromises<Q>,
27
+ ...QueriesToQueryRowsPromises<OQ>,
28
+ ];
29
+ }> = {},
30
+ ): [...QueriesToQueryRows<Q>, ...QueriesToQueryRows<OQ>] => {
31
+ const evolu = useEvolu();
32
+ const once = useRef(options).current.once;
33
+ const allQueries = once ? queries.concat(once) : queries;
34
+ const wasSSR = useWasSSR();
35
+ if (wasSSR) {
36
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
37
+ if (!options.promises) evolu.loadQueries(allQueries);
38
+ } else {
39
+ if (options.promises) options.promises.map(use);
40
+ else evolu.loadQueries(allQueries).map(use);
41
+ }
42
+ return allQueries.map((query, i) =>
43
+ // eslint-disable-next-line react-hooks/rules-of-hooks
44
+ useQuerySubscription(query, { once: i > queries.length - 1 }),
45
+ ) as never;
46
+ };
@@ -0,0 +1,55 @@
1
+ import { Query, QueryRows, Row } from "@evolu/common/evolu";
2
+ import { use } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+ import type { useQueries } from "./useQueries.js";
5
+ import { useQuerySubscription } from "./useQuerySubscription.js";
6
+ import { useWasSSR } from "./useWasSSR.js";
7
+
8
+ /**
9
+ * Load and subscribe to the Query, and return an object with `rows` and `row`
10
+ * properties that are automatically updated when data changes.
11
+ *
12
+ * Note that {@link useQuery} uses React Suspense. It means every usage of
13
+ * {@link useQuery} blocks rendering until loading is completed. To avoid loading
14
+ * waterfall with more queries, use {@link useQueries}.
15
+ *
16
+ * ### Example
17
+ *
18
+ * ```ts
19
+ * // Get all rows.
20
+ * const rows = useQuery(allTodos);
21
+ *
22
+ * // Get the first row (it can be null).
23
+ * const row = useQuery(todoById(1));
24
+ *
25
+ * // Get all rows, but without subscribing to changes.
26
+ * const rows = useQuery(allTodos, { once: true });
27
+ *
28
+ * // Prefetch rows.
29
+ * const allTodos = evolu.createQuery((db) =>
30
+ * db.selectFrom("todo").selectAll(),
31
+ * );
32
+ * const allTodosPromise = evolu.loadQuery(allTodos);
33
+ * // Use prefetched rows.
34
+ * const rows = useQuery(allTodos, { promise: allTodosPromise });
35
+ * ```
36
+ */
37
+ export const useQuery = <R extends Row>(
38
+ query: Query<R>,
39
+ options: Partial<{
40
+ /** Without subscribing to changes. */
41
+ readonly once: boolean;
42
+
43
+ /** Reuse existing promise instead of loading so query will not suspense. */
44
+ readonly promise: Promise<QueryRows<R>>;
45
+ }> = {},
46
+ ): QueryRows<R> => {
47
+ const evolu = useEvolu();
48
+ const wasSSR = useWasSSR();
49
+ if (wasSSR) {
50
+ if (!options.promise) void evolu.loadQuery(query);
51
+ } else {
52
+ use(options.promise ?? evolu.loadQuery(query));
53
+ }
54
+ return useQuerySubscription(query, options);
55
+ };
@@ -0,0 +1,38 @@
1
+ import { constVoid } from "@evolu/common";
2
+ import { Query, QueryRows, Row, emptyRows } from "@evolu/common/evolu";
3
+ import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
4
+ import { useEvolu } from "./useEvolu.js";
5
+
6
+ /** Subscribe to {@link Query} {@link QueryRows} changes. */
7
+ export const useQuerySubscription = <R extends Row>(
8
+ query: Query<R>,
9
+ options: Partial<{
10
+ /**
11
+ * Only subscribe and get the current value once. Subscribed query will not
12
+ * invoke React Suspense after a mutation.
13
+ */
14
+ readonly once: boolean;
15
+ }> = {},
16
+ ): QueryRows<R> => {
17
+ const evolu = useEvolu();
18
+
19
+ // useRef to not break "rules-of-hooks"
20
+ const { once } = useRef(options).current;
21
+
22
+ if (once) {
23
+ /* eslint-disable react-hooks/rules-of-hooks */
24
+ useEffect(
25
+ // No useSyncExternalStore, no unnecessary updates.
26
+ () => evolu.subscribeQuery(query)(constVoid),
27
+ [evolu, query],
28
+ );
29
+ return evolu.getQueryRows(query);
30
+ }
31
+
32
+ return useSyncExternalStore(
33
+ useMemo(() => evolu.subscribeQuery(query), [evolu, query]),
34
+ useMemo(() => () => evolu.getQueryRows(query), [evolu, query]),
35
+ () => emptyRows as QueryRows<R>,
36
+ /* eslint-enable react-hooks/rules-of-hooks */
37
+ );
38
+ };
@@ -0,0 +1,13 @@
1
+ import { SyncState, initialSyncState } from "@evolu/common/evolu";
2
+ import { useSyncExternalStore } from "react";
3
+ import { useEvolu } from "./useEvolu.js";
4
+
5
+ /** Subscribe to {@link SyncState} changes. */
6
+ export const useSyncState = (): SyncState => {
7
+ const evolu = useEvolu();
8
+ return useSyncExternalStore(
9
+ evolu.subscribeSyncState,
10
+ evolu.getSyncState,
11
+ () => initialSyncState,
12
+ );
13
+ };
@@ -0,0 +1,17 @@
1
+ import { useRef, useSyncExternalStore } from "react";
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
4
+ const emptySubscribe = () => () => {};
5
+
6
+ /**
7
+ * "Unlike the typeof window hack, this ensures that the server and hydration
8
+ * sees the same thing."
9
+ */
10
+ export const useWasSSR = (): boolean => {
11
+ const ref = useRef(false);
12
+ return useSyncExternalStore(
13
+ emptySubscribe,
14
+ () => ref.current,
15
+ () => (ref.current = true),
16
+ );
17
+ };