houdini 0.17.9 → 0.17.10
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/README.md +33 -0
- package/build/cmd-cjs/index.js +2 -2
- package/build/cmd-esm/index.js +2 -2
- package/package.json +16 -1
- package/.turbo/turbo-compile.log +0 -5
- package/.turbo/turbo-typedefs.log +0 -5
- package/CHANGELOG.md +0 -377
- package/src/cmd/generate.ts +0 -54
- package/src/cmd/index.ts +0 -60
- package/src/cmd/init.ts +0 -637
- package/src/cmd/pullSchema.ts +0 -40
- package/src/codegen/generators/artifacts/artifacts.test.ts +0 -3246
- package/src/codegen/generators/artifacts/fieldKey.ts +0 -60
- package/src/codegen/generators/artifacts/index.ts +0 -330
- package/src/codegen/generators/artifacts/indexFile.ts +0 -24
- package/src/codegen/generators/artifacts/inputs.ts +0 -81
- package/src/codegen/generators/artifacts/operations.ts +0 -281
- package/src/codegen/generators/artifacts/pagination.test.ts +0 -664
- package/src/codegen/generators/artifacts/policy.test.ts +0 -298
- package/src/codegen/generators/artifacts/selection.ts +0 -208
- package/src/codegen/generators/artifacts/utils.test.ts +0 -118
- package/src/codegen/generators/artifacts/utils.ts +0 -108
- package/src/codegen/generators/definitions/enums.test.ts +0 -61
- package/src/codegen/generators/definitions/enums.ts +0 -68
- package/src/codegen/generators/definitions/index.ts +0 -11
- package/src/codegen/generators/definitions/schema.test.ts +0 -236
- package/src/codegen/generators/index.ts +0 -6
- package/src/codegen/generators/indexFile/index.ts +0 -63
- package/src/codegen/generators/indexFile/indexFile.test.ts +0 -72
- package/src/codegen/generators/persistedQueries/index.ts +0 -55
- package/src/codegen/generators/persistedQueries/persistedQuery.test.ts +0 -26
- package/src/codegen/generators/runtime/index.test.ts +0 -74
- package/src/codegen/generators/runtime/index.ts +0 -64
- package/src/codegen/generators/runtime/runtime.test.ts +0 -25
- package/src/codegen/generators/typescript/addReferencedInputTypes.ts +0 -77
- package/src/codegen/generators/typescript/index.ts +0 -412
- package/src/codegen/generators/typescript/inlineType.ts +0 -409
- package/src/codegen/generators/typescript/typeReference.ts +0 -44
- package/src/codegen/generators/typescript/types.ts +0 -81
- package/src/codegen/generators/typescript/typescript.test.ts +0 -1434
- package/src/codegen/index.ts +0 -406
- package/src/codegen/transforms/addID.test.ts +0 -93
- package/src/codegen/transforms/addID.ts +0 -86
- package/src/codegen/transforms/composeQueries.test.ts +0 -50
- package/src/codegen/transforms/composeQueries.ts +0 -154
- package/src/codegen/transforms/fragmentVariables.test.ts +0 -636
- package/src/codegen/transforms/fragmentVariables.ts +0 -417
- package/src/codegen/transforms/index.ts +0 -7
- package/src/codegen/transforms/list.ts +0 -484
- package/src/codegen/transforms/lists.test.ts +0 -530
- package/src/codegen/transforms/paginate.test.ts +0 -1528
- package/src/codegen/transforms/paginate.ts +0 -770
- package/src/codegen/transforms/schema.test.ts +0 -136
- package/src/codegen/transforms/schema.ts +0 -109
- package/src/codegen/transforms/typename.test.ts +0 -125
- package/src/codegen/transforms/typename.ts +0 -55
- package/src/codegen/utils/commonjs.ts +0 -26
- package/src/codegen/utils/flattenSelections.ts +0 -179
- package/src/codegen/utils/graphql.test.ts +0 -35
- package/src/codegen/utils/graphql.ts +0 -79
- package/src/codegen/utils/index.ts +0 -5
- package/src/codegen/utils/moduleExport.ts +0 -27
- package/src/codegen/utils/murmur.ts +0 -79
- package/src/codegen/validators/index.ts +0 -4
- package/src/codegen/validators/noIDAlias.test.ts +0 -71
- package/src/codegen/validators/noIDAlias.ts +0 -39
- package/src/codegen/validators/plugins.ts +0 -25
- package/src/codegen/validators/typeCheck.test.ts +0 -960
- package/src/codegen/validators/typeCheck.ts +0 -1086
- package/src/codegen/validators/uniqueNames.test.ts +0 -59
- package/src/codegen/validators/uniqueNames.ts +0 -39
- package/src/lib/cleanupFiles.ts +0 -20
- package/src/lib/config.test.ts +0 -13
- package/src/lib/config.ts +0 -954
- package/src/lib/constants.ts +0 -11
- package/src/lib/error.ts +0 -24
- package/src/lib/fs.ts +0 -285
- package/src/lib/graphql.test.ts +0 -211
- package/src/lib/graphql.ts +0 -200
- package/src/lib/imports.ts +0 -82
- package/src/lib/index.ts +0 -17
- package/src/lib/introspection.ts +0 -39
- package/src/lib/parse.test.ts +0 -75
- package/src/lib/parse.ts +0 -23
- package/src/lib/path.ts +0 -49
- package/src/lib/pipeline.ts +0 -17
- package/src/lib/types.ts +0 -34
- package/src/lib/walk.ts +0 -104
- package/src/runtime/cache/cache.ts +0 -1026
- package/src/runtime/cache/gc.ts +0 -56
- package/src/runtime/cache/index.ts +0 -3
- package/src/runtime/cache/lists.ts +0 -516
- package/src/runtime/cache/storage.ts +0 -574
- package/src/runtime/cache/stuff.ts +0 -77
- package/src/runtime/cache/subscription.ts +0 -329
- package/src/runtime/cache/tests/availability.test.ts +0 -408
- package/src/runtime/cache/tests/gc.test.ts +0 -319
- package/src/runtime/cache/tests/keys.test.ts +0 -36
- package/src/runtime/cache/tests/list.test.ts +0 -3854
- package/src/runtime/cache/tests/readwrite.test.ts +0 -1201
- package/src/runtime/cache/tests/scalars.test.ts +0 -218
- package/src/runtime/cache/tests/storage.test.ts +0 -426
- package/src/runtime/cache/tests/subscriptions.test.ts +0 -1757
- package/src/runtime/index.ts +0 -29
- package/src/runtime/lib/config.ts +0 -211
- package/src/runtime/lib/constants.ts +0 -17
- package/src/runtime/lib/deepEquals.ts +0 -32
- package/src/runtime/lib/errors.ts +0 -8
- package/src/runtime/lib/index.ts +0 -8
- package/src/runtime/lib/log.ts +0 -69
- package/src/runtime/lib/network.ts +0 -303
- package/src/runtime/lib/networkUtils.ts +0 -151
- package/src/runtime/lib/scalars.test.ts +0 -877
- package/src/runtime/lib/scalars.ts +0 -195
- package/src/runtime/lib/types.ts +0 -195
- package/src/test/index.ts +0 -294
- package/src/vite/ast.ts +0 -107
- package/src/vite/houdini.ts +0 -113
- package/src/vite/imports.ts +0 -129
- package/src/vite/index.ts +0 -55
- package/src/vite/schema.ts +0 -80
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
import type { GraphQLValue } from '../lib/types'
|
|
2
|
-
import { flattenList } from './stuff'
|
|
3
|
-
|
|
4
|
-
// NOTE: the current implementation of delete is slow. it should try to compare the
|
|
5
|
-
// type of the id being deleted with the type contained in the linked list so that
|
|
6
|
-
// the removal logic is only performed when its possible the ID is found inside.
|
|
7
|
-
// ie: deleting a user should not slow down looking up a list of cats
|
|
8
|
-
|
|
9
|
-
export class InMemoryStorage {
|
|
10
|
-
private data: Layer[]
|
|
11
|
-
private idCount = 0
|
|
12
|
-
private rank = 0
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
this.data = []
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get layerCount(): number {
|
|
19
|
-
return this.data.length
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
get nextRank(): number {
|
|
23
|
-
return this.rank++
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// create a layer and return its id
|
|
27
|
-
createLayer(optimistic: boolean = false): Layer {
|
|
28
|
-
// generate the next layer
|
|
29
|
-
const layer = new Layer(this.idCount++)
|
|
30
|
-
layer.optimistic = optimistic
|
|
31
|
-
|
|
32
|
-
// add the layer to the list
|
|
33
|
-
this.data.push(layer)
|
|
34
|
-
|
|
35
|
-
// pass the layer on so it can be updated
|
|
36
|
-
return layer
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
insert(id: string, field: string, location: OperationLocation, target: string) {
|
|
40
|
-
return this.topLayer.insert(id, field, location, target)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
remove(id: string, field: string, target: string) {
|
|
44
|
-
return this.topLayer.remove(id, field, target)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
delete(id: string) {
|
|
48
|
-
return this.topLayer.delete(id)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
deleteField(id: string, field: string) {
|
|
52
|
-
return this.topLayer.deleteField(id, field)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
getLayer(id: number): Layer {
|
|
56
|
-
for (const layer of this.data) {
|
|
57
|
-
if (layer.id === id) {
|
|
58
|
-
return layer
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// we didn't find the layer
|
|
63
|
-
throw new Error('Could not find layer with id: ' + id)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
replaceID(replacement: { from: string; to: string }) {
|
|
67
|
-
for (const layer of this.data) {
|
|
68
|
-
layer.replaceID(replacement)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
get(
|
|
73
|
-
id: string,
|
|
74
|
-
field: string
|
|
75
|
-
): {
|
|
76
|
-
value: GraphQLField
|
|
77
|
-
kind: 'link' | 'scalar' | 'unknown'
|
|
78
|
-
displayLayers: number[]
|
|
79
|
-
} {
|
|
80
|
-
// the list of operations for the field
|
|
81
|
-
const operations = {
|
|
82
|
-
[OperationKind.insert]: {
|
|
83
|
-
[OperationLocation.start]: [] as string[],
|
|
84
|
-
[OperationLocation.end]: [] as string[],
|
|
85
|
-
},
|
|
86
|
-
[OperationKind.remove]: new Set<string>(),
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// the list of layers we used to build up the value
|
|
90
|
-
const layerIDs: number[] = []
|
|
91
|
-
|
|
92
|
-
// go through the list of layers in reverse
|
|
93
|
-
for (let i = this.data.length - 1; i >= 0; i--) {
|
|
94
|
-
const layer = this.data[i]
|
|
95
|
-
const [layerValue, kind] = layer.get(id, field)
|
|
96
|
-
const layerOperations = layer.getOperations(id, field) || []
|
|
97
|
-
layer.deletedIDs.forEach((v) => {
|
|
98
|
-
// if the layer wants to undo a delete for the id
|
|
99
|
-
if (layer.operations[v]?.undoDeletesInList?.includes(field)) {
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
operations.remove.add(v)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
// if the layer does not contain a value for the field, move on
|
|
106
|
-
if (typeof layerValue === 'undefined' && layerOperations.length === 0) {
|
|
107
|
-
if (layer.deletedIDs.size > 0) {
|
|
108
|
-
layerIDs.push(layer.id)
|
|
109
|
-
}
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// if the result isn't an array we can just use the value since we can't
|
|
114
|
-
// apply operations to the field
|
|
115
|
-
if (typeof layerValue !== 'undefined' && !Array.isArray(layerValue)) {
|
|
116
|
-
return {
|
|
117
|
-
value: layerValue,
|
|
118
|
-
kind,
|
|
119
|
-
displayLayers: [layer.id],
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// if the layer contains operations or values add it to the list of relevant layers
|
|
124
|
-
// add the layer to the list
|
|
125
|
-
layerIDs.push(layer.id)
|
|
126
|
-
|
|
127
|
-
// if we have an operation
|
|
128
|
-
if (layerOperations.length > 0) {
|
|
129
|
-
// process every operation
|
|
130
|
-
for (const op of layerOperations) {
|
|
131
|
-
// remove operation
|
|
132
|
-
if (isRemoveOperation(op)) {
|
|
133
|
-
operations.remove.add(op.id)
|
|
134
|
-
}
|
|
135
|
-
// inserts are sorted by location
|
|
136
|
-
if (isInsertOperation(op)) {
|
|
137
|
-
operations.insert[op.location].unshift(op.id)
|
|
138
|
-
}
|
|
139
|
-
// if we found a delete operation, we're done
|
|
140
|
-
if (isDeleteOperation(op)) {
|
|
141
|
-
return {
|
|
142
|
-
value: undefined,
|
|
143
|
-
kind: 'unknown',
|
|
144
|
-
displayLayers: [],
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// if we don't have a value to return, we're done
|
|
151
|
-
if (typeof layerValue === 'undefined') {
|
|
152
|
-
continue
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// if there are no operations, move along
|
|
156
|
-
if (
|
|
157
|
-
!operations.remove.size &&
|
|
158
|
-
!operations.insert.start.length &&
|
|
159
|
-
!operations.insert.end.length
|
|
160
|
-
) {
|
|
161
|
-
return { value: layerValue, displayLayers: layerIDs, kind: 'link' }
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// we have operations to apply to the list
|
|
165
|
-
return {
|
|
166
|
-
value: [...operations.insert.start, ...layerValue, ...operations.insert.end].filter(
|
|
167
|
-
(value) => !operations.remove.has(value as string)
|
|
168
|
-
),
|
|
169
|
-
displayLayers: layerIDs,
|
|
170
|
-
kind,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
value: undefined,
|
|
176
|
-
kind: 'unknown',
|
|
177
|
-
displayLayers: [],
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
writeLink(id: string, field: string, value: string | LinkedList) {
|
|
182
|
-
// write to the top most layer
|
|
183
|
-
return this.topLayer.writeLink(id, field, value)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
writeField(id: string, field: string, value: GraphQLValue) {
|
|
187
|
-
return this.topLayer.writeField(id, field, value)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
resolveLayer(id: number): void {
|
|
191
|
-
let startingIndex: number | null = null
|
|
192
|
-
|
|
193
|
-
// find the layer with the matching id
|
|
194
|
-
for (const [index, layer] of this.data.entries()) {
|
|
195
|
-
if (layer.id !== id) {
|
|
196
|
-
continue
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// we found the target layer
|
|
200
|
-
startingIndex = index - 1
|
|
201
|
-
|
|
202
|
-
// its not optimistic any more
|
|
203
|
-
this.data[index].optimistic = false
|
|
204
|
-
|
|
205
|
-
// we're done
|
|
206
|
-
break
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// if we didn't find the layer, yell loudly
|
|
210
|
-
if (startingIndex === null) {
|
|
211
|
-
throw new Error('could not find layer with id: ' + id)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// if we are resolving the base layer make sure we start at zero
|
|
215
|
-
if (startingIndex === -1) {
|
|
216
|
-
startingIndex = 0
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// if the starting layer is optimistic then we can't write to it
|
|
220
|
-
if (this.data[startingIndex].optimistic) {
|
|
221
|
-
startingIndex++
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// start walking down the list of layers, applying any non-optimistic ones to the target
|
|
225
|
-
const baseLayer = this.data[startingIndex]
|
|
226
|
-
let layerIndex = startingIndex
|
|
227
|
-
while (layerIndex < this.data.length) {
|
|
228
|
-
// the layer in question and move the counter up one after we index
|
|
229
|
-
const layer = this.data[layerIndex++]
|
|
230
|
-
|
|
231
|
-
// if the layer is optimistic, we can't go further
|
|
232
|
-
if (layer.optimistic) {
|
|
233
|
-
layerIndex--
|
|
234
|
-
break
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// apply the layer onto our base
|
|
238
|
-
baseLayer.writeLayer(layer)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// delete the layers we merged
|
|
242
|
-
this.data.splice(startingIndex + 1, layerIndex - startingIndex - 1)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
get topLayer(): Layer {
|
|
246
|
-
// if there is no base layer
|
|
247
|
-
if (this.data.length === 0) {
|
|
248
|
-
this.createLayer()
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// if the last layer is optimistic, create another layer on top of it
|
|
252
|
-
// since optimistic layers have to be written to directly
|
|
253
|
-
if (this.data[this.data.length - 1]?.optimistic) {
|
|
254
|
-
this.createLayer()
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// the top layer is safe to write to (its non-null and guaranteed not optimistic)
|
|
258
|
-
return this.data[this.data.length - 1]
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export class Layer {
|
|
263
|
-
readonly id: LayerID
|
|
264
|
-
|
|
265
|
-
optimistic: boolean = false
|
|
266
|
-
|
|
267
|
-
fields: EntityFieldMap = {}
|
|
268
|
-
links: LinkMap = {}
|
|
269
|
-
operations: OperationMap = {}
|
|
270
|
-
deletedIDs = new Set<string>()
|
|
271
|
-
|
|
272
|
-
constructor(id: number) {
|
|
273
|
-
this.id = id
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
get(id: string, field: string): [GraphQLField, 'link' | 'scalar'] {
|
|
277
|
-
// if its a link return the value
|
|
278
|
-
if (typeof this.links[id]?.[field] !== 'undefined') {
|
|
279
|
-
return [this.links[id][field], 'link']
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// only other option is a value
|
|
283
|
-
return [this.fields[id]?.[field], 'scalar']
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
getOperations(id: string, field: string): Operation[] | undefined {
|
|
287
|
-
// if the id has been deleted
|
|
288
|
-
if (this.operations[id]?.deleted) {
|
|
289
|
-
return [
|
|
290
|
-
{
|
|
291
|
-
kind: OperationKind.delete,
|
|
292
|
-
target: id,
|
|
293
|
-
},
|
|
294
|
-
]
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// there could be a mutation for the specific field
|
|
298
|
-
if (this.operations[id]?.fields?.[field]) {
|
|
299
|
-
return this.operations[id].fields[field]
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
writeField(id: string, field: string, value: GraphQLField): LayerID {
|
|
304
|
-
this.fields[id] = {
|
|
305
|
-
...this.fields[id],
|
|
306
|
-
[field]: value,
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return this.id
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
writeLink(id: string, field: string, value: null | string | LinkedList): LayerID {
|
|
313
|
-
// if any of the values in this link are flagged to be deleted, undelete it
|
|
314
|
-
const valueList = Array.isArray(value) ? value : [value]
|
|
315
|
-
for (const value of flattenList(valueList)) {
|
|
316
|
-
if (!value) {
|
|
317
|
-
continue
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const fieldOperations = this.operations[id]?.fields[field]
|
|
321
|
-
|
|
322
|
-
// if the operation was globally deleted
|
|
323
|
-
if (this.operations[value]?.deleted || this.deletedIDs.has(value)) {
|
|
324
|
-
// undo the delete
|
|
325
|
-
this.operations[value] = {
|
|
326
|
-
...this.operations[value],
|
|
327
|
-
undoDeletesInList: [...(this.operations[id]?.undoDeletesInList || []), field],
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// the value could have been removed specifically from the list
|
|
331
|
-
} else if (value && fieldOperations?.length > 0) {
|
|
332
|
-
// if we have a field operation to remove the list, undo the operation
|
|
333
|
-
this.operations[id].fields[field] = fieldOperations.filter(
|
|
334
|
-
(op) => op.kind !== 'remove' || op.id !== value
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
this.links[id] = {
|
|
340
|
-
...this.links[id],
|
|
341
|
-
[field]: value,
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return this.id
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
isDisplayLayer(displayLayers: number[]) {
|
|
348
|
-
return (
|
|
349
|
-
displayLayers.length === 0 ||
|
|
350
|
-
displayLayers.includes(this.id) ||
|
|
351
|
-
Math.max(...displayLayers) < this.id
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
clear() {
|
|
356
|
-
// before we clear the data of the layer, look for any subscribers that need to be updated
|
|
357
|
-
|
|
358
|
-
// now that everything has been notified we can reset the data
|
|
359
|
-
this.links = {}
|
|
360
|
-
this.fields = {}
|
|
361
|
-
this.operations = {}
|
|
362
|
-
this.deletedIDs = new Set<string>()
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
replaceID({ from, to }: { from: string; to: string }) {
|
|
366
|
-
// any fields that existing in from, assign to to
|
|
367
|
-
this.fields[to] = this.fields[from]
|
|
368
|
-
this.links[to] = this.links[from]
|
|
369
|
-
this.operations[to] = this.operations[from] || { fields: {} }
|
|
370
|
-
if (this.deletedIDs.has(from)) {
|
|
371
|
-
this.deletedIDs.add(to)
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
removeUndefinedFields() {
|
|
376
|
-
// any field that's marked as undefined needs to be deleted
|
|
377
|
-
for (const [id, fields] of Object.entries(this.fields)) {
|
|
378
|
-
for (const [field, value] of Object.entries(fields)) {
|
|
379
|
-
if (typeof value === 'undefined') {
|
|
380
|
-
try {
|
|
381
|
-
delete this.fields[id][field]
|
|
382
|
-
} catch {}
|
|
383
|
-
try {
|
|
384
|
-
delete this.links[id][field]
|
|
385
|
-
} catch {}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// if there are no fields left for the object, clean it up
|
|
390
|
-
if (Object.keys(fields || {}).length === 0) {
|
|
391
|
-
delete this.fields[id]
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// if there are no more links, clean it up
|
|
395
|
-
if (Object.keys(this.links[id] || {}).length === 0) {
|
|
396
|
-
delete this.links[id]
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
delete(id: string) {
|
|
402
|
-
// add an insert operation to the map
|
|
403
|
-
this.operations = {
|
|
404
|
-
...this.operations,
|
|
405
|
-
[id]: {
|
|
406
|
-
...this.operations[id],
|
|
407
|
-
deleted: true,
|
|
408
|
-
// reapply any delete undos
|
|
409
|
-
undoDeletesInList: [],
|
|
410
|
-
},
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// add the id to the list of ids that have been deleted in this layer (so we can filter them out later)
|
|
414
|
-
this.deletedIDs.add(id)
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
deleteField(id: string, field: string) {
|
|
418
|
-
this.fields[id] = {
|
|
419
|
-
...this.fields[id],
|
|
420
|
-
[field]: undefined,
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
insert(id: string, field: string, where: OperationLocation, target: string) {
|
|
425
|
-
// add an insert operation for the field
|
|
426
|
-
this.addFieldOperation(id, field, {
|
|
427
|
-
kind: OperationKind.insert,
|
|
428
|
-
id: target,
|
|
429
|
-
location: where,
|
|
430
|
-
})
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
remove(id: string, field: string, target: string) {
|
|
434
|
-
// add a remove operation for the field
|
|
435
|
-
this.addFieldOperation(id, field, {
|
|
436
|
-
kind: OperationKind.remove,
|
|
437
|
-
id: target,
|
|
438
|
-
})
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
writeLayer(layer: Layer): void {
|
|
442
|
-
// if we are merging into ourselves, we're done
|
|
443
|
-
if (layer.id === this.id) {
|
|
444
|
-
return
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// we have to apply operations before we move fields so we can clean up existing
|
|
448
|
-
// data if we have a delete before we copy over the values
|
|
449
|
-
for (const [id, ops] of Object.entries(layer.operations)) {
|
|
450
|
-
const fields: OperationMap['fieldName']['fields'] = {}
|
|
451
|
-
|
|
452
|
-
// merge the two operation maps
|
|
453
|
-
for (const opMap of [this.operations[id], layer.operations[id]].filter(Boolean)) {
|
|
454
|
-
for (const [fieldName, operations] of Object.entries(opMap.fields || {})) {
|
|
455
|
-
fields[fieldName] = [...(fields[fieldName] || []), ...operations]
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// only copy a field key if there is something
|
|
460
|
-
if (Object.keys(fields).length > 0) {
|
|
461
|
-
this.operations[id] = {
|
|
462
|
-
...this.operations[id],
|
|
463
|
-
fields,
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// if we are applying
|
|
468
|
-
if (ops?.deleted) {
|
|
469
|
-
delete this.fields[id]
|
|
470
|
-
delete this.links[id]
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// copy the field values
|
|
475
|
-
for (const [id, values] of Object.entries(layer.fields)) {
|
|
476
|
-
if (!values) {
|
|
477
|
-
continue
|
|
478
|
-
}
|
|
479
|
-
// we do have a record matching this id, copy the individual fields
|
|
480
|
-
for (const [field, value] of Object.entries(values)) {
|
|
481
|
-
this.writeField(id, field, value)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// copy the link values
|
|
486
|
-
for (const [id, values] of Object.entries(layer.links)) {
|
|
487
|
-
if (!values) {
|
|
488
|
-
continue
|
|
489
|
-
}
|
|
490
|
-
// we do have a record matching this id, copy the individual links
|
|
491
|
-
for (const [field, value] of Object.entries(values)) {
|
|
492
|
-
this.writeLink(id, field, value)
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// add the list of deleted ids to this layer
|
|
497
|
-
layer.deletedIDs.forEach((v) => this.deletedIDs.add(v))
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
private addFieldOperation(id: string, field: string, operation: ListOperation) {
|
|
501
|
-
this.operations = {
|
|
502
|
-
...this.operations,
|
|
503
|
-
[id]: {
|
|
504
|
-
...this.operations[id],
|
|
505
|
-
fields: {
|
|
506
|
-
[field]: [...(this.operations[id]?.fields[field] || []), operation],
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
type GraphQLField = GraphQLValue | LinkedList
|
|
514
|
-
|
|
515
|
-
type EntityMap<_Value> = { [id: string]: { [field: string]: _Value } }
|
|
516
|
-
|
|
517
|
-
type EntityFieldMap = EntityMap<GraphQLField>
|
|
518
|
-
|
|
519
|
-
type LinkMap = EntityMap<string | null | LinkedList>
|
|
520
|
-
|
|
521
|
-
type OperationMap = {
|
|
522
|
-
[id: string]: {
|
|
523
|
-
deleted?: boolean
|
|
524
|
-
undoDeletesInList?: string[]
|
|
525
|
-
fields: { [field: string]: ListOperation[] }
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
type LinkedList<_Result = string> = (_Result | null | LinkedList<_Result>)[]
|
|
530
|
-
|
|
531
|
-
type InsertOperation = {
|
|
532
|
-
kind: OperationKind.insert
|
|
533
|
-
location: OperationLocation
|
|
534
|
-
id: string
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
type RemoveOperation = {
|
|
538
|
-
kind: OperationKind.remove
|
|
539
|
-
id: string
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
type DeleteOperation = {
|
|
543
|
-
kind: OperationKind.delete
|
|
544
|
-
target: string
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
type ListOperation = InsertOperation | RemoveOperation
|
|
548
|
-
|
|
549
|
-
function isDeleteOperation(value: GraphQLField | Operation): value is DeleteOperation {
|
|
550
|
-
return !!value && (value as Operation).kind === OperationKind.delete
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function isInsertOperation(value: GraphQLField | Operation): value is InsertOperation {
|
|
554
|
-
return !!value && (value as Operation).kind === OperationKind.insert
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function isRemoveOperation(value: GraphQLField | Operation): value is RemoveOperation {
|
|
558
|
-
return !!value && (value as Operation).kind === OperationKind.remove
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
type Operation = ListOperation | DeleteOperation
|
|
562
|
-
|
|
563
|
-
export enum OperationLocation {
|
|
564
|
-
start = 'start',
|
|
565
|
-
end = 'end',
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
export enum OperationKind {
|
|
569
|
-
delete = 'delete',
|
|
570
|
-
insert = 'insert',
|
|
571
|
-
remove = 'remove',
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
export type LayerID = number
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { GraphQLValue } from '../lib/types'
|
|
2
|
-
import { LinkedList } from './cache'
|
|
3
|
-
|
|
4
|
-
export function flattenList<T>(source: LinkedList<T>): T[] {
|
|
5
|
-
const flat: T[] = []
|
|
6
|
-
|
|
7
|
-
// we need to flatten the list links
|
|
8
|
-
const unvisited = [source || []]
|
|
9
|
-
while (unvisited.length > 0) {
|
|
10
|
-
const target = unvisited.shift() as T[]
|
|
11
|
-
|
|
12
|
-
for (const id of target) {
|
|
13
|
-
if (Array.isArray(id)) {
|
|
14
|
-
unvisited.push(id)
|
|
15
|
-
continue
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
flat.push(id)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return flat
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// given a raw key and a set of variables, generate the fully qualified key
|
|
26
|
-
export function evaluateKey(key: string, variables: { [key: string]: GraphQLValue } = {}): string {
|
|
27
|
-
// accumulate the evaluated key
|
|
28
|
-
let evaluated = ''
|
|
29
|
-
// accumulate a variable name that we're evaluating
|
|
30
|
-
let varName = ''
|
|
31
|
-
// some state to track if we are "in" a string
|
|
32
|
-
let inString = false
|
|
33
|
-
|
|
34
|
-
for (const char of key) {
|
|
35
|
-
// if we are building up a variable
|
|
36
|
-
if (varName) {
|
|
37
|
-
// if we are looking at a valid variable character
|
|
38
|
-
if (varChars.includes(char)) {
|
|
39
|
-
// add it to the variable name
|
|
40
|
-
varName += char
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
// we are at the end of a variable name so we
|
|
44
|
-
// need to clean up and add before continuing with the string
|
|
45
|
-
|
|
46
|
-
// look up the variable and add the result (varName starts with a $)
|
|
47
|
-
const value = variables[varName.slice(1)]
|
|
48
|
-
|
|
49
|
-
evaluated += typeof value !== 'undefined' ? JSON.stringify(value) : 'undefined'
|
|
50
|
-
|
|
51
|
-
// clear the variable name accumulator
|
|
52
|
-
varName = ''
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// if we are looking at the start of a variable
|
|
56
|
-
if (char === '$' && !inString) {
|
|
57
|
-
// start the accumulator
|
|
58
|
-
varName = '$'
|
|
59
|
-
|
|
60
|
-
// move along
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// if we found a quote, invert the string state
|
|
65
|
-
if (char === '"') {
|
|
66
|
-
inString = !inString
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// this isn't a special case, just add the letter to the value
|
|
70
|
-
evaluated += char
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return evaluated
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// the list of characters that make up a valid graphql variable name
|
|
77
|
-
const varChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'
|