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.
- package/build/cmd.js +0 -0
- package/build/runtime/cache/cache.d.ts +4 -2
- package/build/runtime/cache/cache.js +7 -7
- package/build/runtime/cache/storage.d.ts +1 -0
- package/build/runtime/cache/storage.js +7 -0
- package/build/runtime/mutation.d.ts +4 -1
- package/build/runtime/mutation.js +43 -8
- package/build/runtime/scalars.d.ts +5 -0
- package/build/runtime/scalars.js +43 -1
- package/build/runtime-cjs/cache/cache.d.ts +4 -2
- package/build/runtime-cjs/cache/cache.js +7 -7
- package/build/runtime-cjs/cache/storage.d.ts +1 -0
- package/build/runtime-cjs/cache/storage.js +7 -0
- package/build/runtime-cjs/mutation.d.ts +4 -1
- package/build/runtime-cjs/mutation.js +43 -8
- package/build/runtime-cjs/scalars.d.ts +5 -0
- package/build/runtime-cjs/scalars.js +43 -1
- package/build/runtime-esm/cache/cache.d.ts +4 -2
- package/build/runtime-esm/cache/cache.js +7 -7
- package/build/runtime-esm/cache/list.d.ts +35 -0
- package/build/runtime-esm/cache/list.js +203 -0
- package/build/runtime-esm/cache/record.d.ts +40 -0
- package/build/runtime-esm/cache/record.js +195 -0
- package/build/runtime-esm/cache/storage.d.ts +1 -0
- package/build/runtime-esm/cache/storage.js +7 -0
- package/build/runtime-esm/mutation.d.ts +4 -1
- package/build/runtime-esm/mutation.js +43 -3
- package/build/runtime-esm/scalars.d.ts +5 -0
- package/build/runtime-esm/scalars.js +39 -0
- package/package.json +2 -2
- package/runtime/cache/cache.ts +10 -7
- package/runtime/cache/storage.ts +11 -0
- package/runtime/cache/tests/subscriptions.test.ts +169 -0
- package/runtime/mutation.ts +57 -5
- package/runtime/scalars.test.ts +322 -14
- 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"
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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;
|
package/build/runtime/scalars.js
CHANGED
|
@@ -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"
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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"
|
|
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 =
|
|
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
|
|
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
|
+
}
|