@pomwik/afterdb-react 0.0.0-stage → 0.20.1

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 (52) hide show
  1. package/CHANGES-FROM-UPSTREAM.md +57 -0
  2. package/LICENSE +177 -0
  3. package/NOTICE +9 -0
  4. package/README.md +10 -2
  5. package/dist/RealmContext.d.ts +153 -0
  6. package/dist/RealmContext.d.ts.map +1 -0
  7. package/dist/RealmProvider.d.ts +95 -0
  8. package/dist/RealmProvider.d.ts.map +1 -0
  9. package/dist/cachedCollection.d.ts +58 -0
  10. package/dist/cachedCollection.d.ts.map +1 -0
  11. package/dist/cachedObject.d.ts +48 -0
  12. package/dist/cachedObject.d.ts.map +1 -0
  13. package/dist/helpers.d.ts +22 -0
  14. package/dist/helpers.d.ts.map +1 -0
  15. package/dist/index.cjs +2818 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.ts +100 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +2809 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/tsconfig.types.tsbuildinfo +1 -0
  22. package/dist/types.d.ts +64 -0
  23. package/dist/types.d.ts.map +1 -0
  24. package/dist/useObject.d.ts +30 -0
  25. package/dist/useObject.d.ts.map +1 -0
  26. package/dist/useQuery.d.ts +31 -0
  27. package/dist/useQuery.d.ts.map +1 -0
  28. package/dist/useRealm.d.ts +12 -0
  29. package/dist/useRealm.d.ts.map +1 -0
  30. package/package.json +117 -4
  31. package/src/RealmContext.ts +196 -0
  32. package/src/RealmProvider.tsx +251 -0
  33. package/src/__tests__/RealmProvider.test.tsx +583 -0
  34. package/src/__tests__/createRealmTestContext.ts +68 -0
  35. package/src/__tests__/helpers.ts +35 -0
  36. package/src/__tests__/mocks.ts +154 -0
  37. package/src/__tests__/profileHook.tsx +76 -0
  38. package/src/__tests__/setup.ts +22 -0
  39. package/src/__tests__/tsconfig.json +14 -0
  40. package/src/__tests__/useObjectHook.test.tsx +158 -0
  41. package/src/__tests__/useObjectRender.test.tsx +630 -0
  42. package/src/__tests__/useQueryHook.test.tsx +209 -0
  43. package/src/__tests__/useQueryRender.test.tsx +463 -0
  44. package/src/cachedCollection.ts +210 -0
  45. package/src/cachedObject.ts +164 -0
  46. package/src/helpers.ts +60 -0
  47. package/src/index.tsx +130 -0
  48. package/src/runtime.d.ts +31 -0
  49. package/src/types.ts +92 -0
  50. package/src/useObject.tsx +227 -0
  51. package/src/useQuery.tsx +205 -0
  52. package/src/useRealm.tsx +41 -0
@@ -0,0 +1,196 @@
1
+ ////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // Copyright 2024 Realm Inc.
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ //
17
+ ////////////////////////////////////////////////////////////////////////////
18
+
19
+ import { createUseObject } from "./useObject";
20
+ import { createUseQuery } from "./useQuery";
21
+ import { createUseRealm } from "./useRealm";
22
+ import {
23
+ DynamicRealmProvider,
24
+ RealmProviderFromConfiguration,
25
+ RealmProviderFromRealm,
26
+ createRealmProvider,
27
+ } from "./RealmProvider";
28
+ import { createContext } from "react";
29
+ import Realm from "realm";
30
+
31
+ export type RealmContext<RealmProvider = DynamicRealmProvider> = {
32
+ /**
33
+ * The Provider component that is required to wrap any component using
34
+ * the Realm hooks.
35
+ * @example
36
+ * ```
37
+ * const AppRoot = () => {
38
+ * const syncConfig = {
39
+ * flexible: true,
40
+ * user: currentUser
41
+ * };
42
+ *
43
+ * return (
44
+ * <RealmProvider schema={[Task, User]} path={"data.realm"} sync={syncConfig}>
45
+ * <App/>
46
+ * </RealmProvider>
47
+ * )
48
+ * }
49
+ * ```
50
+ * @param props - The {@link Realm.Configuration} or {@link Realm} of the provider
51
+ * are set based on the options passed to `createRealmProvider`. When using a
52
+ * {@link Realm.Configuration}, individual config keys can be overridden when
53
+ * creating a `<RealmProvider>` by passing them as props. For example, to override
54
+ * the `path` config value, use a prop named `path` e.g., `path="newPath.realm"` an
55
+ * attribute of the same key.
56
+ */
57
+ RealmProvider: RealmProvider;
58
+ /**
59
+ * Returns the instance of the {@link Realm} opened by the `RealmProvider`.
60
+ * @example
61
+ * ```
62
+ * const realm = useRealm();
63
+ * ```
64
+ * @returns a realm instance
65
+ */
66
+ useRealm: ReturnType<typeof createUseRealm>;
67
+
68
+ /**
69
+ * Returns a {@link Realm.Collection} of {@link Realm.Object}s from a given type.
70
+ * The hook will update on any changes to any object in the collection.
71
+ *
72
+ * The result of this can be consumed directly by the `data` argument of any React Native
73
+ * VirtualizedList or FlatList. If the component used for the list's `renderItem` prop is {@link React.Memo}ized,
74
+ * then only the modified object will re-render.
75
+ * @example
76
+ * ```tsx
77
+ * // Return all collection items
78
+ * const collection = useQuery({ type: Object });
79
+ *
80
+ * // Return all collection items sorted by name and filtered by category
81
+ * const filteredAndSorted = useQuery({
82
+ * type: Object,
83
+ * query: (collection) => collection.filtered('category == $0',category).sorted('name'),
84
+ * }, [category]);
85
+ *
86
+ * // Return all collection items sorted by name and filtered by category, triggering re-renders only if "name" changes
87
+ * const filteredAndSorted = useQuery({
88
+ * type: Object,
89
+ * query: (collection) => collection.filtered('category == $0',category).sorted('name'),
90
+ * keyPaths: ["name"]
91
+ * }, [category]);
92
+ * ```
93
+ * @param options
94
+ * @param options.type - The object type, depicted by a string or a class extending Realm.Object
95
+ * @param options.query - A function that takes a {@link Realm.Collection} and returns a {@link Realm.Collection} of the same type. This allows for filtering and sorting of the collection, before it is returned.
96
+ * @param options.keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the collection. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present.
97
+ * @param deps - An array of dependencies that will be passed to {@link React.useMemo}
98
+ * @returns a collection of realm objects or an empty array
99
+ */
100
+ useQuery: ReturnType<typeof createUseQuery>;
101
+ /**
102
+ * Returns a {@link Realm.Object} from a given type and value of primary key.
103
+ * The hook will update on any changes to the properties on the returned object
104
+ * and return null if it either doesn't exists or has been deleted.
105
+ * @example
106
+ * ```
107
+ * const object = useObject(ObjectClass, objectId);
108
+ * ```
109
+ * @param type - The object type, depicted by a string or a class extending {@link Realm.Object}
110
+ * @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey}
111
+ * @param keyPaths - Indicates a lower bound on the changes relevant for the hook. This is a lower bound, since if multiple hooks add listeners (each with their own `keyPaths`) the union of these key-paths will determine the changes that are considered relevant for all listeners registered on the object. In other words: A listener might fire and cause a re-render more than the key-paths specify, if other listeners with different key-paths are present.
112
+ * @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing.
113
+ */
114
+ useObject: ReturnType<typeof createUseObject>;
115
+ };
116
+
117
+ /**
118
+ * Creates Realm React hooks and Provider component for a given Realm configuration
119
+ * @example
120
+ * ```
121
+ *class Task extends Realm.Object {
122
+ * ...
123
+ *
124
+ * static schema: ObjectSchema = {
125
+ * name: 'Task',
126
+ * primaryKey: '_id',
127
+ * properties: {
128
+ * ...
129
+ * },
130
+ * };
131
+ *}
132
+ *
133
+ *const {useRealm, useQuery, useObject, RealmProvider} = createRealmContext({schema: [Task]});
134
+ * ```
135
+ * @param realmConfig - {@link Realm.Configuration} used to open the Realm
136
+ * @returns An object containing a `RealmProvider` component, and `useRealm`, `useQuery` and `useObject` hooks
137
+ */
138
+ export function createRealmContext(realmConfig: Realm.Configuration): RealmContext<RealmProviderFromConfiguration>;
139
+ /**
140
+ * Creates Realm React hooks and Provider component for a given Realm instance.
141
+ *
142
+ * **Note:** the hooks returned from `createRealmContext` using an existing Realm can be used outside of the scope of the provider.
143
+ * @example
144
+ * ```
145
+ * const realm = new Realm({ schema: [...] });
146
+ * const {useRealm, useQuery, useObject, RealmProvider} = createRealmContext(realm);
147
+ * ```
148
+ * @param realm - {@link Realm} instance
149
+ * @returns An object containing a `RealmProvider` component, and `useRealm`, `useQuery` and `useObject` hooks
150
+ */
151
+ export function createRealmContext(realm: Realm): RealmContext<RealmProviderFromRealm>;
152
+ /**
153
+ * Creates Realm React hooks and Provider component.
154
+ * @example
155
+ * ```
156
+ * class Task extends Realm.Object {
157
+ * ...
158
+ * static schema: ObjectSchema = {
159
+ * name: 'Task',
160
+ * primaryKey: '_id',
161
+ * properties: {
162
+ * ...
163
+ * },
164
+ * };
165
+ * }
166
+ * const {useRealm, useQuery, useObject, RealmProvider} = createRealmContext();
167
+ * ...
168
+ * <RealmProvider schema={[Task]}></RealmProvider>
169
+ * ```
170
+ * @example
171
+ * ```
172
+ * const realm = await Realm.open({ path: "example.realm", schema: [Task] });
173
+ * const {RealmProvider} = createRealmContext();
174
+ * ...
175
+ * <RealmProvider realm={realm}></RealmProvider>
176
+ * ```
177
+ * @returns An object containing a `RealmProvider` component, and `useRealm`, `useQuery` and `useObject` hooks
178
+ */
179
+ export function createRealmContext(): RealmContext<DynamicRealmProvider>;
180
+ export function createRealmContext(
181
+ realmOrConfig?: Realm | Realm.Configuration,
182
+ ): RealmContext<RealmProviderFromConfiguration | RealmProviderFromRealm | DynamicRealmProvider> {
183
+ const RealmContext = createContext<Realm | null>(realmOrConfig instanceof Realm ? realmOrConfig : null);
184
+ const RealmProvider = createRealmProvider(realmOrConfig, RealmContext);
185
+
186
+ const useRealm = createUseRealm(RealmContext);
187
+ const useQuery = createUseQuery(useRealm);
188
+ const useObject = createUseObject(useRealm);
189
+
190
+ return {
191
+ RealmProvider,
192
+ useRealm,
193
+ useQuery,
194
+ useObject,
195
+ };
196
+ }
@@ -0,0 +1,251 @@
1
+ ////////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // Copyright 2021 Realm Inc.
4
+ //
5
+ // Licensed under the Apache License, Version 2.0 (the "License");
6
+ // you may not use this file except in compliance with the License.
7
+ // You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing, software
12
+ // distributed under the License is distributed on an "AS IS" BASIS,
13
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ // See the License for the specific language governing permissions and
15
+ // limitations under the License.
16
+ //
17
+ ////////////////////////////////////////////////////////////////////////////
18
+
19
+ import React, { useContext, useEffect, useRef, useState } from "react";
20
+ import Realm from "realm";
21
+ import isEqual from "lodash.isequal";
22
+
23
+ import { RestrictivePick } from "./helpers";
24
+
25
+ type PartialRealmConfiguration = Partial<Realm.Configuration>;
26
+
27
+ export type RealmProviderFallback = React.ComponentType;
28
+
29
+ /** Props used for a configuration-based Realm provider */
30
+ type RealmProviderConfigurationProps = {
31
+ /**
32
+ * If false, Realm will not be closed when the component unmounts.
33
+ * @default true
34
+ */
35
+ closeOnUnmount?: boolean;
36
+ /**
37
+ * A ref to the Realm instance. This is useful if you need to access the Realm
38
+ * instance outside of a component that uses the Realm hooks.
39
+ */
40
+ realmRef?: React.MutableRefObject<Realm | null>;
41
+ /**
42
+ * The fallback component to render if the Realm is not open.
43
+ */
44
+ fallback?: RealmProviderFallback | React.ComponentType | React.ReactElement | null | undefined;
45
+ children: React.ReactNode;
46
+ } & PartialRealmConfiguration;
47
+
48
+ /** Props used for a Realm instance-based Realm provider */
49
+ type RealmProviderRealmProps = {
50
+ /**
51
+ * The Realm instance to be used by the provider.
52
+ */
53
+ realm: Realm;
54
+ children: React.ReactNode;
55
+ };
56
+
57
+ type RealmProviderProps = RealmProviderConfigurationProps & RealmProviderRealmProps;
58
+
59
+ /**
60
+ * Represents the provider returned from `createRealmContext` with a Realm instance i.e. `createRealmContext(new Realm(...))`.
61
+ * Omits "realm" as it gets set at creation and cannot be changed.
62
+
63
+ * **Note:** the hooks returned from `createRealmContext` using an existing Realm can be used outside of the scope of the provider.
64
+ */
65
+ export type RealmProviderFromRealm = React.FC<Omit<RealmProviderRealmProps, "realm">>;
66
+
67
+ /**
68
+ * Represents the provider returned from `createRealmContext` with a configuration, i.e. `createRealmContext({schema: [...]})`.
69
+ */
70
+ export type RealmProviderFromConfiguration = React.FC<RealmProviderConfigurationProps>;
71
+
72
+ /**
73
+ * Represents properties of a {@link DynamicRealmProvider} where Realm instance props are set and Configuration props are disallowed.
74
+ */
75
+ export type DynamicRealmProviderWithRealmProps = RestrictivePick<RealmProviderProps, keyof RealmProviderRealmProps>;
76
+
77
+ /**
78
+ * Represents properties of a {@link DynamicRealmProvider} where Realm configuration props are set and Realm instance props are disallowed.
79
+ */
80
+ export type DynamicRealmProviderWithConfigurationProps = RestrictivePick<
81
+ RealmProviderProps,
82
+ keyof RealmProviderConfigurationProps
83
+ >;
84
+
85
+ /**
86
+ * Represents the provider returned from creating context with no arguments (including the default context).
87
+ * Supports either {@link RealmProviderRealmProps} or {@link RealmProviderConfigurationProps}.
88
+ */
89
+ export type DynamicRealmProvider = React.FC<
90
+ DynamicRealmProviderWithRealmProps | DynamicRealmProviderWithConfigurationProps
91
+ >;
92
+
93
+ export function createRealmProviderFromRealm(
94
+ realm: Realm,
95
+ RealmContext: React.Context<Realm | null>,
96
+ ): RealmProviderFromRealm {
97
+ return ({ children }) => {
98
+ return <RealmContext.Provider value={realm} children={children} />;
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Generates a `RealmProvider` given a {@link Realm.Configuration} and {@link React.Context}.
104
+ * @param realmConfig - The configuration of the Realm to be instantiated
105
+ * @param RealmContext - The context that will contain the Realm instance
106
+ * @returns a RealmProvider component that provides context to all context hooks
107
+ */
108
+ export function createRealmProviderFromConfig(
109
+ realmConfig: Realm.Configuration,
110
+ RealmContext: React.Context<Realm | null>,
111
+ ): RealmProviderFromConfiguration {
112
+ return ({ children, fallback: Fallback, closeOnUnmount = true, realmRef, ...restProps }) => {
113
+ const [realm, setRealm] = useState<Realm | null>(() =>
114
+ realmConfig.sync === undefined && restProps.sync === undefined
115
+ ? new Realm(mergeRealmConfiguration(realmConfig, restProps))
116
+ : null,
117
+ );
118
+
119
+ // We increment `configVersion` when a config override passed as a prop
120
+ // changes, which triggers a `useEffect` to re-open the Realm with the
121
+ // new config
122
+ const [configVersion, setConfigVersion] = useState(0);
123
+
124
+ // We put realm in a ref to avoid have an endless loop of updates when the realm is updated
125
+ const currentRealm = useRef(realm);
126
+
127
+ // This will merge the configuration provided by createRealmContext and any configuration properties
128
+ // set directly on the RealmProvider component. Any settings on the component will override the original configuration.
129
+ const configuration = useRef<Realm.Configuration>(mergeRealmConfiguration(realmConfig, restProps));
130
+
131
+ // Merge and set the configuration again and increment the version if any
132
+ // of the RealmProvider properties change.
133
+ useEffect(() => {
134
+ const combinedConfig = mergeRealmConfiguration(realmConfig, restProps);
135
+
136
+ if (!areConfigurationsIdentical(configuration.current, combinedConfig)) {
137
+ configuration.current = combinedConfig;
138
+ // Only rerender if realm has already been configured
139
+ if (currentRealm.current != null) {
140
+ setConfigVersion((x) => x + 1);
141
+ }
142
+ }
143
+ }, [restProps]);
144
+
145
+ useEffect(() => {
146
+ currentRealm.current = realm;
147
+ if (realmRef) {
148
+ realmRef.current = realm;
149
+ }
150
+ }, [realm]);
151
+
152
+ useEffect(() => {
153
+ const realmRef = currentRealm.current;
154
+ // Check if we currently have an open Realm. If we do not (i.e. it is the first
155
+ // render, or the Realm has been closed due to a config change), then we
156
+ // need to open a new Realm.
157
+ const shouldInitRealm = realmRef === null;
158
+ const initRealm = async () => {
159
+ const openRealm = await Realm.open(configuration.current);
160
+ setRealm(openRealm);
161
+ };
162
+ if (shouldInitRealm) {
163
+ initRealm().catch(console.error);
164
+ }
165
+
166
+ return () => {
167
+ if (realm) {
168
+ if (closeOnUnmount) {
169
+ realm.close();
170
+ }
171
+ setRealm(null);
172
+ }
173
+ };
174
+ }, [configVersion, realm, setRealm, closeOnUnmount]);
175
+
176
+ if (!realm) {
177
+ if (typeof Fallback === "function") {
178
+ return <Fallback />;
179
+ }
180
+ return <>{Fallback}</>;
181
+ }
182
+
183
+ return <RealmContext.Provider value={realm} children={children} />;
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Generates a `RealmProvider` which is either based on a configuration
189
+ * or based on a realm, depending on its props.
190
+ * @param RealmContext - The context that will contain the Realm instance
191
+ * @returns a RealmProvider component that provides context to all context hooks
192
+ */
193
+ export function createDynamicRealmProvider(RealmContext: React.Context<Realm | null>): DynamicRealmProvider {
194
+ const RealmProviderFromConfig = createRealmProviderFromConfig({}, RealmContext);
195
+ return ({ realm, children, ...config }) => {
196
+ if (realm) {
197
+ if (Object.keys(config).length > 0) {
198
+ throw new Error("Cannot use configuration props when using an existing Realm instance.");
199
+ }
200
+ return <RealmContext.Provider value={realm} children={children} />;
201
+ } else {
202
+ return <RealmProviderFromConfig {...config} children={children} />;
203
+ }
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Generates the appropriate `RealmProvider` based on whether there is a config, realm, or neither given.
209
+ * @param realmOrConfig - A Realm instance, a configuration, or undefined (including default provider).
210
+ * @param RealmContext - The context that will contain the Realm instance
211
+ * @returns a RealmProvider component that provides context to all context hooks
212
+ */
213
+ export function createRealmProvider(
214
+ realmOrConfig: Realm.Configuration | Realm | undefined,
215
+ RealmContext: React.Context<Realm | null>,
216
+ ): RealmProviderFromConfiguration | RealmProviderFromRealm | DynamicRealmProvider {
217
+ if (!realmOrConfig) {
218
+ return createDynamicRealmProvider(RealmContext);
219
+ } else if (realmOrConfig instanceof Realm) {
220
+ return createRealmProviderFromRealm(realmOrConfig, RealmContext);
221
+ } else {
222
+ return createRealmProviderFromConfig(realmOrConfig, RealmContext);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Merge two configurations, creating a configuration using `configA` as the default,
228
+ * merged with `configB`, with properties in `configB` overriding `configA`.
229
+ * @param configA - The default config object
230
+ * @param configB - Config overrides object
231
+ * @returns Merged config object
232
+ */
233
+ export function mergeRealmConfiguration(
234
+ configA: PartialRealmConfiguration,
235
+ configB: PartialRealmConfiguration,
236
+ ): Realm.Configuration {
237
+ return {
238
+ ...configA,
239
+ ...configB,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Utility function that does a deep comparison (key: value) of object a with object b
245
+ * @param a - Object to compare
246
+ * @param b - Object to compare
247
+ * @returns True if the objects are identical
248
+ */
249
+ export function areConfigurationsIdentical(a: Realm.Configuration, b: Realm.Configuration): boolean {
250
+ return isEqual(a, b);
251
+ }