@isograph/react 0.1.1 → 0.2.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.
Files changed (112) hide show
  1. package/dist/core/FragmentReference.d.ts +15 -0
  2. package/dist/core/FragmentReference.js +17 -0
  3. package/dist/core/IsographEnvironment.d.ts +71 -0
  4. package/dist/core/IsographEnvironment.js +72 -0
  5. package/dist/core/PromiseWrapper.d.ts +27 -0
  6. package/dist/core/PromiseWrapper.js +58 -0
  7. package/dist/core/areEqualWithDeepComparison.d.ts +3 -0
  8. package/dist/core/areEqualWithDeepComparison.js +61 -0
  9. package/dist/core/cache.d.ts +28 -0
  10. package/dist/core/cache.js +452 -0
  11. package/dist/core/componentCache.d.ts +5 -0
  12. package/dist/core/componentCache.js +38 -0
  13. package/dist/core/entrypoint.d.ts +50 -0
  14. package/dist/core/entrypoint.js +8 -0
  15. package/dist/core/garbageCollection.d.ts +11 -0
  16. package/dist/core/garbageCollection.js +74 -0
  17. package/dist/core/makeNetworkRequest.d.ts +6 -0
  18. package/dist/core/makeNetworkRequest.js +62 -0
  19. package/dist/core/read.d.ts +12 -0
  20. package/dist/core/read.js +415 -0
  21. package/dist/core/reader.d.ts +63 -0
  22. package/dist/core/reader.js +2 -0
  23. package/dist/core/util.d.ts +17 -0
  24. package/dist/core/util.js +2 -0
  25. package/dist/index.d.ts +21 -120
  26. package/dist/index.js +49 -306
  27. package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -0
  28. package/dist/loadable-hooks/useClientSideDefer.js +15 -0
  29. package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +5 -0
  30. package/dist/loadable-hooks/useImperativeExposedMutationField.js +15 -0
  31. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +9 -0
  32. package/dist/loadable-hooks/useImperativeLoadableField.js +15 -0
  33. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +33 -0
  34. package/dist/loadable-hooks/useSkipLimitPagination.js +118 -0
  35. package/dist/react/FragmentReader.d.ts +13 -0
  36. package/dist/{EntrypointReader.js → react/FragmentReader.js} +5 -5
  37. package/dist/react/IsographEnvironmentProvider.d.ts +10 -0
  38. package/dist/{IsographEnvironment.js → react/IsographEnvironmentProvider.js} +2 -20
  39. package/dist/react/useImperativeReference.d.ts +7 -0
  40. package/dist/react/useImperativeReference.js +36 -0
  41. package/dist/react/useLazyReference.d.ts +5 -0
  42. package/dist/react/useLazyReference.js +14 -0
  43. package/dist/react/useReadAndSubscribe.d.ts +11 -0
  44. package/dist/react/useReadAndSubscribe.js +41 -0
  45. package/dist/react/useRerenderOnChange.d.ts +3 -0
  46. package/dist/react/useRerenderOnChange.js +23 -0
  47. package/dist/react/useResult.d.ts +5 -0
  48. package/dist/react/useResult.js +36 -0
  49. package/docs/how-useLazyReference-works.md +117 -0
  50. package/package.json +11 -6
  51. package/src/core/FragmentReference.ts +37 -0
  52. package/src/core/IsographEnvironment.ts +183 -0
  53. package/src/core/PromiseWrapper.ts +86 -0
  54. package/src/core/areEqualWithDeepComparison.ts +78 -0
  55. package/src/core/cache.ts +721 -0
  56. package/src/core/componentCache.ts +61 -0
  57. package/src/core/entrypoint.ts +99 -0
  58. package/src/core/garbageCollection.ts +122 -0
  59. package/src/core/makeNetworkRequest.ts +99 -0
  60. package/src/core/read.ts +615 -0
  61. package/src/core/reader.ts +133 -0
  62. package/src/core/util.ts +23 -0
  63. package/src/index.ts +86 -0
  64. package/src/loadable-hooks/useClientSideDefer.ts +28 -0
  65. package/src/loadable-hooks/useImperativeExposedMutationField.ts +17 -0
  66. package/src/loadable-hooks/useImperativeLoadableField.ts +26 -0
  67. package/src/loadable-hooks/useSkipLimitPagination.ts +211 -0
  68. package/src/react/FragmentReader.tsx +34 -0
  69. package/src/react/IsographEnvironmentProvider.tsx +33 -0
  70. package/src/react/useImperativeReference.ts +57 -0
  71. package/src/react/useLazyReference.ts +22 -0
  72. package/src/react/useReadAndSubscribe.ts +66 -0
  73. package/src/react/useRerenderOnChange.ts +33 -0
  74. package/src/react/useResult.ts +65 -0
  75. package/src/tests/__isograph/Query/meName/entrypoint.ts +47 -0
  76. package/src/tests/__isograph/Query/meName/output_type.ts +3 -0
  77. package/src/tests/__isograph/Query/meName/param_type.ts +6 -0
  78. package/src/tests/__isograph/Query/meName/resolver_reader.ts +32 -0
  79. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +83 -0
  80. package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +3 -0
  81. package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +11 -0
  82. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +54 -0
  83. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +46 -0
  84. package/src/tests/__isograph/Query/nodeField/output_type.ts +3 -0
  85. package/src/tests/__isograph/Query/nodeField/param_type.ts +6 -0
  86. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +37 -0
  87. package/src/tests/__isograph/iso.ts +88 -0
  88. package/src/tests/garbageCollection.test.ts +136 -0
  89. package/src/tests/isograph.config.json +7 -0
  90. package/src/tests/meNameSuccessor.ts +20 -0
  91. package/src/tests/nodeQuery.ts +17 -0
  92. package/src/tests/schema.graphql +16 -0
  93. package/src/tests/tsconfig.json +21 -0
  94. package/tsconfig.json +6 -0
  95. package/tsconfig.pkg.json +2 -1
  96. package/dist/EntrypointReader.d.ts +0 -6
  97. package/dist/IsographEnvironment.d.ts +0 -56
  98. package/dist/PromiseWrapper.d.ts +0 -13
  99. package/dist/PromiseWrapper.js +0 -22
  100. package/dist/cache.d.ts +0 -26
  101. package/dist/cache.js +0 -274
  102. package/dist/componentCache.d.ts +0 -6
  103. package/dist/componentCache.js +0 -31
  104. package/dist/useImperativeReference.d.ts +0 -8
  105. package/dist/useImperativeReference.js +0 -28
  106. package/src/EntrypointReader.tsx +0 -20
  107. package/src/IsographEnvironment.tsx +0 -120
  108. package/src/PromiseWrapper.ts +0 -29
  109. package/src/cache.tsx +0 -484
  110. package/src/componentCache.ts +0 -41
  111. package/src/index.tsx +0 -617
  112. package/src/useImperativeReference.ts +0 -58
@@ -0,0 +1,615 @@
1
+ import { CleanupFn } from '@isograph/isograph-disposable-types/dist';
2
+ import { getParentRecordKey, onNextChange } from './cache';
3
+ import { getOrCreateCachedComponent } from './componentCache';
4
+ import {
5
+ IsographEntrypoint,
6
+ RefetchQueryNormalizationArtifactWrapper,
7
+ } from './entrypoint';
8
+ import { FragmentReference, Variables } from './FragmentReference';
9
+ import {
10
+ assertLink,
11
+ DataId,
12
+ defaultMissingFieldHandler,
13
+ getOrLoadIsographArtifact,
14
+ IsographEnvironment,
15
+ } from './IsographEnvironment';
16
+ import { makeNetworkRequest } from './makeNetworkRequest';
17
+ import {
18
+ getPromiseState,
19
+ PromiseWrapper,
20
+ readPromise,
21
+ wrapPromise,
22
+ wrapResolvedValue,
23
+ } from './PromiseWrapper';
24
+ import { ReaderAst } from './reader';
25
+ import { Arguments } from './util';
26
+
27
+ export type WithEncounteredRecords<T> = {
28
+ readonly encounteredRecords: Set<DataId>;
29
+ readonly item: T;
30
+ };
31
+
32
+ export function readButDoNotEvaluate<TReadFromStore extends Object>(
33
+ environment: IsographEnvironment,
34
+ fragmentReference: FragmentReference<TReadFromStore, unknown>,
35
+ networkRequestOptions: NetworkRequestReaderOptions,
36
+ ): WithEncounteredRecords<TReadFromStore> {
37
+ const mutableEncounteredRecords = new Set<DataId>();
38
+
39
+ const readerWithRefetchQueries = readPromise(
40
+ fragmentReference.readerWithRefetchQueries,
41
+ );
42
+
43
+ const response = readData(
44
+ environment,
45
+ readerWithRefetchQueries.readerArtifact.readerAst,
46
+ fragmentReference.root,
47
+ fragmentReference.variables ?? {},
48
+ readerWithRefetchQueries.nestedRefetchQueries,
49
+ fragmentReference.networkRequest,
50
+ networkRequestOptions,
51
+ mutableEncounteredRecords,
52
+ );
53
+ // @ts-expect-error
54
+ if (typeof window !== 'undefined' && window.__LOG) {
55
+ console.log('done reading', { response });
56
+ }
57
+ if (response.kind === 'MissingData') {
58
+ // There are two cases here that we care about:
59
+ // 1. the network request is in flight, we haven't suspend on it, and we want
60
+ // to throw if it errors out. So, networkRequestOptions.suspendIfInFlight === false
61
+ // and networkRequestOptions.throwOnNetworkError === true.
62
+ // 2. everything else
63
+ //
64
+ // In the first case, we cannot simply throw onNextChange, because if the network
65
+ // response errors out, we will not update the store, so the onNextChange promise
66
+ // will not resolve.
67
+ if (
68
+ !networkRequestOptions.suspendIfInFlight &&
69
+ networkRequestOptions.throwOnNetworkError
70
+ ) {
71
+ // TODO assert that the network request state is not Err
72
+ throw new Promise((resolve, reject) => {
73
+ onNextChange(environment).then(resolve);
74
+ fragmentReference.networkRequest.promise.catch(reject);
75
+ });
76
+ }
77
+ throw onNextChange(environment);
78
+ } else {
79
+ return {
80
+ encounteredRecords: mutableEncounteredRecords,
81
+ item: response.data,
82
+ };
83
+ }
84
+ }
85
+
86
+ type ReadDataResult<TReadFromStore> =
87
+ | {
88
+ readonly kind: 'Success';
89
+ readonly data: TReadFromStore;
90
+ readonly encounteredRecords: Set<DataId>;
91
+ }
92
+ | {
93
+ readonly kind: 'MissingData';
94
+ readonly reason: string;
95
+ readonly nestedReason?: ReadDataResult<unknown>;
96
+ };
97
+
98
+ function readData<TReadFromStore>(
99
+ environment: IsographEnvironment,
100
+ ast: ReaderAst<TReadFromStore>,
101
+ root: DataId,
102
+ variables: Variables,
103
+ nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[],
104
+ networkRequest: PromiseWrapper<void, any>,
105
+ networkRequestOptions: NetworkRequestReaderOptions,
106
+ mutableEncounteredRecords: Set<DataId>,
107
+ ): ReadDataResult<TReadFromStore> {
108
+ mutableEncounteredRecords.add(root);
109
+ let storeRecord = environment.store[root];
110
+ if (storeRecord === undefined) {
111
+ return {
112
+ kind: 'MissingData',
113
+ reason: 'No record for root ' + root,
114
+ };
115
+ }
116
+
117
+ if (storeRecord === null) {
118
+ return {
119
+ kind: 'Success',
120
+ data: null as any,
121
+ encounteredRecords: mutableEncounteredRecords,
122
+ };
123
+ }
124
+
125
+ let target: { [index: string]: any } = {};
126
+
127
+ for (const field of ast) {
128
+ switch (field.kind) {
129
+ case 'Scalar': {
130
+ const storeRecordName = getParentRecordKey(field, variables);
131
+ const value = storeRecord[storeRecordName];
132
+ // TODO consider making scalars into discriminated unions. This probably has
133
+ // to happen for when we handle errors.
134
+ if (value === undefined) {
135
+ return {
136
+ kind: 'MissingData',
137
+ reason: 'No value for ' + storeRecordName + ' on root ' + root,
138
+ };
139
+ }
140
+ target[field.alias ?? field.fieldName] = value;
141
+ break;
142
+ }
143
+ case 'Linked': {
144
+ const storeRecordName = getParentRecordKey(field, variables);
145
+ const value = storeRecord[storeRecordName];
146
+ if (Array.isArray(value)) {
147
+ const results = [];
148
+ for (const item of value) {
149
+ const link = assertLink(item);
150
+ if (link === undefined) {
151
+ return {
152
+ kind: 'MissingData',
153
+ reason:
154
+ 'No link for ' +
155
+ storeRecordName +
156
+ ' on root ' +
157
+ root +
158
+ '. Link is ' +
159
+ JSON.stringify(item),
160
+ };
161
+ } else if (link === null) {
162
+ results.push(null);
163
+ continue;
164
+ }
165
+ const result = readData(
166
+ environment,
167
+ field.selections,
168
+ link.__link,
169
+ variables,
170
+ nestedRefetchQueries,
171
+ networkRequest,
172
+ networkRequestOptions,
173
+ mutableEncounteredRecords,
174
+ );
175
+ if (result.kind === 'MissingData') {
176
+ return {
177
+ kind: 'MissingData',
178
+ reason:
179
+ 'Missing data for ' +
180
+ storeRecordName +
181
+ ' on root ' +
182
+ root +
183
+ '. Link is ' +
184
+ JSON.stringify(item),
185
+ nestedReason: result,
186
+ };
187
+ }
188
+ results.push(result.data);
189
+ }
190
+ target[field.alias ?? field.fieldName] = results;
191
+ break;
192
+ }
193
+ let link = assertLink(value);
194
+ if (link === undefined) {
195
+ // TODO make this configurable, and also generated and derived from the schema
196
+ const missingFieldHandler =
197
+ environment.missingFieldHandler ?? defaultMissingFieldHandler;
198
+ const altLink = missingFieldHandler(
199
+ storeRecord,
200
+ root,
201
+ field.fieldName,
202
+ field.arguments,
203
+ variables,
204
+ );
205
+ if (altLink === undefined) {
206
+ return {
207
+ kind: 'MissingData',
208
+ reason:
209
+ 'No link for ' +
210
+ storeRecordName +
211
+ ' on root ' +
212
+ root +
213
+ '. Link is ' +
214
+ JSON.stringify(value),
215
+ };
216
+ } else {
217
+ link = altLink;
218
+ }
219
+ } else if (link === null) {
220
+ target[field.alias ?? field.fieldName] = null;
221
+ break;
222
+ }
223
+ const targetId = link.__link;
224
+ const data = readData(
225
+ environment,
226
+ field.selections,
227
+ targetId,
228
+ variables,
229
+ nestedRefetchQueries,
230
+ networkRequest,
231
+ networkRequestOptions,
232
+ mutableEncounteredRecords,
233
+ );
234
+ if (data.kind === 'MissingData') {
235
+ return {
236
+ kind: 'MissingData',
237
+ reason: 'Missing data for ' + storeRecordName + ' on root ' + root,
238
+ nestedReason: data,
239
+ };
240
+ }
241
+ target[field.alias ?? field.fieldName] = data.data;
242
+ break;
243
+ }
244
+ case 'ImperativelyLoadedField': {
245
+ // First, we read the data using the refetch reader AST (i.e. read out the
246
+ // id field).
247
+ const data = readData(
248
+ environment,
249
+ field.refetchReaderArtifact.readerAst,
250
+ root,
251
+ variables,
252
+ // Refetch fields just read the id, and don't need refetch query artifacts
253
+ [],
254
+ // This is probably indicative of the fact that we are doing redundant checks
255
+ // on the status of this network request...
256
+ networkRequest,
257
+ networkRequestOptions,
258
+ mutableEncounteredRecords,
259
+ );
260
+ if (data.kind === 'MissingData') {
261
+ return {
262
+ kind: 'MissingData',
263
+ reason: 'Missing data for ' + field.alias + ' on root ' + root,
264
+ nestedReason: data,
265
+ };
266
+ } else {
267
+ const refetchQueryIndex = field.refetchQuery;
268
+ if (refetchQueryIndex == null) {
269
+ throw new Error('refetchQuery is null in RefetchField');
270
+ }
271
+ const refetchQuery = nestedRefetchQueries[refetchQueryIndex];
272
+ const refetchQueryArtifact = refetchQuery.artifact;
273
+ const allowedVariables = refetchQuery.allowedVariables;
274
+
275
+ // Second, we allow the user to call the resolver, which will ultimately
276
+ // use the resolver reader AST to get the resolver parameters.
277
+ target[field.alias] = (args: any) => [
278
+ // Stable id
279
+ root + '__' + field.name,
280
+ // Fetcher
281
+ field.refetchReaderArtifact.resolver(
282
+ environment,
283
+ refetchQueryArtifact,
284
+ data.data,
285
+ filterVariables({ ...args, ...variables }, allowedVariables),
286
+ root,
287
+ // TODO these params should be removed
288
+ null,
289
+ [],
290
+ ),
291
+ ];
292
+ }
293
+ break;
294
+ }
295
+ case 'Resolver': {
296
+ const usedRefetchQueries = field.usedRefetchQueries;
297
+ const resolverRefetchQueries = usedRefetchQueries.map(
298
+ (index) => nestedRefetchQueries[index],
299
+ );
300
+
301
+ switch (field.readerArtifact.kind) {
302
+ case 'EagerReaderArtifact': {
303
+ const data = readData(
304
+ environment,
305
+ field.readerArtifact.readerAst,
306
+ root,
307
+ variables,
308
+ resolverRefetchQueries,
309
+ networkRequest,
310
+ networkRequestOptions,
311
+ mutableEncounteredRecords,
312
+ );
313
+ if (data.kind === 'MissingData') {
314
+ return {
315
+ kind: 'MissingData',
316
+ reason: 'Missing data for ' + field.alias + ' on root ' + root,
317
+ nestedReason: data,
318
+ };
319
+ } else {
320
+ target[field.alias] = field.readerArtifact.resolver(data.data);
321
+ }
322
+ break;
323
+ }
324
+ case 'ComponentReaderArtifact': {
325
+ target[field.alias] = getOrCreateCachedComponent(
326
+ environment,
327
+ field.readerArtifact.componentName,
328
+ {
329
+ kind: 'FragmentReference',
330
+ readerWithRefetchQueries: wrapResolvedValue({
331
+ kind: 'ReaderWithRefetchQueries',
332
+ readerArtifact: field.readerArtifact,
333
+ nestedRefetchQueries: resolverRefetchQueries,
334
+ }),
335
+ root,
336
+ variables: generateChildVariableMap(variables, field.arguments),
337
+ networkRequest,
338
+ } as const,
339
+ networkRequestOptions,
340
+ );
341
+ break;
342
+ }
343
+ default: {
344
+ let _: never = field.readerArtifact;
345
+ _;
346
+ throw new Error('Unexpected kind');
347
+ }
348
+ }
349
+ break;
350
+ }
351
+ case 'LoadablySelectedField': {
352
+ const refetchReaderParams = readData(
353
+ environment,
354
+ field.refetchReaderAst,
355
+ root,
356
+ variables,
357
+ // Refetch fields just read the id, and don't need refetch query artifacts
358
+ [],
359
+ networkRequest,
360
+ networkRequestOptions,
361
+ mutableEncounteredRecords,
362
+ );
363
+ if (refetchReaderParams.kind === 'MissingData') {
364
+ return {
365
+ kind: 'MissingData',
366
+ reason: 'Missing data for ' + field.alias + ' on root ' + root,
367
+ nestedReason: refetchReaderParams,
368
+ };
369
+ } else {
370
+ target[field.alias] = (args: any) => {
371
+ // TODO we should use the reader AST for this
372
+ const includeReadOutData = (variables: any, readOutData: any) => {
373
+ variables.id = readOutData.id;
374
+ return variables;
375
+ };
376
+ const localVariables = includeReadOutData(
377
+ args ?? {},
378
+ refetchReaderParams.data,
379
+ );
380
+ writeQueryArgsToVariables(
381
+ localVariables,
382
+ field.queryArguments,
383
+ variables,
384
+ );
385
+
386
+ return [
387
+ // Stable id
388
+ root +
389
+ '/' +
390
+ field.name +
391
+ '/' +
392
+ stableStringifyArgs(localVariables),
393
+ // Fetcher
394
+ () => {
395
+ const fragmentReferenceAndDisposeFromEntrypoint = (
396
+ entrypoint: IsographEntrypoint<any, any>,
397
+ ): [FragmentReference<any, any>, CleanupFn] => {
398
+ const [networkRequest, disposeNetworkRequest] =
399
+ makeNetworkRequest(environment, entrypoint, localVariables);
400
+
401
+ const fragmentReference: FragmentReference<any, any> = {
402
+ kind: 'FragmentReference',
403
+ readerWithRefetchQueries: wrapResolvedValue({
404
+ kind: 'ReaderWithRefetchQueries',
405
+ readerArtifact:
406
+ entrypoint.readerWithRefetchQueries.readerArtifact,
407
+ nestedRefetchQueries:
408
+ entrypoint.readerWithRefetchQueries
409
+ .nestedRefetchQueries,
410
+ } as const),
411
+
412
+ // TODO localVariables is not guaranteed to have an id field
413
+ root: localVariables.id,
414
+ variables: localVariables,
415
+ networkRequest,
416
+ };
417
+ return [fragmentReference, disposeNetworkRequest];
418
+ };
419
+
420
+ if (field.entrypoint.kind === 'Entrypoint') {
421
+ return fragmentReferenceAndDisposeFromEntrypoint(
422
+ field.entrypoint,
423
+ );
424
+ } else {
425
+ const isographArtifactPromiseWrapper =
426
+ getOrLoadIsographArtifact(
427
+ environment,
428
+ field.entrypoint.typeAndField,
429
+ field.entrypoint.loader,
430
+ );
431
+ const state = getPromiseState(isographArtifactPromiseWrapper);
432
+ if (state.kind === 'Ok') {
433
+ return fragmentReferenceAndDisposeFromEntrypoint(
434
+ state.value,
435
+ );
436
+ } else {
437
+ // Promise is pending or thrown
438
+
439
+ let entrypointLoaderState:
440
+ | {
441
+ kind: 'EntrypointNotLoaded';
442
+ }
443
+ | {
444
+ kind: 'NetworkRequestStarted';
445
+ disposeNetworkRequest: CleanupFn;
446
+ }
447
+ | { kind: 'Disposed' } = { kind: 'EntrypointNotLoaded' };
448
+
449
+ const networkRequest = wrapPromise(
450
+ isographArtifactPromiseWrapper.promise.then(
451
+ (entrypoint) => {
452
+ if (
453
+ entrypointLoaderState.kind === 'EntrypointNotLoaded'
454
+ ) {
455
+ const [networkRequest, disposeNetworkRequest] =
456
+ makeNetworkRequest(
457
+ environment,
458
+ entrypoint,
459
+ localVariables,
460
+ );
461
+ entrypointLoaderState = {
462
+ kind: 'NetworkRequestStarted',
463
+ disposeNetworkRequest,
464
+ };
465
+ return networkRequest.promise;
466
+ }
467
+ },
468
+ ),
469
+ );
470
+ const readerWithRefetchPromise =
471
+ isographArtifactPromiseWrapper.promise.then(
472
+ (entrypoint) => entrypoint.readerWithRefetchQueries,
473
+ );
474
+
475
+ const fragmentReference: FragmentReference<any, any> = {
476
+ kind: 'FragmentReference',
477
+ readerWithRefetchQueries: wrapPromise(
478
+ readerWithRefetchPromise,
479
+ ),
480
+
481
+ // TODO localVariables is not guaranteed to have an id field
482
+ root: localVariables.id,
483
+ variables: localVariables,
484
+ networkRequest,
485
+ };
486
+
487
+ return [
488
+ fragmentReference,
489
+ () => {
490
+ if (
491
+ entrypointLoaderState.kind === 'NetworkRequestStarted'
492
+ ) {
493
+ entrypointLoaderState.disposeNetworkRequest();
494
+ }
495
+ entrypointLoaderState = { kind: 'Disposed' };
496
+ },
497
+ ];
498
+ }
499
+ }
500
+ },
501
+ ];
502
+ };
503
+ }
504
+ break;
505
+ }
506
+ default: {
507
+ // Ensure we have covered all variants
508
+ let _: never = field;
509
+ _;
510
+ throw new Error('Unexpected case.');
511
+ }
512
+ }
513
+ }
514
+ return {
515
+ kind: 'Success',
516
+ data: target as any,
517
+ encounteredRecords: mutableEncounteredRecords,
518
+ };
519
+ }
520
+
521
+ function filterVariables(
522
+ variables: Variables,
523
+ allowedVariables: string[],
524
+ ): Variables {
525
+ const result: Variables = {};
526
+ for (const key of allowedVariables) {
527
+ // @ts-expect-error
528
+ result[key] = variables[key];
529
+ }
530
+ return result;
531
+ }
532
+
533
+ function generateChildVariableMap(
534
+ variables: Variables,
535
+ fieldArguments: Arguments | null,
536
+ ): Variables {
537
+ if (fieldArguments == null) {
538
+ return {};
539
+ }
540
+
541
+ type Writable<T> = { -readonly [P in keyof T]: T[P] };
542
+ const childVars: Writable<Variables> = {};
543
+ for (const [name, value] of fieldArguments) {
544
+ if (value.kind === 'Variable') {
545
+ childVars[name] = variables[value.name];
546
+ } else {
547
+ childVars[name] = value.value;
548
+ }
549
+ }
550
+ return childVars;
551
+ }
552
+
553
+ function writeQueryArgsToVariables(
554
+ targetVariables: any,
555
+ queryArgs: Arguments | null,
556
+ variables: Variables,
557
+ ) {
558
+ if (queryArgs == null) {
559
+ return;
560
+ }
561
+ for (const [name, argType] of queryArgs) {
562
+ switch (argType.kind) {
563
+ case 'Variable': {
564
+ targetVariables[name] = variables[argType.name];
565
+ break;
566
+ }
567
+ case 'Enum': {
568
+ targetVariables[name] = argType.value;
569
+ break;
570
+ }
571
+ case 'Literal': {
572
+ targetVariables[name] = argType.value;
573
+ break;
574
+ }
575
+ case 'String': {
576
+ targetVariables[name] = argType.value;
577
+ break;
578
+ }
579
+ default: {
580
+ const _: never = argType;
581
+ _;
582
+ throw new Error('Unexpected case');
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ export type NetworkRequestReaderOptions = {
589
+ suspendIfInFlight: boolean;
590
+ throwOnNetworkError: boolean;
591
+ };
592
+
593
+ export function getNetworkRequestOptionsWithDefaults(
594
+ networkRequestOptions?: Partial<NetworkRequestReaderOptions> | void,
595
+ ): NetworkRequestReaderOptions {
596
+ return {
597
+ suspendIfInFlight: networkRequestOptions?.suspendIfInFlight ?? false,
598
+ throwOnNetworkError: networkRequestOptions?.throwOnNetworkError ?? true,
599
+ };
600
+ }
601
+
602
+ // TODO use a description of the params for this?
603
+ // TODO call stableStringifyArgs on the variable values, as well.
604
+ // This doesn't matter for now, since we are just using primitive values
605
+ // in the demo.
606
+ function stableStringifyArgs(args: Object) {
607
+ const keys = Object.keys(args);
608
+ keys.sort();
609
+ let s = '';
610
+ for (const key of keys) {
611
+ // @ts-expect-error
612
+ s += `${key}=${JSON.stringify(args[key])};`;
613
+ }
614
+ return s;
615
+ }