relay-runtime 9.0.0 → 9.1.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 (111) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +34 -0
  2. package/handlers/connection/ConnectionHandler.js.flow +549 -0
  3. package/handlers/connection/ConnectionInterface.js.flow +92 -0
  4. package/index.js +1 -1
  5. package/index.js.flow +314 -0
  6. package/lib/handlers/connection/ConnectionHandler.js +1 -3
  7. package/lib/index.js +1 -2
  8. package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
  9. package/lib/mutations/RelayRecordProxy.js +1 -3
  10. package/lib/mutations/RelayRecordSourceMutator.js +1 -3
  11. package/lib/mutations/RelayRecordSourceProxy.js +1 -3
  12. package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
  13. package/lib/mutations/commitMutation.js +2 -0
  14. package/lib/mutations/validateMutation.js +13 -4
  15. package/lib/network/RelayObservable.js +9 -9
  16. package/lib/network/RelayQueryResponseCache.js +8 -6
  17. package/lib/query/fetchQueryInternal.js +1 -8
  18. package/lib/store/DataChecker.js +23 -51
  19. package/lib/store/RelayConcreteVariables.js +6 -2
  20. package/lib/store/RelayModernEnvironment.js +30 -12
  21. package/lib/store/RelayModernFragmentSpecResolver.js +9 -13
  22. package/lib/store/RelayModernQueryExecutor.js +73 -37
  23. package/lib/store/RelayModernRecord.js +14 -9
  24. package/lib/store/RelayModernStore.js +107 -70
  25. package/lib/store/RelayOperationTracker.js +35 -78
  26. package/lib/store/RelayOptimisticRecordSource.js +7 -5
  27. package/lib/store/RelayPublishQueue.js +1 -3
  28. package/lib/store/RelayReader.js +1 -3
  29. package/lib/store/RelayRecordSource.js +1 -3
  30. package/lib/store/RelayRecordSourceMapImpl.js +13 -18
  31. package/lib/store/RelayReferenceMarker.js +2 -6
  32. package/lib/store/RelayResponseNormalizer.js +9 -10
  33. package/lib/store/StoreInspector.js +7 -5
  34. package/lib/store/normalizeRelayPayload.js +6 -2
  35. package/lib/subscription/requestSubscription.js +4 -2
  36. package/lib/util/RelayFeatureFlags.js +1 -1
  37. package/lib/util/RelayReplaySubject.js +1 -3
  38. package/lib/util/createPayloadFor3DField.js +7 -2
  39. package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
  40. package/mutations/RelayRecordProxy.js.flow +165 -0
  41. package/mutations/RelayRecordSourceMutator.js.flow +238 -0
  42. package/mutations/RelayRecordSourceProxy.js.flow +164 -0
  43. package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
  44. package/mutations/applyOptimisticMutation.js.flow +76 -0
  45. package/mutations/commitLocalUpdate.js.flow +24 -0
  46. package/mutations/commitMutation.js.flow +184 -0
  47. package/mutations/validateMutation.js.flow +211 -0
  48. package/network/ConvertToExecuteFunction.js.flow +49 -0
  49. package/network/RelayNetwork.js.flow +84 -0
  50. package/network/RelayNetworkTypes.js.flow +123 -0
  51. package/network/RelayObservable.js.flow +634 -0
  52. package/network/RelayQueryResponseCache.js.flow +111 -0
  53. package/package.json +1 -1
  54. package/query/GraphQLTag.js.flow +166 -0
  55. package/query/fetchQuery.js.flow +47 -0
  56. package/query/fetchQueryInternal.js.flow +349 -0
  57. package/relay-runtime.js +2 -2
  58. package/relay-runtime.min.js +2 -2
  59. package/store/ClientID.js.flow +43 -0
  60. package/store/DataChecker.js.flow +426 -0
  61. package/store/RelayConcreteVariables.js.flow +96 -0
  62. package/store/RelayModernEnvironment.js.flow +526 -0
  63. package/store/RelayModernFragmentSpecResolver.js.flow +426 -0
  64. package/store/RelayModernOperationDescriptor.js.flow +88 -0
  65. package/store/RelayModernQueryExecutor.js.flow +1327 -0
  66. package/store/RelayModernRecord.js.flow +403 -0
  67. package/store/RelayModernSelector.js.flow +444 -0
  68. package/store/RelayModernStore.js.flow +757 -0
  69. package/store/RelayOperationTracker.js.flow +164 -0
  70. package/store/RelayOptimisticRecordSource.js.flow +119 -0
  71. package/store/RelayPublishQueue.js.flow +401 -0
  72. package/store/RelayReader.js.flow +376 -0
  73. package/store/RelayRecordSource.js.flow +29 -0
  74. package/store/RelayRecordSourceMapImpl.js.flow +87 -0
  75. package/store/RelayRecordState.js.flow +37 -0
  76. package/store/RelayReferenceMarker.js.flow +236 -0
  77. package/store/RelayResponseNormalizer.js.flow +556 -0
  78. package/store/RelayStoreTypes.js.flow +873 -0
  79. package/store/RelayStoreUtils.js.flow +218 -0
  80. package/store/StoreInspector.js.flow +173 -0
  81. package/store/ViewerPattern.js.flow +26 -0
  82. package/store/cloneRelayHandleSourceField.js.flow +66 -0
  83. package/store/createFragmentSpecResolver.js.flow +55 -0
  84. package/store/createRelayContext.js.flow +44 -0
  85. package/store/defaultGetDataID.js.flow +27 -0
  86. package/store/hasOverlappingIDs.js.flow +34 -0
  87. package/store/isRelayModernEnvironment.js.flow +27 -0
  88. package/store/normalizeRelayPayload.js.flow +51 -0
  89. package/store/readInlineData.js.flow +75 -0
  90. package/subscription/requestSubscription.js.flow +100 -0
  91. package/util/JSResourceTypes.flow.js.flow +20 -0
  92. package/util/NormalizationNode.js.flow +191 -0
  93. package/util/ReaderNode.js.flow +208 -0
  94. package/util/RelayConcreteNode.js.flow +80 -0
  95. package/util/RelayDefaultHandleKey.js.flow +17 -0
  96. package/util/RelayError.js.flow +33 -0
  97. package/util/RelayFeatureFlags.js.flow +30 -0
  98. package/util/RelayProfiler.js.flow +284 -0
  99. package/util/RelayReplaySubject.js.flow +134 -0
  100. package/util/RelayRuntimeTypes.js.flow +70 -0
  101. package/util/createPayloadFor3DField.js.flow +43 -0
  102. package/util/deepFreeze.js.flow +36 -0
  103. package/util/generateID.js.flow +21 -0
  104. package/util/getFragmentIdentifier.js.flow +52 -0
  105. package/util/getRelayHandleKey.js.flow +41 -0
  106. package/util/getRequestIdentifier.js.flow +41 -0
  107. package/util/isPromise.js.flow +21 -0
  108. package/util/isScalarAndEqual.js.flow +26 -0
  109. package/util/recycleNodesInto.js.flow +80 -0
  110. package/util/resolveImmediate.js.flow +30 -0
  111. package/util/stableCopy.js.flow +35 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ const ConnectionHandler = require('./connection/ConnectionHandler');
16
+
17
+ const invariant = require('invariant');
18
+
19
+ import type {Handler} from '../store/RelayStoreTypes';
20
+ export type HandlerProvider = (name: string) => ?Handler;
21
+
22
+ function RelayDefaultHandlerProvider(handle: string): Handler {
23
+ switch (handle) {
24
+ case 'connection':
25
+ return ConnectionHandler;
26
+ }
27
+ invariant(
28
+ false,
29
+ 'RelayDefaultHandlerProvider: No handler provided for `%s`.',
30
+ handle,
31
+ );
32
+ }
33
+
34
+ module.exports = RelayDefaultHandlerProvider;
@@ -0,0 +1,549 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ const ConnectionInterface = require('./ConnectionInterface');
16
+
17
+ const getRelayHandleKey = require('../../util/getRelayHandleKey');
18
+ const invariant = require('invariant');
19
+ const warning = require('warning');
20
+
21
+ const {generateClientID} = require('../../store/ClientID');
22
+
23
+ import type {
24
+ HandleFieldPayload,
25
+ RecordProxy,
26
+ ReadOnlyRecordProxy,
27
+ RecordSourceProxy,
28
+ } from '../../store/RelayStoreTypes';
29
+ import type {DataID, Variables} from '../../util/RelayRuntimeTypes';
30
+
31
+ export type ConnectionMetadata = {
32
+ path: ?Array<string>,
33
+ direction: ?('forward' | 'backward' | 'bidirectional'),
34
+ cursor: ?string,
35
+ count: ?string,
36
+ stream?: boolean,
37
+ ...
38
+ };
39
+
40
+ const CONNECTION = 'connection';
41
+
42
+ // Per-instance incrementing index used to generate unique edge IDs
43
+ const NEXT_EDGE_INDEX = '__connection_next_edge_index';
44
+
45
+ /**
46
+ * @public
47
+ *
48
+ * A default runtime handler for connection fields that appends newly fetched
49
+ * edges onto the end of a connection, regardless of the arguments used to fetch
50
+ * those edges.
51
+ */
52
+ function update(store: RecordSourceProxy, payload: HandleFieldPayload): void {
53
+ const record = store.get(payload.dataID);
54
+ if (!record) {
55
+ return;
56
+ }
57
+
58
+ const {
59
+ EDGES,
60
+ END_CURSOR,
61
+ HAS_NEXT_PAGE,
62
+ HAS_PREV_PAGE,
63
+ PAGE_INFO,
64
+ PAGE_INFO_TYPE,
65
+ START_CURSOR,
66
+ } = ConnectionInterface.get();
67
+
68
+ const serverConnection = record.getLinkedRecord(payload.fieldKey);
69
+ const serverPageInfo =
70
+ serverConnection && serverConnection.getLinkedRecord(PAGE_INFO);
71
+ if (!serverConnection) {
72
+ record.setValue(null, payload.handleKey);
73
+ return;
74
+ }
75
+ // In rare cases the handleKey field may be unset even though the client
76
+ // connection record exists, in this case new edges should still be merged
77
+ // into the existing client connection record (and the field reset to point
78
+ // to that record).
79
+ const clientConnectionID = generateClientID(
80
+ record.getDataID(),
81
+ payload.handleKey,
82
+ );
83
+ const clientConnectionField = record.getLinkedRecord(payload.handleKey);
84
+ const clientConnection =
85
+ clientConnectionField ?? store.get(clientConnectionID);
86
+ let clientPageInfo =
87
+ clientConnection && clientConnection.getLinkedRecord(PAGE_INFO);
88
+ if (!clientConnection) {
89
+ // Initial fetch with data: copy fields from the server record
90
+ const connection = store.create(
91
+ clientConnectionID,
92
+ serverConnection.getType(),
93
+ );
94
+ connection.setValue(0, NEXT_EDGE_INDEX);
95
+ connection.copyFieldsFrom(serverConnection);
96
+ let serverEdges = serverConnection.getLinkedRecords(EDGES);
97
+ if (serverEdges) {
98
+ serverEdges = serverEdges.map(edge =>
99
+ buildConnectionEdge(store, connection, edge),
100
+ );
101
+ connection.setLinkedRecords(serverEdges, EDGES);
102
+ }
103
+ record.setLinkedRecord(connection, payload.handleKey);
104
+
105
+ clientPageInfo = store.create(
106
+ generateClientID(connection.getDataID(), PAGE_INFO),
107
+ PAGE_INFO_TYPE,
108
+ );
109
+ clientPageInfo.setValue(false, HAS_NEXT_PAGE);
110
+ clientPageInfo.setValue(false, HAS_PREV_PAGE);
111
+ clientPageInfo.setValue(null, END_CURSOR);
112
+ clientPageInfo.setValue(null, START_CURSOR);
113
+ if (serverPageInfo) {
114
+ clientPageInfo.copyFieldsFrom(serverPageInfo);
115
+ }
116
+ connection.setLinkedRecord(clientPageInfo, PAGE_INFO);
117
+ } else {
118
+ if (clientConnectionField == null) {
119
+ // If the handleKey field was unset but the client connection record
120
+ // existed, update the field to point to the record
121
+ record.setLinkedRecord(clientConnection, payload.handleKey);
122
+ }
123
+ const connection = clientConnection;
124
+ // Subsequent fetches:
125
+ // - updated fields on the connection
126
+ // - merge prev/next edges, de-duplicating by node id
127
+ // - synthesize page info fields
128
+ let serverEdges = serverConnection.getLinkedRecords(EDGES);
129
+ if (serverEdges) {
130
+ serverEdges = serverEdges.map(edge =>
131
+ buildConnectionEdge(store, connection, edge),
132
+ );
133
+ }
134
+ const prevEdges = connection.getLinkedRecords(EDGES);
135
+ const prevPageInfo = connection.getLinkedRecord(PAGE_INFO);
136
+ connection.copyFieldsFrom(serverConnection);
137
+ // Reset EDGES and PAGE_INFO fields
138
+ if (prevEdges) {
139
+ connection.setLinkedRecords(prevEdges, EDGES);
140
+ }
141
+ if (prevPageInfo) {
142
+ connection.setLinkedRecord(prevPageInfo, PAGE_INFO);
143
+ }
144
+
145
+ let nextEdges = [];
146
+ const args = payload.args;
147
+ if (prevEdges && serverEdges) {
148
+ if (args.after != null) {
149
+ // Forward pagination from the end of the connection: append edges
150
+ if (
151
+ clientPageInfo &&
152
+ args.after === clientPageInfo.getValue(END_CURSOR)
153
+ ) {
154
+ const nodeIDs = new Set();
155
+ mergeEdges(prevEdges, nextEdges, nodeIDs);
156
+ mergeEdges(serverEdges, nextEdges, nodeIDs);
157
+ } else {
158
+ warning(
159
+ false,
160
+ 'Relay: Unexpected after cursor `%s`, edges must ' +
161
+ 'be fetched from the end of the list (`%s`).',
162
+ args.after,
163
+ clientPageInfo && clientPageInfo.getValue(END_CURSOR),
164
+ );
165
+ return;
166
+ }
167
+ } else if (args.before != null) {
168
+ // Backward pagination from the start of the connection: prepend edges
169
+ if (
170
+ clientPageInfo &&
171
+ args.before === clientPageInfo.getValue(START_CURSOR)
172
+ ) {
173
+ const nodeIDs = new Set();
174
+ mergeEdges(serverEdges, nextEdges, nodeIDs);
175
+ mergeEdges(prevEdges, nextEdges, nodeIDs);
176
+ } else {
177
+ warning(
178
+ false,
179
+ 'Relay: Unexpected before cursor `%s`, edges must ' +
180
+ 'be fetched from the beginning of the list (`%s`).',
181
+ args.before,
182
+ clientPageInfo && clientPageInfo.getValue(START_CURSOR),
183
+ );
184
+ return;
185
+ }
186
+ } else {
187
+ // The connection was refetched from the beginning/end: replace edges
188
+ nextEdges = serverEdges;
189
+ }
190
+ } else if (serverEdges) {
191
+ nextEdges = serverEdges;
192
+ } else {
193
+ nextEdges = prevEdges;
194
+ }
195
+ // Update edges only if they were updated, the null check is
196
+ // for Flow (prevEdges could be null).
197
+ if (nextEdges != null && nextEdges !== prevEdges) {
198
+ connection.setLinkedRecords(nextEdges, EDGES);
199
+ }
200
+ // Page info should be updated even if no new edge were returned.
201
+ if (clientPageInfo && serverPageInfo) {
202
+ if (args.after == null && args.before == null) {
203
+ // The connection was refetched from the beginning/end: replace
204
+ // page_info
205
+ clientPageInfo.copyFieldsFrom(serverPageInfo);
206
+ } else if (args.before != null || (args.after == null && args.last)) {
207
+ clientPageInfo.setValue(
208
+ !!serverPageInfo.getValue(HAS_PREV_PAGE),
209
+ HAS_PREV_PAGE,
210
+ );
211
+ const startCursor = serverPageInfo.getValue(START_CURSOR);
212
+ if (typeof startCursor === 'string') {
213
+ clientPageInfo.setValue(startCursor, START_CURSOR);
214
+ }
215
+ } else if (args.after != null || (args.before == null && args.first)) {
216
+ clientPageInfo.setValue(
217
+ !!serverPageInfo.getValue(HAS_NEXT_PAGE),
218
+ HAS_NEXT_PAGE,
219
+ );
220
+ const endCursor = serverPageInfo.getValue(END_CURSOR);
221
+ if (typeof endCursor === 'string') {
222
+ clientPageInfo.setValue(endCursor, END_CURSOR);
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * @public
231
+ *
232
+ * Given a record and the name of the schema field for which a connection was
233
+ * fetched, returns the linked connection record.
234
+ *
235
+ * Example:
236
+ *
237
+ * Given that data has already been fetched on some user `<id>` on the `friends`
238
+ * field:
239
+ *
240
+ * ```
241
+ * fragment FriendsFragment on User {
242
+ * friends(first: 10) @connection(key: "FriendsFragment_friends") {
243
+ * edges {
244
+ * node {
245
+ * id
246
+ * }
247
+ * }
248
+ * }
249
+ * }
250
+ * ```
251
+ *
252
+ * The `friends` connection record can be accessed with:
253
+ *
254
+ * ```
255
+ * store => {
256
+ * const user = store.get('<id>');
257
+ * const friends = ConnectionHandler.getConnection(user, 'FriendsFragment_friends');
258
+ * // Access fields on the connection:
259
+ * const edges = friends.getLinkedRecords('edges');
260
+ * }
261
+ * ```
262
+ *
263
+ * TODO: t15733312
264
+ * Currently we haven't run into this case yet, but we need to add a `getConnections`
265
+ * that returns an array of the connections under the same `key` regardless of the variables.
266
+ */
267
+ function getConnection(
268
+ record: ReadOnlyRecordProxy,
269
+ key: string,
270
+ filters?: ?Variables,
271
+ ): ?RecordProxy {
272
+ const handleKey = getRelayHandleKey(CONNECTION, key, null);
273
+ return record.getLinkedRecord(handleKey, filters);
274
+ }
275
+
276
+ /**
277
+ * @public
278
+ *
279
+ * Inserts an edge after the given cursor, or at the end of the list if no
280
+ * cursor is provided.
281
+ *
282
+ * Example:
283
+ *
284
+ * Given that data has already been fetched on some user `<id>` on the `friends`
285
+ * field:
286
+ *
287
+ * ```
288
+ * fragment FriendsFragment on User {
289
+ * friends(first: 10) @connection(key: "FriendsFragment_friends") {
290
+ * edges {
291
+ * node {
292
+ * id
293
+ * }
294
+ * }
295
+ * }
296
+ * }
297
+ * ```
298
+ *
299
+ * An edge can be appended with:
300
+ *
301
+ * ```
302
+ * store => {
303
+ * const user = store.get('<id>');
304
+ * const friends = ConnectionHandler.getConnection(user, 'FriendsFragment_friends');
305
+ * const edge = store.create('<edge-id>', 'FriendsEdge');
306
+ * ConnectionHandler.insertEdgeAfter(friends, edge);
307
+ * }
308
+ * ```
309
+ */
310
+ function insertEdgeAfter(
311
+ record: RecordProxy,
312
+ newEdge: RecordProxy,
313
+ cursor?: ?string,
314
+ ): void {
315
+ const {CURSOR, EDGES} = ConnectionInterface.get();
316
+
317
+ const edges = record.getLinkedRecords(EDGES);
318
+ if (!edges) {
319
+ record.setLinkedRecords([newEdge], EDGES);
320
+ return;
321
+ }
322
+ let nextEdges;
323
+ if (cursor == null) {
324
+ nextEdges = edges.concat(newEdge);
325
+ } else {
326
+ nextEdges = [];
327
+ let foundCursor = false;
328
+ for (let ii = 0; ii < edges.length; ii++) {
329
+ const edge = edges[ii];
330
+ nextEdges.push(edge);
331
+ if (edge == null) {
332
+ continue;
333
+ }
334
+ const edgeCursor = edge.getValue(CURSOR);
335
+ if (cursor === edgeCursor) {
336
+ nextEdges.push(newEdge);
337
+ foundCursor = true;
338
+ }
339
+ }
340
+ if (!foundCursor) {
341
+ nextEdges.push(newEdge);
342
+ }
343
+ }
344
+ record.setLinkedRecords(nextEdges, EDGES);
345
+ }
346
+
347
+ /**
348
+ * @public
349
+ *
350
+ * Creates an edge for a connection record, given a node and edge type.
351
+ */
352
+ function createEdge(
353
+ store: RecordSourceProxy,
354
+ record: RecordProxy,
355
+ node: RecordProxy,
356
+ edgeType: string,
357
+ ): RecordProxy {
358
+ const {NODE} = ConnectionInterface.get();
359
+
360
+ // An index-based client ID could easily conflict (unless it was
361
+ // auto-incrementing, but there is nowhere to the store the id)
362
+ // Instead, construct a client ID based on the connection ID and node ID,
363
+ // which will only conflict if the same node is added to the same connection
364
+ // twice. This is acceptable since the `insertEdge*` functions ignore
365
+ // duplicates.
366
+ const edgeID = generateClientID(record.getDataID(), node.getDataID());
367
+ let edge = store.get(edgeID);
368
+ if (!edge) {
369
+ edge = store.create(edgeID, edgeType);
370
+ }
371
+ edge.setLinkedRecord(node, NODE);
372
+ return edge;
373
+ }
374
+
375
+ /**
376
+ * @public
377
+ *
378
+ * Inserts an edge before the given cursor, or at the beginning of the list if
379
+ * no cursor is provided.
380
+ *
381
+ * Example:
382
+ *
383
+ * Given that data has already been fetched on some user `<id>` on the `friends`
384
+ * field:
385
+ *
386
+ * ```
387
+ * fragment FriendsFragment on User {
388
+ * friends(first: 10) @connection(key: "FriendsFragment_friends") {
389
+ * edges {
390
+ * node {
391
+ * id
392
+ * }
393
+ * }
394
+ * }
395
+ * }
396
+ * ```
397
+ *
398
+ * An edge can be prepended with:
399
+ *
400
+ * ```
401
+ * store => {
402
+ * const user = store.get('<id>');
403
+ * const friends = ConnectionHandler.getConnection(user, 'FriendsFragment_friends');
404
+ * const edge = store.create('<edge-id>', 'FriendsEdge');
405
+ * ConnectionHandler.insertEdgeBefore(friends, edge);
406
+ * }
407
+ * ```
408
+ */
409
+ function insertEdgeBefore(
410
+ record: RecordProxy,
411
+ newEdge: RecordProxy,
412
+ cursor?: ?string,
413
+ ): void {
414
+ const {CURSOR, EDGES} = ConnectionInterface.get();
415
+
416
+ const edges = record.getLinkedRecords(EDGES);
417
+ if (!edges) {
418
+ record.setLinkedRecords([newEdge], EDGES);
419
+ return;
420
+ }
421
+ let nextEdges;
422
+ if (cursor == null) {
423
+ nextEdges = [newEdge].concat(edges);
424
+ } else {
425
+ nextEdges = [];
426
+ let foundCursor = false;
427
+ for (let ii = 0; ii < edges.length; ii++) {
428
+ const edge = edges[ii];
429
+ if (edge != null) {
430
+ const edgeCursor = edge.getValue(CURSOR);
431
+ if (cursor === edgeCursor) {
432
+ nextEdges.push(newEdge);
433
+ foundCursor = true;
434
+ }
435
+ }
436
+ nextEdges.push(edge);
437
+ }
438
+ if (!foundCursor) {
439
+ nextEdges.unshift(newEdge);
440
+ }
441
+ }
442
+ record.setLinkedRecords(nextEdges, EDGES);
443
+ }
444
+
445
+ /**
446
+ * @public
447
+ *
448
+ * Remove any edges whose `node.id` matches the given id.
449
+ */
450
+ function deleteNode(record: RecordProxy, nodeID: DataID): void {
451
+ const {EDGES, NODE} = ConnectionInterface.get();
452
+
453
+ const edges = record.getLinkedRecords(EDGES);
454
+ if (!edges) {
455
+ return;
456
+ }
457
+ let nextEdges;
458
+ for (let ii = 0; ii < edges.length; ii++) {
459
+ const edge = edges[ii];
460
+ const node = edge && edge.getLinkedRecord(NODE);
461
+ if (node != null && node.getDataID() === nodeID) {
462
+ if (nextEdges === undefined) {
463
+ nextEdges = edges.slice(0, ii);
464
+ }
465
+ } else if (nextEdges !== undefined) {
466
+ nextEdges.push(edge);
467
+ }
468
+ }
469
+ if (nextEdges !== undefined) {
470
+ record.setLinkedRecords(nextEdges, EDGES);
471
+ }
472
+ }
473
+
474
+ /**
475
+ * @internal
476
+ *
477
+ * Creates a copy of an edge with a unique ID based on per-connection-instance
478
+ * incrementing edge index. This is necessary to avoid collisions between edges,
479
+ * which can occur because (edge) client IDs are assigned deterministically
480
+ * based on the path from the nearest node with an id.
481
+ *
482
+ * Example: if the first N edges of the same connection are refetched, the edges
483
+ * from the second fetch will be assigned the same IDs as the first fetch, even
484
+ * though the nodes they point to may be different (or the same and in different
485
+ * order).
486
+ */
487
+ function buildConnectionEdge(
488
+ store: RecordSourceProxy,
489
+ connection: RecordProxy,
490
+ edge: ?RecordProxy,
491
+ ): ?RecordProxy {
492
+ if (edge == null) {
493
+ return edge;
494
+ }
495
+ const {EDGES} = ConnectionInterface.get();
496
+
497
+ const edgeIndex = connection.getValue(NEXT_EDGE_INDEX);
498
+ invariant(
499
+ typeof edgeIndex === 'number',
500
+ 'ConnectionHandler: Expected %s to be a number, got `%s`.',
501
+ NEXT_EDGE_INDEX,
502
+ edgeIndex,
503
+ );
504
+ const edgeID = generateClientID(connection.getDataID(), EDGES, edgeIndex);
505
+ const connectionEdge = store.create(edgeID, edge.getType());
506
+ connectionEdge.copyFieldsFrom(edge);
507
+ connection.setValue(edgeIndex + 1, NEXT_EDGE_INDEX);
508
+ return connectionEdge;
509
+ }
510
+
511
+ /**
512
+ * @internal
513
+ *
514
+ * Adds the source edges to the target edges, skipping edges with
515
+ * duplicate node ids.
516
+ */
517
+ function mergeEdges(
518
+ sourceEdges: Array<?RecordProxy>,
519
+ targetEdges: Array<?RecordProxy>,
520
+ nodeIDs: Set<mixed>,
521
+ ): void {
522
+ const {NODE} = ConnectionInterface.get();
523
+
524
+ for (let ii = 0; ii < sourceEdges.length; ii++) {
525
+ const edge = sourceEdges[ii];
526
+ if (!edge) {
527
+ continue;
528
+ }
529
+ const node = edge.getLinkedRecord(NODE);
530
+ const nodeID = node && node.getDataID();
531
+ if (nodeID) {
532
+ if (nodeIDs.has(nodeID)) {
533
+ continue;
534
+ }
535
+ nodeIDs.add(nodeID);
536
+ }
537
+ targetEdges.push(edge);
538
+ }
539
+ }
540
+
541
+ module.exports = {
542
+ buildConnectionEdge,
543
+ createEdge,
544
+ deleteNode,
545
+ getConnection,
546
+ insertEdgeAfter,
547
+ insertEdgeBefore,
548
+ update,
549
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ import type {Record} from '../../store/RelayStoreTypes';
16
+
17
+ type Call = {name: string, ...};
18
+
19
+ export type EdgeRecord = Record & {
20
+ cursor: mixed,
21
+ node: Record,
22
+ ...
23
+ };
24
+
25
+ export type PageInfo = {
26
+ endCursor: ?string,
27
+ hasNextPage: boolean,
28
+ hasPreviousPage: boolean,
29
+ startCursor: ?string,
30
+ ...
31
+ };
32
+
33
+ type ConnectionConfig = {|
34
+ CLIENT_MUTATION_ID: string,
35
+ CURSOR: string,
36
+ EDGES: string,
37
+ END_CURSOR: string,
38
+ HAS_NEXT_PAGE: string,
39
+ HAS_PREV_PAGE: string,
40
+ NODE: string,
41
+ PAGE_INFO: string,
42
+ PAGE_INFO_TYPE: string,
43
+ START_CURSOR: string,
44
+ |};
45
+
46
+ const CONNECTION_CALLS = {
47
+ after: true,
48
+ before: true,
49
+ find: true,
50
+ first: true,
51
+ last: true,
52
+ surrounds: true,
53
+ };
54
+
55
+ let config: ConnectionConfig = {
56
+ CLIENT_MUTATION_ID: 'clientMutationId',
57
+ CURSOR: 'cursor',
58
+ EDGES: 'edges',
59
+ END_CURSOR: 'endCursor',
60
+ HAS_NEXT_PAGE: 'hasNextPage',
61
+ HAS_PREV_PAGE: 'hasPreviousPage',
62
+ NODE: 'node',
63
+ PAGE_INFO_TYPE: 'PageInfo',
64
+ PAGE_INFO: 'pageInfo',
65
+ START_CURSOR: 'startCursor',
66
+ };
67
+
68
+ /**
69
+ * @internal
70
+ *
71
+ * Defines logic relevant to the informal "Connection" GraphQL interface.
72
+ */
73
+ const ConnectionInterface = {
74
+ inject(newConfig: ConnectionConfig) {
75
+ config = newConfig;
76
+ },
77
+
78
+ get(): ConnectionConfig {
79
+ return config;
80
+ },
81
+
82
+ /**
83
+ * Checks whether a call exists strictly to encode which parts of a connection
84
+ * to fetch. Fields that only differ by connection call values should have the
85
+ * same identity.
86
+ */
87
+ isConnectionCall(call: Call): boolean {
88
+ return CONNECTION_CALLS.hasOwnProperty(call.name);
89
+ },
90
+ };
91
+
92
+ module.exports = ConnectionInterface;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relay v9.0.0
2
+ * Relay v9.1.0
3
3
  *
4
4
  * Copyright (c) Facebook, Inc. and its affiliates.
5
5
  *