houdini 0.13.6 → 0.13.7-alpha.2

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 (36) hide show
  1. package/build/cmd.js +0 -0
  2. package/build/runtime/cache/cache.d.ts +4 -2
  3. package/build/runtime/cache/cache.js +7 -7
  4. package/build/runtime/cache/storage.d.ts +1 -0
  5. package/build/runtime/cache/storage.js +7 -0
  6. package/build/runtime/mutation.d.ts +4 -1
  7. package/build/runtime/mutation.js +43 -8
  8. package/build/runtime/scalars.d.ts +5 -0
  9. package/build/runtime/scalars.js +43 -1
  10. package/build/runtime-cjs/cache/cache.d.ts +4 -2
  11. package/build/runtime-cjs/cache/cache.js +7 -7
  12. package/build/runtime-cjs/cache/storage.d.ts +1 -0
  13. package/build/runtime-cjs/cache/storage.js +7 -0
  14. package/build/runtime-cjs/mutation.d.ts +4 -1
  15. package/build/runtime-cjs/mutation.js +43 -8
  16. package/build/runtime-cjs/scalars.d.ts +5 -0
  17. package/build/runtime-cjs/scalars.js +43 -1
  18. package/build/runtime-esm/cache/cache.d.ts +4 -2
  19. package/build/runtime-esm/cache/cache.js +7 -7
  20. package/build/runtime-esm/cache/list.d.ts +35 -0
  21. package/build/runtime-esm/cache/list.js +203 -0
  22. package/build/runtime-esm/cache/record.d.ts +40 -0
  23. package/build/runtime-esm/cache/record.js +195 -0
  24. package/build/runtime-esm/cache/storage.d.ts +1 -0
  25. package/build/runtime-esm/cache/storage.js +7 -0
  26. package/build/runtime-esm/mutation.d.ts +4 -1
  27. package/build/runtime-esm/mutation.js +43 -3
  28. package/build/runtime-esm/scalars.d.ts +5 -0
  29. package/build/runtime-esm/scalars.js +39 -0
  30. package/package.json +2 -2
  31. package/runtime/cache/cache.ts +10 -7
  32. package/runtime/cache/storage.ts +11 -0
  33. package/runtime/cache/tests/subscriptions.test.ts +169 -0
  34. package/runtime/mutation.ts +57 -5
  35. package/runtime/scalars.test.ts +322 -14
  36. package/runtime/scalars.ts +57 -0
package/build/cmd.js CHANGED
File without changes
@@ -14,8 +14,9 @@ export declare class Cache {
14
14
  selection: SubscriptionSelection;
15
15
  variables?: {};
16
16
  parent?: string;
17
- layer?: LayerID;
17
+ layer?: LayerID | null;
18
18
  applyUpdates?: boolean;
19
+ forceNotify?: boolean;
19
20
  }): LayerID;
20
21
  read(...args: Parameters<CacheInternal['getSelection']>): {
21
22
  data: GraphQLObject | null;
@@ -42,7 +43,7 @@ declare class CacheInternal {
42
43
  cache: Cache;
43
44
  lifetimes: GarbageCollector;
44
45
  });
45
- writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, toNotify, }: {
46
+ writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, forceNotify, toNotify, }: {
46
47
  data: {
47
48
  [key: string]: GraphQLValue;
48
49
  };
@@ -55,6 +56,7 @@ declare class CacheInternal {
55
56
  layer: Layer;
56
57
  toNotify?: SubscriptionSpec[];
57
58
  applyUpdates?: boolean;
59
+ forceNotify?: boolean;
58
60
  }): SubscriptionSpec[];
59
61
  getSelection({ selection, parent, variables, }: {
60
62
  selection: SubscriptionSelection;
@@ -187,7 +187,7 @@ var CacheInternal = /** @class */ (function () {
187
187
  var e_2, _b;
188
188
  var _this = this;
189
189
  var _c;
190
- var data = _a.data, selection = _a.selection, _d = _a.variables, variables = _d === void 0 ? {} : _d, _e = _a.root, root = _e === void 0 ? exports.rootID : _e, _f = _a.parent, parent = _f === void 0 ? exports.rootID : _f, _g = _a.applyUpdates, applyUpdates = _g === void 0 ? false : _g, layer = _a.layer, _h = _a.toNotify, toNotify = _h === void 0 ? [] : _h;
190
+ var data = _a.data, selection = _a.selection, _d = _a.variables, variables = _d === void 0 ? {} : _d, _e = _a.root, root = _e === void 0 ? exports.rootID : _e, _f = _a.parent, parent = _f === void 0 ? exports.rootID : _f, _g = _a.applyUpdates, applyUpdates = _g === void 0 ? false : _g, layer = _a.layer, forceNotify = _a.forceNotify, _h = _a.toNotify, toNotify = _h === void 0 ? [] : _h;
191
191
  // if the cache is disabled, dont do anything
192
192
  if (this._disabled) {
193
193
  return [];
@@ -210,14 +210,14 @@ var CacheInternal = /** @class */ (function () {
210
210
  // look up the previous value
211
211
  var _h = this_1.storage.get(parent, key), previousValue = _h.value, displayLayers = _h.displayLayers;
212
212
  // if the layer we are updating is the top most layer for the field
213
- // then its value is "live", it is providing the current value and
213
+ // then its value is "live". It is providing the current value and
214
214
  // subscribers need to know if the value changed
215
- var displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
215
+ var displayLayer = layer.isDisplayLayer(displayLayers);
216
216
  // if we are writing to the display layer we need to refresh the lifetime of the value
217
217
  if (displayLayer) {
218
218
  this_1.lifetimes.resetLifetime(parent, key);
219
219
  }
220
- // any non-scalar is defined as a field with no selection
220
+ // any scalar is defined as a field with no selection
221
221
  if (!fields) {
222
222
  // the value to write to the layer
223
223
  var newValue = value;
@@ -234,7 +234,7 @@ var CacheInternal = /** @class */ (function () {
234
234
  }
235
235
  // if the value changed on a layer that impacts the current latest value
236
236
  var valueChanged = JSON.stringify(newValue) !== JSON.stringify(previousValue);
237
- if (valueChanged && displayLayer) {
237
+ if ((valueChanged || forceNotify) && displayLayer) {
238
238
  // we need to add the fields' subscribers to the set of callbacks
239
239
  // we need to invoke
240
240
  toNotify.push.apply(toNotify, __spread(currentSubcribers));
@@ -290,7 +290,7 @@ var CacheInternal = /** @class */ (function () {
290
290
  // write the link to the layer
291
291
  layer.writeLink(parent, key, linkedID);
292
292
  // if the link target of this field changed and it was responsible for the current subscription
293
- if (linkedID && displayLayer && linkChange) {
293
+ if (linkedID && displayLayer && (linkChange || forceNotify)) {
294
294
  // we need to clear the subscriptions in the previous link
295
295
  // and add them to the new link
296
296
  if (previousValue && typeof previousValue === 'string') {
@@ -447,7 +447,7 @@ var CacheInternal = /** @class */ (function () {
447
447
  // is still valid would not be triggered
448
448
  var contentChanged = JSON.stringify(linkedIDs) !== JSON.stringify(oldIDs_2);
449
449
  // we need to look at the last time we saw each subscriber to check if they need to be added to the spec
450
- if (contentChanged) {
450
+ if (contentChanged || forceNotify) {
451
451
  toNotify.push.apply(toNotify, __spread(currentSubcribers));
452
452
  }
453
453
  try {
@@ -32,6 +32,7 @@ export declare class Layer {
32
32
  getOperations(id: string, field: string): Operation[] | undefined;
33
33
  writeField(id: string, field: string, value: GraphQLField): LayerID;
34
34
  writeLink(id: string, field: string, value: null | string | LinkedList): LayerID;
35
+ isDisplayLayer(displayLayers: number[]): boolean;
35
36
  clear(): void;
36
37
  applyDeletes(): void;
37
38
  delete(id: string): void;
@@ -351,7 +351,14 @@ var Layer = /** @class */ (function () {
351
351
  this.links[id] = __assign(__assign({}, this.links[id]), (_b = {}, _b[field] = value, _b));
352
352
  return this.id;
353
353
  };
354
+ Layer.prototype.isDisplayLayer = function (displayLayers) {
355
+ return (displayLayers.length === 0 ||
356
+ displayLayers.includes(this.id) ||
357
+ Math.max.apply(Math, __spread(displayLayers)) < this.id);
358
+ };
354
359
  Layer.prototype.clear = function () {
360
+ // before we clear the data of the layer, look for any subscribers that need to be updated
361
+ // now that everything has been notified we can reset the data
355
362
  this.links = {};
356
363
  this.fields = {};
357
364
  this.operations = {};
@@ -1,2 +1,5 @@
1
1
  import { Operation, GraphQLTagResult } from './types';
2
- export declare function mutation<_Mutation extends Operation<any, any>>(document: GraphQLTagResult): (_input: _Mutation['input']) => Promise<_Mutation['result']>;
2
+ export declare type MutationConfig<_Mutation extends Operation<any, any>> = {
3
+ optimisticResponse: _Mutation['result'];
4
+ };
5
+ export declare function mutation<_Mutation extends Operation<any, any>>(document: GraphQLTagResult): (_input: _Mutation['input'], config?: MutationConfig<_Mutation>) => Promise<_Mutation['result']>;
@@ -62,30 +62,65 @@ function mutation(document) {
62
62
  // grab the session from the adapter
63
63
  var sessionStore = adapter_mjs_1.getSession();
64
64
  // return an async function that sends the mutation go the server
65
- return function (variables) { return __awaiter(_this, void 0, void 0, function () {
66
- var result, error_1;
65
+ return function (variables, mutationConfig) { return __awaiter(_this, void 0, void 0, function () {
66
+ var optimisticResponse, layer, result, error_1;
67
67
  return __generator(this, function (_a) {
68
68
  switch (_a.label) {
69
69
  case 0:
70
- _a.trys.push([0, 2, , 3]);
70
+ optimisticResponse = mutationConfig === null || mutationConfig === void 0 ? void 0 : mutationConfig.optimisticResponse;
71
+ layer = cache_1.default._internal_unstable.storage.createLayer(true);
72
+ // if there is an optimistic response then we need to write the value immediately
73
+ if (optimisticResponse) {
74
+ cache_1.default.write({
75
+ selection: artifact.selection,
76
+ // make sure that any scalar values get processed into something we can cache
77
+ data: scalars_1.marshalSelection({
78
+ config: config,
79
+ selection: artifact.selection,
80
+ data: optimisticResponse,
81
+ }),
82
+ variables: variables,
83
+ layer: layer.id,
84
+ });
85
+ }
86
+ _a.label = 1;
87
+ case 1:
88
+ _a.trys.push([1, 3, , 4]);
71
89
  return [4 /*yield*/, network_1.executeQuery(artifact, scalars_1.marshalInputs({
72
90
  input: variables,
73
91
  artifact: document.artifact,
74
92
  config: config,
75
- }), sessionStore, false)];
76
- case 1:
93
+ }), sessionStore, false)
94
+ // clear the layer holding any mutation results
95
+ ];
96
+ case 2:
77
97
  result = (_a.sent()).result;
98
+ // clear the layer holding any mutation results
99
+ layer.clear();
100
+ // write the result of the mutation to the cache
78
101
  cache_1.default.write({
79
102
  selection: artifact.selection,
80
103
  data: result.data,
81
104
  variables: variables,
105
+ // if we had an optimistic response we need to write to the appropriate layer
106
+ layer: layer.id,
107
+ // anything that we right here should notify the parents even if the content didn't change
108
+ // this is to avoid a situation where the value before the layer clear is the same as
109
+ // the response from the mutation but the optimistic result was incorrect and changed the display value
110
+ forceNotify: true,
82
111
  });
83
- // unmarshal any scalars on the body
112
+ // merge the layer back into the cache
113
+ cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
114
+ // turn any scalars in the response into their complex form
84
115
  return [2 /*return*/, scalars_1.unmarshalSelection(config, artifact.selection, result.data)];
85
- case 2:
116
+ case 3:
86
117
  error_1 = _a.sent();
118
+ // if the mutation failed, roll the layer back and delete it
119
+ layer.clear();
120
+ cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
121
+ // bubble the mutation error up to the caller
87
122
  throw error_1;
88
- case 3: return [2 /*return*/];
123
+ case 4: return [2 /*return*/];
89
124
  }
90
125
  });
91
126
  }); };
@@ -1,5 +1,10 @@
1
1
  import type { Config } from 'houdini-common';
2
2
  import { MutationArtifact, QueryArtifact, SubscriptionArtifact, SubscriptionSelection } from './types';
3
+ export declare function marshalSelection({ config, selection, data, }: {
4
+ config: Config;
5
+ selection: SubscriptionSelection;
6
+ data: unknown;
7
+ }): {} | null | undefined;
3
8
  export declare function marshalInputs<T>({ artifact, config, input, rootType, }: {
4
9
  artifact: QueryArtifact | MutationArtifact | SubscriptionArtifact;
5
10
  config: Config;
@@ -16,7 +16,49 @@ var __read = (this && this.__read) || function (o, n) {
16
16
  return ar;
17
17
  };
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.isScalar = exports.unmarshalSelection = exports.marshalInputs = void 0;
19
+ exports.isScalar = exports.unmarshalSelection = exports.marshalInputs = exports.marshalSelection = void 0;
20
+ function marshalSelection(_a) {
21
+ var config = _a.config, selection = _a.selection, data = _a.data;
22
+ if (data === null || typeof data === 'undefined') {
23
+ return data;
24
+ }
25
+ // if we are looking at a list
26
+ if (Array.isArray(data)) {
27
+ // unmarshal every entry in the list
28
+ return data.map(function (val) { return marshalSelection({ config: config, selection: selection, data: val }); });
29
+ }
30
+ // we're looking at an object, build it up from the current input
31
+ return Object.fromEntries(Object.entries(data).map(function (_a) {
32
+ var _b;
33
+ var _c = __read(_a, 2), fieldName = _c[0], value = _c[1];
34
+ // look up the type for the field
35
+ var _d = selection[fieldName], type = _d.type, fields = _d.fields;
36
+ // if we don't have type information for this field, just use it directly
37
+ // it's most likely a non-custom scalars or enums
38
+ if (!type) {
39
+ return [fieldName, value];
40
+ }
41
+ // if there is a sub selection, walk down the selection
42
+ if (fields) {
43
+ return [fieldName, marshalSelection({ config: config, selection: fields, data: value })];
44
+ }
45
+ // is the type something that requires marshaling
46
+ if ((_b = config.scalars) === null || _b === void 0 ? void 0 : _b[type]) {
47
+ var marshalFn = config.scalars[type].marshal;
48
+ if (!marshalFn) {
49
+ throw new Error("scalar type " + type + " is missing a `marshal` function. see https://github.com/AlecAivazis/houdini#%EF%B8%8Fcustom-scalars");
50
+ }
51
+ if (Array.isArray(value)) {
52
+ return [fieldName, value.map(marshalFn)];
53
+ }
54
+ return [fieldName, marshalFn(value)];
55
+ }
56
+ // if the type doesn't require marshaling and isn't a referenced type
57
+ // then the type is a scalar that doesn't require marshaling
58
+ return [fieldName, value];
59
+ }));
60
+ }
61
+ exports.marshalSelection = marshalSelection;
20
62
  function marshalInputs(_a) {
21
63
  var artifact = _a.artifact, config = _a.config, input = _a.input, _b = _a.rootType, rootType = _b === void 0 ? '@root' : _b;
22
64
  if (input === null || typeof input === 'undefined') {
@@ -14,8 +14,9 @@ export declare class Cache {
14
14
  selection: SubscriptionSelection;
15
15
  variables?: {};
16
16
  parent?: string;
17
- layer?: LayerID;
17
+ layer?: LayerID | null;
18
18
  applyUpdates?: boolean;
19
+ forceNotify?: boolean;
19
20
  }): LayerID;
20
21
  read(...args: Parameters<CacheInternal['getSelection']>): {
21
22
  data: GraphQLObject | null;
@@ -42,7 +43,7 @@ declare class CacheInternal {
42
43
  cache: Cache;
43
44
  lifetimes: GarbageCollector;
44
45
  });
45
- writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, toNotify, }: {
46
+ writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, forceNotify, toNotify, }: {
46
47
  data: {
47
48
  [key: string]: GraphQLValue;
48
49
  };
@@ -55,6 +56,7 @@ declare class CacheInternal {
55
56
  layer: Layer;
56
57
  toNotify?: SubscriptionSpec[];
57
58
  applyUpdates?: boolean;
59
+ forceNotify?: boolean;
58
60
  }): SubscriptionSpec[];
59
61
  getSelection({ selection, parent, variables, }: {
60
62
  selection: SubscriptionSelection;
@@ -187,7 +187,7 @@ var CacheInternal = /** @class */ (function () {
187
187
  var e_2, _b;
188
188
  var _this = this;
189
189
  var _c;
190
- var data = _a.data, selection = _a.selection, _d = _a.variables, variables = _d === void 0 ? {} : _d, _e = _a.root, root = _e === void 0 ? exports.rootID : _e, _f = _a.parent, parent = _f === void 0 ? exports.rootID : _f, _g = _a.applyUpdates, applyUpdates = _g === void 0 ? false : _g, layer = _a.layer, _h = _a.toNotify, toNotify = _h === void 0 ? [] : _h;
190
+ var data = _a.data, selection = _a.selection, _d = _a.variables, variables = _d === void 0 ? {} : _d, _e = _a.root, root = _e === void 0 ? exports.rootID : _e, _f = _a.parent, parent = _f === void 0 ? exports.rootID : _f, _g = _a.applyUpdates, applyUpdates = _g === void 0 ? false : _g, layer = _a.layer, forceNotify = _a.forceNotify, _h = _a.toNotify, toNotify = _h === void 0 ? [] : _h;
191
191
  // if the cache is disabled, dont do anything
192
192
  if (this._disabled) {
193
193
  return [];
@@ -210,14 +210,14 @@ var CacheInternal = /** @class */ (function () {
210
210
  // look up the previous value
211
211
  var _h = this_1.storage.get(parent, key), previousValue = _h.value, displayLayers = _h.displayLayers;
212
212
  // if the layer we are updating is the top most layer for the field
213
- // then its value is "live", it is providing the current value and
213
+ // then its value is "live". It is providing the current value and
214
214
  // subscribers need to know if the value changed
215
- var displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
215
+ var displayLayer = layer.isDisplayLayer(displayLayers);
216
216
  // if we are writing to the display layer we need to refresh the lifetime of the value
217
217
  if (displayLayer) {
218
218
  this_1.lifetimes.resetLifetime(parent, key);
219
219
  }
220
- // any non-scalar is defined as a field with no selection
220
+ // any scalar is defined as a field with no selection
221
221
  if (!fields) {
222
222
  // the value to write to the layer
223
223
  var newValue = value;
@@ -234,7 +234,7 @@ var CacheInternal = /** @class */ (function () {
234
234
  }
235
235
  // if the value changed on a layer that impacts the current latest value
236
236
  var valueChanged = JSON.stringify(newValue) !== JSON.stringify(previousValue);
237
- if (valueChanged && displayLayer) {
237
+ if ((valueChanged || forceNotify) && displayLayer) {
238
238
  // we need to add the fields' subscribers to the set of callbacks
239
239
  // we need to invoke
240
240
  toNotify.push.apply(toNotify, __spread(currentSubcribers));
@@ -290,7 +290,7 @@ var CacheInternal = /** @class */ (function () {
290
290
  // write the link to the layer
291
291
  layer.writeLink(parent, key, linkedID);
292
292
  // if the link target of this field changed and it was responsible for the current subscription
293
- if (linkedID && displayLayer && linkChange) {
293
+ if (linkedID && displayLayer && (linkChange || forceNotify)) {
294
294
  // we need to clear the subscriptions in the previous link
295
295
  // and add them to the new link
296
296
  if (previousValue && typeof previousValue === 'string') {
@@ -447,7 +447,7 @@ var CacheInternal = /** @class */ (function () {
447
447
  // is still valid would not be triggered
448
448
  var contentChanged = JSON.stringify(linkedIDs) !== JSON.stringify(oldIDs_2);
449
449
  // we need to look at the last time we saw each subscriber to check if they need to be added to the spec
450
- if (contentChanged) {
450
+ if (contentChanged || forceNotify) {
451
451
  toNotify.push.apply(toNotify, __spread(currentSubcribers));
452
452
  }
453
453
  try {
@@ -32,6 +32,7 @@ export declare class Layer {
32
32
  getOperations(id: string, field: string): Operation[] | undefined;
33
33
  writeField(id: string, field: string, value: GraphQLField): LayerID;
34
34
  writeLink(id: string, field: string, value: null | string | LinkedList): LayerID;
35
+ isDisplayLayer(displayLayers: number[]): boolean;
35
36
  clear(): void;
36
37
  applyDeletes(): void;
37
38
  delete(id: string): void;
@@ -351,7 +351,14 @@ var Layer = /** @class */ (function () {
351
351
  this.links[id] = __assign(__assign({}, this.links[id]), (_b = {}, _b[field] = value, _b));
352
352
  return this.id;
353
353
  };
354
+ Layer.prototype.isDisplayLayer = function (displayLayers) {
355
+ return (displayLayers.length === 0 ||
356
+ displayLayers.includes(this.id) ||
357
+ Math.max.apply(Math, __spread(displayLayers)) < this.id);
358
+ };
354
359
  Layer.prototype.clear = function () {
360
+ // before we clear the data of the layer, look for any subscribers that need to be updated
361
+ // now that everything has been notified we can reset the data
355
362
  this.links = {};
356
363
  this.fields = {};
357
364
  this.operations = {};
@@ -1,2 +1,5 @@
1
1
  import { Operation, GraphQLTagResult } from './types';
2
- export declare function mutation<_Mutation extends Operation<any, any>>(document: GraphQLTagResult): (_input: _Mutation['input']) => Promise<_Mutation['result']>;
2
+ export declare type MutationConfig<_Mutation extends Operation<any, any>> = {
3
+ optimisticResponse: _Mutation['result'];
4
+ };
5
+ export declare function mutation<_Mutation extends Operation<any, any>>(document: GraphQLTagResult): (_input: _Mutation['input'], config?: MutationConfig<_Mutation>) => Promise<_Mutation['result']>;
@@ -62,30 +62,65 @@ function mutation(document) {
62
62
  // grab the session from the adapter
63
63
  var sessionStore = adapter_mjs_1.getSession();
64
64
  // return an async function that sends the mutation go the server
65
- return function (variables) { return __awaiter(_this, void 0, void 0, function () {
66
- var result, error_1;
65
+ return function (variables, mutationConfig) { return __awaiter(_this, void 0, void 0, function () {
66
+ var optimisticResponse, layer, result, error_1;
67
67
  return __generator(this, function (_a) {
68
68
  switch (_a.label) {
69
69
  case 0:
70
- _a.trys.push([0, 2, , 3]);
70
+ optimisticResponse = mutationConfig === null || mutationConfig === void 0 ? void 0 : mutationConfig.optimisticResponse;
71
+ layer = cache_1.default._internal_unstable.storage.createLayer(true);
72
+ // if there is an optimistic response then we need to write the value immediately
73
+ if (optimisticResponse) {
74
+ cache_1.default.write({
75
+ selection: artifact.selection,
76
+ // make sure that any scalar values get processed into something we can cache
77
+ data: scalars_1.marshalSelection({
78
+ config: config,
79
+ selection: artifact.selection,
80
+ data: optimisticResponse,
81
+ }),
82
+ variables: variables,
83
+ layer: layer.id,
84
+ });
85
+ }
86
+ _a.label = 1;
87
+ case 1:
88
+ _a.trys.push([1, 3, , 4]);
71
89
  return [4 /*yield*/, network_1.executeQuery(artifact, scalars_1.marshalInputs({
72
90
  input: variables,
73
91
  artifact: document.artifact,
74
92
  config: config,
75
- }), sessionStore, false)];
76
- case 1:
93
+ }), sessionStore, false)
94
+ // clear the layer holding any mutation results
95
+ ];
96
+ case 2:
77
97
  result = (_a.sent()).result;
98
+ // clear the layer holding any mutation results
99
+ layer.clear();
100
+ // write the result of the mutation to the cache
78
101
  cache_1.default.write({
79
102
  selection: artifact.selection,
80
103
  data: result.data,
81
104
  variables: variables,
105
+ // if we had an optimistic response we need to write to the appropriate layer
106
+ layer: layer.id,
107
+ // anything that we right here should notify the parents even if the content didn't change
108
+ // this is to avoid a situation where the value before the layer clear is the same as
109
+ // the response from the mutation but the optimistic result was incorrect and changed the display value
110
+ forceNotify: true,
82
111
  });
83
- // unmarshal any scalars on the body
112
+ // merge the layer back into the cache
113
+ cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
114
+ // turn any scalars in the response into their complex form
84
115
  return [2 /*return*/, scalars_1.unmarshalSelection(config, artifact.selection, result.data)];
85
- case 2:
116
+ case 3:
86
117
  error_1 = _a.sent();
118
+ // if the mutation failed, roll the layer back and delete it
119
+ layer.clear();
120
+ cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
121
+ // bubble the mutation error up to the caller
87
122
  throw error_1;
88
- case 3: return [2 /*return*/];
123
+ case 4: return [2 /*return*/];
89
124
  }
90
125
  });
91
126
  }); };
@@ -1,5 +1,10 @@
1
1
  import type { Config } from 'houdini-common';
2
2
  import { MutationArtifact, QueryArtifact, SubscriptionArtifact, SubscriptionSelection } from './types';
3
+ export declare function marshalSelection({ config, selection, data, }: {
4
+ config: Config;
5
+ selection: SubscriptionSelection;
6
+ data: unknown;
7
+ }): {} | null | undefined;
3
8
  export declare function marshalInputs<T>({ artifact, config, input, rootType, }: {
4
9
  artifact: QueryArtifact | MutationArtifact | SubscriptionArtifact;
5
10
  config: Config;
@@ -16,7 +16,49 @@ var __read = (this && this.__read) || function (o, n) {
16
16
  return ar;
17
17
  };
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.isScalar = exports.unmarshalSelection = exports.marshalInputs = void 0;
19
+ exports.isScalar = exports.unmarshalSelection = exports.marshalInputs = exports.marshalSelection = void 0;
20
+ function marshalSelection(_a) {
21
+ var config = _a.config, selection = _a.selection, data = _a.data;
22
+ if (data === null || typeof data === 'undefined') {
23
+ return data;
24
+ }
25
+ // if we are looking at a list
26
+ if (Array.isArray(data)) {
27
+ // unmarshal every entry in the list
28
+ return data.map(function (val) { return marshalSelection({ config: config, selection: selection, data: val }); });
29
+ }
30
+ // we're looking at an object, build it up from the current input
31
+ return Object.fromEntries(Object.entries(data).map(function (_a) {
32
+ var _b;
33
+ var _c = __read(_a, 2), fieldName = _c[0], value = _c[1];
34
+ // look up the type for the field
35
+ var _d = selection[fieldName], type = _d.type, fields = _d.fields;
36
+ // if we don't have type information for this field, just use it directly
37
+ // it's most likely a non-custom scalars or enums
38
+ if (!type) {
39
+ return [fieldName, value];
40
+ }
41
+ // if there is a sub selection, walk down the selection
42
+ if (fields) {
43
+ return [fieldName, marshalSelection({ config: config, selection: fields, data: value })];
44
+ }
45
+ // is the type something that requires marshaling
46
+ if ((_b = config.scalars) === null || _b === void 0 ? void 0 : _b[type]) {
47
+ var marshalFn = config.scalars[type].marshal;
48
+ if (!marshalFn) {
49
+ throw new Error("scalar type " + type + " is missing a `marshal` function. see https://github.com/AlecAivazis/houdini#%EF%B8%8Fcustom-scalars");
50
+ }
51
+ if (Array.isArray(value)) {
52
+ return [fieldName, value.map(marshalFn)];
53
+ }
54
+ return [fieldName, marshalFn(value)];
55
+ }
56
+ // if the type doesn't require marshaling and isn't a referenced type
57
+ // then the type is a scalar that doesn't require marshaling
58
+ return [fieldName, value];
59
+ }));
60
+ }
61
+ exports.marshalSelection = marshalSelection;
20
62
  function marshalInputs(_a) {
21
63
  var artifact = _a.artifact, config = _a.config, input = _a.input, _b = _a.rootType, rootType = _b === void 0 ? '@root' : _b;
22
64
  if (input === null || typeof input === 'undefined') {
@@ -14,8 +14,9 @@ export declare class Cache {
14
14
  selection: SubscriptionSelection;
15
15
  variables?: {};
16
16
  parent?: string;
17
- layer?: LayerID;
17
+ layer?: LayerID | null;
18
18
  applyUpdates?: boolean;
19
+ forceNotify?: boolean;
19
20
  }): LayerID;
20
21
  read(...args: Parameters<CacheInternal['getSelection']>): {
21
22
  data: GraphQLObject;
@@ -42,7 +43,7 @@ declare class CacheInternal {
42
43
  cache: Cache;
43
44
  lifetimes: GarbageCollector;
44
45
  });
45
- writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, toNotify, }: {
46
+ writeSelection({ data, selection, variables, root, parent, applyUpdates, layer, forceNotify, toNotify, }: {
46
47
  data: {
47
48
  [key: string]: GraphQLValue;
48
49
  };
@@ -55,6 +56,7 @@ declare class CacheInternal {
55
56
  layer: Layer;
56
57
  toNotify?: SubscriptionSpec[];
57
58
  applyUpdates?: boolean;
59
+ forceNotify?: boolean;
58
60
  }): SubscriptionSpec[];
59
61
  getSelection({ selection, parent, variables, }: {
60
62
  selection: SubscriptionSelection;
@@ -105,7 +105,7 @@ class CacheInternal {
105
105
  this._disabled = true;
106
106
  }
107
107
  }
108
- writeSelection({ data, selection, variables = {}, root = rootID, parent = rootID, applyUpdates = false, layer, toNotify = [], }) {
108
+ writeSelection({ data, selection, variables = {}, root = rootID, parent = rootID, applyUpdates = false, layer, forceNotify, toNotify = [], }) {
109
109
  var _a;
110
110
  // if the cache is disabled, dont do anything
111
111
  if (this._disabled) {
@@ -129,14 +129,14 @@ class CacheInternal {
129
129
  // look up the previous value
130
130
  const { value: previousValue, displayLayers } = this.storage.get(parent, key);
131
131
  // if the layer we are updating is the top most layer for the field
132
- // then its value is "live", it is providing the current value and
132
+ // then its value is "live". It is providing the current value and
133
133
  // subscribers need to know if the value changed
134
- const displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
134
+ const displayLayer = layer.isDisplayLayer(displayLayers);
135
135
  // if we are writing to the display layer we need to refresh the lifetime of the value
136
136
  if (displayLayer) {
137
137
  this.lifetimes.resetLifetime(parent, key);
138
138
  }
139
- // any non-scalar is defined as a field with no selection
139
+ // any scalar is defined as a field with no selection
140
140
  if (!fields) {
141
141
  // the value to write to the layer
142
142
  let newValue = value;
@@ -153,7 +153,7 @@ class CacheInternal {
153
153
  }
154
154
  // if the value changed on a layer that impacts the current latest value
155
155
  const valueChanged = JSON.stringify(newValue) !== JSON.stringify(previousValue);
156
- if (valueChanged && displayLayer) {
156
+ if ((valueChanged || forceNotify) && displayLayer) {
157
157
  // we need to add the fields' subscribers to the set of callbacks
158
158
  // we need to invoke
159
159
  toNotify.push(...currentSubcribers);
@@ -199,7 +199,7 @@ class CacheInternal {
199
199
  // write the link to the layer
200
200
  layer.writeLink(parent, key, linkedID);
201
201
  // if the link target of this field changed and it was responsible for the current subscription
202
- if (linkedID && displayLayer && linkChange) {
202
+ if (linkedID && displayLayer && (linkChange || forceNotify)) {
203
203
  // we need to clear the subscriptions in the previous link
204
204
  // and add them to the new link
205
205
  if (previousValue && typeof previousValue === 'string') {
@@ -346,7 +346,7 @@ class CacheInternal {
346
346
  // is still valid would not be triggered
347
347
  const contentChanged = JSON.stringify(linkedIDs) !== JSON.stringify(oldIDs);
348
348
  // we need to look at the last time we saw each subscriber to check if they need to be added to the spec
349
- if (contentChanged) {
349
+ if (contentChanged || forceNotify) {
350
350
  toNotify.push(...currentSubcribers);
351
351
  }
352
352
  // any ids that don't show up in the new list need to have their subscribers wiped
@@ -0,0 +1,35 @@
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
+ }