Comparing version
@@ -14,9 +14,16 @@ "use strict"; | ||
} | ||
var initialValue = fragment.applyMask(reference); | ||
// wrap the result in a store we can use to keep this query up to date | ||
var value = store_1.readable(fragment.applyMask(reference), function (set) { | ||
var value = store_1.readable(initialValue, function (set) { | ||
// build up the store object | ||
var store = { | ||
loaded: false, | ||
name: fragment.name, | ||
set: set, | ||
currentValue: {}, | ||
updateValue: function (newValue) { | ||
// update the public store | ||
set(newValue); | ||
// keep the internal value up to date aswell | ||
store.currentValue = newValue; | ||
}, | ||
currentValue: initialValue, | ||
}; | ||
@@ -27,6 +34,2 @@ // when the component monuts | ||
runtime_1.registerDocumentStore(store); | ||
// keep the stores' values in sync | ||
value.subscribe(function (val) { | ||
store.currentValue = val; | ||
}); | ||
}); | ||
@@ -33,0 +36,0 @@ // the function used to clean up the store |
@@ -68,3 +68,2 @@ "use strict"; | ||
// locals | ||
var environment_1 = require("./environment"); | ||
var runtime_1 = require("./runtime"); | ||
@@ -81,7 +80,2 @@ // mutation returns a handler that will send the mutation to the server when | ||
var text = document.raw, linkModule = document.links; | ||
// if there is no environment configured | ||
var currentEnv = environment_1.getEnvironment(); | ||
if (!currentEnv) { | ||
throw new Error('Please provide an environment'); | ||
} | ||
// return an async function that sends the mutation go the server | ||
@@ -93,3 +87,3 @@ return function (variables) { return __awaiter(_this, void 0, void 0, function () { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, currentEnv.sendRequest({ text: text, variables: variables }) | ||
case 0: return [4 /*yield*/, runtime_1.fetchQuery({ text: text, variables: variables }) | ||
// we could have gotten a null response | ||
@@ -110,5 +104,5 @@ ]; | ||
Promise.all(Object.entries(links()).map(function (_a) { | ||
var _b = __read(_a, 2), queryName = _b[0], patchModule = _b[1]; | ||
var _b = __read(_a, 2), documentName = _b[0], patchModule = _b[1]; | ||
return __awaiter(_this, void 0, void 0, function () { | ||
var patch, _c, _d, _e, currentValue, set; | ||
var patch, _c, _d, _e, currentValue, updateValue; | ||
var e_1, _f; | ||
@@ -124,6 +118,6 @@ return __generator(this, function (_g) { | ||
// apply the changes to any stores that have registered themselves | ||
for (_c = __values(runtime_1.getDocumentStores(queryName)), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
_e = _d.value, currentValue = _e.currentValue, set = _e.set; | ||
for (_c = __values(runtime_1.getDocumentStores(documentName)), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
_e = _d.value, currentValue = _e.currentValue, updateValue = _e.updateValue; | ||
// apply the patch | ||
runtime_1.applyPatch(patch, set, currentValue, data); | ||
runtime_1.applyPatch(patch, updateValue, currentValue, data, variables); | ||
} | ||
@@ -130,0 +124,0 @@ } |
@@ -18,3 +18,3 @@ "use strict"; | ||
name: document.name, | ||
set: set, | ||
updateValue: function (val) { return set(document.processResult(val)); }, | ||
currentValue: {}, | ||
@@ -21,0 +21,0 @@ }; |
@@ -11,3 +11,3 @@ import { Patch } from 'houdini-compiler'; | ||
currentValue: any; | ||
set: (value: any) => void; | ||
updateValue: (value: any) => void; | ||
}; | ||
@@ -23,4 +23,6 @@ export declare function getDocumentStores(name: string): DocumentStore[]; | ||
declare type Data = Record | Record[]; | ||
export declare function applyPatch(patch: Patch, set: (newValue: Data) => void, currentState: Data, payload: Data): void; | ||
export declare function applyPatch(patch: Patch, updateValue: (newValue: Data) => void, currentState: Data, payload: Data, variables: { | ||
[key: string]: any; | ||
}): void; | ||
export {}; | ||
//# sourceMappingURL=runtime.d.ts.map |
@@ -57,18 +57,18 @@ "use strict"; | ||
_stores[target.name] = getDocumentStores(target.name).filter(function (_a) { | ||
var set = _a.set; | ||
return set !== target.set; | ||
var updateValue = _a.updateValue; | ||
return updateValue !== target.updateValue; | ||
}); | ||
} | ||
exports.unregisterDocumentStore = unregisterDocumentStore; | ||
function applyPatch(patch, set, currentState, payload) { | ||
function applyPatch(patch, updateValue, currentState, payload, variables) { | ||
// a place to write updates to | ||
var target = currentState; | ||
// walk down the the patch and if there was a mutation, commit the update | ||
if (walkPatch(patch, payload, target)) { | ||
set(target); | ||
if (walkPatch(patch, payload, target, variables)) { | ||
updateValue(target); | ||
} | ||
} | ||
exports.applyPatch = applyPatch; | ||
function walkPatch(patch, payload, target) { | ||
var e_1, _a, e_2, _b, e_3, _c, e_4, _d; | ||
function walkPatch(patch, payload, target, variables) { | ||
var e_1, _a, e_2, _b, e_3, _c, e_4, _d, e_5, _e, e_6, _f; | ||
// track if we update something | ||
@@ -82,3 +82,3 @@ var updated = false; | ||
// if walking down updated something and we don't think we have | ||
if (walkPatch(patch, subobj, target) && !updated) { | ||
if (walkPatch(patch, subobj, target, variables) && !updated) { | ||
// keep us up to date | ||
@@ -102,8 +102,8 @@ updated = true; | ||
// many nodes for the response | ||
for (var _e = __values(Object.keys(patch.fields)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
var fieldName = _f.value; | ||
for (var _g = __values(Object.entries(patch.fields)), _h = _g.next(); !_h.done; _h = _g.next()) { | ||
var _j = __read(_h.value, 2), fieldName = _j[0], targetPaths = _j[1]; | ||
try { | ||
// update the target object at every path we need to | ||
for (var _g = (e_3 = void 0, __values(patch.fields[fieldName])), _h = _g.next(); !_h.done; _h = _g.next()) { | ||
var path = _h.value; | ||
for (var targetPaths_1 = (e_3 = void 0, __values(targetPaths)), targetPaths_1_1 = targetPaths_1.next(); !targetPaths_1_1.done; targetPaths_1_1 = targetPaths_1.next()) { | ||
var path = targetPaths_1_1.value; | ||
// if there is no id, we can update the fields | ||
@@ -121,3 +121,3 @@ if (!payload.id) { | ||
try { | ||
if (_h && !_h.done && (_c = _g.return)) _c.call(_g); | ||
if (targetPaths_1_1 && !targetPaths_1_1.done && (_c = targetPaths_1.return)) _c.call(targetPaths_1); | ||
} | ||
@@ -131,3 +131,3 @@ finally { if (e_3) throw e_3.error; } | ||
try { | ||
if (_f && !_f.done && (_b = _e.return)) _b.call(_e); | ||
if (_h && !_h.done && (_b = _g.return)) _b.call(_g); | ||
} | ||
@@ -137,7 +137,44 @@ finally { if (e_2) throw e_2.error; } | ||
try { | ||
// we might need to add this entity to some connections in the response | ||
for (var _k = __values(Object.entries(patch.operations)), _l = _k.next(); !_l.done; _l = _k.next()) { | ||
var _m = __read(_l.value, 2), operation = _m[0], paths = _m[1]; | ||
// if this is undefined, ill admit typescript saved me from something | ||
if (!paths) { | ||
continue; | ||
} | ||
try { | ||
// copy the entry into every path in the response | ||
for (var paths_1 = (e_5 = void 0, __values(paths)), paths_1_1 = paths_1.next(); !paths_1_1.done; paths_1_1 = paths_1.next()) { | ||
var _o = paths_1_1.value, path = _o.path, parentID = _o.parentID, position = _o.position; | ||
if (operation === 'add') { | ||
// add the entity to the connection | ||
if (insertInConnection(path, target, parentID, position, payload, variables, path.length) && | ||
!updated) { | ||
updated = true; | ||
} | ||
} | ||
} | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
finally { | ||
try { | ||
if (paths_1_1 && !paths_1_1.done && (_e = paths_1.return)) _e.call(paths_1); | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
} | ||
} | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
try { | ||
if (_l && !_l.done && (_d = _k.return)) _d.call(_k); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
try { | ||
// walk down any related fields | ||
for (var _j = __values(Object.keys(patch.edges)), _k = _j.next(); !_k.done; _k = _j.next()) { | ||
var edgeName = _k.value; | ||
for (var _p = __values(Object.keys(patch.edges)), _q = _p.next(); !_q.done; _q = _p.next()) { | ||
var edgeName = _q.value; | ||
// walk down and keep track if we updated anything | ||
if (walkPatch(patch.edges[edgeName], payload[edgeName], target) && !updated) { | ||
if (walkPatch(patch.edges[edgeName], payload[edgeName], target, variables) && !updated) { | ||
updated = true; | ||
@@ -147,8 +184,8 @@ } | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
finally { | ||
try { | ||
if (_k && !_k.done && (_d = _j.return)) _d.call(_j); | ||
if (_q && !_q.done && (_f = _p.return)) _f.call(_p); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
finally { if (e_6) throw e_6.error; } | ||
} | ||
@@ -158,4 +195,108 @@ // bubble up if there was an update | ||
} | ||
function insertInConnection(path, target, parentID, position, value, variables, pathLength) { | ||
var e_7, _a, e_8, _b; | ||
// keep track if we updated a field | ||
var updated = false; | ||
// dry | ||
var head = path[0]; | ||
// since we are entering something into a list, we need to stop on the second to | ||
// last element to find the node with matching id | ||
if (path.length <= 2) { | ||
var attributeName = path[1]; | ||
// if we are entering something from root the target should be an object | ||
if (parentID.kind === 'Root') { | ||
// if there is an element after this then we need to treat it as an | ||
// attribute for the item pointed at by head | ||
if (attributeName) { | ||
target[head][attributeName] = | ||
position === 'end' | ||
? __spread((target[head][attributeName] || []), [value]) : __spread([value], (target[head][attributeName] || [])); | ||
} | ||
// no attribute name means head is in fact the accesor and we just need to push | ||
else { | ||
// target[head] = [...(target[head] || []), value] | ||
target[head] = | ||
position === 'end' | ||
? __spread((target[head] || []), [value]) : __spread([value], (target[head] || [])); | ||
} | ||
// we did update something | ||
updated = true; | ||
} | ||
// the head points to the list we have to look at for possible parents | ||
var parents = target[head]; | ||
if (!Array.isArray(parents)) { | ||
throw new Error('Expected array in response'); | ||
} | ||
try { | ||
// look at every option for a matching id | ||
for (var parents_1 = __values(parents), parents_1_1 = parents_1.next(); !parents_1_1.done; parents_1_1 = parents_1.next()) { | ||
var entry = parents_1_1.value; | ||
// the id we are looking for | ||
var targetID = parentID.kind === 'String' ? parentID.value : variables[parentID.value]; | ||
// if the id matches | ||
if (entry.id === targetID) { | ||
// we found it! | ||
// check if we're supposed to add it to the end | ||
if (position === 'end') { | ||
entry[attributeName] = __spread((entry[attributeName] || []), [value]); | ||
} | ||
// we're supposed to add it to the front | ||
else { | ||
entry[attributeName] = __spread([value], (entry[attributeName] || [])); | ||
} | ||
// we did in fact update something | ||
return true; | ||
} | ||
} | ||
} | ||
catch (e_7_1) { e_7 = { error: e_7_1 }; } | ||
finally { | ||
try { | ||
if (parents_1_1 && !parents_1_1.done && (_a = parents_1.return)) _a.call(parents_1); | ||
} | ||
finally { if (e_7) throw e_7.error; } | ||
} | ||
} | ||
// keep going walking the path | ||
else { | ||
// pull the first element off of the list | ||
var head_1 = path[0]; | ||
var tail = path.slice(1, path.length); | ||
// look at the value in the response | ||
var element = target[head_1]; | ||
// if the element is a list | ||
if (Array.isArray(element)) { | ||
try { | ||
// walk down every element in the list | ||
for (var element_1 = __values(element), element_1_1 = element_1.next(); !element_1_1.done; element_1_1 = element_1.next()) { | ||
var entry = element_1_1.value; | ||
// if we applied the udpate | ||
if (insertInConnection(tail, entry, parentID, position, value, variables, pathLength)) { | ||
updated = true; | ||
// dont keep searching | ||
break; | ||
} | ||
} | ||
} | ||
catch (e_8_1) { e_8 = { error: e_8_1 }; } | ||
finally { | ||
try { | ||
if (element_1_1 && !element_1_1.done && (_b = element_1.return)) _b.call(element_1); | ||
} | ||
finally { if (e_8) throw e_8.error; } | ||
} | ||
} | ||
// the element is an object | ||
else { | ||
// keep going down | ||
if (insertInConnection(tail, element, parentID, position, value, variables, pathLength) && | ||
!updated) { | ||
updated = true; | ||
} | ||
} | ||
} | ||
return updated; | ||
} | ||
function updateField(path, target, targetId, value) { | ||
var e_5, _a; | ||
var e_9, _a; | ||
// keep track if we updated a field | ||
@@ -187,4 +328,4 @@ var updated = false; | ||
// walk down every element in the list | ||
for (var element_1 = __values(element), element_1_1 = element_1.next(); !element_1_1.done; element_1_1 = element_1.next()) { | ||
var entry = element_1_1.value; | ||
for (var element_2 = __values(element), element_2_1 = element_2.next(); !element_2_1.done; element_2_1 = element_2.next()) { | ||
var entry = element_2_1.value; | ||
// if we applied the udpate | ||
@@ -198,8 +339,8 @@ if (updateField(tail, entry, targetId, value)) { | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
catch (e_9_1) { e_9 = { error: e_9_1 }; } | ||
finally { | ||
try { | ||
if (element_1_1 && !element_1_1.done && (_a = element_1.return)) _a.call(element_1); | ||
if (element_2_1 && !element_2_1.done && (_a = element_2.return)) _a.call(element_2); | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
finally { if (e_9) throw e_9.error; } | ||
} | ||
@@ -206,0 +347,0 @@ } |
{ | ||
"name": "houdini", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"scripts": { | ||
@@ -9,7 +9,7 @@ "build": "tsc", | ||
"devDependencies": { | ||
"houdini-compiler": "^0.0.4", | ||
"houdini-preprocess": "^0.0.4" | ||
"houdini-compiler": "^0.0.5", | ||
"houdini-preprocess": "^0.0.5" | ||
}, | ||
"main": "build/index.js", | ||
"gitHead": "efc8cd674e33a9ba5127870e0ad90cbf20232209" | ||
"gitHead": "1eb8593ec14856e8df50d433828a94969287c08a" | ||
} |
@@ -20,9 +20,16 @@ // externals | ||
const initialValue = fragment.applyMask(reference) | ||
// wrap the result in a store we can use to keep this query up to date | ||
const value = readable(fragment.applyMask(reference), (set) => { | ||
const value = readable(initialValue, (set) => { | ||
// build up the store object | ||
const store = { | ||
loaded: false, | ||
name: fragment.name, | ||
set, | ||
currentValue: {}, | ||
updateValue: (newValue: _Fragment['shape']) => { | ||
// update the public store | ||
set(newValue) | ||
// keep the internal value up to date aswell | ||
store.currentValue = newValue | ||
}, | ||
currentValue: initialValue, | ||
} | ||
@@ -34,7 +41,2 @@ | ||
registerDocumentStore(store) | ||
// keep the stores' values in sync | ||
value.subscribe((val) => { | ||
store.currentValue = val | ||
}) | ||
}) | ||
@@ -41,0 +43,0 @@ |
@@ -5,4 +5,3 @@ // externals | ||
// locals | ||
import { getEnvironment } from './environment' | ||
import { getDocumentStores, applyPatch } from './runtime' | ||
import { getDocumentStores, applyPatch, fetchQuery } from './runtime' | ||
import { Operation } from './types' | ||
@@ -22,12 +21,6 @@ | ||
// if there is no environment configured | ||
const currentEnv = getEnvironment() | ||
if (!currentEnv) { | ||
throw new Error('Please provide an environment') | ||
} | ||
// return an async function that sends the mutation go the server | ||
return async (variables: _Mutation['input']) => { | ||
// grab the response from the server | ||
const { data } = await currentEnv.sendRequest({ text, variables }) | ||
const { data } = await fetchQuery({ text, variables }) | ||
@@ -45,9 +38,9 @@ // we could have gotten a null response | ||
Promise.all( | ||
Object.entries(links()).map(async ([queryName, patchModule]) => { | ||
Object.entries(links()).map(async ([documentName, patchModule]) => { | ||
// wait for the patch to load | ||
const { default: patch } = await patchModule | ||
// apply the changes to any stores that have registered themselves | ||
for (const { currentValue, set } of getDocumentStores(queryName)) { | ||
for (const { currentValue, updateValue } of getDocumentStores(documentName)) { | ||
// apply the patch | ||
applyPatch(patch, set, currentValue, data) | ||
applyPatch(patch, updateValue, currentValue, data, variables) | ||
} | ||
@@ -54,0 +47,0 @@ }) |
@@ -24,3 +24,3 @@ // externals | ||
name: document.name, | ||
set, | ||
updateValue: (val: _Query['result']) => set(document.processResult(val)), | ||
currentValue: {}, | ||
@@ -27,0 +27,0 @@ } |
@@ -16,4 +16,10 @@ // externals | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -38,3 +44,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -57,4 +63,10 @@ // make sure we got the expected value | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -79,3 +91,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -98,6 +110,15 @@ // make sure we got the expected value | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -124,3 +145,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -143,4 +164,10 @@ // make sure we got the expected value | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -167,3 +194,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -186,4 +213,10 @@ // make sure we got the expected value | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -210,3 +243,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -222,2 +255,331 @@ // make sure we got the expected value | ||
test('add to root lists', function () { | ||
const patch: Patch = { | ||
fields: {}, | ||
edges: { | ||
mutationName: { | ||
edges: {}, | ||
fields: {}, | ||
operations: { | ||
add: [ | ||
{ | ||
path: ['outer'], | ||
position: 'end', | ||
parentID: { | ||
kind: 'Root', | ||
value: 'root', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
// a function to spy on the update | ||
const set = jest.fn() | ||
// the current data | ||
const current = { | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
} | ||
// the mutation payload | ||
const payload = { | ||
mutationName: { | ||
id: '2', | ||
target: 'world', | ||
}, | ||
} | ||
// apply the patch | ||
applyPatch(patch, set, current, payload, {}) | ||
// make sure we got the expected value | ||
expect(set).toHaveBeenCalledWith({ | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
{ | ||
id: '2', | ||
target: 'world', | ||
}, | ||
], | ||
}) | ||
}) | ||
test('add to connection under root', function () { | ||
const patch: Patch = { | ||
fields: {}, | ||
edges: { | ||
mutationName: { | ||
edges: {}, | ||
fields: {}, | ||
operations: { | ||
add: [ | ||
{ | ||
path: ['outer'], | ||
position: 'end', | ||
parentID: { | ||
kind: 'Root', | ||
value: 'root', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
// a function to spy on the update | ||
const set = jest.fn() | ||
// the current data | ||
const current = { | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
} | ||
// the mutation payload | ||
const payload = { | ||
mutationName: { | ||
id: '2', | ||
target: 'world', | ||
}, | ||
} | ||
// apply the patch | ||
applyPatch(patch, set, current, payload, {}) | ||
// make sure we got the expected value | ||
expect(set).toHaveBeenCalledWith({ | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
{ | ||
id: '2', | ||
target: 'world', | ||
}, | ||
], | ||
}) | ||
}) | ||
test('prepend connection', function () { | ||
const patch: Patch = { | ||
fields: {}, | ||
edges: { | ||
mutationName: { | ||
edges: {}, | ||
fields: {}, | ||
operations: { | ||
add: [ | ||
{ | ||
path: ['outer'], | ||
position: 'start', | ||
parentID: { | ||
kind: 'Root', | ||
value: 'root', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
// a function to spy on the update | ||
const set = jest.fn() | ||
// the current data | ||
const current = { | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
} | ||
// the mutation payload | ||
const payload = { | ||
mutationName: { | ||
id: '2', | ||
target: 'world', | ||
}, | ||
} | ||
// apply the patch | ||
applyPatch(patch, set, current, payload, {}) | ||
// make sure we got the expected value | ||
expect(set).toHaveBeenCalledWith({ | ||
outer: [ | ||
{ | ||
id: '2', | ||
target: 'world', | ||
}, | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
}) | ||
}) | ||
test('add to connection with literal ID', function () { | ||
const patch: Patch = { | ||
fields: {}, | ||
edges: { | ||
mutationName: { | ||
edges: {}, | ||
fields: {}, | ||
operations: { | ||
add: [ | ||
{ | ||
path: ['outer', 'inner'], | ||
position: 'end', | ||
parentID: { | ||
kind: 'String', | ||
value: '1', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
// a function to spy on the update | ||
const set = jest.fn() | ||
// the current data | ||
const current = { | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
} | ||
// the mutation payload | ||
const payload = { | ||
mutationName: { | ||
id: '2', | ||
target: 'world', | ||
}, | ||
} | ||
// apply the patch | ||
applyPatch(patch, set, current, payload, {}) | ||
// make sure we got the expected value | ||
expect(set).toHaveBeenCalledWith({ | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
inner: [ | ||
{ | ||
id: '2', | ||
target: 'world', | ||
}, | ||
], | ||
}, | ||
], | ||
}) | ||
}) | ||
test('add to connection with variable ID', function () { | ||
const patch: Patch = { | ||
fields: {}, | ||
edges: { | ||
mutationName: { | ||
edges: {}, | ||
fields: {}, | ||
operations: { | ||
add: [ | ||
{ | ||
path: ['outer', 'inner'], | ||
position: 'end', | ||
parentID: { | ||
kind: 'Variable', | ||
value: 'testID', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
// a function to spy on the update | ||
const set = jest.fn() | ||
// the current data | ||
const current = { | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
}, | ||
], | ||
} | ||
// the mutation payload | ||
const payload = { | ||
mutationName: { | ||
id: '2', | ||
target: 'world', | ||
}, | ||
} | ||
// apply the patch | ||
applyPatch(patch, set, current, payload, { testID: '1' }) | ||
// make sure we got the expected value | ||
expect(set).toHaveBeenCalledWith({ | ||
outer: [ | ||
{ | ||
id: '1', | ||
target: 'hello', | ||
inner: [ | ||
{ | ||
id: '2', | ||
target: 'world', | ||
}, | ||
], | ||
}, | ||
], | ||
}) | ||
}) | ||
test('put values into lists', function () { | ||
@@ -232,4 +594,10 @@ const patch: Patch = { | ||
edges: {}, | ||
operations: { | ||
add: [], | ||
}, | ||
}, | ||
}, | ||
operations: { | ||
add: [], | ||
}, | ||
} | ||
@@ -259,3 +627,3 @@ | ||
// apply the patch | ||
applyPatch(patch, set, current, payload) | ||
applyPatch(patch, set, current, payload, {}) | ||
@@ -273,3 +641,3 @@ // make sure we got the expected value | ||
test.skip('null values in current state', function () {}) | ||
test.todo('null values in current state') | ||
}) |
@@ -21,3 +21,3 @@ // externals | ||
currentValue: any | ||
set: (value: any) => void | ||
updateValue: (value: any) => void | ||
} | ||
@@ -38,3 +38,5 @@ | ||
export function unregisterDocumentStore(target: DocumentStore) { | ||
_stores[target.name] = getDocumentStores(target.name).filter(({ set }) => set !== target.set) | ||
_stores[target.name] = getDocumentStores(target.name).filter( | ||
({ updateValue }) => updateValue !== target.updateValue | ||
) | ||
} | ||
@@ -47,16 +49,21 @@ | ||
patch: Patch, | ||
set: (newValue: Data) => void, | ||
updateValue: (newValue: Data) => void, | ||
currentState: Data, | ||
payload: Data | ||
payload: Data, | ||
variables: { [key: string]: any } | ||
) { | ||
// a place to write updates to | ||
const target = currentState | ||
// walk down the the patch and if there was a mutation, commit the update | ||
if (walkPatch(patch, payload, target)) { | ||
set(target) | ||
if (walkPatch(patch, payload, target, variables)) { | ||
updateValue(target) | ||
} | ||
} | ||
function walkPatch(patch: Patch, payload: Data, target: Record): boolean { | ||
function walkPatch( | ||
patch: Patch, | ||
payload: Data, | ||
target: Record, | ||
variables: { [key: string]: any } | ||
): boolean { | ||
// track if we update something | ||
@@ -69,3 +76,3 @@ let updated = false | ||
// if walking down updated something and we don't think we have | ||
if (walkPatch(patch, subobj, target) && !updated) { | ||
if (walkPatch(patch, subobj, target, variables) && !updated) { | ||
// keep us up to date | ||
@@ -82,5 +89,5 @@ updated = true | ||
// many nodes for the response | ||
for (const fieldName of Object.keys(patch.fields)) { | ||
for (const [fieldName, targetPaths] of Object.entries(patch.fields)) { | ||
// update the target object at every path we need to | ||
for (const path of patch.fields[fieldName]) { | ||
for (const path of targetPaths) { | ||
// if there is no id, we can update the fields | ||
@@ -97,6 +104,35 @@ if (!payload.id) { | ||
// we might need to add this entity to some connections in the response | ||
for (const [operation, paths] of Object.entries(patch.operations)) { | ||
// if this is undefined, ill admit typescript saved me from something | ||
if (!paths) { | ||
continue | ||
} | ||
// copy the entry into every path in the response | ||
for (const { path, parentID, position } of paths) { | ||
if (operation === 'add') { | ||
// add the entity to the connection | ||
if ( | ||
insertInConnection( | ||
path, | ||
target, | ||
parentID, | ||
position, | ||
payload, | ||
variables, | ||
path.length | ||
) && | ||
!updated | ||
) { | ||
updated = true | ||
} | ||
} | ||
} | ||
} | ||
// walk down any related fields | ||
for (const edgeName of Object.keys(patch.edges)) { | ||
// walk down and keep track if we updated anything | ||
if (walkPatch(patch.edges[edgeName], payload[edgeName], target) && !updated) { | ||
if (walkPatch(patch.edges[edgeName], payload[edgeName], target, variables) && !updated) { | ||
updated = true | ||
@@ -110,2 +146,127 @@ } | ||
function insertInConnection( | ||
path: string[], | ||
target: Record, | ||
parentID: { kind: 'Variable' | 'String' | 'Root'; value: string }, | ||
position: 'start' | 'end', | ||
value: Record, | ||
variables: { [key: string]: any }, | ||
pathLength: number | ||
) { | ||
// keep track if we updated a field | ||
let updated = false | ||
// dry | ||
const head = path[0] | ||
// since we are entering something into a list, we need to stop on the second to | ||
// last element to find the node with matching id | ||
if (path.length <= 2) { | ||
const attributeName = path[1] | ||
// if we are entering something from root the target should be an object | ||
if (parentID.kind === 'Root') { | ||
// if there is an element after this then we need to treat it as an | ||
// attribute for the item pointed at by head | ||
if (attributeName) { | ||
target[head][attributeName] = | ||
position === 'end' | ||
? [...(target[head][attributeName] || []), value] | ||
: [value, ...(target[head][attributeName] || [])] | ||
} | ||
// no attribute name means head is in fact the accesor and we just need to push | ||
else { | ||
// target[head] = [...(target[head] || []), value] | ||
target[head] = | ||
position === 'end' | ||
? [...(target[head] || []), value] | ||
: [value, ...(target[head] || [])] | ||
} | ||
// we did update something | ||
updated = true | ||
} | ||
// the head points to the list we have to look at for possible parents | ||
const parents = target[head] | ||
if (!Array.isArray(parents)) { | ||
throw new Error('Expected array in response') | ||
} | ||
// look at every option for a matching id | ||
for (const entry of parents) { | ||
// the id we are looking for | ||
const targetID = parentID.kind === 'String' ? parentID.value : variables[parentID.value] | ||
// if the id matches | ||
if (entry.id === targetID) { | ||
// we found it! | ||
// check if we're supposed to add it to the end | ||
if (position === 'end') { | ||
entry[attributeName] = [...(entry[attributeName] || []), value] | ||
} | ||
// we're supposed to add it to the front | ||
else { | ||
entry[attributeName] = [value, ...(entry[attributeName] || [])] | ||
} | ||
// we did in fact update something | ||
return true | ||
} | ||
} | ||
} | ||
// keep going walking the path | ||
else { | ||
// pull the first element off of the list | ||
const head = path[0] | ||
const tail = path.slice(1, path.length) | ||
// look at the value in the response | ||
const element = target[head] | ||
// if the element is a list | ||
if (Array.isArray(element)) { | ||
// walk down every element in the list | ||
for (const entry of element) { | ||
// if we applied the udpate | ||
if ( | ||
insertInConnection( | ||
tail, | ||
entry, | ||
parentID, | ||
position, | ||
value, | ||
variables, | ||
pathLength | ||
) | ||
) { | ||
updated = true | ||
// dont keep searching | ||
break | ||
} | ||
} | ||
} | ||
// the element is an object | ||
else { | ||
// keep going down | ||
if ( | ||
insertInConnection( | ||
tail, | ||
element, | ||
parentID, | ||
position, | ||
value, | ||
variables, | ||
pathLength | ||
) && | ||
!updated | ||
) { | ||
updated = true | ||
} | ||
} | ||
} | ||
return updated | ||
} | ||
function updateField(path: string[], target: Record, targetId: string, value: any): boolean { | ||
@@ -112,0 +273,0 @@ // keep track if we updated a field |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
58574
37.12%1722
56.69%