@linkedrecords/react 0.5.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 CHANGED
@@ -27,12 +27,27 @@ interface LinkedRecordsProviderProps {
27
27
  declare function LinkedRecordsProvider({ children, serverUrl, config }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
28
28
 
29
29
  interface KVValue {
30
- [key: string]: KVValue | string | boolean | number | undefined;
30
+ [key: string]: KVValue | string | boolean | number | Date | undefined;
31
31
  }
32
- declare function useKeyValueRecords(query: any[]): KVValue[];
32
+ interface KVRecord extends KVValue {
33
+ _id?: string;
34
+ _createdAt?: Date;
35
+ }
36
+ declare function useKeyValueRecordsWithStatus(query: any[]): {
37
+ records: KVRecord[];
38
+ isLoading: boolean;
39
+ };
40
+ declare function useKeyValueRecords(query: any[]): KVRecord[];
33
41
  /** @deprecated Use useKeyValueRecords instead. */
34
42
  declare const useKeyValueAttributes: typeof useKeyValueRecords;
35
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
+
36
51
  declare function useLinkedRecords(): LinkedRecordsContextType;
37
52
 
38
53
  type UserInfo = {
@@ -40,4 +55,6 @@ type UserInfo = {
40
55
  };
41
56
  declare function useUserInfo(): UserInfo | null;
42
57
 
43
- export { type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
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
@@ -27,12 +27,27 @@ interface LinkedRecordsProviderProps {
27
27
  declare function LinkedRecordsProvider({ children, serverUrl, config }: LinkedRecordsProviderProps): react_jsx_runtime.JSX.Element;
28
28
 
29
29
  interface KVValue {
30
- [key: string]: KVValue | string | boolean | number | undefined;
30
+ [key: string]: KVValue | string | boolean | number | Date | undefined;
31
31
  }
32
- declare function useKeyValueRecords(query: any[]): KVValue[];
32
+ interface KVRecord extends KVValue {
33
+ _id?: string;
34
+ _createdAt?: Date;
35
+ }
36
+ declare function useKeyValueRecordsWithStatus(query: any[]): {
37
+ records: KVRecord[];
38
+ isLoading: boolean;
39
+ };
40
+ declare function useKeyValueRecords(query: any[]): KVRecord[];
33
41
  /** @deprecated Use useKeyValueRecords instead. */
34
42
  declare const useKeyValueAttributes: typeof useKeyValueRecords;
35
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
+
36
51
  declare function useLinkedRecords(): LinkedRecordsContextType;
37
52
 
38
53
  type UserInfo = {
@@ -40,4 +55,6 @@ type UserInfo = {
40
55
  };
41
56
  declare function useUserInfo(): UserInfo | null;
42
57
 
43
- export { type LinkedRecordsConfig, LinkedRecordsContext, type LinkedRecordsContextType, LinkedRecordsProvider, useKeyValueAttributes, useKeyValueRecords, useLinkedRecords, useUserInfo };
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
  });
@@ -79,16 +82,20 @@ function useLinkedRecords() {
79
82
  return context;
80
83
  }
81
84
 
82
- // src/useRecords.ts
83
- var import_browser2 = require("@linkedrecords/browser");
85
+ // src/queryKey.ts
84
86
  function getQueryKey(query) {
85
87
  return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
86
88
  }
87
- function useKeyValueRecords(query) {
89
+
90
+ // src/useRecords.ts
91
+ var import_browser2 = require("@linkedrecords/browser");
92
+ function useKeyValueRecordsWithStatus(query) {
88
93
  const { lr } = useLinkedRecords();
89
94
  const [records, setRecords] = (0, import_react4.useState)([]);
95
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
90
96
  const queryKey = getQueryKey(query);
91
97
  (0, import_react4.useEffect)(() => {
98
+ setIsLoading(true);
92
99
  let cancelled = false;
93
100
  const subscribedRecordIds = /* @__PURE__ */ new Set();
94
101
  const unsubscribeFnPromise = new Promise((resolve) => {
@@ -112,12 +119,14 @@ function useKeyValueRecords(query) {
112
119
  }, async ({ records: records2 }) => {
113
120
  const values = await Promise.all(records2.map(async (r) => ({
114
121
  _id: r.id,
122
+ _createdAt: r.createdAt,
115
123
  ...await r.getValue()
116
124
  })));
117
125
  if (cancelled) {
118
126
  return;
119
127
  }
120
128
  setRecords(values);
129
+ setIsLoading(false);
121
130
  records2.forEach((r) => {
122
131
  if (subscribedRecordIds.has(r.id)) {
123
132
  return;
@@ -132,7 +141,7 @@ function useKeyValueRecords(query) {
132
141
  return;
133
142
  }
134
143
  setRecords((prev) => prev.map(
135
- (v) => v._id === r.id ? { _id: r.id, ...newValue } : v
144
+ (v) => v._id === r.id ? { _id: r.id, _createdAt: r.createdAt, ...newValue } : v
136
145
  ));
137
146
  });
138
147
  });
@@ -146,26 +155,73 @@ function useKeyValueRecords(query) {
146
155
  unsubscribeFnPromise.then((fn) => fn());
147
156
  };
148
157
  }, [lr, queryKey]);
149
- return records;
158
+ return { records, isLoading };
159
+ }
160
+ function useKeyValueRecords(query) {
161
+ return useKeyValueRecordsWithStatus(query).records;
150
162
  }
151
163
  var useKeyValueAttributes = useKeyValueRecords;
152
164
 
153
- // src/useUserInfo.ts
165
+ // src/useFacts.ts
154
166
  var import_react5 = require("react");
155
- function useUserInfo() {
167
+ function useFacts(query) {
156
168
  const { lr } = useLinkedRecords();
157
- const [userInfo, setUserInfo] = (0, import_react5.useState)(null);
169
+ const [facts, setFacts] = (0, import_react5.useState)([]);
170
+ const [reloadCount, setReloadCount] = (0, import_react5.useState)(0);
171
+ const queryKey = getQueryKey(query);
158
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)(() => {
159
193
  lr.getCurrentUserEmail().then((email) => setUserInfo({ email })).catch(() => setUserInfo(null));
160
194
  }, [lr]);
161
195
  return userInfo;
162
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
+ }
163
216
  // Annotate the CommonJS export names for ESM import in node:
164
217
  0 && (module.exports = {
165
218
  LinkedRecordsContext,
166
219
  LinkedRecordsProvider,
220
+ useActorId,
221
+ useFacts,
167
222
  useKeyValueAttributes,
168
223
  useKeyValueRecords,
224
+ useKeyValueRecordsWithStatus,
169
225
  useLinkedRecords,
170
226
  useUserInfo
171
227
  });
package/dist/index.mjs CHANGED
@@ -38,16 +38,20 @@ function useLinkedRecords() {
38
38
  return context;
39
39
  }
40
40
 
41
- // src/useRecords.ts
42
- import { KeyValueRecord } from "@linkedrecords/browser";
41
+ // src/queryKey.ts
43
42
  function getQueryKey(query) {
44
43
  return JSON.stringify(query, (_, v) => typeof v === "function" ? `fn:${v.name}` : v);
45
44
  }
46
- function useKeyValueRecords(query) {
45
+
46
+ // src/useRecords.ts
47
+ import { KeyValueRecord } from "@linkedrecords/browser";
48
+ function useKeyValueRecordsWithStatus(query) {
47
49
  const { lr } = useLinkedRecords();
48
50
  const [records, setRecords] = useState2([]);
51
+ const [isLoading, setIsLoading] = useState2(true);
49
52
  const queryKey = getQueryKey(query);
50
53
  useEffect(() => {
54
+ setIsLoading(true);
51
55
  let cancelled = false;
52
56
  const subscribedRecordIds = /* @__PURE__ */ new Set();
53
57
  const unsubscribeFnPromise = new Promise((resolve) => {
@@ -71,12 +75,14 @@ function useKeyValueRecords(query) {
71
75
  }, async ({ records: records2 }) => {
72
76
  const values = await Promise.all(records2.map(async (r) => ({
73
77
  _id: r.id,
78
+ _createdAt: r.createdAt,
74
79
  ...await r.getValue()
75
80
  })));
76
81
  if (cancelled) {
77
82
  return;
78
83
  }
79
84
  setRecords(values);
85
+ setIsLoading(false);
80
86
  records2.forEach((r) => {
81
87
  if (subscribedRecordIds.has(r.id)) {
82
88
  return;
@@ -91,7 +97,7 @@ function useKeyValueRecords(query) {
91
97
  return;
92
98
  }
93
99
  setRecords((prev) => prev.map(
94
- (v) => v._id === r.id ? { _id: r.id, ...newValue } : v
100
+ (v) => v._id === r.id ? { _id: r.id, _createdAt: r.createdAt, ...newValue } : v
95
101
  ));
96
102
  });
97
103
  });
@@ -105,25 +111,72 @@ function useKeyValueRecords(query) {
105
111
  unsubscribeFnPromise.then((fn) => fn());
106
112
  };
107
113
  }, [lr, queryKey]);
108
- return records;
114
+ return { records, isLoading };
115
+ }
116
+ function useKeyValueRecords(query) {
117
+ return useKeyValueRecordsWithStatus(query).records;
109
118
  }
110
119
  var useKeyValueAttributes = useKeyValueRecords;
111
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
+
112
143
  // src/useUserInfo.ts
113
- import { useEffect as useEffect2, useState as useState3 } from "react";
144
+ import { useEffect as useEffect3, useState as useState4 } from "react";
114
145
  function useUserInfo() {
115
146
  const { lr } = useLinkedRecords();
116
- const [userInfo, setUserInfo] = useState3(null);
117
- useEffect2(() => {
147
+ const [userInfo, setUserInfo] = useState4(null);
148
+ useEffect3(() => {
118
149
  lr.getCurrentUserEmail().then((email) => setUserInfo({ email })).catch(() => setUserInfo(null));
119
150
  }, [lr]);
120
151
  return userInfo;
121
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
+ }
122
172
  export {
123
173
  LinkedRecordsContext,
124
174
  LinkedRecordsProvider,
175
+ useActorId,
176
+ useFacts,
125
177
  useKeyValueAttributes,
126
178
  useKeyValueRecords,
179
+ useKeyValueRecordsWithStatus,
127
180
  useLinkedRecords,
128
181
  useUserInfo
129
182
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkedrecords/react",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
package/src/index.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './linkedRecordsContext';
2
2
  export * from './LinkedRecordsProvider';
3
3
  export * from './useRecords';
4
+ export * from './useFacts';
4
5
  export * from './useLinkedRecords';
5
6
  export * from './useUserInfo';
7
+ export * from './useActorId';
@@ -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
+ }
@@ -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,25 +1,27 @@
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
- // 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));
11
+ export interface KVRecord extends KVValue {
12
+ _id?: string;
13
+ _createdAt?: Date;
15
14
  }
16
15
 
17
- export function useKeyValueRecords(query: any[]): KVValue[] {
16
+ export function useKeyValueRecordsWithStatus(query: any[]): { records: KVRecord[], isLoading: boolean } {
18
17
  const { lr } = useLinkedRecords();
19
- const [ records, setRecords ] = useState<KVValue[]>([]);
18
+ const [ records, setRecords ] = useState<KVRecord[]>([]);
19
+ const [ isLoading, setIsLoading ] = useState(true);
20
20
  const queryKey = getQueryKey(query);
21
21
 
22
22
  useEffect(() => {
23
+ setIsLoading(true);
24
+
23
25
  // The SDK has no way to remove a record observer, so observers registered
24
26
  // by this effect run check this flag and become no-ops after cleanup.
25
27
  let cancelled = false;
@@ -49,6 +51,7 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
49
51
  }, async ({ records }) => {
50
52
  const values = await Promise.all(records.map(async (r) => ({
51
53
  _id: r.id,
54
+ _createdAt: r.createdAt,
52
55
  ...(await r.getValue()),
53
56
  })));
54
57
 
@@ -57,6 +60,7 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
57
60
  }
58
61
 
59
62
  setRecords(values);
63
+ setIsLoading(false);
60
64
 
61
65
  records.forEach((r) => {
62
66
  if (subscribedRecordIds.has(r.id)) {
@@ -79,7 +83,7 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
79
83
  // Use functional update to avoid stale closure
80
84
  setRecords(prev => prev.map(v =>
81
85
  v._id === r.id
82
- ? { _id: r.id, ...newValue }
86
+ ? { _id: r.id, _createdAt: r.createdAt, ...newValue }
83
87
  : v
84
88
  ));
85
89
  });
@@ -100,7 +104,11 @@ export function useKeyValueRecords(query: any[]): KVValue[] {
100
104
  // eslint-disable-next-line react-hooks/exhaustive-deps
101
105
  }, [ lr, queryKey ]);
102
106
 
103
- return records;
107
+ return { records, isLoading };
108
+ }
109
+
110
+ export function useKeyValueRecords(query: any[]): KVRecord[] {
111
+ return useKeyValueRecordsWithStatus(query).records;
104
112
  }
105
113
 
106
114
  /** @deprecated Use useKeyValueRecords instead. */