@isograph/react 0.0.0-main-945e49cc → 0.0.0-main-dc9ca2f6
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/IsographEnvironment.d.ts +12 -0
- package/dist/IsographEnvironment.js +85 -1
- package/dist/cache.js +38 -11
- package/package.json +3 -3
- package/src/IsographEnvironment.tsx +130 -0
- package/src/cache.tsx +67 -10
- package/src/index.tsx +0 -1
@@ -1,6 +1,7 @@
|
|
1
1
|
import { ReactNode } from 'react';
|
2
2
|
import * as React from 'react';
|
3
3
|
import { ParentCache } from '@isograph/isograph-react-disposable-state';
|
4
|
+
import { NormalizationAst } from './index';
|
4
5
|
export declare const IsographEnvironmentContext: React.Context<IsographEnvironment | null>;
|
5
6
|
type ComponentName = string;
|
6
7
|
type StringifiedArgs = string;
|
@@ -15,6 +16,10 @@ export type Subscriptions = Set<() => void>;
|
|
15
16
|
type SuspenseCache = {
|
16
17
|
[index: string]: ParentCache<any>;
|
17
18
|
};
|
19
|
+
export type RetainedQuery = {
|
20
|
+
normalizationAst: NormalizationAst;
|
21
|
+
variables: {};
|
22
|
+
};
|
18
23
|
export type IsographEnvironment = {
|
19
24
|
store: IsographStore;
|
20
25
|
networkFunction: IsographNetworkFunction;
|
@@ -22,6 +27,9 @@ export type IsographEnvironment = {
|
|
22
27
|
componentCache: ComponentCache;
|
23
28
|
subscriptions: Subscriptions;
|
24
29
|
suspenseCache: SuspenseCache;
|
30
|
+
retainedQueries: Set<RetainedQuery>;
|
31
|
+
gcBuffer: Array<RetainedQuery>;
|
32
|
+
gcBufferSize: number;
|
25
33
|
};
|
26
34
|
export type MissingFieldHandler = (storeRecord: StoreRecord, root: DataId, fieldName: string, arguments_: {
|
27
35
|
[index: string]: any;
|
@@ -53,4 +61,8 @@ export declare function createIsographEnvironment(store: IsographStore, networkF
|
|
53
61
|
export declare function createIsographStore(): {
|
54
62
|
__ROOT: {};
|
55
63
|
};
|
64
|
+
export declare function garbageCollectEnvironment(environment: IsographEnvironment): void;
|
65
|
+
type DidUnretainSomeQuery = boolean;
|
66
|
+
export declare function unretainQuery(environment: IsographEnvironment, retainedQuery: RetainedQuery): DidUnretainSomeQuery;
|
67
|
+
export declare function retainQuery(environment: IsographEnvironment, queryToRetain: RetainedQuery): void;
|
56
68
|
export {};
|
@@ -23,9 +23,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
23
|
return result;
|
24
24
|
};
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
-
exports.createIsographStore = exports.createIsographEnvironment = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.ROOT_ID = exports.IsographEnvironmentContext = void 0;
|
26
|
+
exports.retainQuery = exports.unretainQuery = exports.garbageCollectEnvironment = exports.createIsographStore = exports.createIsographEnvironment = exports.useIsographEnvironment = exports.IsographEnvironmentProvider = exports.ROOT_ID = exports.IsographEnvironmentContext = void 0;
|
27
27
|
const react_1 = require("react");
|
28
28
|
const React = __importStar(require("react"));
|
29
|
+
const cache_1 = require("./cache");
|
29
30
|
exports.IsographEnvironmentContext = (0, react_1.createContext)(null);
|
30
31
|
exports.ROOT_ID = '__ROOT';
|
31
32
|
function IsographEnvironmentProvider({ environment, children, }) {
|
@@ -41,6 +42,7 @@ function useIsographEnvironment() {
|
|
41
42
|
return context;
|
42
43
|
}
|
43
44
|
exports.useIsographEnvironment = useIsographEnvironment;
|
45
|
+
const DEFAULT_GC_BUFFER_SIZE = 10;
|
44
46
|
function createIsographEnvironment(store, networkFunction, missingFieldHandler) {
|
45
47
|
return {
|
46
48
|
store,
|
@@ -49,6 +51,9 @@ function createIsographEnvironment(store, networkFunction, missingFieldHandler)
|
|
49
51
|
componentCache: {},
|
50
52
|
subscriptions: new Set(),
|
51
53
|
suspenseCache: {},
|
54
|
+
retainedQueries: new Set(),
|
55
|
+
gcBuffer: [],
|
56
|
+
gcBufferSize: DEFAULT_GC_BUFFER_SIZE,
|
52
57
|
};
|
53
58
|
}
|
54
59
|
exports.createIsographEnvironment = createIsographEnvironment;
|
@@ -58,3 +63,82 @@ function createIsographStore() {
|
|
58
63
|
};
|
59
64
|
}
|
60
65
|
exports.createIsographStore = createIsographStore;
|
66
|
+
function garbageCollectEnvironment(environment) {
|
67
|
+
const retainedIds = new Set([exports.ROOT_ID]);
|
68
|
+
for (const query of environment.retainedQueries) {
|
69
|
+
recordReachableIds(environment.store, query, retainedIds);
|
70
|
+
}
|
71
|
+
for (const query of environment.gcBuffer) {
|
72
|
+
recordReachableIds(environment.store, query, retainedIds);
|
73
|
+
}
|
74
|
+
for (const dataId in environment.store) {
|
75
|
+
if (!retainedIds.has(dataId)) {
|
76
|
+
delete environment.store[dataId];
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
exports.garbageCollectEnvironment = garbageCollectEnvironment;
|
81
|
+
function recordReachableIds(store, retainedQuery, mutableRetainedIds) {
|
82
|
+
recordReachableIdsFromRecord(store, store[exports.ROOT_ID], mutableRetainedIds, retainedQuery.normalizationAst, retainedQuery.variables);
|
83
|
+
}
|
84
|
+
function getLinkedId(data) {
|
85
|
+
// @ts-expect-error
|
86
|
+
if (data.__link != null) {
|
87
|
+
// @ts-expect-error
|
88
|
+
return data.__link;
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
throw new Error('Record in an invalid state');
|
92
|
+
}
|
93
|
+
}
|
94
|
+
function recordReachableIdsFromRecord(store, currentRecord, mutableRetainedIds, selections, variables) {
|
95
|
+
for (const selection of selections) {
|
96
|
+
switch (selection.kind) {
|
97
|
+
case 'Linked':
|
98
|
+
const linkKey = (0, cache_1.getParentRecordKey)(selection, variables !== null && variables !== void 0 ? variables : {});
|
99
|
+
const linkedFieldOrFields = currentRecord[linkKey];
|
100
|
+
const ids = [];
|
101
|
+
if (Array.isArray(linkedFieldOrFields)) {
|
102
|
+
for (const link of linkedFieldOrFields) {
|
103
|
+
if (link != null) {
|
104
|
+
const id = getLinkedId(link);
|
105
|
+
ids.push(id);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
else {
|
110
|
+
if (linkedFieldOrFields != null) {
|
111
|
+
const id = getLinkedId(linkedFieldOrFields);
|
112
|
+
ids.push(id);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
for (const nextRecordId of ids) {
|
116
|
+
const nextRecord = store[nextRecordId];
|
117
|
+
if (nextRecord != null) {
|
118
|
+
mutableRetainedIds.add(nextRecordId);
|
119
|
+
recordReachableIdsFromRecord(store, nextRecord, mutableRetainedIds, selection.selections, variables);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
continue;
|
123
|
+
case 'Scalar':
|
124
|
+
continue;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
function unretainQuery(environment, retainedQuery) {
|
129
|
+
environment.retainedQueries.delete(retainedQuery);
|
130
|
+
environment.gcBuffer.push(retainedQuery);
|
131
|
+
if (environment.gcBuffer.length > environment.gcBufferSize) {
|
132
|
+
environment.gcBuffer.shift();
|
133
|
+
return true;
|
134
|
+
}
|
135
|
+
return false;
|
136
|
+
}
|
137
|
+
exports.unretainQuery = unretainQuery;
|
138
|
+
function retainQuery(environment, queryToRetain) {
|
139
|
+
environment.retainedQueries.add(queryToRetain);
|
140
|
+
// TODO can we remove this query from the buffer somehow?
|
141
|
+
// We are relying on === equality, but we really should be comparing
|
142
|
+
// id + variables
|
143
|
+
}
|
144
|
+
exports.retainQuery = retainQuery;
|
package/dist/cache.js
CHANGED
@@ -49,34 +49,60 @@ function makeNetworkRequest(environment, artifact, variables) {
|
|
49
49
|
if (typeof window !== 'undefined' && window.__LOG) {
|
50
50
|
console.log('make network request', artifact, variables);
|
51
51
|
}
|
52
|
+
let status = {
|
53
|
+
kind: 'UndisposedIncomplete',
|
54
|
+
};
|
55
|
+
// This should be an observable, not a promise
|
52
56
|
const promise = environment
|
53
57
|
.networkFunction(artifact.queryText, variables)
|
54
58
|
.then((networkResponse) => {
|
55
59
|
if (typeof window !== 'undefined' && window.__LOG) {
|
56
60
|
console.log('network response', artifact, artifact);
|
57
61
|
}
|
58
|
-
|
59
|
-
|
62
|
+
if (status.kind === 'UndisposedIncomplete') {
|
63
|
+
normalizeData(environment, artifact.normalizationAst, networkResponse.data, variables, artifact.nestedRefetchQueries);
|
64
|
+
const retainedQuery = {
|
65
|
+
normalizationAst: artifact.normalizationAst,
|
66
|
+
variables,
|
67
|
+
};
|
68
|
+
status = {
|
69
|
+
kind: 'UndisposedComplete',
|
70
|
+
retainedQuery,
|
71
|
+
};
|
72
|
+
(0, IsographEnvironment_1.retainQuery)(environment, retainedQuery);
|
73
|
+
}
|
74
|
+
// TODO return null
|
75
|
+
return networkResponse;
|
60
76
|
});
|
61
77
|
const wrapper = (0, PromiseWrapper_1.wrapPromise)(promise);
|
62
78
|
const response = [
|
63
79
|
wrapper,
|
64
80
|
() => {
|
65
|
-
|
81
|
+
if (status.kind === 'UndisposedComplete') {
|
82
|
+
const didUnretainSomeQuery = (0, IsographEnvironment_1.unretainQuery)(environment, status.retainedQuery);
|
83
|
+
if (didUnretainSomeQuery) {
|
84
|
+
(0, IsographEnvironment_1.garbageCollectEnvironment)(environment);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
status = {
|
88
|
+
kind: 'Disposed',
|
89
|
+
};
|
66
90
|
},
|
67
91
|
];
|
68
92
|
return response;
|
69
93
|
}
|
70
94
|
exports.makeNetworkRequest = makeNetworkRequest;
|
71
95
|
function normalizeData(environment, normalizationAst, networkResponse, variables, nestedRefetchQueries) {
|
96
|
+
const encounteredIds = new Set();
|
72
97
|
if (typeof window !== 'undefined' && window.__LOG) {
|
73
98
|
console.log('about to normalize', normalizationAst, networkResponse, variables);
|
74
99
|
}
|
75
|
-
normalizeDataIntoRecord(environment, normalizationAst, networkResponse, environment.store.__ROOT, IsographEnvironment_1.ROOT_ID, variables, nestedRefetchQueries);
|
100
|
+
normalizeDataIntoRecord(environment, normalizationAst, networkResponse, environment.store.__ROOT, IsographEnvironment_1.ROOT_ID, variables, nestedRefetchQueries, encounteredIds);
|
76
101
|
if (typeof window !== 'undefined' && window.__LOG) {
|
77
102
|
console.log('after normalization', { store: environment.store });
|
78
103
|
}
|
79
104
|
callSubscriptions(environment);
|
105
|
+
return encounteredIds;
|
80
106
|
}
|
81
107
|
function subscribe(environment, callback) {
|
82
108
|
environment.subscriptions.add(callback);
|
@@ -98,7 +124,8 @@ function callSubscriptions(environment) {
|
|
98
124
|
/**
|
99
125
|
* Mutate targetParentRecord according to the normalizationAst and networkResponseParentRecord.
|
100
126
|
*/
|
101
|
-
function normalizeDataIntoRecord(environment, normalizationAst, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries) {
|
127
|
+
function normalizeDataIntoRecord(environment, normalizationAst, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds) {
|
128
|
+
mutableEncounteredIds.add(targetParentRecordId);
|
102
129
|
for (const normalizationNode of normalizationAst) {
|
103
130
|
switch (normalizationNode.kind) {
|
104
131
|
case 'Scalar': {
|
@@ -106,7 +133,7 @@ function normalizeDataIntoRecord(environment, normalizationAst, networkResponseP
|
|
106
133
|
break;
|
107
134
|
}
|
108
135
|
case 'Linked': {
|
109
|
-
normalizeLinkedField(environment, normalizationNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries);
|
136
|
+
normalizeLinkedField(environment, normalizationNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds);
|
110
137
|
break;
|
111
138
|
}
|
112
139
|
}
|
@@ -127,7 +154,7 @@ function normalizeScalarField(astNode, networkResponseParentRecord, targetStoreR
|
|
127
154
|
/**
|
128
155
|
* Mutate targetParentRecord with a given linked field ast node.
|
129
156
|
*/
|
130
|
-
function normalizeLinkedField(environment, astNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries) {
|
157
|
+
function normalizeLinkedField(environment, astNode, networkResponseParentRecord, targetParentRecord, targetParentRecordId, variables, nestedRefetchQueries, mutableEncounteredIds) {
|
131
158
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
132
159
|
const networkResponseData = networkResponseParentRecord[networkResponseKey];
|
133
160
|
const parentRecordKey = getParentRecordKey(astNode, variables);
|
@@ -143,24 +170,24 @@ function normalizeLinkedField(environment, astNode, networkResponseParentRecord,
|
|
143
170
|
const dataIds = [];
|
144
171
|
for (let i = 0; i < networkResponseData.length; i++) {
|
145
172
|
const networkResponseObject = networkResponseData[i];
|
146
|
-
const newStoreRecordId = normalizeNetworkResponseObject(environment, astNode, networkResponseObject, targetParentRecordId, variables, i, nestedRefetchQueries);
|
173
|
+
const newStoreRecordId = normalizeNetworkResponseObject(environment, astNode, networkResponseObject, targetParentRecordId, variables, i, nestedRefetchQueries, mutableEncounteredIds);
|
147
174
|
dataIds.push({ __link: newStoreRecordId });
|
148
175
|
}
|
149
176
|
targetParentRecord[parentRecordKey] = dataIds;
|
150
177
|
}
|
151
178
|
else {
|
152
|
-
const newStoreRecordId = normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, null, nestedRefetchQueries);
|
179
|
+
const newStoreRecordId = normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, null, nestedRefetchQueries, mutableEncounteredIds);
|
153
180
|
targetParentRecord[parentRecordKey] = {
|
154
181
|
__link: newStoreRecordId,
|
155
182
|
};
|
156
183
|
}
|
157
184
|
}
|
158
|
-
function normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, index, nestedRefetchQueries) {
|
185
|
+
function normalizeNetworkResponseObject(environment, astNode, networkResponseData, targetParentRecordId, variables, index, nestedRefetchQueries, mutableEncounteredIds) {
|
159
186
|
var _a;
|
160
187
|
const newStoreRecordId = getDataIdOfNetworkResponse(targetParentRecordId, networkResponseData, astNode, variables, index);
|
161
188
|
const newStoreRecord = (_a = environment.store[newStoreRecordId]) !== null && _a !== void 0 ? _a : {};
|
162
189
|
environment.store[newStoreRecordId] = newStoreRecord;
|
163
|
-
normalizeDataIntoRecord(environment, astNode.selections, networkResponseData, newStoreRecord, newStoreRecordId, variables, nestedRefetchQueries);
|
190
|
+
normalizeDataIntoRecord(environment, astNode.selections, networkResponseData, newStoreRecord, newStoreRecordId, variables, nestedRefetchQueries, mutableEncounteredIds);
|
164
191
|
return newStoreRecordId;
|
165
192
|
}
|
166
193
|
function isScalarOrEmptyArray(data) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@isograph/react",
|
3
|
-
"version": "0.0.0-main-
|
3
|
+
"version": "0.0.0-main-dc9ca2f6",
|
4
4
|
"description": "Use Isograph with React",
|
5
5
|
"homepage": "https://isograph.dev",
|
6
6
|
"main": "dist/index.js",
|
@@ -16,8 +16,8 @@
|
|
16
16
|
"prepack": "yarn run test && yarn run compile"
|
17
17
|
},
|
18
18
|
"dependencies": {
|
19
|
-
"@isograph/disposable-types": "0.0.0-main-
|
20
|
-
"@isograph/react-disposable-state": "0.0.0-main-
|
19
|
+
"@isograph/disposable-types": "0.0.0-main-dc9ca2f6",
|
20
|
+
"@isograph/react-disposable-state": "0.0.0-main-dc9ca2f6",
|
21
21
|
"react": "^18.2.0"
|
22
22
|
},
|
23
23
|
"devDependencies": {
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { ReactNode, createContext, useContext } from 'react';
|
2
2
|
import * as React from 'react';
|
3
3
|
import { ParentCache } from '@isograph/isograph-react-disposable-state';
|
4
|
+
import { NormalizationAst } from './index';
|
5
|
+
import { getParentRecordKey } from './cache';
|
4
6
|
|
5
7
|
export const IsographEnvironmentContext =
|
6
8
|
createContext<IsographEnvironment | null>(null);
|
@@ -16,6 +18,11 @@ type ComponentCache = {
|
|
16
18
|
export type Subscriptions = Set<() => void>;
|
17
19
|
type SuspenseCache = { [index: string]: ParentCache<any> };
|
18
20
|
|
21
|
+
export type RetainedQuery = {
|
22
|
+
normalizationAst: NormalizationAst;
|
23
|
+
variables: {};
|
24
|
+
};
|
25
|
+
|
19
26
|
export type IsographEnvironment = {
|
20
27
|
store: IsographStore;
|
21
28
|
networkFunction: IsographNetworkFunction;
|
@@ -23,6 +30,9 @@ export type IsographEnvironment = {
|
|
23
30
|
componentCache: ComponentCache;
|
24
31
|
subscriptions: Subscriptions;
|
25
32
|
suspenseCache: SuspenseCache;
|
33
|
+
retainedQueries: Set<RetainedQuery>;
|
34
|
+
gcBuffer: Array<RetainedQuery>;
|
35
|
+
gcBufferSize: number;
|
26
36
|
};
|
27
37
|
|
28
38
|
export type MissingFieldHandler = (
|
@@ -98,6 +108,7 @@ export function useIsographEnvironment(): IsographEnvironment {
|
|
98
108
|
return context;
|
99
109
|
}
|
100
110
|
|
111
|
+
const DEFAULT_GC_BUFFER_SIZE = 10;
|
101
112
|
export function createIsographEnvironment(
|
102
113
|
store: IsographStore,
|
103
114
|
networkFunction: IsographNetworkFunction,
|
@@ -110,6 +121,9 @@ export function createIsographEnvironment(
|
|
110
121
|
componentCache: {},
|
111
122
|
subscriptions: new Set(),
|
112
123
|
suspenseCache: {},
|
124
|
+
retainedQueries: new Set(),
|
125
|
+
gcBuffer: [],
|
126
|
+
gcBufferSize: DEFAULT_GC_BUFFER_SIZE,
|
113
127
|
};
|
114
128
|
}
|
115
129
|
|
@@ -118,3 +132,119 @@ export function createIsographStore() {
|
|
118
132
|
[ROOT_ID]: {},
|
119
133
|
};
|
120
134
|
}
|
135
|
+
|
136
|
+
export function garbageCollectEnvironment(environment: IsographEnvironment) {
|
137
|
+
const retainedIds = new Set<DataId>([ROOT_ID]);
|
138
|
+
|
139
|
+
for (const query of environment.retainedQueries) {
|
140
|
+
recordReachableIds(environment.store, query, retainedIds);
|
141
|
+
}
|
142
|
+
for (const query of environment.gcBuffer) {
|
143
|
+
recordReachableIds(environment.store, query, retainedIds);
|
144
|
+
}
|
145
|
+
|
146
|
+
for (const dataId in environment.store) {
|
147
|
+
if (!retainedIds.has(dataId)) {
|
148
|
+
delete environment.store[dataId];
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
function recordReachableIds(
|
154
|
+
store: IsographStore,
|
155
|
+
retainedQuery: RetainedQuery,
|
156
|
+
mutableRetainedIds: Set<DataId>,
|
157
|
+
) {
|
158
|
+
recordReachableIdsFromRecord(
|
159
|
+
store,
|
160
|
+
store[ROOT_ID],
|
161
|
+
mutableRetainedIds,
|
162
|
+
retainedQuery.normalizationAst,
|
163
|
+
retainedQuery.variables,
|
164
|
+
);
|
165
|
+
}
|
166
|
+
|
167
|
+
function getLinkedId(data: Exclude<DataTypeValue, null | void>): string {
|
168
|
+
// @ts-expect-error
|
169
|
+
if (data.__link != null) {
|
170
|
+
// @ts-expect-error
|
171
|
+
return data.__link;
|
172
|
+
} else {
|
173
|
+
throw new Error('Record in an invalid state');
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
function recordReachableIdsFromRecord(
|
178
|
+
store: IsographStore,
|
179
|
+
currentRecord: StoreRecord,
|
180
|
+
mutableRetainedIds: Set<DataId>,
|
181
|
+
selections: NormalizationAst,
|
182
|
+
variables: { [index: string]: string } | null,
|
183
|
+
) {
|
184
|
+
for (const selection of selections) {
|
185
|
+
switch (selection.kind) {
|
186
|
+
case 'Linked':
|
187
|
+
const linkKey = getParentRecordKey(selection, variables ?? {});
|
188
|
+
const linkedFieldOrFields = currentRecord[linkKey];
|
189
|
+
|
190
|
+
const ids = [];
|
191
|
+
if (Array.isArray(linkedFieldOrFields)) {
|
192
|
+
for (const link of linkedFieldOrFields) {
|
193
|
+
if (link != null) {
|
194
|
+
const id = getLinkedId(link);
|
195
|
+
ids.push(id);
|
196
|
+
}
|
197
|
+
}
|
198
|
+
} else {
|
199
|
+
if (linkedFieldOrFields != null) {
|
200
|
+
const id = getLinkedId(linkedFieldOrFields);
|
201
|
+
ids.push(id);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
for (const nextRecordId of ids) {
|
206
|
+
const nextRecord = store[nextRecordId];
|
207
|
+
if (nextRecord != null) {
|
208
|
+
mutableRetainedIds.add(nextRecordId);
|
209
|
+
recordReachableIdsFromRecord(
|
210
|
+
store,
|
211
|
+
nextRecord,
|
212
|
+
mutableRetainedIds,
|
213
|
+
selection.selections,
|
214
|
+
variables,
|
215
|
+
);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
continue;
|
220
|
+
case 'Scalar':
|
221
|
+
continue;
|
222
|
+
}
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
type DidUnretainSomeQuery = boolean;
|
227
|
+
export function unretainQuery(
|
228
|
+
environment: IsographEnvironment,
|
229
|
+
retainedQuery: RetainedQuery,
|
230
|
+
): DidUnretainSomeQuery {
|
231
|
+
environment.retainedQueries.delete(retainedQuery);
|
232
|
+
environment.gcBuffer.push(retainedQuery);
|
233
|
+
|
234
|
+
if (environment.gcBuffer.length > environment.gcBufferSize) {
|
235
|
+
environment.gcBuffer.shift();
|
236
|
+
return true;
|
237
|
+
}
|
238
|
+
|
239
|
+
return false;
|
240
|
+
}
|
241
|
+
|
242
|
+
export function retainQuery(
|
243
|
+
environment: IsographEnvironment,
|
244
|
+
queryToRetain: RetainedQuery,
|
245
|
+
) {
|
246
|
+
environment.retainedQueries.add(queryToRetain);
|
247
|
+
// TODO can we remove this query from the buffer somehow?
|
248
|
+
// We are relying on === equality, but we really should be comparing
|
249
|
+
// id + variables
|
250
|
+
}
|
package/src/cache.tsx
CHANGED
@@ -21,6 +21,10 @@ import {
|
|
21
21
|
StoreRecord,
|
22
22
|
Link,
|
23
23
|
type IsographEnvironment,
|
24
|
+
garbageCollectEnvironment,
|
25
|
+
RetainedQuery,
|
26
|
+
unretainQuery,
|
27
|
+
retainQuery,
|
24
28
|
} from './IsographEnvironment';
|
25
29
|
|
26
30
|
declare global {
|
@@ -83,6 +87,18 @@ export function getOrCreateCacheForArtifact<T>(
|
|
83
87
|
return getOrCreateCache<PromiseWrapper<T>>(environment, cacheKey, factory);
|
84
88
|
}
|
85
89
|
|
90
|
+
type NetworkRequestStatus =
|
91
|
+
| {
|
92
|
+
kind: 'UndisposedIncomplete';
|
93
|
+
}
|
94
|
+
| {
|
95
|
+
kind: 'Disposed';
|
96
|
+
}
|
97
|
+
| {
|
98
|
+
kind: 'UndisposedComplete';
|
99
|
+
retainedQuery: RetainedQuery;
|
100
|
+
};
|
101
|
+
|
86
102
|
export function makeNetworkRequest<T>(
|
87
103
|
environment: IsographEnvironment,
|
88
104
|
artifact: IsoResolver,
|
@@ -91,20 +107,37 @@ export function makeNetworkRequest<T>(
|
|
91
107
|
if (typeof window !== 'undefined' && window.__LOG) {
|
92
108
|
console.log('make network request', artifact, variables);
|
93
109
|
}
|
110
|
+
let status: NetworkRequestStatus = {
|
111
|
+
kind: 'UndisposedIncomplete',
|
112
|
+
};
|
113
|
+
// This should be an observable, not a promise
|
94
114
|
const promise = environment
|
95
115
|
.networkFunction(artifact.queryText, variables)
|
96
116
|
.then((networkResponse) => {
|
97
117
|
if (typeof window !== 'undefined' && window.__LOG) {
|
98
118
|
console.log('network response', artifact, artifact);
|
99
119
|
}
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
120
|
+
|
121
|
+
if (status.kind === 'UndisposedIncomplete') {
|
122
|
+
normalizeData(
|
123
|
+
environment,
|
124
|
+
artifact.normalizationAst,
|
125
|
+
networkResponse.data,
|
126
|
+
variables,
|
127
|
+
artifact.nestedRefetchQueries,
|
128
|
+
);
|
129
|
+
const retainedQuery = {
|
130
|
+
normalizationAst: artifact.normalizationAst,
|
131
|
+
variables,
|
132
|
+
};
|
133
|
+
status = {
|
134
|
+
kind: 'UndisposedComplete',
|
135
|
+
retainedQuery,
|
136
|
+
};
|
137
|
+
retainQuery(environment, retainedQuery);
|
138
|
+
}
|
139
|
+
// TODO return null
|
140
|
+
return networkResponse;
|
108
141
|
});
|
109
142
|
|
110
143
|
const wrapper = wrapPromise(promise);
|
@@ -112,7 +145,18 @@ export function makeNetworkRequest<T>(
|
|
112
145
|
const response: ItemCleanupPair<PromiseWrapper<T>> = [
|
113
146
|
wrapper,
|
114
147
|
() => {
|
115
|
-
|
148
|
+
if (status.kind === 'UndisposedComplete') {
|
149
|
+
const didUnretainSomeQuery = unretainQuery(
|
150
|
+
environment,
|
151
|
+
status.retainedQuery,
|
152
|
+
);
|
153
|
+
if (didUnretainSomeQuery) {
|
154
|
+
garbageCollectEnvironment(environment);
|
155
|
+
}
|
156
|
+
}
|
157
|
+
status = {
|
158
|
+
kind: 'Disposed',
|
159
|
+
};
|
116
160
|
},
|
117
161
|
];
|
118
162
|
return response;
|
@@ -138,7 +182,9 @@ function normalizeData(
|
|
138
182
|
networkResponse: NetworkResponseObject,
|
139
183
|
variables: Object,
|
140
184
|
nestedRefetchQueries: RefetchQueryArtifactWrapper[],
|
141
|
-
) {
|
185
|
+
): Set<DataId> {
|
186
|
+
const encounteredIds = new Set<DataId>();
|
187
|
+
|
142
188
|
if (typeof window !== 'undefined' && window.__LOG) {
|
143
189
|
console.log(
|
144
190
|
'about to normalize',
|
@@ -155,11 +201,13 @@ function normalizeData(
|
|
155
201
|
ROOT_ID,
|
156
202
|
variables as any,
|
157
203
|
nestedRefetchQueries,
|
204
|
+
encounteredIds,
|
158
205
|
);
|
159
206
|
if (typeof window !== 'undefined' && window.__LOG) {
|
160
207
|
console.log('after normalization', { store: environment.store });
|
161
208
|
}
|
162
209
|
callSubscriptions(environment);
|
210
|
+
return encounteredIds;
|
163
211
|
}
|
164
212
|
|
165
213
|
export function subscribe(
|
@@ -194,7 +242,9 @@ function normalizeDataIntoRecord(
|
|
194
242
|
targetParentRecordId: DataId,
|
195
243
|
variables: { [index: string]: string },
|
196
244
|
nestedRefetchQueries: RefetchQueryArtifactWrapper[],
|
245
|
+
mutableEncounteredIds: Set<DataId>,
|
197
246
|
) {
|
247
|
+
mutableEncounteredIds.add(targetParentRecordId);
|
198
248
|
for (const normalizationNode of normalizationAst) {
|
199
249
|
switch (normalizationNode.kind) {
|
200
250
|
case 'Scalar': {
|
@@ -215,6 +265,7 @@ function normalizeDataIntoRecord(
|
|
215
265
|
targetParentRecordId,
|
216
266
|
variables,
|
217
267
|
nestedRefetchQueries,
|
268
|
+
mutableEncounteredIds,
|
218
269
|
);
|
219
270
|
break;
|
220
271
|
}
|
@@ -253,6 +304,7 @@ function normalizeLinkedField(
|
|
253
304
|
targetParentRecordId: DataId,
|
254
305
|
variables: { [index: string]: string },
|
255
306
|
nestedRefetchQueries: RefetchQueryArtifactWrapper[],
|
307
|
+
mutableEncounteredIds: Set<DataId>,
|
256
308
|
) {
|
257
309
|
const networkResponseKey = getNetworkResponseKey(astNode);
|
258
310
|
const networkResponseData = networkResponseParentRecord[networkResponseKey];
|
@@ -282,7 +334,9 @@ function normalizeLinkedField(
|
|
282
334
|
variables,
|
283
335
|
i,
|
284
336
|
nestedRefetchQueries,
|
337
|
+
mutableEncounteredIds,
|
285
338
|
);
|
339
|
+
|
286
340
|
dataIds.push({ __link: newStoreRecordId });
|
287
341
|
}
|
288
342
|
targetParentRecord[parentRecordKey] = dataIds;
|
@@ -295,6 +349,7 @@ function normalizeLinkedField(
|
|
295
349
|
variables,
|
296
350
|
null,
|
297
351
|
nestedRefetchQueries,
|
352
|
+
mutableEncounteredIds,
|
298
353
|
);
|
299
354
|
targetParentRecord[parentRecordKey] = {
|
300
355
|
__link: newStoreRecordId,
|
@@ -310,6 +365,7 @@ function normalizeNetworkResponseObject(
|
|
310
365
|
variables: { [index: string]: string },
|
311
366
|
index: number | null,
|
312
367
|
nestedRefetchQueries: RefetchQueryArtifactWrapper[],
|
368
|
+
mutableEncounteredIds: Set<DataId>,
|
313
369
|
): DataId /* The id of the modified or newly created item */ {
|
314
370
|
const newStoreRecordId = getDataIdOfNetworkResponse(
|
315
371
|
targetParentRecordId,
|
@@ -330,6 +386,7 @@ function normalizeNetworkResponseObject(
|
|
330
386
|
newStoreRecordId,
|
331
387
|
variables,
|
332
388
|
nestedRefetchQueries,
|
389
|
+
mutableEncounteredIds,
|
333
390
|
);
|
334
391
|
|
335
392
|
return newStoreRecordId;
|
package/src/index.tsx
CHANGED
@@ -120,7 +120,6 @@ export type ReaderMutationField = {
|
|
120
120
|
export type NormalizationAstNode =
|
121
121
|
| NormalizationScalarField
|
122
122
|
| NormalizationLinkedField;
|
123
|
-
// @ts-ignore
|
124
123
|
export type NormalizationAst = NormalizationAstNode[];
|
125
124
|
|
126
125
|
export type NormalizationScalarField = {
|