houdini 0.13.6 → 0.13.7-alpha.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/build/cmd.js +0 -0
- package/build/runtime/cache/cache.d.ts +1 -1
- package/build/runtime/cache/cache.js +1 -1
- package/build/runtime/mutation.d.ts +4 -1
- package/build/runtime/mutation.js +39 -8
- package/build/runtime/scalars.d.ts +5 -0
- package/build/runtime/scalars.js +43 -1
- package/build/runtime-cjs/cache/cache.d.ts +1 -1
- package/build/runtime-cjs/cache/cache.js +1 -1
- package/build/runtime-cjs/mutation.d.ts +4 -1
- package/build/runtime-cjs/mutation.js +39 -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 +1 -1
- package/build/runtime-esm/cache/cache.js +1 -1
- 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/mutation.d.ts +4 -1
- package/build/runtime-esm/mutation.js +39 -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 +2 -2
- package/runtime/mutation.ts +50 -5
- package/runtime/scalars.test.ts +322 -14
- package/runtime/scalars.ts +57 -0
package/build/cmd.js
CHANGED
|
File without changes
|
|
@@ -210,7 +210,7 @@ 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
215
|
var displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
|
|
216
216
|
// if we are writing to the display layer we need to refresh the lifetime of the value
|
|
@@ -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,61 @@ 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,
|
|
82
107
|
});
|
|
83
|
-
//
|
|
108
|
+
// merge the layer back into the cache
|
|
109
|
+
cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
|
|
110
|
+
// turn any scalars in the response into their complex form
|
|
84
111
|
return [2 /*return*/, scalars_1.unmarshalSelection(config, artifact.selection, result.data)];
|
|
85
|
-
case
|
|
112
|
+
case 3:
|
|
86
113
|
error_1 = _a.sent();
|
|
114
|
+
// if the mutation failed, roll the layer back and delete it
|
|
115
|
+
layer.clear();
|
|
116
|
+
cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
|
|
117
|
+
// bubble the mutation error up to the caller
|
|
87
118
|
throw error_1;
|
|
88
|
-
case
|
|
119
|
+
case 4: return [2 /*return*/];
|
|
89
120
|
}
|
|
90
121
|
});
|
|
91
122
|
}); };
|
|
@@ -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') {
|
|
@@ -210,7 +210,7 @@ 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
215
|
var displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
|
|
216
216
|
// if we are writing to the display layer we need to refresh the lifetime of the value
|
|
@@ -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,61 @@ 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,
|
|
82
107
|
});
|
|
83
|
-
//
|
|
108
|
+
// merge the layer back into the cache
|
|
109
|
+
cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
|
|
110
|
+
// turn any scalars in the response into their complex form
|
|
84
111
|
return [2 /*return*/, scalars_1.unmarshalSelection(config, artifact.selection, result.data)];
|
|
85
|
-
case
|
|
112
|
+
case 3:
|
|
86
113
|
error_1 = _a.sent();
|
|
114
|
+
// if the mutation failed, roll the layer back and delete it
|
|
115
|
+
layer.clear();
|
|
116
|
+
cache_1.default._internal_unstable.storage.resolveLayer(layer.id);
|
|
117
|
+
// bubble the mutation error up to the caller
|
|
87
118
|
throw error_1;
|
|
88
|
-
case
|
|
119
|
+
case 4: return [2 /*return*/];
|
|
89
120
|
}
|
|
90
121
|
});
|
|
91
122
|
}); };
|
|
@@ -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') {
|
|
@@ -129,7 +129,7 @@ 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
134
|
const displayLayer = displayLayers.length === 0 || displayLayers.includes(layer.id);
|
|
135
135
|
// if we are writing to the display layer we need to refresh the lifetime of the value
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
}
|