@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.
- package/CHANGES-FROM-UPSTREAM.md +57 -0
- package/LICENSE +177 -0
- package/NOTICE +9 -0
- package/README.md +10 -2
- package/dist/RealmContext.d.ts +153 -0
- package/dist/RealmContext.d.ts.map +1 -0
- package/dist/RealmProvider.d.ts +95 -0
- package/dist/RealmProvider.d.ts.map +1 -0
- package/dist/cachedCollection.d.ts +58 -0
- package/dist/cachedCollection.d.ts.map +1 -0
- package/dist/cachedObject.d.ts +48 -0
- package/dist/cachedObject.d.ts.map +1 -0
- package/dist/helpers.d.ts +22 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/index.cjs +2818 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2809 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.types.tsbuildinfo +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useObject.d.ts +30 -0
- package/dist/useObject.d.ts.map +1 -0
- package/dist/useQuery.d.ts +31 -0
- package/dist/useQuery.d.ts.map +1 -0
- package/dist/useRealm.d.ts +12 -0
- package/dist/useRealm.d.ts.map +1 -0
- package/package.json +117 -4
- package/src/RealmContext.ts +196 -0
- package/src/RealmProvider.tsx +251 -0
- package/src/__tests__/RealmProvider.test.tsx +583 -0
- package/src/__tests__/createRealmTestContext.ts +68 -0
- package/src/__tests__/helpers.ts +35 -0
- package/src/__tests__/mocks.ts +154 -0
- package/src/__tests__/profileHook.tsx +76 -0
- package/src/__tests__/setup.ts +22 -0
- package/src/__tests__/tsconfig.json +14 -0
- package/src/__tests__/useObjectHook.test.tsx +158 -0
- package/src/__tests__/useObjectRender.test.tsx +630 -0
- package/src/__tests__/useQueryHook.test.tsx +209 -0
- package/src/__tests__/useQueryRender.test.tsx +463 -0
- package/src/cachedCollection.ts +210 -0
- package/src/cachedObject.ts +164 -0
- package/src/helpers.ts +60 -0
- package/src/index.tsx +130 -0
- package/src/runtime.d.ts +31 -0
- package/src/types.ts +92 -0
- package/src/useObject.tsx +227 -0
- package/src/useQuery.tsx +205 -0
- package/src/useRealm.tsx +41 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2022 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 Realm from "realm";
|
|
20
|
+
|
|
21
|
+
import { CollectionCallback } from "./helpers";
|
|
22
|
+
|
|
23
|
+
const numericRegEx = /^-?\d+$/;
|
|
24
|
+
|
|
25
|
+
function getCacheKey(id: string) {
|
|
26
|
+
return `${id}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Arguments object for {@link createCachedCollection}.
|
|
31
|
+
*/
|
|
32
|
+
type CachedCollectionArgs<T> = {
|
|
33
|
+
/**
|
|
34
|
+
* The {@link Realm.Collection} to proxy
|
|
35
|
+
*/
|
|
36
|
+
collection: Realm.List<T> | Realm.Results<T>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The {@link Realm} instance
|
|
40
|
+
*/
|
|
41
|
+
realm: Realm;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Callback which is called whenever an object in the collection changes
|
|
45
|
+
*/
|
|
46
|
+
updateCallback: () => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reference boolean which is set to true whenever an object in the collection changes
|
|
50
|
+
* It is used to determine if the collection's object reference should be updated
|
|
51
|
+
* The implementing component should reset this to false when updating its object reference
|
|
52
|
+
*/
|
|
53
|
+
updatedRef: React.MutableRefObject<boolean>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Optional Map to be used as the cache. This is used to allow a `sorted` or `filtered`
|
|
57
|
+
* (derived) version of the collection to reuse the same cache, preventing excess new object
|
|
58
|
+
* references being created.
|
|
59
|
+
*/
|
|
60
|
+
objectCache?: Map<string, T>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Optional flag specifying that this is a derived (`sorted` or `filtered`) version of
|
|
64
|
+
* an existing collection, so we should not create or remove listeners or clear the cache
|
|
65
|
+
* when this is torn down.
|
|
66
|
+
*/
|
|
67
|
+
isDerived?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Optional list of key-paths to limit notifications.
|
|
71
|
+
*/
|
|
72
|
+
keyPaths?: string[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a proxy around a {@link Realm.Collection} that will create new {@link Realm.Object}
|
|
77
|
+
* references on any relevant change (update, insert, deletion) and return the same
|
|
78
|
+
* object reference if no changes have occurred since the last access.
|
|
79
|
+
*
|
|
80
|
+
* This makes the {@link Realm.Collection} behaves in an immutable way, as React expects, so
|
|
81
|
+
* that a {@link Realm.Object} can be wrapped in {@link React.memo} to prevent unnecessary
|
|
82
|
+
* rendering (see {@link useQuery} hook).
|
|
83
|
+
* @param args {@link CachedCollectionArgs} object arguments
|
|
84
|
+
* @returns Proxy object wrapping the collection
|
|
85
|
+
*/
|
|
86
|
+
export function createCachedCollection<T extends Realm.Object<any>>({
|
|
87
|
+
collection,
|
|
88
|
+
realm,
|
|
89
|
+
updateCallback,
|
|
90
|
+
updatedRef,
|
|
91
|
+
objectCache = new Map(),
|
|
92
|
+
isDerived = false,
|
|
93
|
+
keyPaths,
|
|
94
|
+
}: CachedCollectionArgs<T>): { collection: Realm.Results<T> | Realm.List<T>; tearDown: () => void } {
|
|
95
|
+
const cachedCollectionHandler: ProxyHandler<Realm.Results<T> | Realm.List<T>> = {
|
|
96
|
+
get: function (target, key, receiver) {
|
|
97
|
+
// Pass functions through
|
|
98
|
+
const value = Reflect.get(target, key, receiver);
|
|
99
|
+
if (typeof value === "function") {
|
|
100
|
+
if (key === "sorted" || key === "filtered") {
|
|
101
|
+
return (...args: unknown[]) => {
|
|
102
|
+
const col: Realm.Results<T & Realm.Object> = Reflect.apply(value, target, args);
|
|
103
|
+
const { collection: newCol } = createCachedCollection({
|
|
104
|
+
collection: col,
|
|
105
|
+
realm,
|
|
106
|
+
updateCallback,
|
|
107
|
+
updatedRef,
|
|
108
|
+
objectCache,
|
|
109
|
+
isDerived: true,
|
|
110
|
+
keyPaths,
|
|
111
|
+
});
|
|
112
|
+
return newCol;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If the key is not numeric, pass it through
|
|
119
|
+
if (typeof key === "symbol" || !numericRegEx.test(key)) {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// If the key is numeric, check if we have a cached object for this key
|
|
124
|
+
const index = Number(key);
|
|
125
|
+
const object = target[index];
|
|
126
|
+
|
|
127
|
+
// If the collection is modeled in a way that objects can be null
|
|
128
|
+
// then we should return null instead of undefined to stay semantically
|
|
129
|
+
// correct
|
|
130
|
+
if (object === null) {
|
|
131
|
+
return null;
|
|
132
|
+
} else if (typeof object === "undefined") {
|
|
133
|
+
// If there is no object at this index, return undefined
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const objectId = object._objectKey();
|
|
138
|
+
const cacheKey = getCacheKey(objectId);
|
|
139
|
+
|
|
140
|
+
// If we do, return it...
|
|
141
|
+
if (objectCache.get(cacheKey)) {
|
|
142
|
+
return objectCache.get(cacheKey);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If not then this index has either not been accessed before, or has been invalidated due
|
|
146
|
+
// to a modification. Fetch it from the collection and store it in the cache
|
|
147
|
+
objectCache.set(cacheKey, object);
|
|
148
|
+
|
|
149
|
+
return object;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const cachedCollectionResult = new Proxy(collection, cachedCollectionHandler);
|
|
154
|
+
|
|
155
|
+
const listenerCallback: CollectionCallback = (listenerCollection, changes) => {
|
|
156
|
+
if (changes.deletions.length > 0 || changes.insertions.length > 0 || changes.newModifications.length > 0) {
|
|
157
|
+
// TODO: There is currently no way to rebuild the cache key from the changes array for deleted object.
|
|
158
|
+
// Until it is possible, we clear the cache on deletions.
|
|
159
|
+
// Blocking issue: https://github.com/realm/realm-core/issues/5220
|
|
160
|
+
|
|
161
|
+
// Possible solutions:
|
|
162
|
+
// a. the listenerCollection is a frozen copy of the collection before the deletion,
|
|
163
|
+
// allowing accessing the _objectKey() using listenerCollection[index]._objectKey()
|
|
164
|
+
// b. the callback provides an array of changed objectIds
|
|
165
|
+
|
|
166
|
+
if (changes.deletions.length > 0) {
|
|
167
|
+
objectCache.clear();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Item(s) were modified, just clear them from the cache so that we return new instances for them
|
|
171
|
+
changes.newModifications.forEach((index) => {
|
|
172
|
+
const objectId = listenerCollection[index]._objectKey();
|
|
173
|
+
if (objectId) {
|
|
174
|
+
const cacheKey = getCacheKey(objectId);
|
|
175
|
+
if (objectCache.has(cacheKey)) {
|
|
176
|
+
objectCache.delete(cacheKey);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
updatedRef.current = true;
|
|
181
|
+
updateCallback();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
let setImmediateId: ReturnType<typeof setImmediate> | undefined = undefined;
|
|
186
|
+
if (!isDerived) {
|
|
187
|
+
// If we are in a transaction, then push adding the listener to the event loop. This will allow the write transaction to finish.
|
|
188
|
+
// see https://github.com/realm/realm-js/issues/4375
|
|
189
|
+
if (realm.isInTransaction) {
|
|
190
|
+
setImmediateId = setImmediate(() => {
|
|
191
|
+
collection.addListener(listenerCallback, keyPaths);
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
collection.addListener(listenerCallback, keyPaths);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const tearDown = () => {
|
|
199
|
+
if (!isDerived) {
|
|
200
|
+
if (setImmediateId) {
|
|
201
|
+
clearImmediate(setImmediateId);
|
|
202
|
+
setImmediateId = undefined;
|
|
203
|
+
}
|
|
204
|
+
collection.removeListener(listenerCallback);
|
|
205
|
+
objectCache.clear();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
return { collection: cachedCollectionResult, tearDown };
|
|
210
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2022 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 Realm from "realm";
|
|
20
|
+
|
|
21
|
+
import { createCachedCollection } from "./cachedCollection";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Arguments object for `cachedObject`.
|
|
25
|
+
*/
|
|
26
|
+
type CachedObjectArgs = {
|
|
27
|
+
/**
|
|
28
|
+
* The {@link Realm.Object} to proxy
|
|
29
|
+
*/
|
|
30
|
+
object: Realm.Object | null;
|
|
31
|
+
/**
|
|
32
|
+
* The {@link Realm} instance
|
|
33
|
+
*/
|
|
34
|
+
realm: Realm;
|
|
35
|
+
/**
|
|
36
|
+
* Callback function called whenver the object changes. Used to force a component
|
|
37
|
+
* using the {@link useObject} hook to re-render.
|
|
38
|
+
*/
|
|
39
|
+
updateCallback: () => void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Reference boolean which is set to true whenever the object changes
|
|
43
|
+
* It is used to determine if the object's reference should be updated
|
|
44
|
+
* The implementing component should reset this to false when updating its object reference
|
|
45
|
+
*/
|
|
46
|
+
updatedRef: React.MutableRefObject<boolean>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional list of key-paths to limit notifications.
|
|
50
|
+
*/
|
|
51
|
+
keyPaths?: string[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type CachedObject = {
|
|
55
|
+
object: Realm.Object | null;
|
|
56
|
+
tearDown: () => void;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a proxy around a {@link Realm.Object} that will return a new reference
|
|
61
|
+
* on any relevant update to the object itself. It also wraps accesses to {@link Realm.List}
|
|
62
|
+
* attributes into a {@link cachedCollection}, so that any update, insert or deletion to the
|
|
63
|
+
* list will also return a new object reference.
|
|
64
|
+
*
|
|
65
|
+
* See {@link cachedCollection} and the `useObject` hook for description of how this
|
|
66
|
+
* helps prevent unnecessary rendering.
|
|
67
|
+
* @param args - {@link CachedObjectArgs} object arguments
|
|
68
|
+
* @returns Proxy object wrapping the {@link Realm.Object}
|
|
69
|
+
*/
|
|
70
|
+
export function createCachedObject({
|
|
71
|
+
object,
|
|
72
|
+
realm,
|
|
73
|
+
updateCallback,
|
|
74
|
+
updatedRef,
|
|
75
|
+
keyPaths,
|
|
76
|
+
}: CachedObjectArgs): CachedObject {
|
|
77
|
+
const listCaches = new Map();
|
|
78
|
+
const listTearDowns: Array<() => void> = [];
|
|
79
|
+
// If the object doesn't exist, just return it with an noop tearDown
|
|
80
|
+
//
|
|
81
|
+
if (object === null) {
|
|
82
|
+
return { object, tearDown: () => undefined };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Create a cache for any Realm.List properties on the object
|
|
86
|
+
for (const key of object.keys()) {
|
|
87
|
+
//@ts-expect-error - TS doesn't know that the key is a valid property
|
|
88
|
+
const value = object[key];
|
|
89
|
+
if (value instanceof Realm.List && value.type === "object") {
|
|
90
|
+
const updatedRef = { current: true };
|
|
91
|
+
const { collection, tearDown } = createCachedCollection({ collection: value, realm, updateCallback, updatedRef });
|
|
92
|
+
listCaches.set(key, { collection, updatedRef });
|
|
93
|
+
listTearDowns.push(tearDown);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// This Proxy handler intercepts any accesses into properties of the cached object
|
|
98
|
+
// of type `Realm.List`, and returns a `cachedCollection` wrapping those properties
|
|
99
|
+
// to allow changes in the list to trigger re-renders
|
|
100
|
+
const cachedObjectHandler: ProxyHandler<Realm.Object> = {
|
|
101
|
+
get: function (target, key, receiver) {
|
|
102
|
+
const value = Reflect.get(target, key, receiver);
|
|
103
|
+
|
|
104
|
+
// If its a Realm.List we need to add a proxy cache around it
|
|
105
|
+
if (value instanceof Realm.List && value.type === "object") {
|
|
106
|
+
if (listCaches.has(key)) {
|
|
107
|
+
// Return a new proxy wrapping the cachedCollection so that its reference gets updated,
|
|
108
|
+
// otherwise the list component will not re-render. The cachedCollection then ensures that
|
|
109
|
+
// only the modified children of the list component actually re-render.
|
|
110
|
+
const { collection, updatedRef } = listCaches.get(key);
|
|
111
|
+
if (updatedRef.current) {
|
|
112
|
+
updatedRef.current = false;
|
|
113
|
+
const proxyCollection = new Proxy(collection, {});
|
|
114
|
+
listCaches.set(key, { collection: proxyCollection, updatedRef });
|
|
115
|
+
return proxyCollection;
|
|
116
|
+
}
|
|
117
|
+
return collection;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const cachedObjectResult = new Proxy(object, cachedObjectHandler);
|
|
125
|
+
const listenerCallback: Realm.ObjectChangeCallback<any> = (obj, changes) => {
|
|
126
|
+
updatedRef.current = true;
|
|
127
|
+
if (changes.deleted) {
|
|
128
|
+
updateCallback();
|
|
129
|
+
} else if (changes.changedProperties.length > 0) {
|
|
130
|
+
// Don't force a second re-render if any of the changed properties is a Realm.List,
|
|
131
|
+
// as the List's cachedCollection will force a re-render itself
|
|
132
|
+
const anyListPropertyModified = changes.changedProperties.some((property) => {
|
|
133
|
+
return obj[property] instanceof Realm.List && obj[property].type === "object";
|
|
134
|
+
});
|
|
135
|
+
const shouldRerender = !anyListPropertyModified;
|
|
136
|
+
|
|
137
|
+
if (shouldRerender) {
|
|
138
|
+
updateCallback();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// We cannot add a listener to an invalid object
|
|
144
|
+
if (object.isValid()) {
|
|
145
|
+
// If we are in a transaction, then push adding the listener to the event loop. This will allow the write transaction to finish.
|
|
146
|
+
// see https://github.com/realm/realm-js/issues/4375
|
|
147
|
+
if (realm.isInTransaction) {
|
|
148
|
+
setImmediate(() => {
|
|
149
|
+
object.addListener(listenerCallback, keyPaths);
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
object.addListener(listenerCallback, keyPaths);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const tearDown = () => {
|
|
157
|
+
object.removeListener(listenerCallback);
|
|
158
|
+
for (const listTearDown of listTearDowns) {
|
|
159
|
+
listTearDown();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return { object: cachedObjectResult, tearDown };
|
|
164
|
+
}
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2023 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 Realm from "realm";
|
|
20
|
+
|
|
21
|
+
// Convenience function that returns the correct type for the objectForPrimaryKey function
|
|
22
|
+
// Since we don't have a combined declaration for this function, typescript needs to know
|
|
23
|
+
// which return to use based on the typeof the type argument
|
|
24
|
+
export function getObjectForPrimaryKey<T extends Realm.Object>(
|
|
25
|
+
realm: Realm,
|
|
26
|
+
type: string | { new (...args: any): T },
|
|
27
|
+
primaryKey: T[keyof T],
|
|
28
|
+
) {
|
|
29
|
+
return typeof type === "string"
|
|
30
|
+
? realm.objectForPrimaryKey(type, primaryKey)
|
|
31
|
+
: realm.objectForPrimaryKey(type, primaryKey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Convenience function that returns the correct type for the objects function
|
|
35
|
+
// Since we don't have a combined declaration for this function, typescript needs to know
|
|
36
|
+
// which return to use based on the typeof the type argument
|
|
37
|
+
export function getObjects<T extends Realm.Object>(
|
|
38
|
+
realm: Realm,
|
|
39
|
+
type: string | { new (...args: any): T },
|
|
40
|
+
): Realm.Results<T> {
|
|
41
|
+
return (typeof type === "string" ? realm.objects(type) : realm.objects(type)) as Realm.Results<T>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type CollectionCallback = Parameters<typeof Realm.Results.prototype.addListener>[0];
|
|
45
|
+
|
|
46
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
47
|
+
export type AnyRealmObject = Realm.Object<any>;
|
|
48
|
+
|
|
49
|
+
export type RealmClassType<T = any> = { new (...args: any): T };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Explicitly sets the unpicked properties of a type to never instead of dropping them like in Pick.
|
|
53
|
+
* Useful for ensuring different prop types are mutually exclusive as React expects the union type
|
|
54
|
+
* of different prop types to include all the fields.
|
|
55
|
+
*/
|
|
56
|
+
export type RestrictivePick<T, K extends keyof T> = Pick<T, K> & { [RestrictedKey in keyof Omit<T, K>]?: never };
|
|
57
|
+
|
|
58
|
+
export function isClassModelConstructor(value: unknown): value is RealmClassType<unknown> {
|
|
59
|
+
return Object.getPrototypeOf(value) === Realm.Object;
|
|
60
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
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 Realm from "realm";
|
|
20
|
+
import React from "react";
|
|
21
|
+
import { createRealmContext } from "./RealmContext";
|
|
22
|
+
|
|
23
|
+
export type { UseObjectHook } from "./useObject";
|
|
24
|
+
export type { UseQueryHook, QueryHookOptions } from "./useQuery";
|
|
25
|
+
|
|
26
|
+
const defaultContext = createRealmContext();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The Provider component that is required to wrap any component using
|
|
30
|
+
* the Realm hooks.
|
|
31
|
+
* @example
|
|
32
|
+
* ```
|
|
33
|
+
* const AppRoot = () => {
|
|
34
|
+
* const syncConfig = {
|
|
35
|
+
* flexible: true,
|
|
36
|
+
* user: currentUser
|
|
37
|
+
* };
|
|
38
|
+
*
|
|
39
|
+
* return (
|
|
40
|
+
* <RealmProvider schema={[Task, User]} path={"data.realm"} sync={syncConfig}>
|
|
41
|
+
* <App/>
|
|
42
|
+
* </RealmProvider>
|
|
43
|
+
* )
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
* @example
|
|
47
|
+
* const syncConfig = {
|
|
48
|
+
* flexible: true,
|
|
49
|
+
* user: currentUser
|
|
50
|
+
* };
|
|
51
|
+
* const realm = new Realm(schema:[Task, User], path:"data.realm", sync: syncConfig);
|
|
52
|
+
* ...
|
|
53
|
+
* const AppRoot = () => {
|
|
54
|
+
* return (
|
|
55
|
+
* <RealmProvider>
|
|
56
|
+
* <App/>
|
|
57
|
+
* </RealmProvider>
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
* @param props - The {@link Realm.Configuration} or {@link Realm} for this Provider, passed as props.
|
|
61
|
+
*/
|
|
62
|
+
export const RealmProvider = defaultContext.RealmProvider;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns the instance of the {@link Realm} opened by the `RealmProvider`.
|
|
66
|
+
* @example
|
|
67
|
+
* ```
|
|
68
|
+
* const realm = useRealm();
|
|
69
|
+
* ```
|
|
70
|
+
* @returns a realm instance
|
|
71
|
+
*/
|
|
72
|
+
export const useRealm = defaultContext.useRealm;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns a {@link Realm.Collection} of {@link Realm.Object}s from a given type.
|
|
76
|
+
* The hook will update on any changes to any object in the collection.
|
|
77
|
+
*
|
|
78
|
+
* The result of this can be consumed directly by the `data` argument of any React Native
|
|
79
|
+
* VirtualizedList or FlatList. If the component used for the list's `renderItem` prop is {@link React.Memo}ized,
|
|
80
|
+
* then only the modified object will re-render.
|
|
81
|
+
* @example
|
|
82
|
+
* ```tsx
|
|
83
|
+
* // Return all collection items
|
|
84
|
+
* const collection = useQuery({ type: Object });
|
|
85
|
+
*
|
|
86
|
+
* // Return all collection items sorted by name and filtered by category
|
|
87
|
+
* const filteredAndSorted = useQuery({
|
|
88
|
+
* type: Object,
|
|
89
|
+
* query: (collection) => collection.filtered('category == $0',category).sorted('name'),
|
|
90
|
+
* }, [category]);
|
|
91
|
+
*
|
|
92
|
+
* // Return all collection items sorted by name and filtered by category, triggering re-renders only if "name" changes
|
|
93
|
+
* const filteredAndSorted = useQuery({
|
|
94
|
+
* type: Object,
|
|
95
|
+
* query: (collection) => collection.filtered('category == $0',category).sorted('name'),
|
|
96
|
+
* keyPaths: ["name"]
|
|
97
|
+
* }, [category]);
|
|
98
|
+
* ```
|
|
99
|
+
* @param options
|
|
100
|
+
* @param options.type - The object type, depicted by a string or a class extending Realm.Object
|
|
101
|
+
* @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.
|
|
102
|
+
* @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.
|
|
103
|
+
* @param deps - An array of dependencies that will be passed to {@link React.useMemo}
|
|
104
|
+
* @returns a collection of realm objects or an empty array
|
|
105
|
+
*/
|
|
106
|
+
export const useQuery = defaultContext.useQuery;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns a {@link Realm.Object} from a given type and value of primary key.
|
|
110
|
+
* The hook will update on any changes to the properties on the returned object
|
|
111
|
+
* and return null if it either doesn't exists or has been deleted.
|
|
112
|
+
* @example
|
|
113
|
+
* ```
|
|
114
|
+
* const object = useObject(ObjectClass, objectId);
|
|
115
|
+
* ```
|
|
116
|
+
* @param type - The object type, depicted by a string or a class extending {@link Realm.Object}
|
|
117
|
+
* @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey}
|
|
118
|
+
* @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.
|
|
119
|
+
* @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing.
|
|
120
|
+
*/
|
|
121
|
+
export const useObject = defaultContext.useObject;
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* @ignore This will end up documenting all of Realm, which is documented elsewhere
|
|
125
|
+
*/
|
|
126
|
+
export { Realm };
|
|
127
|
+
export { createRealmContext } from "./RealmContext";
|
|
128
|
+
export * from "./types";
|
|
129
|
+
export { createUseObject } from "./useObject";
|
|
130
|
+
export { createUseQuery } from "./useQuery";
|
package/src/runtime.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
declare type Console = {
|
|
20
|
+
log(...args: unknown[]): void;
|
|
21
|
+
error(...args: unknown[]): void;
|
|
22
|
+
warn(...args: unknown[]): void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
declare const console: Console;
|
|
26
|
+
|
|
27
|
+
declare function setImmediate(cb: (...args: unknown[]) => void);
|
|
28
|
+
|
|
29
|
+
declare type Timer = unknown;
|
|
30
|
+
declare function clearImmediate(timer: Timer): void;
|
|
31
|
+
|