@isograph/react 0.1.0 → 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 (107) 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 -118
  26. package/dist/index.js +50 -303
  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/react/FragmentReader.js +33 -0
  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 +12 -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/IsographEnvironment.d.ts +0 -56
  97. package/dist/PromiseWrapper.d.ts +0 -13
  98. package/dist/PromiseWrapper.js +0 -22
  99. package/dist/cache.d.ts +0 -26
  100. package/dist/cache.js +0 -274
  101. package/dist/componentCache.d.ts +0 -6
  102. package/dist/componentCache.js +0 -31
  103. package/src/IsographEnvironment.tsx +0 -120
  104. package/src/PromiseWrapper.ts +0 -29
  105. package/src/cache.tsx +0 -484
  106. package/src/componentCache.ts +0 -44
  107. package/src/index.tsx +0 -651
@@ -0,0 +1,61 @@
1
+ import { stableCopy } from './cache';
2
+ import { IsographEnvironment } from './IsographEnvironment';
3
+ import { FragmentReference } from './FragmentReference';
4
+ import { useReadAndSubscribe } from '../react/useReadAndSubscribe';
5
+ import { NetworkRequestReaderOptions } from './read';
6
+ import { readPromise } from './PromiseWrapper';
7
+
8
+ export function getOrCreateCachedComponent(
9
+ environment: IsographEnvironment,
10
+ componentName: string,
11
+ fragmentReference: FragmentReference<any, any>,
12
+ networkRequestOptions: NetworkRequestReaderOptions,
13
+ ): React.FC<any> {
14
+ // cachedComponentsById is a three layer cache: id, then component name, then
15
+ // stringified args. These three, together, uniquely identify a read at a given
16
+ // time.
17
+ const cachedComponentsById = environment.componentCache;
18
+
19
+ cachedComponentsById[fragmentReference.root] =
20
+ cachedComponentsById[fragmentReference.root] ?? {};
21
+ const componentsByName = cachedComponentsById[fragmentReference.root];
22
+
23
+ componentsByName[componentName] = componentsByName[componentName] ?? {};
24
+ const byArgs = componentsByName[componentName];
25
+
26
+ const stringifiedArgs = JSON.stringify(
27
+ stableCopy(fragmentReference.variables),
28
+ );
29
+ byArgs[stringifiedArgs] =
30
+ byArgs[stringifiedArgs] ??
31
+ (() => {
32
+ function Component(additionalRuntimeProps: { [key: string]: any }) {
33
+ const data = useReadAndSubscribe(
34
+ fragmentReference,
35
+ networkRequestOptions,
36
+ );
37
+
38
+ // @ts-expect-error
39
+ if (typeof window !== 'undefined' && window.__LOG) {
40
+ console.log(
41
+ 'Component re-rendered: ' +
42
+ componentName +
43
+ ' ' +
44
+ fragmentReference.root,
45
+ );
46
+ }
47
+
48
+ const readerWithRefetchQueries = readPromise(
49
+ fragmentReference.readerWithRefetchQueries,
50
+ );
51
+
52
+ return readerWithRefetchQueries.readerArtifact.resolver(
53
+ data,
54
+ additionalRuntimeProps,
55
+ );
56
+ }
57
+ Component.displayName = `${componentName} (id: ${fragmentReference.root}) @component`;
58
+ return Component;
59
+ })();
60
+ return byArgs[stringifiedArgs];
61
+ }
@@ -0,0 +1,99 @@
1
+ import { TopLevelReaderArtifact } from './reader';
2
+ import { Arguments } from './util';
3
+
4
+ export type ReaderWithRefetchQueries<
5
+ TReadFromStore extends Object,
6
+ TClientFieldValue,
7
+ > = {
8
+ readonly kind: 'ReaderWithRefetchQueries';
9
+ readonly readerArtifact: TopLevelReaderArtifact<
10
+ TReadFromStore,
11
+ TClientFieldValue,
12
+ // TODO don't type this as any
13
+ any
14
+ >;
15
+ readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[];
16
+ };
17
+
18
+ // This type should be treated as an opaque type.
19
+ export type IsographEntrypoint<
20
+ TReadFromStore extends Object,
21
+ TClientFieldValue,
22
+ > = {
23
+ readonly kind: 'Entrypoint';
24
+ readonly queryText: string;
25
+ readonly normalizationAst: NormalizationAst;
26
+ readonly readerWithRefetchQueries: ReaderWithRefetchQueries<
27
+ TReadFromStore,
28
+ TClientFieldValue
29
+ >;
30
+ };
31
+
32
+ export type IsographEntrypointLoader<
33
+ TReadFromStore extends Object,
34
+ TClientFieldValue,
35
+ > = {
36
+ readonly kind: 'EntrypointLoader';
37
+ readonly typeAndField: string;
38
+ readonly loader: () => Promise<
39
+ IsographEntrypoint<TReadFromStore, TClientFieldValue>
40
+ >;
41
+ };
42
+
43
+ export type NormalizationAstNode =
44
+ | NormalizationScalarField
45
+ | NormalizationLinkedField
46
+ | NormalizationInlineFragment;
47
+ export type NormalizationAst = ReadonlyArray<NormalizationAstNode>;
48
+
49
+ export type NormalizationScalarField = {
50
+ readonly kind: 'Scalar';
51
+ readonly fieldName: string;
52
+ readonly arguments: Arguments | null;
53
+ };
54
+
55
+ export type NormalizationLinkedField = {
56
+ readonly kind: 'Linked';
57
+ readonly fieldName: string;
58
+ readonly arguments: Arguments | null;
59
+ readonly selections: NormalizationAst;
60
+ };
61
+
62
+ export type NormalizationInlineFragment = {
63
+ readonly kind: 'InlineFragment';
64
+ readonly type: string;
65
+ readonly selections: NormalizationAst;
66
+ };
67
+
68
+ // This is more like an entrypoint, but one specifically for a refetch query/mutation
69
+ export type RefetchQueryNormalizationArtifact = {
70
+ readonly kind: 'RefetchQuery';
71
+ readonly queryText: string;
72
+ readonly normalizationAst: NormalizationAst;
73
+ };
74
+
75
+ // TODO rename
76
+ export type RefetchQueryNormalizationArtifactWrapper = {
77
+ readonly artifact: RefetchQueryNormalizationArtifact;
78
+ readonly allowedVariables: string[];
79
+ };
80
+
81
+ export function assertIsEntrypoint<
82
+ TReadFromStore extends Object,
83
+ TClientFieldValue,
84
+ >(
85
+ value:
86
+ | IsographEntrypoint<TReadFromStore, TClientFieldValue>
87
+ | ((_: any) => any)
88
+ // Temporarily, allow any here. Once we automatically provide
89
+ // types to entrypoints, we probably don't need this.
90
+ | any,
91
+ ): asserts value is IsographEntrypoint<TReadFromStore, TClientFieldValue> {
92
+ if (typeof value === 'function') throw new Error('Not a string');
93
+ }
94
+
95
+ export type ExtractReadFromStore<Type> =
96
+ Type extends IsographEntrypoint<infer X, any> ? X : never;
97
+ export type ExtractResolverResult<Type> =
98
+ Type extends IsographEntrypoint<any, infer X> ? X : never;
99
+ export type ExtractProps<Type> = Type extends React.FC<infer X> ? X : never;
@@ -0,0 +1,122 @@
1
+ import { Variables } from './FragmentReference';
2
+ import {
3
+ DataId,
4
+ IsographEnvironment,
5
+ IsographStore,
6
+ ROOT_ID,
7
+ StoreRecord,
8
+ assertLink,
9
+ } from './IsographEnvironment';
10
+ import { getParentRecordKey } from './cache';
11
+ import { NormalizationAst } from './entrypoint';
12
+
13
+ export type RetainedQuery = {
14
+ readonly normalizationAst: NormalizationAst;
15
+ readonly variables: {};
16
+ };
17
+
18
+ type DidUnretainSomeQuery = boolean;
19
+ export function unretainQuery(
20
+ environment: IsographEnvironment,
21
+ retainedQuery: RetainedQuery,
22
+ ): DidUnretainSomeQuery {
23
+ environment.retainedQueries.delete(retainedQuery);
24
+ environment.gcBuffer.push(retainedQuery);
25
+
26
+ if (environment.gcBuffer.length > environment.gcBufferSize) {
27
+ environment.gcBuffer.shift();
28
+ return true;
29
+ }
30
+
31
+ return false;
32
+ }
33
+
34
+ export function retainQuery(
35
+ environment: IsographEnvironment,
36
+ queryToRetain: RetainedQuery,
37
+ ) {
38
+ environment.retainedQueries.add(queryToRetain);
39
+ // TODO can we remove this query from the buffer somehow?
40
+ // We are relying on === equality, but we really should be comparing
41
+ // id + variables
42
+ }
43
+
44
+ export function garbageCollectEnvironment(environment: IsographEnvironment) {
45
+ const retainedIds = new Set<DataId>([ROOT_ID]);
46
+
47
+ for (const query of environment.retainedQueries) {
48
+ recordReachableIds(environment.store, query, retainedIds);
49
+ }
50
+ for (const query of environment.gcBuffer) {
51
+ recordReachableIds(environment.store, query, retainedIds);
52
+ }
53
+
54
+ for (const dataId in environment.store) {
55
+ if (!retainedIds.has(dataId)) {
56
+ delete environment.store[dataId];
57
+ }
58
+ }
59
+ }
60
+
61
+ function recordReachableIds(
62
+ store: IsographStore,
63
+ retainedQuery: RetainedQuery,
64
+ mutableRetainedIds: Set<DataId>,
65
+ ) {
66
+ recordReachableIdsFromRecord(
67
+ store,
68
+ store[ROOT_ID],
69
+ mutableRetainedIds,
70
+ retainedQuery.normalizationAst,
71
+ retainedQuery.variables,
72
+ );
73
+ }
74
+
75
+ function recordReachableIdsFromRecord(
76
+ store: IsographStore,
77
+ currentRecord: StoreRecord,
78
+ mutableRetainedIds: Set<DataId>,
79
+ selections: NormalizationAst,
80
+ variables: Variables | null,
81
+ ) {
82
+ for (const selection of selections) {
83
+ switch (selection.kind) {
84
+ case 'Linked':
85
+ const linkKey = getParentRecordKey(selection, variables ?? {});
86
+ const linkedFieldOrFields = currentRecord[linkKey];
87
+
88
+ const ids = [];
89
+ if (Array.isArray(linkedFieldOrFields)) {
90
+ for (const maybeLink of linkedFieldOrFields) {
91
+ const link = assertLink(maybeLink);
92
+ if (link != null) {
93
+ ids.push(link.__link);
94
+ }
95
+ }
96
+ } else {
97
+ const link = assertLink(linkedFieldOrFields);
98
+ if (link != null) {
99
+ ids.push(link.__link);
100
+ }
101
+ }
102
+
103
+ for (const nextRecordId of ids) {
104
+ const nextRecord = store[nextRecordId];
105
+ if (nextRecord != null) {
106
+ mutableRetainedIds.add(nextRecordId);
107
+ recordReachableIdsFromRecord(
108
+ store,
109
+ nextRecord,
110
+ mutableRetainedIds,
111
+ selection.selections,
112
+ variables,
113
+ );
114
+ }
115
+ }
116
+
117
+ continue;
118
+ case 'Scalar':
119
+ continue;
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,99 @@
1
+ import { ItemCleanupPair } from '@isograph/disposable-types';
2
+ import {
3
+ IsographEntrypoint,
4
+ RefetchQueryNormalizationArtifact,
5
+ } from './entrypoint';
6
+ import { Variables } from './FragmentReference';
7
+ import {
8
+ garbageCollectEnvironment,
9
+ RetainedQuery,
10
+ retainQuery,
11
+ unretainQuery,
12
+ } from './garbageCollection';
13
+ import { IsographEnvironment } from './IsographEnvironment';
14
+ import { AnyError, PromiseWrapper, wrapPromise } from './PromiseWrapper';
15
+ import { normalizeData } from './cache';
16
+
17
+ export function makeNetworkRequest(
18
+ environment: IsographEnvironment,
19
+ artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint<any, any>,
20
+ variables: Variables,
21
+ ): ItemCleanupPair<PromiseWrapper<void, AnyError>> {
22
+ // @ts-expect-error
23
+ if (typeof window !== 'undefined' && window.__LOG) {
24
+ console.log('make network request', artifact, variables);
25
+ }
26
+ let status: NetworkRequestStatus = {
27
+ kind: 'UndisposedIncomplete',
28
+ };
29
+ // This should be an observable, not a promise
30
+ const promise = environment
31
+ .networkFunction(artifact.queryText, variables)
32
+ .then((networkResponse) => {
33
+ // @ts-expect-error
34
+ if (typeof window !== 'undefined' && window.__LOG) {
35
+ console.log('network response', artifact, networkResponse);
36
+ }
37
+
38
+ if (networkResponse.errors != null) {
39
+ // @ts-expect-error Why are we getting the wrong constructor here?
40
+ throw new Error('GraphQL network response had errors', {
41
+ cause: networkResponse,
42
+ });
43
+ }
44
+
45
+ if (status.kind === 'UndisposedIncomplete') {
46
+ normalizeData(
47
+ environment,
48
+ artifact.normalizationAst,
49
+ networkResponse.data ?? {},
50
+ variables,
51
+ artifact.kind === 'Entrypoint'
52
+ ? artifact.readerWithRefetchQueries.nestedRefetchQueries
53
+ : [],
54
+ );
55
+ const retainedQuery = {
56
+ normalizationAst: artifact.normalizationAst,
57
+ variables,
58
+ };
59
+ status = {
60
+ kind: 'UndisposedComplete',
61
+ retainedQuery,
62
+ };
63
+ retainQuery(environment, retainedQuery);
64
+ }
65
+ });
66
+
67
+ const wrapper = wrapPromise(promise);
68
+
69
+ const response: ItemCleanupPair<PromiseWrapper<void, AnyError>> = [
70
+ wrapper,
71
+ () => {
72
+ if (status.kind === 'UndisposedComplete') {
73
+ const didUnretainSomeQuery = unretainQuery(
74
+ environment,
75
+ status.retainedQuery,
76
+ );
77
+ if (didUnretainSomeQuery) {
78
+ garbageCollectEnvironment(environment);
79
+ }
80
+ }
81
+ status = {
82
+ kind: 'Disposed',
83
+ };
84
+ },
85
+ ];
86
+ return response;
87
+ }
88
+
89
+ type NetworkRequestStatus =
90
+ | {
91
+ readonly kind: 'UndisposedIncomplete';
92
+ }
93
+ | {
94
+ readonly kind: 'Disposed';
95
+ }
96
+ | {
97
+ readonly kind: 'UndisposedComplete';
98
+ readonly retainedQuery: RetainedQuery;
99
+ };