@linkedrecords/react 0.4.0 → 0.5.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 +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +40 -3
- package/dist/index.mjs +45 -8
- package/package.json +1 -1
- package/src/LinkedRecordsProvider.tsx +41 -3
- package/src/useRecords.ts +42 -1
package/dist/index.d.mts
CHANGED
|
@@ -8,11 +8,23 @@ 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
30
|
[key: string]: KVValue | string | boolean | number | undefined;
|
|
@@ -28,4 +40,4 @@ type UserInfo = {
|
|
|
28
40
|
};
|
|
29
41
|
declare function useUserInfo(): UserInfo | null;
|
|
30
42
|
|
|
31
|
-
export { LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
|
|
43
|
+
export { type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,11 +8,23 @@ 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
30
|
[key: string]: KVValue | string | boolean | number | undefined;
|
|
@@ -28,4 +40,4 @@ type UserInfo = {
|
|
|
28
40
|
};
|
|
29
41
|
declare function useUserInfo(): UserInfo | null;
|
|
30
42
|
|
|
31
|
-
export { LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
|
|
43
|
+
export { type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
|
package/dist/index.js
CHANGED
|
@@ -47,8 +47,22 @@ var LinkedRecordsContext = (0, import_react.createContext)(void 0);
|
|
|
47
47
|
var import_react2 = require("react");
|
|
48
48
|
var import_browser = __toESM(require("@linkedrecords/browser"));
|
|
49
49
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
50
|
-
function
|
|
51
|
-
const lr = import_browser.default
|
|
50
|
+
function createClient(serverUrl, config) {
|
|
51
|
+
const lr = new import_browser.default(
|
|
52
|
+
new URL(serverUrl),
|
|
53
|
+
config.oidcConfig,
|
|
54
|
+
config.autoHandleRedirect,
|
|
55
|
+
config.deferUserInfoFetching
|
|
56
|
+
);
|
|
57
|
+
if (config.loginHandler) lr.setLoginHandler(config.loginHandler);
|
|
58
|
+
if (config.connectionLostHandler) lr.setConnectionLostHandler(config.connectionLostHandler);
|
|
59
|
+
if (config.unknownServerErrorHandler) lr.setUnknownServerErrorHandler(config.unknownServerErrorHandler);
|
|
60
|
+
if (config.quotaViolationErrorHandler) lr.setQuotaViolationErrorHandler(config.quotaViolationErrorHandler);
|
|
61
|
+
if (config.authorizationErrorHandler) lr.setAuthorizationErrorHandler(config.authorizationErrorHandler);
|
|
62
|
+
return lr;
|
|
63
|
+
}
|
|
64
|
+
function LinkedRecordsProvider({ children, serverUrl, config }) {
|
|
65
|
+
const [lr] = (0, import_react2.useState)(() => config ? createClient(serverUrl, config) : import_browser.default.getPublicClient(serverUrl));
|
|
52
66
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedRecordsContext.Provider, { value: { lr }, children });
|
|
53
67
|
}
|
|
54
68
|
|
|
@@ -67,12 +81,21 @@ function useLinkedRecords() {
|
|
|
67
81
|
|
|
68
82
|
// src/useRecords.ts
|
|
69
83
|
var import_browser2 = require("@linkedrecords/browser");
|
|
84
|
+
function getQueryKey(query) {
|
|
85
|
+
return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
|
|
86
|
+
}
|
|
70
87
|
function useKeyValueRecords(query) {
|
|
71
88
|
const { lr } = useLinkedRecords();
|
|
72
89
|
const [records, setRecords] = (0, import_react4.useState)([]);
|
|
90
|
+
const queryKey = getQueryKey(query);
|
|
73
91
|
(0, import_react4.useEffect)(() => {
|
|
92
|
+
let cancelled = false;
|
|
93
|
+
const subscribedRecordIds = /* @__PURE__ */ new Set();
|
|
74
94
|
const unsubscribeFnPromise = new Promise((resolve) => {
|
|
75
95
|
const checkActorId = () => {
|
|
96
|
+
if (cancelled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
76
99
|
if (lr.actorId !== void 0) {
|
|
77
100
|
resolve();
|
|
78
101
|
} else {
|
|
@@ -91,10 +114,23 @@ function useKeyValueRecords(query) {
|
|
|
91
114
|
_id: r.id,
|
|
92
115
|
...await r.getValue()
|
|
93
116
|
})));
|
|
117
|
+
if (cancelled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
94
120
|
setRecords(values);
|
|
95
121
|
records2.forEach((r) => {
|
|
122
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
subscribedRecordIds.add(r.id);
|
|
96
126
|
r.subscribe(async () => {
|
|
127
|
+
if (cancelled) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
97
130
|
const newValue = await r.getValue();
|
|
131
|
+
if (cancelled) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
98
134
|
setRecords((prev) => prev.map(
|
|
99
135
|
(v) => v._id === r.id ? { _id: r.id, ...newValue } : v
|
|
100
136
|
));
|
|
@@ -106,9 +142,10 @@ function useKeyValueRecords(query) {
|
|
|
106
142
|
};
|
|
107
143
|
});
|
|
108
144
|
return () => {
|
|
145
|
+
cancelled = true;
|
|
109
146
|
unsubscribeFnPromise.then((fn) => fn());
|
|
110
147
|
};
|
|
111
|
-
}, [lr
|
|
148
|
+
}, [lr, queryKey]);
|
|
112
149
|
return records;
|
|
113
150
|
}
|
|
114
151
|
var useKeyValueAttributes = useKeyValueRecords;
|
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";
|
|
@@ -26,12 +40,21 @@ function useLinkedRecords() {
|
|
|
26
40
|
|
|
27
41
|
// src/useRecords.ts
|
|
28
42
|
import { KeyValueRecord } from "@linkedrecords/browser";
|
|
43
|
+
function getQueryKey(query) {
|
|
44
|
+
return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
|
|
45
|
+
}
|
|
29
46
|
function useKeyValueRecords(query) {
|
|
30
47
|
const { lr } = useLinkedRecords();
|
|
31
|
-
const [records, setRecords] =
|
|
48
|
+
const [records, setRecords] = useState2([]);
|
|
49
|
+
const queryKey = getQueryKey(query);
|
|
32
50
|
useEffect(() => {
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
const subscribedRecordIds = /* @__PURE__ */ new Set();
|
|
33
53
|
const unsubscribeFnPromise = new Promise((resolve) => {
|
|
34
54
|
const checkActorId = () => {
|
|
55
|
+
if (cancelled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
35
58
|
if (lr.actorId !== void 0) {
|
|
36
59
|
resolve();
|
|
37
60
|
} else {
|
|
@@ -50,10 +73,23 @@ function useKeyValueRecords(query) {
|
|
|
50
73
|
_id: r.id,
|
|
51
74
|
...await r.getValue()
|
|
52
75
|
})));
|
|
76
|
+
if (cancelled) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
53
79
|
setRecords(values);
|
|
54
80
|
records2.forEach((r) => {
|
|
81
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
subscribedRecordIds.add(r.id);
|
|
55
85
|
r.subscribe(async () => {
|
|
86
|
+
if (cancelled) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
56
89
|
const newValue = await r.getValue();
|
|
90
|
+
if (cancelled) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
57
93
|
setRecords((prev) => prev.map(
|
|
58
94
|
(v) => v._id === r.id ? { _id: r.id, ...newValue } : v
|
|
59
95
|
));
|
|
@@ -65,18 +101,19 @@ function useKeyValueRecords(query) {
|
|
|
65
101
|
};
|
|
66
102
|
});
|
|
67
103
|
return () => {
|
|
104
|
+
cancelled = true;
|
|
68
105
|
unsubscribeFnPromise.then((fn) => fn());
|
|
69
106
|
};
|
|
70
|
-
}, [lr
|
|
107
|
+
}, [lr, queryKey]);
|
|
71
108
|
return records;
|
|
72
109
|
}
|
|
73
110
|
var useKeyValueAttributes = useKeyValueRecords;
|
|
74
111
|
|
|
75
112
|
// src/useUserInfo.ts
|
|
76
|
-
import { useEffect as useEffect2, useState as
|
|
113
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
77
114
|
function useUserInfo() {
|
|
78
115
|
const { lr } = useLinkedRecords();
|
|
79
|
-
const [userInfo, setUserInfo] =
|
|
116
|
+
const [userInfo, setUserInfo] = useState3(null);
|
|
80
117
|
useEffect2(() => {
|
|
81
118
|
lr.getCurrentUserEmail().then((email) => setUserInfo({ email })).catch(() => setUserInfo(null));
|
|
82
119
|
}, [lr]);
|
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/useRecords.ts
CHANGED
|
@@ -7,13 +7,32 @@ interface KVValue {
|
|
|
7
7
|
[key: string]: KVValue | string | boolean | number | undefined;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// Callers usually pass a new query array on every render, so the effect
|
|
11
|
+
// depends on a serialized key instead of the array identity. Functions
|
|
12
|
+
// (e.g. record classes) would serialize to null, so they are keyed by name.
|
|
13
|
+
function getQueryKey(query: any[]): string {
|
|
14
|
+
return JSON.stringify(query, (_, v) => (typeof v === 'function' ? `fn:${v.name}` : v));
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export function useKeyValueRecords(query: any[]): KVValue[] {
|
|
11
18
|
const { lr } = useLinkedRecords();
|
|
12
19
|
const [ records, setRecords ] = useState<KVValue[]>([]);
|
|
20
|
+
const queryKey = getQueryKey(query);
|
|
13
21
|
|
|
14
22
|
useEffect(() => {
|
|
23
|
+
// The SDK has no way to remove a record observer, so observers registered
|
|
24
|
+
// by this effect run check this flag and become no-ops after cleanup.
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
// Records this effect run already observes, so repeated query results
|
|
27
|
+
// do not stack up observers on the same record.
|
|
28
|
+
const subscribedRecordIds = new Set<string | undefined>();
|
|
29
|
+
|
|
15
30
|
const unsubscribeFnPromise = new Promise<void>((resolve) => {
|
|
16
31
|
const checkActorId = () => {
|
|
32
|
+
if (cancelled) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
if (lr.actorId !== undefined) {
|
|
18
37
|
resolve();
|
|
19
38
|
} else {
|
|
@@ -33,12 +52,30 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
|
|
|
33
52
|
...(await r.getValue()),
|
|
34
53
|
})));
|
|
35
54
|
|
|
55
|
+
if (cancelled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
36
59
|
setRecords(values);
|
|
37
60
|
|
|
38
61
|
records.forEach((r) => {
|
|
62
|
+
if (subscribedRecordIds.has(r.id)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
subscribedRecordIds.add(r.id);
|
|
67
|
+
|
|
39
68
|
r.subscribe(async () => {
|
|
69
|
+
if (cancelled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
40
73
|
const newValue = await r.getValue();
|
|
41
74
|
|
|
75
|
+
if (cancelled) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
42
79
|
// Use functional update to avoid stale closure
|
|
43
80
|
setRecords(prev => prev.map(v =>
|
|
44
81
|
v._id === r.id
|
|
@@ -55,9 +92,13 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
|
|
|
55
92
|
});
|
|
56
93
|
|
|
57
94
|
return () => {
|
|
95
|
+
cancelled = true;
|
|
58
96
|
unsubscribeFnPromise.then(fn => fn());
|
|
59
97
|
}
|
|
60
|
-
|
|
98
|
+
// query is represented by queryKey; depending on the array itself would
|
|
99
|
+
// resubscribe on every render.
|
|
100
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
+
}, [ lr, queryKey ]);
|
|
61
102
|
|
|
62
103
|
return records;
|
|
63
104
|
}
|