Skip to content

Commit c561870

Browse files
committed
Fix cached documents not being fully deleted in Anonymous Mode
fixes #1204
1 parent 5c1dac7 commit c561870

File tree

6 files changed

+130
-17
lines changed

6 files changed

+130
-17
lines changed

src/baseclient.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,17 +624,16 @@ export class BaseClient {
624624
* @example
625625
* client.remove('path/to/object').then(() => console.log('item deleted'));
626626
*/
627-
// TODO add real return type
628627
// TODO Don't return the RemoteResponse directly, handle response properly
629-
remove (path: string): Promise<unknown> {
628+
async remove (path: string): Promise<QueuedRequestResponse> {
630629
if (typeof path !== 'string') {
631630
return Promise.reject('Argument \'path\' of baseClient.remove must be a string');
632631
}
633632
if (!this.storage.access.checkPathPermission(this.makePath(path), 'rw')) {
634633
console.warn('WARNING: Removing a document to which only read access (\'r\') was claimed');
635634
}
636635

637-
return this.storage.delete(this.makePath(path));
636+
return this.storage.delete(this.makePath(path), this.storage.connected);
638637
}
639638

640639
/**

src/cachinglayer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ abstract class CachingLayer {
227227
return this._updateNodes(paths, _processNodes);
228228
}
229229

230-
async delete (path: string): Promise<QueuedRequestResponse> {
230+
async delete (path: string, remoteConnected: boolean): Promise<QueuedRequestResponse> {
231231
const paths = pathsFromRoot(path);
232232

233233
return this._updateNodes(paths, function (nodePaths, nodes) {
@@ -245,7 +245,7 @@ abstract class CachingLayer {
245245
// Document
246246
previous = getLatest(node);
247247
node.local = {
248-
body: false,
248+
body: remoteConnected ? false : undefined,
249249
previousBody: (previous ? previous.body : undefined),
250250
previousContentType: (previous ? previous.contentType : undefined),
251251
};
@@ -371,8 +371,13 @@ abstract class CachingLayer {
371371
newContentType: node.local.contentType
372372
});
373373
}
374-
delete node.local.previousBody;
375-
delete node.local.previousContentType;
374+
if (node.local.body === undefined) {
375+
// no remote connected, remove deleted node from cache immediately
376+
nodes[path] = undefined;
377+
} else {
378+
delete node.local.previousBody;
379+
delete node.local.previousContentType;
380+
}
376381
}
377382
}
378383

src/syncedgetputdelete.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ const SyncedGetPutDelete = {
4242
}
4343
},
4444

45-
'delete': function (path: string): Promise<QueuedRequestResponse | RemoteResponse> {
45+
'delete': function (path: string, remoteConnected: boolean): Promise<QueuedRequestResponse | RemoteResponse> {
4646
if (this.local) {
47-
return this.local.delete(path);
47+
return this.local.delete(path, remoteConnected);
4848
} else {
49-
return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.delete(path));
49+
return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.delete(path, remoteConnected));
5050
}
5151
},
5252

test/unit/cachinglayer-suite.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ define(['require', './build/util', './build/config', './build/inmemorystorage'],
211211
test.assertAnd(nodes, {
212212
'/foo': undefined
213213
}, 'second pass');
214-
nodes['/foo'] = {local: {some: 'data'}};
214+
nodes['/foo'] = {local: {body: 'data'}};
215215
jobTwoCbCalled = true;
216216
return nodes;
217217
}).then(function() {
@@ -235,10 +235,10 @@ define(['require', './build/util', './build/config', './build/inmemorystorage'],
235235

236236
test.assertAnd(nodes, {
237237
'/foo': {
238-
local: {some: 'data'}
238+
local: {body: 'data'}
239239
}
240240
}, 'third pass');
241-
nodes['/foo'] = {local: {some: 'other data'}};
241+
nodes['/foo'] = {local: {body: 'other data'}};
242242
jobThreeCbCalled = true;
243243
return nodes;
244244
}).then(function() {

test/unit/cachinglayer.test.mjs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,115 @@ describe("CachingLayer", function() {
9696
});
9797
});
9898

99+
describe("#delete", function() {
100+
describe("when connected", function() {
101+
beforeEach(async function() {
102+
this.rs.remote.connected = true;
103+
this.rs.remote.online = true;
104+
this.rs.caching.enable("/foo/");
105+
106+
await this.rs.local.setNodes({
107+
"/foo/": {
108+
path: "/foo/",
109+
common: {
110+
itemsMap: {
111+
"one": true,
112+
"two": true
113+
},
114+
contentType: "application/ld+json",
115+
timestamp: new Date().getTime(),
116+
revision: "oldie-but-goodie"
117+
}
118+
},
119+
"/foo/one": {
120+
path: "/foo/one",
121+
common: {
122+
body: "some data",
123+
contentType: "text/plain",
124+
timestamp: new Date().getTime(),
125+
revision: "123456"
126+
}
127+
},
128+
"/foo/two": {
129+
path: "/foo/two",
130+
common: {
131+
body: "some other data",
132+
contentType: "text/plain",
133+
timestamp: new Date().getTime(),
134+
revision: "abcdef"
135+
}
136+
}
137+
});
138+
139+
await this.rs.local.delete('/foo/one', this.rs.remote.connected);
140+
});
141+
142+
it("marks the node for deletion", async function() {
143+
const nodes = await this.rs.local.getNodes(["/foo/", "/foo/one"]);
144+
const folder = nodes["/foo/"];
145+
const node = nodes["/foo/one"];
146+
147+
expect(Object.keys(folder.common.itemsMap)).to.deep.equal(["one", "two"]);
148+
expect(Object.keys(folder.local.itemsMap)).to.deep.equal(["two"]);
149+
expect(node.local.body).to.be.false;
150+
expect(node.push.body).to.be.false;
151+
});
152+
});
153+
154+
describe("when disconnected", function() {
155+
beforeEach(async function() {
156+
this.rs.remote.connected = false;
157+
this.rs.remote.online = false;
158+
this.rs.caching.enable("/foo/");
159+
160+
await this.rs.local.setNodes({
161+
"/foo/": {
162+
path: "/foo/",
163+
local: {
164+
itemsMap: {
165+
"one": true,
166+
"two": true
167+
},
168+
contentType: "application/ld+json",
169+
timestamp: new Date().getTime(),
170+
revision: "oldie-but-goodie"
171+
}
172+
},
173+
"/foo/one": {
174+
path: "/foo/one",
175+
local: {
176+
body: "some data",
177+
contentType: "text/plain",
178+
timestamp: new Date().getTime(),
179+
revision: "123456"
180+
}
181+
},
182+
"/foo/two": {
183+
path: "/foo/two",
184+
local: {
185+
body: "some other data",
186+
contentType: "text/plain",
187+
timestamp: new Date().getTime(),
188+
revision: "abcdef"
189+
}
190+
}
191+
});
192+
193+
await this.rs.local.delete('/foo/one', this.rs.remote.connected);
194+
});
195+
196+
it("deletes the node immediately", async function() {
197+
const nodes = await this.rs.local.getNodes(["/foo/", "/foo/one", "/foo/two"]);
198+
const folder = nodes["/foo/"];
199+
200+
expect(folder.common).to.be.undefined;
201+
expect(Object.keys(folder.local.itemsMap)).to.deep.equal(["two"]);
202+
expect(nodes["/foo/one"]).to.be.undefined;
203+
expect(nodes["/foo/two"].local.revision).to.equal("abcdef");
204+
});
205+
});
206+
});
207+
99208
describe("#_emitChangeEvents", function() {
100209
it("broadcasts the change to other browser tabs", function(done) {
101210
this.rs.local.broadcastChannel = {

test/unit/inmemorycaching-suite.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ define(['./build/config', './build/inmemorystorage'], function (config, InMemory
299299

300300
test.assertAnd(Object.keys(storage), storageKeys);
301301

302-
return env.ims.delete('/foo/bar/baz').then(function (r) {
302+
return env.ims.delete('/foo/bar/baz', true).then(function (r) {
303303
test.assertAnd(r.statusCode, 200, 'Wrong statusCode: '+r.statusCode); //TODO belongs in separate test
304304
test.assertAnd(getLatest(storage['/foo/bar/baz']), undefined);
305305
test.assertAnd(getLatest(storage['/foo/bar/']).itemsMap, {});
@@ -327,7 +327,7 @@ define(['./build/config', './build/inmemorystorage'], function (config, InMemory
327327
});
328328
});
329329

330-
return env.ims.delete('/foo/bar/baz');
330+
return env.ims.delete('/foo/bar/baz', true);
331331
});
332332
}
333333
},
@@ -340,7 +340,7 @@ define(['./build/config', './build/inmemorystorage'], function (config, InMemory
340340

341341
return env.ims.put('/foo/bar/baz', 'bla', 'text/plain', true, 'a1b2c3').then(function () {
342342
return env.ims.put('/foo/baz', 'bla', 'text/plain', true, 'a1b2c3').then(function () {
343-
return env.ims.delete('/foo/bar/baz').then(function (status) {
343+
return env.ims.delete('/foo/bar/baz', true).then(function (status) {
344344
test.assertAnd(getLatest(storage['/']).itemsMap, { 'foo/': true });
345345
test.assertAnd(getLatest(storage['/foo/']).itemsMap, { 'baz': true });
346346
test.assertAnd(getLatest(storage['/foo/baz']).body, 'bla');
@@ -357,7 +357,7 @@ define(['./build/config', './build/inmemorystorage'], function (config, InMemory
357357
run: function (env, test) {
358358
return env.ims.put('/foo/bar/baz', 'bla', 'text/plain', 'a1b2c3').then(function () {
359359
return env.ims.put('/foo/baz', 'bla', 'text/plain', 'a1b2c3').then(function () {
360-
return env.ims.delete('/foo/bar/baz').then(function (status) {
360+
return env.ims.delete('/foo/bar/baz', true).then(function (status) {
361361
test.assert(env.ims._storage['/'].local.itemsMap, {'foo/': true});
362362
});
363363
});

0 commit comments

Comments
 (0)