@linkedrecords/react 0.4.0 → 0.6.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/dist/index.d.mts +33 -4
- package/dist/index.d.ts +33 -4
- package/dist/index.js +102 -9
- package/dist/index.mjs +102 -12
- package/package.json +1 -1
- package/src/LinkedRecordsProvider.tsx +41 -3
- package/src/index.tsx +2 -0
- package/src/queryKey.ts +6 -0
- package/src/useActorId.ts +24 -0
- package/src/useFacts.ts +39 -0
- package/src/useRecords.ts +55 -6
package/dist/index.d.mts
CHANGED
|
@@ -8,19 +8,46 @@ interface LinkedRecordsContextType {
|
|
|
8
8
|
}
|
|
9
9
|
declare const LinkedRecordsContext: react.Context<LinkedRecordsContextType | undefined>;
|
|
10
10
|
|
|
11
|
+
type OIDCConfig = NonNullable<ConstructorParameters<typeof LinkedRecords>[1]>;
|
|
12
|
+
interface LinkedRecordsConfig {
|
|
13
|
+
oidcConfig?: OIDCConfig;
|
|
14
|
+
autoHandleRedirect?: boolean;
|
|
15
|
+
deferUserInfoFetching?: boolean;
|
|
16
|
+
loginHandler?: () => void;
|
|
17
|
+
connectionLostHandler?: (err?: any) => void;
|
|
18
|
+
unknownServerErrorHandler?: (response: any) => void;
|
|
19
|
+
quotaViolationErrorHandler?: (response: any) => void;
|
|
20
|
+
authorizationErrorHandler?: (response: any) => void;
|
|
21
|
+
}
|
|
11
22
|
interface LinkedRecordsProviderProps {
|
|
12
23
|
children: ReactNode;
|
|
13
24
|
serverUrl: string;
|
|
25
|
+
config?: LinkedRecordsConfig;
|
|
14
26
|
}
|
|
15
|
-
declare function LinkedRecordsProvider({ children, serverUrl }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
|
|
27
|
+
declare function LinkedRecordsProvider({ children, serverUrl, config }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
|
|
16
28
|
|
|
17
29
|
interface KVValue {
|
|
18
|
-
[key: string]: KVValue | string | boolean | number | undefined;
|
|
30
|
+
[key: string]: KVValue | string | boolean | number | Date | undefined;
|
|
31
|
+
}
|
|
32
|
+
interface KVRecord extends KVValue {
|
|
33
|
+
_id?: string;
|
|
34
|
+
_createdAt?: Date;
|
|
19
35
|
}
|
|
20
|
-
declare function
|
|
36
|
+
declare function useKeyValueRecordsWithStatus(query: any[]): {
|
|
37
|
+
records: KVRecord[];
|
|
38
|
+
isLoading: boolean;
|
|
39
|
+
};
|
|
40
|
+
declare function useKeyValueRecords(query: any[]): KVRecord[];
|
|
21
41
|
/** @deprecated Use useKeyValueRecords instead. */
|
|
22
42
|
declare const useKeyValueAttributes: typeof useKeyValueRecords;
|
|
23
43
|
|
|
44
|
+
type FactQuery = Parameters<LinkedRecords['Fact']['findAll']>[0];
|
|
45
|
+
type Fact = Awaited<ReturnType<LinkedRecords['Fact']['findAll']>>[number];
|
|
46
|
+
declare function useFacts(query: FactQuery): {
|
|
47
|
+
facts: Fact[];
|
|
48
|
+
reload: () => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
24
51
|
declare function useLinkedRecords(): LinkedRecordsContextType;
|
|
25
52
|
|
|
26
53
|
type UserInfo = {
|
|
@@ -28,4 +55,6 @@ type UserInfo = {
|
|
|
28
55
|
};
|
|
29
56
|
declare function useUserInfo(): UserInfo | null;
|
|
30
57
|
|
|
31
|
-
|
|
58
|
+
declare function useActorId(): string | undefined;
|
|
59
|
+
|
|
60
|
+
export { type KVRecord, type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useActorId, useFacts, useKeyValueAttributes, useKeyValueRecords, useKeyValueRecordsWithStatus, useLinkedRecords, useUserInfo };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,19 +8,46 @@ interface LinkedRecordsContextType {
|
|
|
8
8
|
}
|
|
9
9
|
declare const LinkedRecordsContext: react.Context<LinkedRecordsContextType | undefined>;
|
|
10
10
|
|
|
11
|
+
type OIDCConfig = NonNullable<ConstructorParameters<typeof LinkedRecords>[1]>;
|
|
12
|
+
interface LinkedRecordsConfig {
|
|
13
|
+
oidcConfig?: OIDCConfig;
|
|
14
|
+
autoHandleRedirect?: boolean;
|
|
15
|
+
deferUserInfoFetching?: boolean;
|
|
16
|
+
loginHandler?: () => void;
|
|
17
|
+
connectionLostHandler?: (err?: any) => void;
|
|
18
|
+
unknownServerErrorHandler?: (response: any) => void;
|
|
19
|
+
quotaViolationErrorHandler?: (response: any) => void;
|
|
20
|
+
authorizationErrorHandler?: (response: any) => void;
|
|
21
|
+
}
|
|
11
22
|
interface LinkedRecordsProviderProps {
|
|
12
23
|
children: ReactNode;
|
|
13
24
|
serverUrl: string;
|
|
25
|
+
config?: LinkedRecordsConfig;
|
|
14
26
|
}
|
|
15
|
-
declare function LinkedRecordsProvider({ children, serverUrl }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
|
|
27
|
+
declare function LinkedRecordsProvider({ children, serverUrl, config }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
|
|
16
28
|
|
|
17
29
|
interface KVValue {
|
|
18
|
-
[key: string]: KVValue | string | boolean | number | undefined;
|
|
30
|
+
[key: string]: KVValue | string | boolean | number | Date | undefined;
|
|
31
|
+
}
|
|
32
|
+
interface KVRecord extends KVValue {
|
|
33
|
+
_id?: string;
|
|
34
|
+
_createdAt?: Date;
|
|
19
35
|
}
|
|
20
|
-
declare function
|
|
36
|
+
declare function useKeyValueRecordsWithStatus(query: any[]): {
|
|
37
|
+
records: KVRecord[];
|
|
38
|
+
isLoading: boolean;
|
|
39
|
+
};
|
|
40
|
+
declare function useKeyValueRecords(query: any[]): KVRecord[];
|
|
21
41
|
/** @deprecated Use useKeyValueRecords instead. */
|
|
22
42
|
declare const useKeyValueAttributes: typeof useKeyValueRecords;
|
|
23
43
|
|
|
44
|
+
type FactQuery = Parameters<LinkedRecords['Fact']['findAll']>[0];
|
|
45
|
+
type Fact = Awaited<ReturnType<LinkedRecords['Fact']['findAll']>>[number];
|
|
46
|
+
declare function useFacts(query: FactQuery): {
|
|
47
|
+
facts: Fact[];
|
|
48
|
+
reload: () => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
24
51
|
declare function useLinkedRecords(): LinkedRecordsContextType;
|
|
25
52
|
|
|
26
53
|
type UserInfo = {
|
|
@@ -28,4 +55,6 @@ type UserInfo = {
|
|
|
28
55
|
};
|
|
29
56
|
declare function useUserInfo(): UserInfo | null;
|
|
30
57
|
|
|
31
|
-
|
|
58
|
+
declare function useActorId(): string | undefined;
|
|
59
|
+
|
|
60
|
+
export { type KVRecord, type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useActorId, useFacts, useKeyValueAttributes, useKeyValueRecords, useKeyValueRecordsWithStatus, useLinkedRecords, useUserInfo };
|
package/dist/index.js
CHANGED
|
@@ -32,8 +32,11 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
LinkedRecordsContext: () => LinkedRecordsContext,
|
|
34
34
|
LinkedRecordsProvider: () => LinkedRecordsProvider,
|
|
35
|
+
useActorId: () => useActorId,
|
|
36
|
+
useFacts: () => useFacts,
|
|
35
37
|
useKeyValueAttributes: () => useKeyValueAttributes,
|
|
36
38
|
useKeyValueRecords: () => useKeyValueRecords,
|
|
39
|
+
useKeyValueRecordsWithStatus: () => useKeyValueRecordsWithStatus,
|
|
37
40
|
useLinkedRecords: () => useLinkedRecords,
|
|
38
41
|
useUserInfo: () => useUserInfo
|
|
39
42
|
});
|
|
@@ -47,8 +50,22 @@ var LinkedRecordsContext = (0, import_react.createContext)(void 0);
|
|
|
47
50
|
var import_react2 = require("react");
|
|
48
51
|
var import_browser = __toESM(require("@linkedrecords/browser"));
|
|
49
52
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
50
|
-
function
|
|
51
|
-
const lr = import_browser.default
|
|
53
|
+
function createClient(serverUrl, config) {
|
|
54
|
+
const lr = new import_browser.default(
|
|
55
|
+
new URL(serverUrl),
|
|
56
|
+
config.oidcConfig,
|
|
57
|
+
config.autoHandleRedirect,
|
|
58
|
+
config.deferUserInfoFetching
|
|
59
|
+
);
|
|
60
|
+
if (config.loginHandler) lr.setLoginHandler(config.loginHandler);
|
|
61
|
+
if (config.connectionLostHandler) lr.setConnectionLostHandler(config.connectionLostHandler);
|
|
62
|
+
if (config.unknownServerErrorHandler) lr.setUnknownServerErrorHandler(config.unknownServerErrorHandler);
|
|
63
|
+
if (config.quotaViolationErrorHandler) lr.setQuotaViolationErrorHandler(config.quotaViolationErrorHandler);
|
|
64
|
+
if (config.authorizationErrorHandler) lr.setAuthorizationErrorHandler(config.authorizationErrorHandler);
|
|
65
|
+
return lr;
|
|
66
|
+
}
|
|
67
|
+
function LinkedRecordsProvider({ children, serverUrl, config }) {
|
|
68
|
+
const [lr] = (0, import_react2.useState)(() => config ? createClient(serverUrl, config) : import_browser.default.getPublicClient(serverUrl));
|
|
52
69
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedRecordsContext.Provider, { value: { lr }, children });
|
|
53
70
|
}
|
|
54
71
|
|
|
@@ -65,14 +82,27 @@ function useLinkedRecords() {
|
|
|
65
82
|
return context;
|
|
66
83
|
}
|
|
67
84
|
|
|
85
|
+
// src/queryKey.ts
|
|
86
|
+
function getQueryKey(query) {
|
|
87
|
+
return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
// src/useRecords.ts
|
|
69
91
|
var import_browser2 = require("@linkedrecords/browser");
|
|
70
|
-
function
|
|
92
|
+
function useKeyValueRecordsWithStatus(query) {
|
|
71
93
|
const { lr } = useLinkedRecords();
|
|
72
94
|
const [records, setRecords] = (0, import_react4.useState)([]);
|
|
95
|
+
const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
|
|
96
|
+
const queryKey = getQueryKey(query);
|
|
73
97
|
(0, import_react4.useEffect)(() => {
|
|
98
|
+
setIsLoading(true);
|
|
99
|
+
let cancelled = false;
|
|
100
|
+
const subscribedRecordIds = /* @__PURE__ */ new Set();
|
|
74
101
|
const unsubscribeFnPromise = new Promise((resolve) => {
|
|
75
102
|
const checkActorId = () => {
|
|
103
|
+
if (cancelled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
76
106
|
if (lr.actorId !== void 0) {
|
|
77
107
|
resolve();
|
|
78
108
|
} else {
|
|
@@ -89,14 +119,29 @@ function useKeyValueRecords(query) {
|
|
|
89
119
|
}, async ({ records: records2 }) => {
|
|
90
120
|
const values = await Promise.all(records2.map(async (r) => ({
|
|
91
121
|
_id: r.id,
|
|
122
|
+
_createdAt: r.createdAt,
|
|
92
123
|
...await r.getValue()
|
|
93
124
|
})));
|
|
125
|
+
if (cancelled) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
94
128
|
setRecords(values);
|
|
129
|
+
setIsLoading(false);
|
|
95
130
|
records2.forEach((r) => {
|
|
131
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
subscribedRecordIds.add(r.id);
|
|
96
135
|
r.subscribe(async () => {
|
|
136
|
+
if (cancelled) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
97
139
|
const newValue = await r.getValue();
|
|
140
|
+
if (cancelled) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
98
143
|
setRecords((prev) => prev.map(
|
|
99
|
-
(v) => v._id === r.id ? { _id: r.id, ...newValue } : v
|
|
144
|
+
(v) => v._id === r.id ? { _id: r.id, _createdAt: r.createdAt, ...newValue } : v
|
|
100
145
|
));
|
|
101
146
|
});
|
|
102
147
|
});
|
|
@@ -106,29 +151,77 @@ function useKeyValueRecords(query) {
|
|
|
106
151
|
};
|
|
107
152
|
});
|
|
108
153
|
return () => {
|
|
154
|
+
cancelled = true;
|
|
109
155
|
unsubscribeFnPromise.then((fn) => fn());
|
|
110
156
|
};
|
|
111
|
-
}, [lr
|
|
112
|
-
return records;
|
|
157
|
+
}, [lr, queryKey]);
|
|
158
|
+
return { records, isLoading };
|
|
159
|
+
}
|
|
160
|
+
function useKeyValueRecords(query) {
|
|
161
|
+
return useKeyValueRecordsWithStatus(query).records;
|
|
113
162
|
}
|
|
114
163
|
var useKeyValueAttributes = useKeyValueRecords;
|
|
115
164
|
|
|
116
|
-
// src/
|
|
165
|
+
// src/useFacts.ts
|
|
117
166
|
var import_react5 = require("react");
|
|
118
|
-
function
|
|
167
|
+
function useFacts(query) {
|
|
119
168
|
const { lr } = useLinkedRecords();
|
|
120
|
-
const [
|
|
169
|
+
const [facts, setFacts] = (0, import_react5.useState)([]);
|
|
170
|
+
const [reloadCount, setReloadCount] = (0, import_react5.useState)(0);
|
|
171
|
+
const queryKey = getQueryKey(query);
|
|
121
172
|
(0, import_react5.useEffect)(() => {
|
|
173
|
+
let cancelled = false;
|
|
174
|
+
lr.Fact.findAll(query).then((result) => {
|
|
175
|
+
if (!cancelled) {
|
|
176
|
+
setFacts(result);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return () => {
|
|
180
|
+
cancelled = true;
|
|
181
|
+
};
|
|
182
|
+
}, [lr, queryKey, reloadCount]);
|
|
183
|
+
const reload = (0, import_react5.useCallback)(() => setReloadCount((c) => c + 1), []);
|
|
184
|
+
return { facts, reload };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/useUserInfo.ts
|
|
188
|
+
var import_react6 = require("react");
|
|
189
|
+
function useUserInfo() {
|
|
190
|
+
const { lr } = useLinkedRecords();
|
|
191
|
+
const [userInfo, setUserInfo] = (0, import_react6.useState)(null);
|
|
192
|
+
(0, import_react6.useEffect)(() => {
|
|
122
193
|
lr.getCurrentUserEmail().then((email) => setUserInfo({ email })).catch(() => setUserInfo(null));
|
|
123
194
|
}, [lr]);
|
|
124
195
|
return userInfo;
|
|
125
196
|
}
|
|
197
|
+
|
|
198
|
+
// src/useActorId.ts
|
|
199
|
+
var import_react7 = require("react");
|
|
200
|
+
function useActorId() {
|
|
201
|
+
const { lr } = useLinkedRecords();
|
|
202
|
+
const [actorId, setActorId] = (0, import_react7.useState)(lr.actorId);
|
|
203
|
+
(0, import_react7.useEffect)(() => {
|
|
204
|
+
let cancelled = false;
|
|
205
|
+
lr.ensureUserIdIsKnown().then((id) => {
|
|
206
|
+
if (!cancelled) {
|
|
207
|
+
setActorId(id);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return () => {
|
|
211
|
+
cancelled = true;
|
|
212
|
+
};
|
|
213
|
+
}, [lr]);
|
|
214
|
+
return actorId;
|
|
215
|
+
}
|
|
126
216
|
// Annotate the CommonJS export names for ESM import in node:
|
|
127
217
|
0 && (module.exports = {
|
|
128
218
|
LinkedRecordsContext,
|
|
129
219
|
LinkedRecordsProvider,
|
|
220
|
+
useActorId,
|
|
221
|
+
useFacts,
|
|
130
222
|
useKeyValueAttributes,
|
|
131
223
|
useKeyValueRecords,
|
|
224
|
+
useKeyValueRecordsWithStatus,
|
|
132
225
|
useLinkedRecords,
|
|
133
226
|
useUserInfo
|
|
134
227
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -3,16 +3,30 @@ import { createContext } from "react";
|
|
|
3
3
|
var LinkedRecordsContext = createContext(void 0);
|
|
4
4
|
|
|
5
5
|
// src/LinkedRecordsProvider.tsx
|
|
6
|
-
import "react";
|
|
6
|
+
import { useState } from "react";
|
|
7
7
|
import LinkedRecords from "@linkedrecords/browser";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
|
-
function
|
|
10
|
-
const lr = LinkedRecords
|
|
9
|
+
function createClient(serverUrl, config) {
|
|
10
|
+
const lr = new LinkedRecords(
|
|
11
|
+
new URL(serverUrl),
|
|
12
|
+
config.oidcConfig,
|
|
13
|
+
config.autoHandleRedirect,
|
|
14
|
+
config.deferUserInfoFetching
|
|
15
|
+
);
|
|
16
|
+
if (config.loginHandler) lr.setLoginHandler(config.loginHandler);
|
|
17
|
+
if (config.connectionLostHandler) lr.setConnectionLostHandler(config.connectionLostHandler);
|
|
18
|
+
if (config.unknownServerErrorHandler) lr.setUnknownServerErrorHandler(config.unknownServerErrorHandler);
|
|
19
|
+
if (config.quotaViolationErrorHandler) lr.setQuotaViolationErrorHandler(config.quotaViolationErrorHandler);
|
|
20
|
+
if (config.authorizationErrorHandler) lr.setAuthorizationErrorHandler(config.authorizationErrorHandler);
|
|
21
|
+
return lr;
|
|
22
|
+
}
|
|
23
|
+
function LinkedRecordsProvider({ children, serverUrl, config }) {
|
|
24
|
+
const [lr] = useState(() => config ? createClient(serverUrl, config) : LinkedRecords.getPublicClient(serverUrl));
|
|
11
25
|
return /* @__PURE__ */ jsx(LinkedRecordsContext.Provider, { value: { lr }, children });
|
|
12
26
|
}
|
|
13
27
|
|
|
14
28
|
// src/useRecords.ts
|
|
15
|
-
import { useEffect, useState } from "react";
|
|
29
|
+
import { useEffect, useState as useState2 } from "react";
|
|
16
30
|
|
|
17
31
|
// src/useLinkedRecords.ts
|
|
18
32
|
import { useContext } from "react";
|
|
@@ -24,14 +38,27 @@ function useLinkedRecords() {
|
|
|
24
38
|
return context;
|
|
25
39
|
}
|
|
26
40
|
|
|
41
|
+
// src/queryKey.ts
|
|
42
|
+
function getQueryKey(query) {
|
|
43
|
+
return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
// src/useRecords.ts
|
|
28
47
|
import { KeyValueRecord } from "@linkedrecords/browser";
|
|
29
|
-
function
|
|
48
|
+
function useKeyValueRecordsWithStatus(query) {
|
|
30
49
|
const { lr } = useLinkedRecords();
|
|
31
|
-
const [records, setRecords] =
|
|
50
|
+
const [records, setRecords] = useState2([]);
|
|
51
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
52
|
+
const queryKey = getQueryKey(query);
|
|
32
53
|
useEffect(() => {
|
|
54
|
+
setIsLoading(true);
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
const subscribedRecordIds = /* @__PURE__ */ new Set();
|
|
33
57
|
const unsubscribeFnPromise = new Promise((resolve) => {
|
|
34
58
|
const checkActorId = () => {
|
|
59
|
+
if (cancelled) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
35
62
|
if (lr.actorId !== void 0) {
|
|
36
63
|
resolve();
|
|
37
64
|
} else {
|
|
@@ -48,14 +75,29 @@ function useKeyValueRecords(query) {
|
|
|
48
75
|
}, async ({ records: records2 }) => {
|
|
49
76
|
const values = await Promise.all(records2.map(async (r) => ({
|
|
50
77
|
_id: r.id,
|
|
78
|
+
_createdAt: r.createdAt,
|
|
51
79
|
...await r.getValue()
|
|
52
80
|
})));
|
|
81
|
+
if (cancelled) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
53
84
|
setRecords(values);
|
|
85
|
+
setIsLoading(false);
|
|
54
86
|
records2.forEach((r) => {
|
|
87
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
subscribedRecordIds.add(r.id);
|
|
55
91
|
r.subscribe(async () => {
|
|
92
|
+
if (cancelled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
56
95
|
const newValue = await r.getValue();
|
|
96
|
+
if (cancelled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
57
99
|
setRecords((prev) => prev.map(
|
|
58
|
-
(v) => v._id === r.id ? { _id: r.id, ...newValue } : v
|
|
100
|
+
(v) => v._id === r.id ? { _id: r.id, _createdAt: r.createdAt, ...newValue } : v
|
|
59
101
|
));
|
|
60
102
|
});
|
|
61
103
|
});
|
|
@@ -65,28 +107,76 @@ function useKeyValueRecords(query) {
|
|
|
65
107
|
};
|
|
66
108
|
});
|
|
67
109
|
return () => {
|
|
110
|
+
cancelled = true;
|
|
68
111
|
unsubscribeFnPromise.then((fn) => fn());
|
|
69
112
|
};
|
|
70
|
-
}, [lr
|
|
71
|
-
return records;
|
|
113
|
+
}, [lr, queryKey]);
|
|
114
|
+
return { records, isLoading };
|
|
115
|
+
}
|
|
116
|
+
function useKeyValueRecords(query) {
|
|
117
|
+
return useKeyValueRecordsWithStatus(query).records;
|
|
72
118
|
}
|
|
73
119
|
var useKeyValueAttributes = useKeyValueRecords;
|
|
74
120
|
|
|
121
|
+
// src/useFacts.ts
|
|
122
|
+
import { useCallback, useEffect as useEffect2, useState as useState3 } from "react";
|
|
123
|
+
function useFacts(query) {
|
|
124
|
+
const { lr } = useLinkedRecords();
|
|
125
|
+
const [facts, setFacts] = useState3([]);
|
|
126
|
+
const [reloadCount, setReloadCount] = useState3(0);
|
|
127
|
+
const queryKey = getQueryKey(query);
|
|
128
|
+
useEffect2(() => {
|
|
129
|
+
let cancelled = false;
|
|
130
|
+
lr.Fact.findAll(query).then((result) => {
|
|
131
|
+
if (!cancelled) {
|
|
132
|
+
setFacts(result);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return () => {
|
|
136
|
+
cancelled = true;
|
|
137
|
+
};
|
|
138
|
+
}, [lr, queryKey, reloadCount]);
|
|
139
|
+
const reload = useCallback(() => setReloadCount((c) => c + 1), []);
|
|
140
|
+
return { facts, reload };
|
|
141
|
+
}
|
|
142
|
+
|
|
75
143
|
// src/useUserInfo.ts
|
|
76
|
-
import { useEffect as
|
|
144
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
77
145
|
function useUserInfo() {
|
|
78
146
|
const { lr } = useLinkedRecords();
|
|
79
|
-
const [userInfo, setUserInfo] =
|
|
80
|
-
|
|
147
|
+
const [userInfo, setUserInfo] = useState4(null);
|
|
148
|
+
useEffect3(() => {
|
|
81
149
|
lr.getCurrentUserEmail().then((email) => setUserInfo({ email })).catch(() => setUserInfo(null));
|
|
82
150
|
}, [lr]);
|
|
83
151
|
return userInfo;
|
|
84
152
|
}
|
|
153
|
+
|
|
154
|
+
// src/useActorId.ts
|
|
155
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
156
|
+
function useActorId() {
|
|
157
|
+
const { lr } = useLinkedRecords();
|
|
158
|
+
const [actorId, setActorId] = useState5(lr.actorId);
|
|
159
|
+
useEffect4(() => {
|
|
160
|
+
let cancelled = false;
|
|
161
|
+
lr.ensureUserIdIsKnown().then((id) => {
|
|
162
|
+
if (!cancelled) {
|
|
163
|
+
setActorId(id);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return () => {
|
|
167
|
+
cancelled = true;
|
|
168
|
+
};
|
|
169
|
+
}, [lr]);
|
|
170
|
+
return actorId;
|
|
171
|
+
}
|
|
85
172
|
export {
|
|
86
173
|
LinkedRecordsContext,
|
|
87
174
|
LinkedRecordsProvider,
|
|
175
|
+
useActorId,
|
|
176
|
+
useFacts,
|
|
88
177
|
useKeyValueAttributes,
|
|
89
178
|
useKeyValueRecords,
|
|
179
|
+
useKeyValueRecordsWithStatus,
|
|
90
180
|
useLinkedRecords,
|
|
91
181
|
useUserInfo
|
|
92
182
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { type ReactNode, useState } from 'react';
|
|
2
3
|
import LinkedRecords from '@linkedrecords/browser';
|
|
3
4
|
import { LinkedRecordsContext } from './linkedRecordsContext';
|
|
4
5
|
|
|
6
|
+
type OIDCConfig = NonNullable<ConstructorParameters<typeof LinkedRecords>[1]>;
|
|
7
|
+
|
|
8
|
+
export interface LinkedRecordsConfig {
|
|
9
|
+
// Omit to use cookie based auth, where the LinkedRecords server handles the login.
|
|
10
|
+
oidcConfig?: OIDCConfig;
|
|
11
|
+
autoHandleRedirect?: boolean;
|
|
12
|
+
deferUserInfoFetching?: boolean;
|
|
13
|
+
loginHandler?: () => void;
|
|
14
|
+
connectionLostHandler?: (err?: any) => void;
|
|
15
|
+
unknownServerErrorHandler?: (response: any) => void;
|
|
16
|
+
quotaViolationErrorHandler?: (response: any) => void;
|
|
17
|
+
authorizationErrorHandler?: (response: any) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
interface LinkedRecordsProviderProps {
|
|
6
21
|
children: ReactNode;
|
|
7
22
|
serverUrl: string;
|
|
23
|
+
// Read once on mount. Without it, the shared public client for serverUrl is used.
|
|
24
|
+
config?: LinkedRecordsConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createClient(serverUrl: string, config: LinkedRecordsConfig): LinkedRecords {
|
|
28
|
+
const lr = new LinkedRecords(
|
|
29
|
+
new URL(serverUrl),
|
|
30
|
+
config.oidcConfig,
|
|
31
|
+
config.autoHandleRedirect,
|
|
32
|
+
config.deferUserInfoFetching,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Handlers are installed before any child renders, so the first
|
|
36
|
+
// requests made by child components already go through them.
|
|
37
|
+
if (config.loginHandler) lr.setLoginHandler(config.loginHandler);
|
|
38
|
+
if (config.connectionLostHandler) lr.setConnectionLostHandler(config.connectionLostHandler);
|
|
39
|
+
if (config.unknownServerErrorHandler) lr.setUnknownServerErrorHandler(config.unknownServerErrorHandler);
|
|
40
|
+
if (config.quotaViolationErrorHandler) lr.setQuotaViolationErrorHandler(config.quotaViolationErrorHandler);
|
|
41
|
+
if (config.authorizationErrorHandler) lr.setAuthorizationErrorHandler(config.authorizationErrorHandler);
|
|
42
|
+
|
|
43
|
+
return lr;
|
|
8
44
|
}
|
|
9
45
|
|
|
10
|
-
export function LinkedRecordsProvider({ children, serverUrl }: LinkedRecordsProviderProps) {
|
|
11
|
-
const lr =
|
|
46
|
+
export function LinkedRecordsProvider({ children, serverUrl, config }: LinkedRecordsProviderProps) {
|
|
47
|
+
const [lr] = useState(() => (config
|
|
48
|
+
? createClient(serverUrl, config)
|
|
49
|
+
: LinkedRecords.getPublicClient(serverUrl)));
|
|
12
50
|
|
|
13
51
|
return (
|
|
14
52
|
<LinkedRecordsContext.Provider value={{ lr }}>
|
package/src/index.tsx
CHANGED
package/src/queryKey.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Callers usually pass a new query on every render, so effects depend on a
|
|
2
|
+
// serialized key instead of the query identity. Functions (e.g. record
|
|
3
|
+
// classes) would serialize to null, so they are keyed by name.
|
|
4
|
+
export function getQueryKey(query: unknown): string {
|
|
5
|
+
return JSON.stringify(query, (_, v) => (typeof v === 'function' ? `fn:${v.name}` : v));
|
|
6
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useLinkedRecords } from "./useLinkedRecords";
|
|
3
|
+
|
|
4
|
+
// The id of the logged in user, undefined until it is known.
|
|
5
|
+
export function useActorId(): string | undefined {
|
|
6
|
+
const { lr } = useLinkedRecords();
|
|
7
|
+
const [ actorId, setActorId ] = useState<string | undefined>(lr.actorId);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let cancelled = false;
|
|
11
|
+
|
|
12
|
+
lr.ensureUserIdIsKnown().then((id) => {
|
|
13
|
+
if (!cancelled) {
|
|
14
|
+
setActorId(id);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
cancelled = true;
|
|
20
|
+
}
|
|
21
|
+
}, [ lr ]);
|
|
22
|
+
|
|
23
|
+
return actorId;
|
|
24
|
+
}
|
package/src/useFacts.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import type LinkedRecords from '@linkedrecords/browser';
|
|
3
|
+
import { useLinkedRecords } from "./useLinkedRecords";
|
|
4
|
+
import { getQueryKey } from "./queryKey";
|
|
5
|
+
|
|
6
|
+
type FactQuery = Parameters<LinkedRecords['Fact']['findAll']>[0];
|
|
7
|
+
type Fact = Awaited<ReturnType<LinkedRecords['Fact']['findAll']>>[number];
|
|
8
|
+
|
|
9
|
+
// The SDK can not subscribe to facts, so the facts are fetched whenever the
|
|
10
|
+
// query changes. Call reload after writing facts which affect the result.
|
|
11
|
+
export function useFacts(query: FactQuery): { facts: Fact[], reload: () => void } {
|
|
12
|
+
const { lr } = useLinkedRecords();
|
|
13
|
+
const [ facts, setFacts ] = useState<Fact[]>([]);
|
|
14
|
+
const [ reloadCount, setReloadCount ] = useState(0);
|
|
15
|
+
const queryKey = getQueryKey(query);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// Ignore responses which arrive after the query changed or the
|
|
19
|
+
// component unmounted.
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
|
|
22
|
+
lr.Fact.findAll(query).then((result) => {
|
|
23
|
+
if (!cancelled) {
|
|
24
|
+
setFacts(result);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
cancelled = true;
|
|
30
|
+
}
|
|
31
|
+
// query is represented by queryKey; depending on the object itself would
|
|
32
|
+
// refetch on every render.
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [ lr, queryKey, reloadCount ]);
|
|
35
|
+
|
|
36
|
+
const reload = useCallback(() => setReloadCount((c) => c + 1), []);
|
|
37
|
+
|
|
38
|
+
return { facts, reload };
|
|
39
|
+
}
|
package/src/useRecords.ts
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { useLinkedRecords } from "./useLinkedRecords";
|
|
4
|
+
import { getQueryKey } from "./queryKey";
|
|
4
5
|
import { KeyValueRecord } from '@linkedrecords/browser';
|
|
5
6
|
|
|
6
7
|
interface KVValue {
|
|
7
|
-
[key: string]: KVValue | string | boolean | number | undefined;
|
|
8
|
+
[key: string]: KVValue | string | boolean | number | Date | undefined;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export
|
|
11
|
+
export interface KVRecord extends KVValue {
|
|
12
|
+
_id?: string;
|
|
13
|
+
_createdAt?: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useKeyValueRecordsWithStatus(query: any[]): { records: KVRecord[], isLoading: boolean } {
|
|
11
17
|
const { lr } = useLinkedRecords();
|
|
12
|
-
const [ records, setRecords ] = useState<
|
|
18
|
+
const [ records, setRecords ] = useState<KVRecord[]>([]);
|
|
19
|
+
const [ isLoading, setIsLoading ] = useState(true);
|
|
20
|
+
const queryKey = getQueryKey(query);
|
|
13
21
|
|
|
14
22
|
useEffect(() => {
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
|
|
25
|
+
// The SDK has no way to remove a record observer, so observers registered
|
|
26
|
+
// by this effect run check this flag and become no-ops after cleanup.
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
// Records this effect run already observes, so repeated query results
|
|
29
|
+
// do not stack up observers on the same record.
|
|
30
|
+
const subscribedRecordIds = new Set<string | undefined>();
|
|
31
|
+
|
|
15
32
|
const unsubscribeFnPromise = new Promise<void>((resolve) => {
|
|
16
33
|
const checkActorId = () => {
|
|
34
|
+
if (cancelled) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
if (lr.actorId !== undefined) {
|
|
18
39
|
resolve();
|
|
19
40
|
} else {
|
|
@@ -30,19 +51,39 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
|
|
|
30
51
|
}, async ({ records }) => {
|
|
31
52
|
const values = await Promise.all(records.map(async (r) => ({
|
|
32
53
|
_id: r.id,
|
|
54
|
+
_createdAt: r.createdAt,
|
|
33
55
|
...(await r.getValue()),
|
|
34
56
|
})));
|
|
35
57
|
|
|
58
|
+
if (cancelled) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
setRecords(values);
|
|
63
|
+
setIsLoading(false);
|
|
37
64
|
|
|
38
65
|
records.forEach((r) => {
|
|
66
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
subscribedRecordIds.add(r.id);
|
|
71
|
+
|
|
39
72
|
r.subscribe(async () => {
|
|
73
|
+
if (cancelled) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
const newValue = await r.getValue();
|
|
41
78
|
|
|
79
|
+
if (cancelled) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
42
83
|
// Use functional update to avoid stale closure
|
|
43
84
|
setRecords(prev => prev.map(v =>
|
|
44
85
|
v._id === r.id
|
|
45
|
-
? { _id: r.id, ...newValue }
|
|
86
|
+
? { _id: r.id, _createdAt: r.createdAt, ...newValue }
|
|
46
87
|
: v
|
|
47
88
|
));
|
|
48
89
|
});
|
|
@@ -55,11 +96,19 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
|
|
|
55
96
|
});
|
|
56
97
|
|
|
57
98
|
return () => {
|
|
99
|
+
cancelled = true;
|
|
58
100
|
unsubscribeFnPromise.then(fn => fn());
|
|
59
101
|
}
|
|
60
|
-
|
|
102
|
+
// query is represented by queryKey; depending on the array itself would
|
|
103
|
+
// resubscribe on every render.
|
|
104
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, [ lr, queryKey ]);
|
|
106
|
+
|
|
107
|
+
return { records, isLoading };
|
|
108
|
+
}
|
|
61
109
|
|
|
62
|
-
|
|
110
|
+
export function useKeyValueRecords(query: any[]): KVRecord[] {
|
|
111
|
+
return useKeyValueRecordsWithStatus(query).records;
|
|
63
112
|
}
|
|
64
113
|
|
|
65
114
|
/** @deprecated Use useKeyValueRecords instead. */
|