@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.
- package/dist/core/FragmentReference.d.ts +15 -0
- package/dist/core/FragmentReference.js +17 -0
- package/dist/core/IsographEnvironment.d.ts +71 -0
- package/dist/core/IsographEnvironment.js +72 -0
- package/dist/core/PromiseWrapper.d.ts +27 -0
- package/dist/core/PromiseWrapper.js +58 -0
- package/dist/core/areEqualWithDeepComparison.d.ts +3 -0
- package/dist/core/areEqualWithDeepComparison.js +61 -0
- package/dist/core/cache.d.ts +28 -0
- package/dist/core/cache.js +452 -0
- package/dist/core/componentCache.d.ts +5 -0
- package/dist/core/componentCache.js +38 -0
- package/dist/core/entrypoint.d.ts +50 -0
- package/dist/core/entrypoint.js +8 -0
- package/dist/core/garbageCollection.d.ts +11 -0
- package/dist/core/garbageCollection.js +74 -0
- package/dist/core/makeNetworkRequest.d.ts +6 -0
- package/dist/core/makeNetworkRequest.js +62 -0
- package/dist/core/read.d.ts +12 -0
- package/dist/core/read.js +415 -0
- package/dist/core/reader.d.ts +63 -0
- package/dist/core/reader.js +2 -0
- package/dist/core/util.d.ts +17 -0
- package/dist/core/util.js +2 -0
- package/dist/index.d.ts +21 -118
- package/dist/index.js +50 -303
- package/dist/loadable-hooks/useClientSideDefer.d.ts +4 -0
- package/dist/loadable-hooks/useClientSideDefer.js +15 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.d.ts +5 -0
- package/dist/loadable-hooks/useImperativeExposedMutationField.js +15 -0
- package/dist/loadable-hooks/useImperativeLoadableField.d.ts +9 -0
- package/dist/loadable-hooks/useImperativeLoadableField.js +15 -0
- package/dist/loadable-hooks/useSkipLimitPagination.d.ts +33 -0
- package/dist/loadable-hooks/useSkipLimitPagination.js +118 -0
- package/dist/react/FragmentReader.d.ts +13 -0
- package/dist/react/FragmentReader.js +33 -0
- package/dist/react/IsographEnvironmentProvider.d.ts +10 -0
- package/dist/{IsographEnvironment.js → react/IsographEnvironmentProvider.js} +2 -20
- package/dist/react/useImperativeReference.d.ts +7 -0
- package/dist/react/useImperativeReference.js +36 -0
- package/dist/react/useLazyReference.d.ts +5 -0
- package/dist/react/useLazyReference.js +14 -0
- package/dist/react/useReadAndSubscribe.d.ts +11 -0
- package/dist/react/useReadAndSubscribe.js +41 -0
- package/dist/react/useRerenderOnChange.d.ts +3 -0
- package/dist/react/useRerenderOnChange.js +23 -0
- package/dist/react/useResult.d.ts +5 -0
- package/dist/react/useResult.js +36 -0
- package/docs/how-useLazyReference-works.md +117 -0
- package/package.json +12 -6
- package/src/core/FragmentReference.ts +37 -0
- package/src/core/IsographEnvironment.ts +183 -0
- package/src/core/PromiseWrapper.ts +86 -0
- package/src/core/areEqualWithDeepComparison.ts +78 -0
- package/src/core/cache.ts +721 -0
- package/src/core/componentCache.ts +61 -0
- package/src/core/entrypoint.ts +99 -0
- package/src/core/garbageCollection.ts +122 -0
- package/src/core/makeNetworkRequest.ts +99 -0
- package/src/core/read.ts +615 -0
- package/src/core/reader.ts +133 -0
- package/src/core/util.ts +23 -0
- package/src/index.ts +86 -0
- package/src/loadable-hooks/useClientSideDefer.ts +28 -0
- package/src/loadable-hooks/useImperativeExposedMutationField.ts +17 -0
- package/src/loadable-hooks/useImperativeLoadableField.ts +26 -0
- package/src/loadable-hooks/useSkipLimitPagination.ts +211 -0
- package/src/react/FragmentReader.tsx +34 -0
- package/src/react/IsographEnvironmentProvider.tsx +33 -0
- package/src/react/useImperativeReference.ts +57 -0
- package/src/react/useLazyReference.ts +22 -0
- package/src/react/useReadAndSubscribe.ts +66 -0
- package/src/react/useRerenderOnChange.ts +33 -0
- package/src/react/useResult.ts +65 -0
- package/src/tests/__isograph/Query/meName/entrypoint.ts +47 -0
- package/src/tests/__isograph/Query/meName/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meName/param_type.ts +6 -0
- package/src/tests/__isograph/Query/meName/resolver_reader.ts +32 -0
- package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +83 -0
- package/src/tests/__isograph/Query/meNameSuccessor/output_type.ts +3 -0
- package/src/tests/__isograph/Query/meNameSuccessor/param_type.ts +11 -0
- package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +54 -0
- package/src/tests/__isograph/Query/nodeField/entrypoint.ts +46 -0
- package/src/tests/__isograph/Query/nodeField/output_type.ts +3 -0
- package/src/tests/__isograph/Query/nodeField/param_type.ts +6 -0
- package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +37 -0
- package/src/tests/__isograph/iso.ts +88 -0
- package/src/tests/garbageCollection.test.ts +136 -0
- package/src/tests/isograph.config.json +7 -0
- package/src/tests/meNameSuccessor.ts +20 -0
- package/src/tests/nodeQuery.ts +17 -0
- package/src/tests/schema.graphql +16 -0
- package/src/tests/tsconfig.json +21 -0
- package/tsconfig.json +6 -0
- package/tsconfig.pkg.json +2 -1
- package/dist/IsographEnvironment.d.ts +0 -56
- package/dist/PromiseWrapper.d.ts +0 -13
- package/dist/PromiseWrapper.js +0 -22
- package/dist/cache.d.ts +0 -26
- package/dist/cache.js +0 -274
- package/dist/componentCache.d.ts +0 -6
- package/dist/componentCache.js +0 -31
- package/src/IsographEnvironment.tsx +0 -120
- package/src/PromiseWrapper.ts +0 -29
- package/src/cache.tsx +0 -484
- package/src/componentCache.ts +0 -44
- package/src/index.tsx +0 -651
@@ -0,0 +1,183 @@
|
|
1
|
+
import { ParentCache } from '@isograph/react-disposable-state';
|
2
|
+
import { RetainedQuery } from './garbageCollection';
|
3
|
+
import { WithEncounteredRecords } from './read';
|
4
|
+
import { FragmentReference, Variables } from './FragmentReference';
|
5
|
+
import { PromiseWrapper, wrapPromise } from './PromiseWrapper';
|
6
|
+
import { IsographEntrypoint } from './entrypoint';
|
7
|
+
|
8
|
+
export type ComponentOrFieldName = string;
|
9
|
+
export type StringifiedArgs = string;
|
10
|
+
type ComponentCache = {
|
11
|
+
[key: DataId]: {
|
12
|
+
[key: ComponentOrFieldName]: { [key: StringifiedArgs]: React.FC<any> };
|
13
|
+
};
|
14
|
+
};
|
15
|
+
|
16
|
+
export type FragmentSubscription<TReadFromStore extends Object> = {
|
17
|
+
readonly kind: 'FragmentSubscription';
|
18
|
+
readonly callback: (
|
19
|
+
newEncounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>,
|
20
|
+
) => void;
|
21
|
+
/** The value read out from the previous call to readButDoNotEvaluate */
|
22
|
+
readonly encounteredDataAndRecords: WithEncounteredRecords<TReadFromStore>;
|
23
|
+
readonly fragmentReference: FragmentReference<TReadFromStore, any>;
|
24
|
+
};
|
25
|
+
type AnyRecordSubscription = {
|
26
|
+
readonly kind: 'AnyRecords';
|
27
|
+
readonly callback: () => void;
|
28
|
+
};
|
29
|
+
|
30
|
+
type Subscription = FragmentSubscription<Object> | AnyRecordSubscription;
|
31
|
+
type Subscriptions = Set<Subscription>;
|
32
|
+
// Should this be a map?
|
33
|
+
type CacheMap<T> = { [index: string]: ParentCache<T> };
|
34
|
+
|
35
|
+
export type IsographEnvironment = {
|
36
|
+
readonly store: IsographStore;
|
37
|
+
readonly networkFunction: IsographNetworkFunction;
|
38
|
+
readonly missingFieldHandler: MissingFieldHandler | null;
|
39
|
+
readonly componentCache: ComponentCache;
|
40
|
+
readonly subscriptions: Subscriptions;
|
41
|
+
// N.B. this must be <any, any>, but all *usages* of this should go through
|
42
|
+
// a function that adds type parameters.
|
43
|
+
readonly fragmentCache: CacheMap<FragmentReference<any, any>>;
|
44
|
+
// TODO make this a CacheMap and add GC
|
45
|
+
readonly entrypointArtifactCache: Map<
|
46
|
+
string,
|
47
|
+
PromiseWrapper<IsographEntrypoint<any, any>>
|
48
|
+
>;
|
49
|
+
readonly retainedQueries: Set<RetainedQuery>;
|
50
|
+
readonly gcBuffer: Array<RetainedQuery>;
|
51
|
+
readonly gcBufferSize: number;
|
52
|
+
};
|
53
|
+
|
54
|
+
export type MissingFieldHandler = (
|
55
|
+
storeRecord: StoreRecord,
|
56
|
+
root: DataId,
|
57
|
+
fieldName: string,
|
58
|
+
arguments_: { [index: string]: any } | null,
|
59
|
+
variables: Variables | null,
|
60
|
+
) => Link | undefined;
|
61
|
+
|
62
|
+
export type IsographNetworkFunction = (
|
63
|
+
queryText: string,
|
64
|
+
variables: Variables,
|
65
|
+
) => Promise<any>;
|
66
|
+
|
67
|
+
export type Link = {
|
68
|
+
readonly __link: DataId;
|
69
|
+
};
|
70
|
+
|
71
|
+
export type DataTypeValue =
|
72
|
+
// N.B. undefined is here to support optional id's, but
|
73
|
+
// undefined should not *actually* be present in the store.
|
74
|
+
| undefined
|
75
|
+
// Singular scalar fields:
|
76
|
+
| number
|
77
|
+
| boolean
|
78
|
+
| string
|
79
|
+
| null
|
80
|
+
// Singular linked fields:
|
81
|
+
| Link
|
82
|
+
// Plural scalar and linked fields:
|
83
|
+
| DataTypeValue[];
|
84
|
+
|
85
|
+
export type StoreRecord = {
|
86
|
+
[index: DataId | string]: DataTypeValue;
|
87
|
+
// TODO __typename?: T, which is restricted to being a concrete string
|
88
|
+
// TODO this shouldn't always be named id
|
89
|
+
readonly id?: DataId;
|
90
|
+
};
|
91
|
+
|
92
|
+
export type DataId = string;
|
93
|
+
|
94
|
+
export const ROOT_ID: DataId & '__ROOT' = '__ROOT';
|
95
|
+
|
96
|
+
export type IsographStore = {
|
97
|
+
[index: DataId]: StoreRecord | null;
|
98
|
+
readonly __ROOT: StoreRecord;
|
99
|
+
};
|
100
|
+
|
101
|
+
const DEFAULT_GC_BUFFER_SIZE = 10;
|
102
|
+
export function createIsographEnvironment(
|
103
|
+
store: IsographStore,
|
104
|
+
networkFunction: IsographNetworkFunction,
|
105
|
+
missingFieldHandler?: MissingFieldHandler,
|
106
|
+
): IsographEnvironment {
|
107
|
+
return {
|
108
|
+
store,
|
109
|
+
networkFunction,
|
110
|
+
missingFieldHandler: missingFieldHandler ?? null,
|
111
|
+
componentCache: {},
|
112
|
+
subscriptions: new Set(),
|
113
|
+
fragmentCache: {},
|
114
|
+
entrypointArtifactCache: new Map(),
|
115
|
+
retainedQueries: new Set(),
|
116
|
+
gcBuffer: [],
|
117
|
+
gcBufferSize: DEFAULT_GC_BUFFER_SIZE,
|
118
|
+
};
|
119
|
+
}
|
120
|
+
|
121
|
+
export function createIsographStore(): IsographStore {
|
122
|
+
return {
|
123
|
+
[ROOT_ID]: {},
|
124
|
+
};
|
125
|
+
}
|
126
|
+
|
127
|
+
export function defaultMissingFieldHandler(
|
128
|
+
_storeRecord: StoreRecord,
|
129
|
+
_root: DataId,
|
130
|
+
fieldName: string,
|
131
|
+
arguments_: { [index: string]: any } | null,
|
132
|
+
variables: Variables | null,
|
133
|
+
): Link | undefined {
|
134
|
+
if (fieldName === 'node' || fieldName === 'user') {
|
135
|
+
const variable = arguments_?.['id'];
|
136
|
+
const value = variables?.[variable];
|
137
|
+
|
138
|
+
// TODO can we handle explicit nulls here too? Probably, after wrapping in objects
|
139
|
+
if (value != null) {
|
140
|
+
// @ts-expect-error
|
141
|
+
return { __link: value };
|
142
|
+
}
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
export function assertLink(link: DataTypeValue): Link | null | undefined {
|
147
|
+
if (Array.isArray(link)) {
|
148
|
+
throw new Error('Unexpected array');
|
149
|
+
}
|
150
|
+
if (link == null) {
|
151
|
+
return link;
|
152
|
+
}
|
153
|
+
if (typeof link === 'object') {
|
154
|
+
return link;
|
155
|
+
}
|
156
|
+
throw new Error('Invalid link');
|
157
|
+
}
|
158
|
+
|
159
|
+
export function getLink(maybeLink: DataTypeValue): Link | null {
|
160
|
+
if (
|
161
|
+
maybeLink != null &&
|
162
|
+
typeof maybeLink === 'object' &&
|
163
|
+
// @ts-expect-error this is safe
|
164
|
+
maybeLink.__link != null
|
165
|
+
) {
|
166
|
+
return maybeLink as any;
|
167
|
+
}
|
168
|
+
return null;
|
169
|
+
}
|
170
|
+
|
171
|
+
export function getOrLoadIsographArtifact(
|
172
|
+
environment: IsographEnvironment,
|
173
|
+
key: string,
|
174
|
+
loader: () => Promise<IsographEntrypoint<any, any>>,
|
175
|
+
): PromiseWrapper<IsographEntrypoint<any, any>> {
|
176
|
+
const value = environment.entrypointArtifactCache.get(key);
|
177
|
+
if (value != null) {
|
178
|
+
return value;
|
179
|
+
}
|
180
|
+
const wrapped = wrapPromise(loader());
|
181
|
+
environment.entrypointArtifactCache.set(key, wrapped);
|
182
|
+
return wrapped;
|
183
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
export type AnyError = any;
|
2
|
+
|
3
|
+
const NOT_SET: Symbol = Symbol('NOT_SET');
|
4
|
+
type NotSet = typeof NOT_SET;
|
5
|
+
|
6
|
+
type Result<T, E> =
|
7
|
+
| {
|
8
|
+
kind: 'Ok';
|
9
|
+
value: T;
|
10
|
+
}
|
11
|
+
| {
|
12
|
+
kind: 'Err';
|
13
|
+
error: E;
|
14
|
+
};
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Invariant:
|
18
|
+
* Before the promise is resolved, value becomes non-null.
|
19
|
+
*/
|
20
|
+
export type PromiseWrapper<T, E = any> = {
|
21
|
+
readonly promise: Promise<T>;
|
22
|
+
result: Result<Exclude<T, NotSet>, E> | NotSet;
|
23
|
+
};
|
24
|
+
|
25
|
+
export function wrapPromise<T>(promise: Promise<T>): PromiseWrapper<T, any> {
|
26
|
+
// TODO confirm suspense works if the promise is already resolved.
|
27
|
+
const wrapper: PromiseWrapper<T, any> = { promise, result: NOT_SET };
|
28
|
+
promise
|
29
|
+
.then((v) => {
|
30
|
+
// v is assignable to Exclude<T, Symbol> | Symbol
|
31
|
+
wrapper.result = { kind: 'Ok', value: v as any };
|
32
|
+
})
|
33
|
+
.catch((e) => {
|
34
|
+
// e is assignable to Exclude<T, Symbol> | Symbol
|
35
|
+
wrapper.result = { kind: 'Err', error: e as any };
|
36
|
+
});
|
37
|
+
return wrapper;
|
38
|
+
}
|
39
|
+
|
40
|
+
export function wrapResolvedValue<T>(value: T): PromiseWrapper<T, never> {
|
41
|
+
return {
|
42
|
+
promise: Promise.resolve(value),
|
43
|
+
result: {
|
44
|
+
kind: 'Ok',
|
45
|
+
// @ts-expect-error one should not call wrapResolvedValue with NOT_SET
|
46
|
+
value,
|
47
|
+
},
|
48
|
+
};
|
49
|
+
}
|
50
|
+
|
51
|
+
export function readPromise<T, E>(p: PromiseWrapper<T, E>): T {
|
52
|
+
const { result } = p;
|
53
|
+
if (result !== NOT_SET) {
|
54
|
+
// Safety: p.result is either NOT_SET or an actual value.
|
55
|
+
const resultKind = result as Result<T, any>;
|
56
|
+
if (resultKind.kind === 'Ok') {
|
57
|
+
return resultKind.value;
|
58
|
+
} else {
|
59
|
+
throw resultKind.error;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
throw p.promise;
|
64
|
+
}
|
65
|
+
|
66
|
+
export type PromiseState<T, E> =
|
67
|
+
| {
|
68
|
+
kind: 'Pending';
|
69
|
+
promise: Promise<T>;
|
70
|
+
}
|
71
|
+
| Result<T, E>;
|
72
|
+
|
73
|
+
export function getPromiseState<T, E>(
|
74
|
+
p: PromiseWrapper<T, E>,
|
75
|
+
): PromiseState<T, E> {
|
76
|
+
const { result } = p;
|
77
|
+
if (result !== NOT_SET) {
|
78
|
+
// Safety: p.result is either NOT_SET or an actual value.
|
79
|
+
const resultKind = result as Result<T, any>;
|
80
|
+
return resultKind;
|
81
|
+
}
|
82
|
+
return {
|
83
|
+
kind: 'Pending',
|
84
|
+
promise: p.promise,
|
85
|
+
};
|
86
|
+
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
export function areEqualWithDeepComparison(
|
2
|
+
oldItem: unknown,
|
3
|
+
newItem: unknown,
|
4
|
+
): boolean {
|
5
|
+
if (newItem === null) {
|
6
|
+
return oldItem === null;
|
7
|
+
}
|
8
|
+
|
9
|
+
if (newItem === undefined) {
|
10
|
+
return oldItem === undefined;
|
11
|
+
}
|
12
|
+
|
13
|
+
if (Array.isArray(newItem)) {
|
14
|
+
if (!Array.isArray(oldItem)) {
|
15
|
+
return false;
|
16
|
+
}
|
17
|
+
|
18
|
+
return areEqualArraysWithDeepComparison(oldItem, newItem);
|
19
|
+
}
|
20
|
+
|
21
|
+
if (typeof newItem === 'object') {
|
22
|
+
if (typeof oldItem !== 'object') {
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
|
26
|
+
if (oldItem === null) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
|
30
|
+
return areEqualObjectsWithDeepComparison(oldItem, newItem);
|
31
|
+
}
|
32
|
+
|
33
|
+
return newItem === oldItem;
|
34
|
+
}
|
35
|
+
|
36
|
+
export function areEqualArraysWithDeepComparison(
|
37
|
+
oldItems: ReadonlyArray<unknown>,
|
38
|
+
newItems: ReadonlyArray<unknown>,
|
39
|
+
): boolean {
|
40
|
+
if (newItems.length !== oldItems.length) {
|
41
|
+
return false;
|
42
|
+
}
|
43
|
+
|
44
|
+
for (let i = 0; i < newItems.length; i++) {
|
45
|
+
if (!areEqualWithDeepComparison(oldItems[i], newItems[i])) {
|
46
|
+
return false;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
|
53
|
+
export function areEqualObjectsWithDeepComparison(
|
54
|
+
oldItemObject: object,
|
55
|
+
newItemObject: object,
|
56
|
+
): boolean {
|
57
|
+
const oldKeys = Object.keys(oldItemObject);
|
58
|
+
const newKeys = Object.keys(newItemObject);
|
59
|
+
|
60
|
+
if (oldKeys.length !== newKeys.length) {
|
61
|
+
return false;
|
62
|
+
}
|
63
|
+
|
64
|
+
for (const oldKey of oldKeys) {
|
65
|
+
if (!(oldKey in newItemObject)) {
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
// @ts-expect-error
|
69
|
+
const oldValue = oldItemObject[oldKey];
|
70
|
+
// @ts-expect-error
|
71
|
+
const newValue = newItemObject[oldKey];
|
72
|
+
|
73
|
+
if (!areEqualWithDeepComparison(oldValue, newValue)) {
|
74
|
+
return false;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
return true;
|
78
|
+
}
|