houdini 0.13.0-alpha.3 → 0.13.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/README.md CHANGED
@@ -208,6 +208,8 @@ If you have updated your schema on the server, you can pull down the most recent
208
208
  npx houdini generate --pull-schema
209
209
  ```
210
210
 
211
+ I know this sounds like a huge burden but we have plans for removing this step from the developer experience, it's just not ready yet.
212
+
211
213
  ## 📄 Config File
212
214
 
213
215
  All configuration for your houdini application is defined in a single file that is imported by both the runtime and the
package/build/cmd.js CHANGED
File without changes
@@ -71,7 +71,7 @@ declare class CacheInternal {
71
71
  variables?: {};
72
72
  linkedList: LinkedList;
73
73
  }): LinkedList<GraphQLValue>;
74
- extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, }: {
74
+ extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, startingWith, }: {
75
75
  value: GraphQLValue[];
76
76
  recordID: string;
77
77
  key: string;
@@ -82,6 +82,7 @@ declare class CacheInternal {
82
82
  applyUpdates: boolean;
83
83
  fields: SubscriptionSelection;
84
84
  layer: Layer;
85
+ startingWith: number;
85
86
  }): {
86
87
  nestedIDs: LinkedList;
87
88
  newIDs: (string | null)[];
@@ -335,6 +335,7 @@ var CacheInternal = /** @class */ (function () {
335
335
  variables: variables,
336
336
  fields: fields,
337
337
  layer: layer,
338
+ startingWith: applyUpdates && update === 'append' ? oldIDs.length : 0,
338
339
  }), newIDs = _h.newIDs, nestedIDs = _h.nestedIDs;
339
340
  // if we're supposed to apply this write as an update, we need to figure out how
340
341
  if (applyUpdates && update) {
@@ -655,7 +656,7 @@ var CacheInternal = /** @class */ (function () {
655
656
  CacheInternal.prototype.extractNestedListIDs = function (_a) {
656
657
  var e_9, _b;
657
658
  var _c;
658
- var value = _a.value, abstract = _a.abstract, recordID = _a.recordID, key = _a.key, linkedType = _a.linkedType, fields = _a.fields, variables = _a.variables, applyUpdates = _a.applyUpdates, specs = _a.specs, layer = _a.layer;
659
+ var value = _a.value, abstract = _a.abstract, recordID = _a.recordID, key = _a.key, linkedType = _a.linkedType, fields = _a.fields, variables = _a.variables, applyUpdates = _a.applyUpdates, specs = _a.specs, layer = _a.layer, startingWith = _a.startingWith;
659
660
  // build up the two lists
660
661
  var nestedIDs = [];
661
662
  var newIDs = [];
@@ -675,6 +676,7 @@ var CacheInternal = /** @class */ (function () {
675
676
  applyUpdates: applyUpdates,
676
677
  specs: specs,
677
678
  layer: layer,
679
+ startingWith: startingWith,
678
680
  });
679
681
  // add the list of new ids to our list
680
682
  newIDs.push.apply(newIDs, __spread(inner.newIDs));
@@ -691,7 +693,7 @@ var CacheInternal = /** @class */ (function () {
691
693
  // we know now that entry is an object
692
694
  var entryObj = entry;
693
695
  // start off building up the embedded id
694
- var linkedID = recordID + "." + key + "[" + id++ + "]";
696
+ var linkedID = recordID + "." + key + "[" + (startingWith + id++) + "]";
695
697
  // figure out if this is an embedded list or a linked one by looking for all of the fields marked as
696
698
  // required to compute the entity's id
697
699
  var embedded = ((_c = this_2.idFields(linkedType)) === null || _c === void 0 ? void 0 : _c.filter(function (field) { return typeof entry[field] === 'undefined'; }).length) > 0;
@@ -716,23 +718,6 @@ var CacheInternal = /** @class */ (function () {
716
718
  return "continue";
717
719
  }
718
720
  }
719
- // if the field is marked for pagination and we are looking at edges, we need
720
- // to use the underlying node for the id because the embedded key will conflict
721
- // with entries in the previous loaded value.
722
- // NOTE: this approach might cause weird behavior of a node is loaded in the same
723
- // location in two different pages. In practice, nodes rarely show up in the same
724
- // connection so it might not be a problem.
725
- if (key === 'edges' &&
726
- entryObj.node &&
727
- entryObj.node.__typename) {
728
- var node = entryObj.node;
729
- // @ts-ignore
730
- var typename_1 = node.__typename;
731
- var nodeID = this_2.id(typename_1, node);
732
- if (nodeID) {
733
- linkedID += '#' + nodeID;
734
- }
735
- }
736
721
  // update the linked fields too
737
722
  this_2.writeSelection({
738
723
  root: exports.rootID,
@@ -6,5 +6,5 @@ export { query, routeQuery, componentQuery } from './query';
6
6
  export { mutation } from './mutation';
7
7
  export { fragment } from './fragment';
8
8
  export { subscription } from './subscription';
9
- export { paginatedQuery } from './pagination';
9
+ export { paginatedQuery, paginatedFragment } from './pagination';
10
10
  export declare function graphql(str: TemplateStringsArray): GraphQLTagResult;
@@ -26,6 +26,7 @@ var subscription_1 = require("./subscription");
26
26
  Object.defineProperty(exports, "subscription", { enumerable: true, get: function () { return subscription_1.subscription; } });
27
27
  var pagination_1 = require("./pagination");
28
28
  Object.defineProperty(exports, "paginatedQuery", { enumerable: true, get: function () { return pagination_1.paginatedQuery; } });
29
+ Object.defineProperty(exports, "paginatedFragment", { enumerable: true, get: function () { return pagination_1.paginatedFragment; } });
29
30
  // this template tag gets removed by the preprocessor so it should never be invoked.
30
31
  // this function needs to return the same value as what the preprocessor leaves behind for type consistency
31
32
  function graphql(str) {
@@ -268,7 +268,6 @@ var RequestContext = /** @class */ (function () {
268
268
  RequestContext.prototype.graphqlErrors = function (payload) {
269
269
  // if we have a list of errors
270
270
  if (payload.errors) {
271
- console.log('registering graphql errors', payload.errors);
272
271
  return this.error(500, payload.errors.map(function (_a) {
273
272
  var message = _a.message;
274
273
  return message;
@@ -71,7 +71,7 @@ declare class CacheInternal {
71
71
  variables?: {};
72
72
  linkedList: LinkedList;
73
73
  }): LinkedList<GraphQLValue>;
74
- extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, }: {
74
+ extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, startingWith, }: {
75
75
  value: GraphQLValue[];
76
76
  recordID: string;
77
77
  key: string;
@@ -82,6 +82,7 @@ declare class CacheInternal {
82
82
  applyUpdates: boolean;
83
83
  fields: SubscriptionSelection;
84
84
  layer: Layer;
85
+ startingWith: number;
85
86
  }): {
86
87
  nestedIDs: LinkedList;
87
88
  newIDs: (string | null)[];
@@ -335,6 +335,7 @@ var CacheInternal = /** @class */ (function () {
335
335
  variables: variables,
336
336
  fields: fields,
337
337
  layer: layer,
338
+ startingWith: applyUpdates && update === 'append' ? oldIDs.length : 0,
338
339
  }), newIDs = _h.newIDs, nestedIDs = _h.nestedIDs;
339
340
  // if we're supposed to apply this write as an update, we need to figure out how
340
341
  if (applyUpdates && update) {
@@ -655,7 +656,7 @@ var CacheInternal = /** @class */ (function () {
655
656
  CacheInternal.prototype.extractNestedListIDs = function (_a) {
656
657
  var e_9, _b;
657
658
  var _c;
658
- var value = _a.value, abstract = _a.abstract, recordID = _a.recordID, key = _a.key, linkedType = _a.linkedType, fields = _a.fields, variables = _a.variables, applyUpdates = _a.applyUpdates, specs = _a.specs, layer = _a.layer;
659
+ var value = _a.value, abstract = _a.abstract, recordID = _a.recordID, key = _a.key, linkedType = _a.linkedType, fields = _a.fields, variables = _a.variables, applyUpdates = _a.applyUpdates, specs = _a.specs, layer = _a.layer, startingWith = _a.startingWith;
659
660
  // build up the two lists
660
661
  var nestedIDs = [];
661
662
  var newIDs = [];
@@ -675,6 +676,7 @@ var CacheInternal = /** @class */ (function () {
675
676
  applyUpdates: applyUpdates,
676
677
  specs: specs,
677
678
  layer: layer,
679
+ startingWith: startingWith,
678
680
  });
679
681
  // add the list of new ids to our list
680
682
  newIDs.push.apply(newIDs, __spread(inner.newIDs));
@@ -691,7 +693,7 @@ var CacheInternal = /** @class */ (function () {
691
693
  // we know now that entry is an object
692
694
  var entryObj = entry;
693
695
  // start off building up the embedded id
694
- var linkedID = recordID + "." + key + "[" + id++ + "]";
696
+ var linkedID = recordID + "." + key + "[" + (startingWith + id++) + "]";
695
697
  // figure out if this is an embedded list or a linked one by looking for all of the fields marked as
696
698
  // required to compute the entity's id
697
699
  var embedded = ((_c = this_2.idFields(linkedType)) === null || _c === void 0 ? void 0 : _c.filter(function (field) { return typeof entry[field] === 'undefined'; }).length) > 0;
@@ -716,23 +718,6 @@ var CacheInternal = /** @class */ (function () {
716
718
  return "continue";
717
719
  }
718
720
  }
719
- // if the field is marked for pagination and we are looking at edges, we need
720
- // to use the underlying node for the id because the embedded key will conflict
721
- // with entries in the previous loaded value.
722
- // NOTE: this approach might cause weird behavior of a node is loaded in the same
723
- // location in two different pages. In practice, nodes rarely show up in the same
724
- // connection so it might not be a problem.
725
- if (key === 'edges' &&
726
- entryObj.node &&
727
- entryObj.node.__typename) {
728
- var node = entryObj.node;
729
- // @ts-ignore
730
- var typename_1 = node.__typename;
731
- var nodeID = this_2.id(typename_1, node);
732
- if (nodeID) {
733
- linkedID += '#' + nodeID;
734
- }
735
- }
736
721
  // update the linked fields too
737
722
  this_2.writeSelection({
738
723
  root: exports.rootID,
@@ -6,5 +6,5 @@ export { query, routeQuery, componentQuery } from './query';
6
6
  export { mutation } from './mutation';
7
7
  export { fragment } from './fragment';
8
8
  export { subscription } from './subscription';
9
- export { paginatedQuery } from './pagination';
9
+ export { paginatedQuery, paginatedFragment } from './pagination';
10
10
  export declare function graphql(str: TemplateStringsArray): GraphQLTagResult;
@@ -26,6 +26,7 @@ var subscription_1 = require("./subscription");
26
26
  Object.defineProperty(exports, "subscription", { enumerable: true, get: function () { return subscription_1.subscription; } });
27
27
  var pagination_1 = require("./pagination");
28
28
  Object.defineProperty(exports, "paginatedQuery", { enumerable: true, get: function () { return pagination_1.paginatedQuery; } });
29
+ Object.defineProperty(exports, "paginatedFragment", { enumerable: true, get: function () { return pagination_1.paginatedFragment; } });
29
30
  // this template tag gets removed by the preprocessor so it should never be invoked.
30
31
  // this function needs to return the same value as what the preprocessor leaves behind for type consistency
31
32
  function graphql(str) {
@@ -268,7 +268,6 @@ var RequestContext = /** @class */ (function () {
268
268
  RequestContext.prototype.graphqlErrors = function (payload) {
269
269
  // if we have a list of errors
270
270
  if (payload.errors) {
271
- console.log('registering graphql errors', payload.errors);
272
271
  return this.error(500, payload.errors.map(function (_a) {
273
272
  var message = _a.message;
274
273
  return message;
@@ -71,7 +71,7 @@ declare class CacheInternal {
71
71
  variables?: {};
72
72
  linkedList: LinkedList;
73
73
  }): LinkedList<GraphQLValue>;
74
- extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, }: {
74
+ extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, startingWith, }: {
75
75
  value: GraphQLValue[];
76
76
  recordID: string;
77
77
  key: string;
@@ -82,6 +82,7 @@ declare class CacheInternal {
82
82
  applyUpdates: boolean;
83
83
  fields: SubscriptionSelection;
84
84
  layer: Layer;
85
+ startingWith: number;
85
86
  }): {
86
87
  nestedIDs: LinkedList;
87
88
  newIDs: (string | null)[];
@@ -254,6 +254,7 @@ class CacheInternal {
254
254
  variables: variables,
255
255
  fields,
256
256
  layer,
257
+ startingWith: applyUpdates && update === 'append' ? oldIDs.length : 0,
257
258
  });
258
259
  // if we're supposed to apply this write as an update, we need to figure out how
259
260
  if (applyUpdates && update) {
@@ -492,7 +493,7 @@ class CacheInternal {
492
493
  return this.getSelection({ parent: entry, selection: fields, variables });
493
494
  });
494
495
  }
495
- extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, }) {
496
+ extractNestedListIDs({ value, abstract, recordID, key, linkedType, fields, variables, applyUpdates, specs, layer, startingWith, }) {
496
497
  var _a;
497
498
  // build up the two lists
498
499
  const nestedIDs = [];
@@ -513,6 +514,7 @@ class CacheInternal {
513
514
  applyUpdates,
514
515
  specs,
515
516
  layer,
517
+ startingWith,
516
518
  });
517
519
  // add the list of new ids to our list
518
520
  newIDs.push(...inner.newIDs);
@@ -529,7 +531,7 @@ class CacheInternal {
529
531
  // we know now that entry is an object
530
532
  const entryObj = entry;
531
533
  // start off building up the embedded id
532
- let linkedID = `${recordID}.${key}[${id++}]`;
534
+ let linkedID = `${recordID}.${key}[${startingWith + id++}]`;
533
535
  // figure out if this is an embedded list or a linked one by looking for all of the fields marked as
534
536
  // required to compute the entity's id
535
537
  const embedded = ((_a = this.idFields(linkedType)) === null || _a === void 0 ? void 0 : _a.filter((field) => typeof entry[field] === 'undefined').length) > 0;
@@ -554,23 +556,6 @@ class CacheInternal {
554
556
  continue;
555
557
  }
556
558
  }
557
- // if the field is marked for pagination and we are looking at edges, we need
558
- // to use the underlying node for the id because the embedded key will conflict
559
- // with entries in the previous loaded value.
560
- // NOTE: this approach might cause weird behavior of a node is loaded in the same
561
- // location in two different pages. In practice, nodes rarely show up in the same
562
- // connection so it might not be a problem.
563
- if (key === 'edges' &&
564
- entryObj.node &&
565
- entryObj.node.__typename) {
566
- const node = entryObj.node;
567
- // @ts-ignore
568
- const typename = node.__typename;
569
- let nodeID = this.id(typename, node);
570
- if (nodeID) {
571
- linkedID += '#' + nodeID;
572
- }
573
- }
574
559
  // update the linked fields too
575
560
  this.writeSelection({
576
561
  root: rootID,
@@ -6,5 +6,5 @@ export { query, routeQuery, componentQuery } from './query';
6
6
  export { mutation } from './mutation';
7
7
  export { fragment } from './fragment';
8
8
  export { subscription } from './subscription';
9
- export { paginatedQuery } from './pagination';
9
+ export { paginatedQuery, paginatedFragment } from './pagination';
10
10
  export declare function graphql(str: TemplateStringsArray): GraphQLTagResult;
@@ -5,7 +5,7 @@ export { query, routeQuery, componentQuery } from './query';
5
5
  export { mutation } from './mutation';
6
6
  export { fragment } from './fragment';
7
7
  export { subscription } from './subscription';
8
- export { paginatedQuery } from './pagination';
8
+ export { paginatedQuery, paginatedFragment } from './pagination';
9
9
  // this template tag gets removed by the preprocessor so it should never be invoked.
10
10
  // this function needs to return the same value as what the preprocessor leaves behind for type consistency
11
11
  export function graphql(str) {
@@ -162,7 +162,6 @@ export class RequestContext {
162
162
  graphqlErrors(payload) {
163
163
  // if we have a list of errors
164
164
  if (payload.errors) {
165
- console.log('registering graphql errors', payload.errors);
166
165
  return this.error(500, payload.errors.map(({ message }) => message).join('\n'));
167
166
  }
168
167
  return this.error(500, 'Encountered invalid response: ' + JSON.stringify(payload));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "houdini",
3
- "version": "0.13.0-alpha.3",
3
+ "version": "0.13.0",
4
4
  "description": "The disappearing graphql client for SvelteKit",
5
5
  "scripts": {
6
6
  "build:runtime": "npm run build:runtime:cjs && npm run build:runtime:esm",
@@ -49,7 +49,7 @@
49
49
  "estree-walker": "^2.0.2",
50
50
  "glob": "^7.1.6",
51
51
  "graphql": "^15.5.0",
52
- "houdini-common": "^0.13.0-alpha.2",
52
+ "houdini-common": "^0.13.0",
53
53
  "inquirer": "^7.3.3",
54
54
  "mkdirp": "^1.0.4",
55
55
  "node-fetch": "^2.6.1",
@@ -57,5 +57,5 @@
57
57
  "rollup-plugin-preserve-shebangs": "^0.2.0",
58
58
  "svelte": "^3.34.0"
59
59
  },
60
- "gitHead": "a24e3e7212e93cee2ef554187fa9bc44f57c7da6"
60
+ "gitHead": "db48b5ab58c9c8140f54a7b0a5f9d44208a84f43"
61
61
  }
@@ -384,6 +384,7 @@ class CacheInternal {
384
384
  variables: variables,
385
385
  fields,
386
386
  layer,
387
+ startingWith: applyUpdates && update === 'append' ? oldIDs.length : 0,
387
388
  })
388
389
 
389
390
  // if we're supposed to apply this write as an update, we need to figure out how
@@ -698,6 +699,7 @@ class CacheInternal {
698
699
  applyUpdates,
699
700
  specs,
700
701
  layer,
702
+ startingWith,
701
703
  }: {
702
704
  value: GraphQLValue[]
703
705
  recordID: string
@@ -709,6 +711,7 @@ class CacheInternal {
709
711
  applyUpdates: boolean
710
712
  fields: SubscriptionSelection
711
713
  layer: Layer
714
+ startingWith: number
712
715
  }): { nestedIDs: LinkedList; newIDs: (string | null)[] } {
713
716
  // build up the two lists
714
717
  const nestedIDs: LinkedList = []
@@ -731,6 +734,7 @@ class CacheInternal {
731
734
  applyUpdates,
732
735
  specs,
733
736
  layer,
737
+ startingWith,
734
738
  })
735
739
 
736
740
  // add the list of new ids to our list
@@ -751,7 +755,7 @@ class CacheInternal {
751
755
  const entryObj = entry as GraphQLObject
752
756
 
753
757
  // start off building up the embedded id
754
- let linkedID = `${recordID}.${key}[${id++}]`
758
+ let linkedID = `${recordID}.${key}[${startingWith + id++}]`
755
759
 
756
760
  // figure out if this is an embedded list or a linked one by looking for all of the fields marked as
757
761
  // required to compute the entity's id
@@ -784,26 +788,6 @@ class CacheInternal {
784
788
  }
785
789
  }
786
790
 
787
- // if the field is marked for pagination and we are looking at edges, we need
788
- // to use the underlying node for the id because the embedded key will conflict
789
- // with entries in the previous loaded value.
790
- // NOTE: this approach might cause weird behavior of a node is loaded in the same
791
- // location in two different pages. In practice, nodes rarely show up in the same
792
- // connection so it might not be a problem.
793
- if (
794
- key === 'edges' &&
795
- entryObj.node &&
796
- (entryObj.node as { __typename: string }).__typename
797
- ) {
798
- const node = entryObj.node as {}
799
- // @ts-ignore
800
- const typename = node.__typename
801
- let nodeID = this.id(typename, node)
802
- if (nodeID) {
803
- linkedID += '#' + nodeID
804
- }
805
- }
806
-
807
791
  // update the linked fields too
808
792
  this.writeSelection({
809
793
  root: rootID,
@@ -464,8 +464,7 @@ test('remove from connection', function () {
464
464
  expect(cache._internal_unstable.subscriptions.get('User:3', 'firstName')).toHaveLength(1)
465
465
  // make sure we marked the corresponding edge for deletion
466
466
  expect(
467
- cache._internal_unstable.storage.topLayer.operations['User:1.friends.edges[0]#User:2']
468
- .deleted
467
+ cache._internal_unstable.storage.topLayer.operations['User:1.friends.edges[0]'].deleted
469
468
  ).toBeTruthy()
470
469
  })
471
470
 
package/runtime/index.ts CHANGED
@@ -8,7 +8,7 @@ export { query, routeQuery, componentQuery } from './query'
8
8
  export { mutation } from './mutation'
9
9
  export { fragment } from './fragment'
10
10
  export { subscription } from './subscription'
11
- export { paginatedQuery } from './pagination'
11
+ export { paginatedQuery, paginatedFragment } from './pagination'
12
12
 
13
13
  // this template tag gets removed by the preprocessor so it should never be invoked.
14
14
  // this function needs to return the same value as what the preprocessor leaves behind for type consistency
@@ -289,7 +289,6 @@ export class RequestContext {
289
289
  graphqlErrors(payload: { errors?: GraphQLError[] }) {
290
290
  // if we have a list of errors
291
291
  if (payload.errors) {
292
- console.log('registering graphql errors', payload.errors)
293
292
  return this.error(500, payload.errors.map(({ message }) => message).join('\n'))
294
293
  }
295
294
 
@@ -1,35 +0,0 @@
1
- import { SubscriptionSelection, ListWhen, SubscriptionSpec } from '../types';
2
- import { Cache } from './cache';
3
- import { Record } from './record';
4
- export declare class ListHandler {
5
- readonly record: Record;
6
- readonly key: string;
7
- readonly listType: string;
8
- private cache;
9
- readonly selection: SubscriptionSelection;
10
- private _when?;
11
- private filters?;
12
- readonly name: string;
13
- readonly parentID: SubscriptionSpec['parentID'];
14
- private connection;
15
- constructor({ name, cache, record, key, listType, selection, when, filters, parentID, connection, }: {
16
- name: string;
17
- connection: boolean;
18
- cache: Cache;
19
- record: Record;
20
- key: string;
21
- listType: string;
22
- selection: SubscriptionSelection;
23
- when?: ListWhen;
24
- filters?: ListHandler['filters'];
25
- parentID?: SubscriptionSpec['parentID'];
26
- });
27
- when(when?: ListWhen): ListHandler;
28
- append(selection: SubscriptionSelection, data: {}, variables?: {}): void;
29
- prepend(selection: SubscriptionSelection, data: {}, variables?: {}): void;
30
- addToList(selection: SubscriptionSelection, data: {}, variables: {}, where: 'first' | 'last'): void;
31
- removeID(id: string, variables?: {}): void;
32
- remove(data: {}, variables?: {}): void;
33
- private validateWhen;
34
- [Symbol.iterator](): Generator<Record, void, unknown>;
35
- }
@@ -1,203 +0,0 @@
1
- export class ListHandler {
2
- constructor({ name, cache, record, key, listType, selection, when, filters, parentID, connection, }) {
3
- this.record = record;
4
- this.key = key;
5
- this.listType = listType;
6
- this.cache = cache;
7
- this.selection = selection;
8
- this._when = when;
9
- this.filters = filters;
10
- this.name = name;
11
- this.parentID = parentID;
12
- this.connection = connection;
13
- }
14
- // when applies a when condition to a new list pointing to the same spot
15
- when(when) {
16
- return new ListHandler({
17
- cache: this.cache,
18
- record: this.record,
19
- key: this.key,
20
- listType: this.listType,
21
- selection: this.selection,
22
- when,
23
- filters: this.filters,
24
- parentID: this.parentID,
25
- name: this.name,
26
- connection: this.connection,
27
- });
28
- }
29
- append(selection, data, variables = {}) {
30
- return this.addToList(selection, data, variables, 'last');
31
- }
32
- prepend(selection, data, variables = {}) {
33
- return this.addToList(selection, data, variables, 'first');
34
- }
35
- addToList(selection, data, variables = {}, where) {
36
- // figure out the id of the type we are adding
37
- const dataID = this.cache.id(this.listType, data);
38
- // if there are conditions for this operation
39
- if (!this.validateWhen() || !dataID) {
40
- return;
41
- }
42
- // we are going to implement the insert as a write with an update flag on a field
43
- // that matches the key of the list. We'll have to embed the lists data and selection
44
- // in the appropriate objects
45
- let insertSelection = selection;
46
- let insertData = data;
47
- // if we are wrapping a connection, we have to embed the data under edges > node
48
- if (this.connection) {
49
- insertSelection = {
50
- newEntry: {
51
- keyRaw: this.key,
52
- type: 'Connection',
53
- fields: {
54
- edges: {
55
- keyRaw: 'edges',
56
- type: 'ConnectionEdge',
57
- update: (where === 'first' ? 'prepend' : 'append'),
58
- fields: {
59
- node: {
60
- type: this.listType,
61
- keyRaw: 'node',
62
- fields: {
63
- ...selection,
64
- __typename: {
65
- keyRaw: '__typename',
66
- type: 'String',
67
- },
68
- },
69
- },
70
- },
71
- },
72
- },
73
- },
74
- };
75
- insertData = {
76
- newEntry: {
77
- edges: [{ node: { ...data, __typename: this.listType } }],
78
- },
79
- };
80
- }
81
- else {
82
- insertSelection = {
83
- newEntries: {
84
- keyRaw: this.key,
85
- type: this.listType,
86
- update: (where === 'first' ? 'prepend' : 'append'),
87
- fields: {
88
- ...selection,
89
- __typename: {
90
- keyRaw: '__typename',
91
- type: 'String',
92
- },
93
- },
94
- },
95
- };
96
- insertData = {
97
- newEntries: [{ ...data, __typename: this.listType }],
98
- };
99
- }
100
- // get the list of specs that are subscribing to the list
101
- const subscribers = this.record.getSubscribers(this.key);
102
- // look up the new record in the cache
103
- const newRecord = this.cache.internal.record(dataID);
104
- // walk down the list fields relative to the new record
105
- // and make sure all of the list's subscribers are listening
106
- // to that object
107
- this.cache.internal.insertSubscribers(newRecord, selection, variables, ...subscribers);
108
- // update the cache with the data we just found
109
- this.cache.write({
110
- selection: insertSelection,
111
- data: insertData,
112
- variables,
113
- parent: this.record.id,
114
- applyUpdates: true,
115
- });
116
- }
117
- removeID(id, variables = {}) {
118
- // if there are conditions for this operation
119
- if (!this.validateWhen()) {
120
- return;
121
- }
122
- // if we are removing from a connection, the id we are removing from
123
- // has to be computed
124
- let parentID = this.record.id;
125
- let targetID = id;
126
- let targetKey = this.key;
127
- // if we are removing a record from a connection we have to walk through
128
- // some embedded references first
129
- if (this.connection) {
130
- const embeddedConnection = this.record.linkedRecord(this.key);
131
- if (!embeddedConnection) {
132
- return;
133
- }
134
- // look at every embedded edge for the one with a node corresponding to the element
135
- // we want to delete
136
- for (const edge of embeddedConnection.flatLinkedList('edges') || []) {
137
- if (!edge) {
138
- continue;
139
- }
140
- // look at the edge's node
141
- const node = edge.linkedRecord('node');
142
- if (!node) {
143
- continue;
144
- }
145
- // if we found the node
146
- if (node.id === id) {
147
- targetID = edge.id;
148
- }
149
- }
150
- parentID = embeddedConnection.id;
151
- targetKey = 'edges';
152
- }
153
- // get the list of specs that are subscribing to the list
154
- const subscribers = this.record.getSubscribers(this.key);
155
- // disconnect record from any subscriptions associated with the list
156
- this.cache.internal.unsubscribeSelection(this.cache.internal.record(targetID),
157
- // if we're unsubscribing from a connection, only unsubscribe from the target
158
- this.connection ? this.selection.edges.fields : this.selection, variables, ...subscribers.map(({ set }) => set));
159
- // remove the target from the parent
160
- this.cache.internal.record(parentID).removeFromLinkedList(targetKey, targetID);
161
- // notify the subscribers about the change
162
- this.cache.internal.notifySubscribers(subscribers, variables);
163
- // if we are removing from a connection, delete the embedded edge holding the record
164
- if (this.connection) {
165
- this.cache.internal.deleteID(targetID);
166
- }
167
- }
168
- remove(data, variables = {}) {
169
- const targetID = this.cache.id(this.listType, data);
170
- if (!targetID) {
171
- return;
172
- }
173
- // figure out the id of the type we are adding
174
- this.removeID(targetID, variables);
175
- }
176
- validateWhen() {
177
- // if this when doesn't apply, we should look at others to see if we should update those behind the scenes
178
- let ok = true;
179
- // if there are conditions for this operation
180
- if (this._when) {
181
- // we only NEED there to be target filters for must's
182
- const targets = this.filters;
183
- // check must's first
184
- if (this._when.must && targets) {
185
- ok = Object.entries(this._when.must).reduce((prev, [key, value]) => Boolean(prev && targets[key] == value), ok);
186
- }
187
- // if there are no targets, nothing could be true that can we compare against
188
- if (this._when.must_not) {
189
- ok =
190
- !targets ||
191
- Object.entries(this._when.must_not).reduce((prev, [key, value]) => Boolean(prev && targets[key] != value), ok);
192
- }
193
- }
194
- return ok;
195
- }
196
- // iterating over the list handler should be the same as iterating over
197
- // the underlying linked list
198
- *[Symbol.iterator]() {
199
- for (let record of this.record.flatLinkedList(this.key)) {
200
- yield record;
201
- }
202
- }
203
- }
@@ -1,40 +0,0 @@
1
- import { GraphQLValue, Maybe, SubscriptionSpec } from '../types';
2
- import { Cache, LinkedList } from './cache';
3
- export declare class Record {
4
- fields: {
5
- [key: string]: GraphQLValue;
6
- };
7
- keyVersions: {
8
- [key: string]: Set<string>;
9
- };
10
- readonly id: string;
11
- private subscribers;
12
- private recordLinks;
13
- listLinks: {
14
- [key: string]: LinkedList;
15
- };
16
- private cache;
17
- private referenceCounts;
18
- private lifetimes;
19
- constructor(cache: Cache, id: string);
20
- allSubscribers(): SubscriptionSpec[];
21
- getField(fieldName: string): GraphQLValue;
22
- writeField(fieldName: string, value: GraphQLValue): void;
23
- writeRecordLink(fieldName: string, value: string | null): void;
24
- writeListLink(fieldName: string, value: LinkedList): void;
25
- linkedRecord(fieldName: string): Record;
26
- linkedRecordID(fieldName: string): string;
27
- linkedListIDs(fieldName: string): (string | null)[];
28
- flatLinkedList(fieldName: string): Maybe<Record>[];
29
- appendLinkedList(fieldName: string, id: string): void;
30
- prependLinkedList(fieldName: string, id: string): void;
31
- removeFromLinkedList(fieldName: string, id: string): void;
32
- addSubscriber(rawKey: string, key: string, ...specs: SubscriptionSpec[]): void;
33
- getSubscribers(fieldName: string): SubscriptionSpec[];
34
- forgetSubscribers(...targets: SubscriptionSpec[]): void;
35
- removeAllSubscribers(): void;
36
- removeAllSubscriptionVersions(keyRaw: string, spec: SubscriptionSpec): void;
37
- private forgetSubscribers_walk;
38
- removeSubscribers(fields: string[], sets: SubscriptionSpec['set'][]): void;
39
- onGcTick(): void;
40
- }
@@ -1,195 +0,0 @@
1
- // for the most part, this is a very low-level/dumb class that is meant to track state related
2
- // to a specific entity in the cached graph.
3
- export class Record {
4
- constructor(cache, id) {
5
- this.fields = {};
6
- this.keyVersions = {};
7
- this.subscribers = {};
8
- this.recordLinks = {};
9
- this.listLinks = {};
10
- this.referenceCounts = {};
11
- this.lifetimes = new Map();
12
- this.cache = cache;
13
- this.id = id;
14
- }
15
- allSubscribers() {
16
- return Object.values(this.subscribers).flatMap((subscribers) => subscribers);
17
- }
18
- getField(fieldName) {
19
- return this.fields[fieldName];
20
- }
21
- writeField(fieldName, value) {
22
- this.fields[fieldName] = value;
23
- }
24
- writeRecordLink(fieldName, value) {
25
- this.recordLinks[fieldName] = value;
26
- }
27
- writeListLink(fieldName, value) {
28
- this.listLinks[fieldName] = value;
29
- }
30
- linkedRecord(fieldName) {
31
- const linkedRecord = this.recordLinks[fieldName];
32
- if (linkedRecord === null) {
33
- return null;
34
- }
35
- return this.cache.internal.getRecord(linkedRecord);
36
- }
37
- linkedRecordID(fieldName) {
38
- return this.recordLinks[fieldName];
39
- }
40
- linkedListIDs(fieldName) {
41
- const ids = [];
42
- // we need to flatten the list links
43
- const unvisited = [this.listLinks[fieldName] || []];
44
- while (unvisited.length > 0) {
45
- const target = unvisited.shift();
46
- for (const id of target) {
47
- if (Array.isArray(id)) {
48
- unvisited.push(id);
49
- continue;
50
- }
51
- ids.push(id);
52
- }
53
- }
54
- return ids;
55
- }
56
- flatLinkedList(fieldName) {
57
- return this.linkedListIDs(fieldName).map(this.cache.internal.getRecord);
58
- }
59
- appendLinkedList(fieldName, id) {
60
- // this could be the first time we've seen the list
61
- if (!this.listLinks[fieldName]) {
62
- this.listLinks[fieldName] = [];
63
- }
64
- this.listLinks[fieldName].push(id);
65
- }
66
- prependLinkedList(fieldName, id) {
67
- // this could be the first time we've seen the list
68
- if (!this.listLinks[fieldName]) {
69
- this.listLinks[fieldName] = [];
70
- }
71
- this.listLinks[fieldName].unshift(id);
72
- }
73
- removeFromLinkedList(fieldName, id) {
74
- this.listLinks[fieldName] = (this.listLinks[fieldName] || []).filter((link) => link !== id);
75
- }
76
- addSubscriber(rawKey, key, ...specs) {
77
- // if this is the first time we've seen the raw key
78
- if (!this.keyVersions[rawKey]) {
79
- this.keyVersions[rawKey] = new Set();
80
- }
81
- // add this version of the key if we need to
82
- this.keyVersions[rawKey].add(key);
83
- // the existing list
84
- const existingSubscribers = (this.subscribers[key] || []).map(({ set }) => set);
85
- // the list of new subscribers
86
- const newSubscribers = specs.filter(({ set }) => !existingSubscribers.includes(set));
87
- this.subscribers[key] = this.getSubscribers(key).concat(...newSubscribers);
88
- // if this is the first time we've seen this key
89
- if (!this.referenceCounts[key]) {
90
- this.referenceCounts[key] = new Map();
91
- }
92
- const counts = this.referenceCounts[key];
93
- // clear the lifetime count for the field
94
- this.lifetimes.delete(key);
95
- // increment the reference count for every subscriber
96
- for (const spec of specs) {
97
- // we're going to increment the current value by one
98
- counts.set(spec.set, (counts.get(spec.set) || 0) + 1);
99
- }
100
- }
101
- getSubscribers(fieldName) {
102
- return this.subscribers[fieldName] || [];
103
- }
104
- forgetSubscribers(...targets) {
105
- this.forgetSubscribers_walk(targets.map(({ set }) => set));
106
- }
107
- removeAllSubscribers() {
108
- this.forgetSubscribers(...this.allSubscribers());
109
- }
110
- removeAllSubscriptionVersions(keyRaw, spec) {
111
- // visit every version of the key we've seen and remove the spec from the list of subscribers
112
- const versions = this.keyVersions[keyRaw];
113
- // if there are no known versions, we're done
114
- if (!versions) {
115
- return;
116
- }
117
- this.removeSubscribers([...this.keyVersions[keyRaw]], [spec.set]);
118
- }
119
- forgetSubscribers_walk(targets, visited = []) {
120
- var _a;
121
- // clean up any subscribers that reference the set
122
- this.removeSubscribers(Object.keys(this.subscribers), targets);
123
- visited.push(this.id);
124
- // walk down to every record we know about
125
- const linkedIDs = Object.keys(this.recordLinks);
126
- for (const key of Object.keys(this.listLinks)) {
127
- for (const child of this.linkedListIDs(key)) {
128
- if (child !== null) {
129
- linkedIDs.push(child);
130
- }
131
- }
132
- }
133
- for (const linkedRecordID of linkedIDs) {
134
- if (visited.includes(linkedRecordID)) {
135
- continue;
136
- }
137
- (_a = this.cache.internal.getRecord(linkedRecordID)) === null || _a === void 0 ? void 0 : _a.forgetSubscribers_walk(targets, visited);
138
- }
139
- }
140
- removeSubscribers(fields, sets) {
141
- var _a;
142
- // clean up any subscribers that reference the set
143
- for (const fieldName of fields) {
144
- // build up a list of the sets we actually need to remove after
145
- // checking reference counts
146
- let targets = [];
147
- for (const set of sets) {
148
- // if we dont know this field/set combo, there's nothing to do (probably a bug somewhere)
149
- if (!((_a = this.referenceCounts[fieldName]) === null || _a === void 0 ? void 0 : _a.has(set))) {
150
- continue;
151
- }
152
- const counts = this.referenceCounts[fieldName];
153
- const newVal = (counts.get(set) || 0) - 1;
154
- // decrement the reference of every field
155
- counts.set(set, newVal);
156
- // if that was the last reference we knew of
157
- if (newVal <= 0) {
158
- targets.push(set);
159
- // remove the reference to the set function
160
- counts.delete(set);
161
- }
162
- }
163
- // we do need to remove the set from the list
164
- this.subscribers[fieldName] = this.getSubscribers(fieldName).filter(({ set }) => !targets.includes(set));
165
- }
166
- }
167
- // this function is invoked by the cache when the garbage collector is ticking
168
- onGcTick() {
169
- const fields = Object.keys(this.fields)
170
- .concat(Object.keys(this.listLinks))
171
- .concat(Object.keys(this.recordLinks));
172
- // any fields with no reference counts need to be decremented
173
- for (const field of fields) {
174
- // {value} has a key for every subscriber - can check for no subscribers by looking
175
- // at the number of fields in the value
176
- if (!this.referenceCounts[field] || this.referenceCounts[field].size === 0) {
177
- // lower the current count by 1
178
- const previousCount = this.lifetimes.get(field) || 0;
179
- this.lifetimes.set(field, previousCount + 1);
180
- // if the lifetime exceeds the cache's buffer size we should remove the field, linked record, or list
181
- if (this.lifetimes.get(field) > this.cache.cacheBufferSize) {
182
- delete this.fields[field];
183
- delete this.recordLinks[field];
184
- delete this.recordLinks[field];
185
- // if we dont have any data left, delete the record from the cache
186
- if (Object.keys(this.fields).length === 0 &&
187
- Object.keys(this.recordLinks).length === 0 &&
188
- Object.keys(this.recordLinks).length === 0) {
189
- this.cache.internal.deleteID(this.id);
190
- }
191
- }
192
- }
193
- }
194
- }
195
- }