@isograph/react 0.0.0-main-4ef7c123
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/PromiseWrapper.d.ts +13 -0
- package/dist/PromiseWrapper.js +22 -0
- package/dist/cache.d.ts +30 -0
- package/dist/cache.js +241 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +333 -0
- package/package.json +28 -0
- package/src/PromiseWrapper.ts +29 -0
- package/src/cache.ts +450 -0
- package/src/index.tsx +647 -0
- package/tsconfig.pkg.json +11 -0
package/src/index.tsx
ADDED
@@ -0,0 +1,647 @@
|
|
1
|
+
import {
|
2
|
+
DataId,
|
3
|
+
StoreRecord,
|
4
|
+
DataTypeValue,
|
5
|
+
Link,
|
6
|
+
ROOT_ID,
|
7
|
+
getOrCreateCacheForArtifact,
|
8
|
+
onNextChange,
|
9
|
+
store,
|
10
|
+
getParentRecordKey,
|
11
|
+
} from "./cache";
|
12
|
+
import { useLazyDisposableState } from "@isograph/react-disposable-state";
|
13
|
+
import { type PromiseWrapper } from "./PromiseWrapper";
|
14
|
+
import React from "react";
|
15
|
+
|
16
|
+
export {
|
17
|
+
setNetwork,
|
18
|
+
makeNetworkRequest,
|
19
|
+
subscribe,
|
20
|
+
DataId,
|
21
|
+
Link,
|
22
|
+
StoreRecord,
|
23
|
+
} from "./cache";
|
24
|
+
|
25
|
+
// This type should be treated as an opaque type.
|
26
|
+
export type IsographFetchableResolver<
|
27
|
+
TReadFromStore extends Object,
|
28
|
+
TResolverProps,
|
29
|
+
TResolverResult
|
30
|
+
> = {
|
31
|
+
kind: "FetchableResolver";
|
32
|
+
queryText: string;
|
33
|
+
normalizationAst: NormalizationAst;
|
34
|
+
readerArtifact: ReaderArtifact<
|
35
|
+
TReadFromStore,
|
36
|
+
TResolverProps,
|
37
|
+
TResolverResult
|
38
|
+
>;
|
39
|
+
nestedRefetchQueries: RefetchQueryArtifactWrapper[];
|
40
|
+
};
|
41
|
+
|
42
|
+
export type ReaderArtifact<
|
43
|
+
TReadFromStore extends Object,
|
44
|
+
TResolverProps,
|
45
|
+
TResolverResult
|
46
|
+
> = {
|
47
|
+
kind: "ReaderArtifact";
|
48
|
+
readerAst: ReaderAst<TReadFromStore>;
|
49
|
+
resolver: (data: TResolverProps) => TResolverResult;
|
50
|
+
variant: ReaderResolverVariant;
|
51
|
+
};
|
52
|
+
|
53
|
+
export type ReaderAstNode =
|
54
|
+
| ReaderScalarField
|
55
|
+
| ReaderLinkedField
|
56
|
+
| ReaderResolverField
|
57
|
+
| ReaderRefetchField
|
58
|
+
| ReaderMutationField;
|
59
|
+
|
60
|
+
// @ts-ignore
|
61
|
+
export type ReaderAst<TReadFromStore> = ReaderAstNode[];
|
62
|
+
|
63
|
+
export type ReaderScalarField = {
|
64
|
+
kind: "Scalar";
|
65
|
+
fieldName: string;
|
66
|
+
alias: string | null;
|
67
|
+
arguments: Arguments | null;
|
68
|
+
};
|
69
|
+
export type ReaderLinkedField = {
|
70
|
+
kind: "Linked";
|
71
|
+
fieldName: string;
|
72
|
+
alias: string | null;
|
73
|
+
selections: ReaderAst<unknown>;
|
74
|
+
arguments: Arguments | null;
|
75
|
+
};
|
76
|
+
|
77
|
+
export type ReaderResolverVariant =
|
78
|
+
| { kind: "Eager" }
|
79
|
+
// componentName is the component's cacheKey for getRefReaderByName
|
80
|
+
// and is the type + field concatenated
|
81
|
+
| { kind: "Component"; componentName: string };
|
82
|
+
|
83
|
+
export type ReaderResolverField = {
|
84
|
+
kind: "Resolver";
|
85
|
+
alias: string;
|
86
|
+
readerArtifact: ReaderArtifact<any, any, any>;
|
87
|
+
arguments: Arguments | null;
|
88
|
+
usedRefetchQueries: number[];
|
89
|
+
};
|
90
|
+
|
91
|
+
export type ReaderRefetchField = {
|
92
|
+
kind: "RefetchField";
|
93
|
+
alias: string;
|
94
|
+
// TODO this bad modeling. A refetch field cannot have variant: "Component" (I think)
|
95
|
+
readerArtifact: ReaderArtifact<any, any, any>;
|
96
|
+
refetchQuery: number;
|
97
|
+
};
|
98
|
+
|
99
|
+
export type ReaderMutationField = {
|
100
|
+
kind: "MutationField";
|
101
|
+
alias: string;
|
102
|
+
// TODO this bad modeling. A mutation field cannot have variant: "Component" (I think)
|
103
|
+
readerArtifact: ReaderArtifact<any, any, any>;
|
104
|
+
refetchQuery: number;
|
105
|
+
allowedVariables: string[];
|
106
|
+
};
|
107
|
+
|
108
|
+
export type NormalizationAstNode =
|
109
|
+
| NormalizationScalarField
|
110
|
+
| NormalizationLinkedField;
|
111
|
+
// @ts-ignore
|
112
|
+
export type NormalizationAst = NormalizationAstNode[];
|
113
|
+
|
114
|
+
export type NormalizationScalarField = {
|
115
|
+
kind: "Scalar";
|
116
|
+
fieldName: string;
|
117
|
+
arguments: Arguments | null;
|
118
|
+
};
|
119
|
+
|
120
|
+
export type NormalizationLinkedField = {
|
121
|
+
kind: "Linked";
|
122
|
+
fieldName: string;
|
123
|
+
arguments: Arguments | null;
|
124
|
+
selections: NormalizationAst;
|
125
|
+
};
|
126
|
+
|
127
|
+
// This is more like an entrypoint, but one specifically for a refetch query/mutation
|
128
|
+
export type RefetchQueryArtifact = {
|
129
|
+
kind: "RefetchQuery";
|
130
|
+
queryText: string;
|
131
|
+
normalizationAst: NormalizationAst;
|
132
|
+
};
|
133
|
+
|
134
|
+
// TODO rename
|
135
|
+
export type RefetchQueryArtifactWrapper = {
|
136
|
+
artifact: RefetchQueryArtifact;
|
137
|
+
allowedVariables: string[];
|
138
|
+
};
|
139
|
+
|
140
|
+
export type Arguments = Argument[];
|
141
|
+
export type Argument = {
|
142
|
+
argumentName: string;
|
143
|
+
variableName: string;
|
144
|
+
};
|
145
|
+
|
146
|
+
export type FragmentReference<
|
147
|
+
TReadFromStore extends Object,
|
148
|
+
TResolverProps,
|
149
|
+
TResolverResult
|
150
|
+
> = {
|
151
|
+
kind: "FragmentReference";
|
152
|
+
readerArtifact: ReaderArtifact<
|
153
|
+
TReadFromStore,
|
154
|
+
TResolverProps,
|
155
|
+
TResolverResult
|
156
|
+
>;
|
157
|
+
root: DataId;
|
158
|
+
variables: { [index: string]: string } | null;
|
159
|
+
// TODO: We should instead have ReaderAst<TResolverProps>
|
160
|
+
nestedRefetchQueries: RefetchQueryArtifactWrapper[];
|
161
|
+
};
|
162
|
+
|
163
|
+
export function isoFetch<T extends IsographFetchableResolver<any, any, any>>(
|
164
|
+
_text: TemplateStringsArray
|
165
|
+
): T {
|
166
|
+
return void 0 as any;
|
167
|
+
}
|
168
|
+
|
169
|
+
export function iso<TResolverParameter, TResolverReturn = TResolverParameter>(
|
170
|
+
_queryText: TemplateStringsArray
|
171
|
+
): (
|
172
|
+
x: ((param: TResolverParameter) => TResolverReturn) | void
|
173
|
+
) => (param: TResolverParameter) => TResolverReturn {
|
174
|
+
// The name `identity` here is a bit of a double entendre.
|
175
|
+
// First, it is the identity function, constrained to operate
|
176
|
+
// on a very specific type. Thus, the value of b Declare`...`(
|
177
|
+
// someFunction) is someFunction. But furthermore, if one
|
178
|
+
// write b Declare`...` and passes no function, the resolver itself
|
179
|
+
// is the identity function. At that point, the types
|
180
|
+
// TResolverParameter and TResolverReturn must be identical.
|
181
|
+
|
182
|
+
return function identity(
|
183
|
+
x: (param: TResolverParameter) => TResolverReturn
|
184
|
+
): (param: TResolverParameter) => TResolverReturn {
|
185
|
+
return x;
|
186
|
+
};
|
187
|
+
}
|
188
|
+
|
189
|
+
export function useLazyReference<
|
190
|
+
TReadFromStore extends Object,
|
191
|
+
TResolverProps,
|
192
|
+
TResolverResult
|
193
|
+
>(
|
194
|
+
fetchableResolverArtifact: IsographFetchableResolver<
|
195
|
+
TReadFromStore,
|
196
|
+
TResolverProps,
|
197
|
+
TResolverResult
|
198
|
+
>,
|
199
|
+
variables: object
|
200
|
+
): {
|
201
|
+
queryReference: FragmentReference<
|
202
|
+
TReadFromStore,
|
203
|
+
TResolverProps,
|
204
|
+
TResolverResult
|
205
|
+
>;
|
206
|
+
} {
|
207
|
+
// Typechecking fails here... TODO investigate
|
208
|
+
const cache = getOrCreateCacheForArtifact(
|
209
|
+
fetchableResolverArtifact,
|
210
|
+
variables
|
211
|
+
);
|
212
|
+
|
213
|
+
// TODO add comment explaining why we never use this value
|
214
|
+
// @ts-ignore
|
215
|
+
const data =
|
216
|
+
// @ts-ignore
|
217
|
+
useLazyDisposableState<PromiseWrapper<TResolverResult>>(cache).state;
|
218
|
+
|
219
|
+
return {
|
220
|
+
queryReference: {
|
221
|
+
kind: "FragmentReference",
|
222
|
+
readerArtifact: fetchableResolverArtifact.readerArtifact,
|
223
|
+
root: ROOT_ID,
|
224
|
+
variables,
|
225
|
+
nestedRefetchQueries: fetchableResolverArtifact.nestedRefetchQueries,
|
226
|
+
},
|
227
|
+
};
|
228
|
+
}
|
229
|
+
|
230
|
+
export function read<
|
231
|
+
TReadFromStore extends Object,
|
232
|
+
TResolverProps,
|
233
|
+
TResolverResult
|
234
|
+
>(
|
235
|
+
fragmentReference: FragmentReference<
|
236
|
+
TReadFromStore,
|
237
|
+
TResolverProps,
|
238
|
+
TResolverResult
|
239
|
+
>
|
240
|
+
): TResolverResult {
|
241
|
+
const variant = fragmentReference.readerArtifact.variant;
|
242
|
+
if (variant.kind === "Eager") {
|
243
|
+
const data = readData(
|
244
|
+
fragmentReference.readerArtifact.readerAst,
|
245
|
+
fragmentReference.root,
|
246
|
+
fragmentReference.variables ?? {},
|
247
|
+
fragmentReference.nestedRefetchQueries
|
248
|
+
);
|
249
|
+
if (data.kind === "MissingData") {
|
250
|
+
throw onNextChange();
|
251
|
+
} else {
|
252
|
+
return fragmentReference.readerArtifact.resolver(data.data);
|
253
|
+
}
|
254
|
+
} else if (variant.kind === "Component") {
|
255
|
+
return (additionalRuntimeProps: any) => {
|
256
|
+
// TODO also incorporate the typename
|
257
|
+
const RefReaderForName = getRefReaderForName(variant.componentName);
|
258
|
+
// TODO do not create a new reference on every render?
|
259
|
+
return (
|
260
|
+
<RefReaderForName
|
261
|
+
reference={{
|
262
|
+
kind: "FragmentReference",
|
263
|
+
readerArtifact: fragmentReference.readerArtifact,
|
264
|
+
root: fragmentReference.root,
|
265
|
+
variables: fragmentReference.variables,
|
266
|
+
nestedRefetchQueries: fragmentReference.nestedRefetchQueries,
|
267
|
+
}}
|
268
|
+
additionalRuntimeProps={additionalRuntimeProps}
|
269
|
+
/>
|
270
|
+
);
|
271
|
+
};
|
272
|
+
}
|
273
|
+
// Why can't Typescript realize that this is unreachable??
|
274
|
+
throw new Error("This is unreachable");
|
275
|
+
}
|
276
|
+
|
277
|
+
export function readButDoNotEvaluate<TReadFromStore extends Object>(
|
278
|
+
reference: FragmentReference<TReadFromStore, unknown, unknown>
|
279
|
+
): TReadFromStore {
|
280
|
+
const response = readData(
|
281
|
+
reference.readerArtifact.readerAst,
|
282
|
+
reference.root,
|
283
|
+
reference.variables ?? {},
|
284
|
+
reference.nestedRefetchQueries
|
285
|
+
);
|
286
|
+
console.log("done reading but not evaluating", { response });
|
287
|
+
if (response.kind === "MissingData") {
|
288
|
+
throw onNextChange();
|
289
|
+
} else {
|
290
|
+
return response.data;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
type ReadDataResult<TReadFromStore> =
|
295
|
+
| {
|
296
|
+
kind: "Success";
|
297
|
+
data: TReadFromStore;
|
298
|
+
}
|
299
|
+
| {
|
300
|
+
kind: "MissingData";
|
301
|
+
reason: string;
|
302
|
+
nestedReason?: ReadDataResult<unknown>;
|
303
|
+
};
|
304
|
+
|
305
|
+
function readData<TReadFromStore>(
|
306
|
+
ast: ReaderAst<TReadFromStore>,
|
307
|
+
root: DataId,
|
308
|
+
variables: { [index: string]: string },
|
309
|
+
nestedRefetchQueries: RefetchQueryArtifactWrapper[]
|
310
|
+
): ReadDataResult<TReadFromStore> {
|
311
|
+
let storeRecord = store[root];
|
312
|
+
if (storeRecord === undefined) {
|
313
|
+
return { kind: "MissingData", reason: "No record for root " + root };
|
314
|
+
}
|
315
|
+
|
316
|
+
if (storeRecord === null) {
|
317
|
+
return { kind: "Success", data: null as any };
|
318
|
+
}
|
319
|
+
|
320
|
+
let target: { [index: string]: any } = {};
|
321
|
+
|
322
|
+
for (const field of ast) {
|
323
|
+
switch (field.kind) {
|
324
|
+
case "Scalar": {
|
325
|
+
const storeRecordName = getParentRecordKey(field, variables);
|
326
|
+
const value = storeRecord[storeRecordName];
|
327
|
+
// TODO consider making scalars into discriminated unions. This probably has
|
328
|
+
// to happen for when we handle errors.
|
329
|
+
if (value === undefined) {
|
330
|
+
return {
|
331
|
+
kind: "MissingData",
|
332
|
+
reason: "No value for " + storeRecordName + " on root " + root,
|
333
|
+
};
|
334
|
+
}
|
335
|
+
target[field.alias ?? field.fieldName] = value;
|
336
|
+
break;
|
337
|
+
}
|
338
|
+
case "Linked": {
|
339
|
+
const storeRecordName = getParentRecordKey(field, variables);
|
340
|
+
const value = storeRecord[storeRecordName];
|
341
|
+
if (Array.isArray(value)) {
|
342
|
+
const results = [];
|
343
|
+
for (const item of value) {
|
344
|
+
const link = assertLink(item);
|
345
|
+
if (link === undefined) {
|
346
|
+
return {
|
347
|
+
kind: "MissingData",
|
348
|
+
reason:
|
349
|
+
"No link for " +
|
350
|
+
storeRecordName +
|
351
|
+
" on root " +
|
352
|
+
root +
|
353
|
+
". Link is " +
|
354
|
+
JSON.stringify(item),
|
355
|
+
};
|
356
|
+
} else if (link === null) {
|
357
|
+
results.push(null);
|
358
|
+
continue;
|
359
|
+
}
|
360
|
+
const result = readData(
|
361
|
+
field.selections,
|
362
|
+
link.__link,
|
363
|
+
variables,
|
364
|
+
nestedRefetchQueries
|
365
|
+
);
|
366
|
+
if (result.kind === "MissingData") {
|
367
|
+
return {
|
368
|
+
kind: "MissingData",
|
369
|
+
reason:
|
370
|
+
"Missing data for " +
|
371
|
+
storeRecordName +
|
372
|
+
" on root " +
|
373
|
+
root +
|
374
|
+
". Link is " +
|
375
|
+
JSON.stringify(item),
|
376
|
+
nestedReason: result,
|
377
|
+
};
|
378
|
+
}
|
379
|
+
results.push(result.data);
|
380
|
+
}
|
381
|
+
target[field.alias ?? field.fieldName] = results;
|
382
|
+
break;
|
383
|
+
}
|
384
|
+
let link = assertLink(value);
|
385
|
+
if (link === undefined) {
|
386
|
+
// TODO make this configurable, and also generated and derived from the schema
|
387
|
+
const altLink = missingFieldHandler(
|
388
|
+
storeRecord,
|
389
|
+
root,
|
390
|
+
field.fieldName,
|
391
|
+
field.arguments,
|
392
|
+
variables
|
393
|
+
);
|
394
|
+
if (altLink === undefined) {
|
395
|
+
return {
|
396
|
+
kind: "MissingData",
|
397
|
+
reason:
|
398
|
+
"No link for " +
|
399
|
+
storeRecordName +
|
400
|
+
" on root " +
|
401
|
+
root +
|
402
|
+
". Link is " +
|
403
|
+
JSON.stringify(value),
|
404
|
+
};
|
405
|
+
} else {
|
406
|
+
link = altLink;
|
407
|
+
}
|
408
|
+
} else if (link === null) {
|
409
|
+
target[field.alias ?? field.fieldName] = null;
|
410
|
+
break;
|
411
|
+
}
|
412
|
+
const targetId = link.__link;
|
413
|
+
const data = readData(
|
414
|
+
field.selections,
|
415
|
+
targetId,
|
416
|
+
variables,
|
417
|
+
nestedRefetchQueries
|
418
|
+
);
|
419
|
+
if (data.kind === "MissingData") {
|
420
|
+
return {
|
421
|
+
kind: "MissingData",
|
422
|
+
reason: "Missing data for " + storeRecordName + " on root " + root,
|
423
|
+
nestedReason: data,
|
424
|
+
};
|
425
|
+
}
|
426
|
+
target[field.alias ?? field.fieldName] = data.data;
|
427
|
+
break;
|
428
|
+
}
|
429
|
+
case "RefetchField": {
|
430
|
+
const data = readData(
|
431
|
+
field.readerArtifact.readerAst,
|
432
|
+
root,
|
433
|
+
variables,
|
434
|
+
// Refetch fields just read the id, and don't need refetch query artifacts
|
435
|
+
[]
|
436
|
+
);
|
437
|
+
console.log("refetch field data", data, field);
|
438
|
+
if (data.kind === "MissingData") {
|
439
|
+
return {
|
440
|
+
kind: "MissingData",
|
441
|
+
reason: "Missing data for " + field.alias + " on root " + root,
|
442
|
+
nestedReason: data,
|
443
|
+
};
|
444
|
+
} else {
|
445
|
+
const refetchQueryIndex = field.refetchQuery;
|
446
|
+
if (refetchQueryIndex == null) {
|
447
|
+
throw new Error("refetchQuery is null in RefetchField");
|
448
|
+
}
|
449
|
+
const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
|
450
|
+
const refetchQueryArtifact = refetchQuery.artifact;
|
451
|
+
const allowedVariables = refetchQuery.allowedVariables;
|
452
|
+
|
453
|
+
target[field.alias] = field.readerArtifact.resolver(
|
454
|
+
refetchQueryArtifact,
|
455
|
+
{
|
456
|
+
...data.data,
|
457
|
+
// TODO continue from here
|
458
|
+
// variables need to be filtered for what we need just for the refetch query
|
459
|
+
...filterVariables(variables, allowedVariables),
|
460
|
+
}
|
461
|
+
);
|
462
|
+
}
|
463
|
+
break;
|
464
|
+
}
|
465
|
+
case "MutationField": {
|
466
|
+
const data = readData(
|
467
|
+
field.readerArtifact.readerAst,
|
468
|
+
root,
|
469
|
+
variables,
|
470
|
+
// Refetch fields just read the id, and don't need refetch query artifacts
|
471
|
+
[]
|
472
|
+
);
|
473
|
+
console.log("refetch field data", data, field);
|
474
|
+
if (data.kind === "MissingData") {
|
475
|
+
return {
|
476
|
+
kind: "MissingData",
|
477
|
+
reason: "Missing data for " + field.alias + " on root " + root,
|
478
|
+
nestedReason: data,
|
479
|
+
};
|
480
|
+
} else {
|
481
|
+
const refetchQueryIndex = field.refetchQuery;
|
482
|
+
if (refetchQueryIndex == null) {
|
483
|
+
throw new Error("refetchQuery is null in MutationField");
|
484
|
+
}
|
485
|
+
const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
|
486
|
+
const refetchQueryArtifact = refetchQuery.artifact;
|
487
|
+
const allowedVariables = refetchQuery.allowedVariables;
|
488
|
+
|
489
|
+
target[field.alias] = field.readerArtifact.resolver(
|
490
|
+
refetchQueryArtifact,
|
491
|
+
data.data,
|
492
|
+
filterVariables(variables, allowedVariables)
|
493
|
+
);
|
494
|
+
}
|
495
|
+
break;
|
496
|
+
}
|
497
|
+
case "Resolver": {
|
498
|
+
const usedRefetchQueries = field.usedRefetchQueries;
|
499
|
+
const resolverRefetchQueries = usedRefetchQueries.map(
|
500
|
+
(index) => nestedRefetchQueries[index]
|
501
|
+
);
|
502
|
+
|
503
|
+
const variant = field.readerArtifact.variant;
|
504
|
+
if (variant.kind === "Eager") {
|
505
|
+
const data = readData(
|
506
|
+
field.readerArtifact.readerAst,
|
507
|
+
root,
|
508
|
+
variables,
|
509
|
+
resolverRefetchQueries
|
510
|
+
);
|
511
|
+
if (data.kind === "MissingData") {
|
512
|
+
return {
|
513
|
+
kind: "MissingData",
|
514
|
+
reason: "Missing data for " + field.alias + " on root " + root,
|
515
|
+
nestedReason: data,
|
516
|
+
};
|
517
|
+
} else {
|
518
|
+
target[field.alias] = field.readerArtifact.resolver(data.data);
|
519
|
+
}
|
520
|
+
} else if (variant.kind === "Component") {
|
521
|
+
target[field.alias] = (additionalRuntimeProps: any) => {
|
522
|
+
// TODO also incorporate the typename
|
523
|
+
const RefReaderForName = getRefReaderForName(variant.componentName);
|
524
|
+
// TODO do not create a new reference on every render?
|
525
|
+
return (
|
526
|
+
<RefReaderForName
|
527
|
+
reference={{
|
528
|
+
kind: "FragmentReference",
|
529
|
+
readerArtifact: field.readerArtifact,
|
530
|
+
root,
|
531
|
+
variables,
|
532
|
+
nestedRefetchQueries: resolverRefetchQueries,
|
533
|
+
}}
|
534
|
+
additionalRuntimeProps={additionalRuntimeProps}
|
535
|
+
/>
|
536
|
+
);
|
537
|
+
};
|
538
|
+
}
|
539
|
+
break;
|
540
|
+
}
|
541
|
+
}
|
542
|
+
}
|
543
|
+
return { kind: "Success", data: target as any };
|
544
|
+
}
|
545
|
+
|
546
|
+
let customMissingFieldHandler: typeof defaultMissingFieldHandler | null = null;
|
547
|
+
|
548
|
+
function missingFieldHandler(
|
549
|
+
storeRecord: StoreRecord,
|
550
|
+
root: DataId,
|
551
|
+
fieldName: string,
|
552
|
+
arguments_: { [index: string]: any } | null,
|
553
|
+
variables: { [index: string]: any } | null
|
554
|
+
): Link | undefined {
|
555
|
+
if (customMissingFieldHandler != null) {
|
556
|
+
return customMissingFieldHandler(
|
557
|
+
storeRecord,
|
558
|
+
root,
|
559
|
+
fieldName,
|
560
|
+
arguments_,
|
561
|
+
variables
|
562
|
+
);
|
563
|
+
} else {
|
564
|
+
return defaultMissingFieldHandler(
|
565
|
+
storeRecord,
|
566
|
+
root,
|
567
|
+
fieldName,
|
568
|
+
arguments_,
|
569
|
+
variables
|
570
|
+
);
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
export function defaultMissingFieldHandler(
|
575
|
+
storeRecord: StoreRecord,
|
576
|
+
root: DataId,
|
577
|
+
fieldName: string,
|
578
|
+
arguments_: { [index: string]: any } | null,
|
579
|
+
variables: { [index: string]: any } | null
|
580
|
+
): Link | undefined {
|
581
|
+
if (fieldName === "node" || fieldName === "user") {
|
582
|
+
const variable = arguments_?.["id"];
|
583
|
+
const value = variables?.[variable];
|
584
|
+
|
585
|
+
// TODO can we handle explicit nulls here too? Probably, after wrapping in objects
|
586
|
+
if (value != null) {
|
587
|
+
return { __link: value };
|
588
|
+
}
|
589
|
+
}
|
590
|
+
}
|
591
|
+
|
592
|
+
export function setMissingFieldHandler(
|
593
|
+
handler: typeof defaultMissingFieldHandler
|
594
|
+
) {
|
595
|
+
customMissingFieldHandler = handler;
|
596
|
+
}
|
597
|
+
|
598
|
+
function assertLink(link: DataTypeValue): Link | undefined | null {
|
599
|
+
if (Array.isArray(link)) {
|
600
|
+
throw new Error("Unexpected array");
|
601
|
+
}
|
602
|
+
if (typeof link === "object") {
|
603
|
+
return link;
|
604
|
+
}
|
605
|
+
if (link === undefined) {
|
606
|
+
return undefined;
|
607
|
+
}
|
608
|
+
throw new Error("Invalid link");
|
609
|
+
}
|
610
|
+
|
611
|
+
const refReaders: { [index: string]: any } = {};
|
612
|
+
export function getRefReaderForName(name: string) {
|
613
|
+
if (refReaders[name] == null) {
|
614
|
+
function Component({
|
615
|
+
reference,
|
616
|
+
additionalRuntimeProps,
|
617
|
+
}: {
|
618
|
+
reference: FragmentReference<any, any, any>;
|
619
|
+
additionalRuntimeProps: any;
|
620
|
+
}) {
|
621
|
+
const data = readButDoNotEvaluate(reference);
|
622
|
+
|
623
|
+
return reference.readerArtifact.resolver({
|
624
|
+
data,
|
625
|
+
...additionalRuntimeProps,
|
626
|
+
});
|
627
|
+
}
|
628
|
+
Component.displayName = `${name} @component`;
|
629
|
+
refReaders[name] = Component;
|
630
|
+
}
|
631
|
+
return refReaders[name];
|
632
|
+
}
|
633
|
+
|
634
|
+
export type IsographComponentProps<TDataType, TOtherProps = Object> = {
|
635
|
+
data: TDataType;
|
636
|
+
} & TOtherProps;
|
637
|
+
|
638
|
+
function filterVariables(
|
639
|
+
variables: { [index: string]: string },
|
640
|
+
allowedVariables: string[]
|
641
|
+
): { [index: string]: string } {
|
642
|
+
const result: { [index: string]: string } = {};
|
643
|
+
for (const key of allowedVariables) {
|
644
|
+
result[key] = variables[key];
|
645
|
+
}
|
646
|
+
return result;
|
647
|
+
}
|