Skip to content

Commit 6898f84

Browse files
committed
JS Server: support updatedBlobs in /verifyDocuments
1 parent 362d68e commit 6898f84

3 files changed

Lines changed: 73 additions & 39 deletions

File tree

servers/javascript/src/snapshot.test.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as cbl from "@couchbase/lite-js";
44
import { beforeEach, test, describe, expect, afterEach } from "vitest";
55

66

7+
/* eslint-disable @typescript-eslint/require-await */
8+
79
cbl.Database.useIndexedDB(indexedDB, IDBKeyRange);
810

911

@@ -24,6 +26,11 @@ afterEach( async() => {
2426
});
2527

2628

29+
async function noBlobLoader(_url: string): Promise<cbl.NewBlob> {
30+
throw Error("Should not be called");
31+
}
32+
33+
2734
describe("Snapshot", () => {
2835

2936
test("doc wasn't created", async () => {
@@ -34,7 +41,7 @@ describe("Snapshot", () => {
3441
type: 'UPDATE',
3542
collection: "red",
3643
documentID: cbl.DocID("nose"),
37-
}]);
44+
}], noBlobLoader);
3845
expect(response).toMatchInlineSnapshot(`
3946
{
4047
"description": "Document nose in collection red was not found",
@@ -62,7 +69,7 @@ describe("Snapshot", () => {
6269
name: "Santa",
6370
reindeer: ["Dasher", "Prancer", "etc."],
6471
}],
65-
}]);
72+
}], noBlobLoader);
6673
expect(response).toEqual({result: true});
6774
});
6875

@@ -83,7 +90,7 @@ describe("Snapshot", () => {
8390
collection: "red",
8491
documentID: cbl.DocID("nose"),
8592
type: 'DELETE',
86-
}]);
93+
}], noBlobLoader);
8794
expect(response).toEqual({result: true});
8895
});
8996

@@ -102,7 +109,7 @@ describe("Snapshot", () => {
102109
collection: "red",
103110
documentID: cbl.DocID("nose"),
104111
type: 'DELETE',
105-
}]);
112+
}], noBlobLoader);
106113
expect(response).toEqual({
107114
"result": false,
108115
"description": "Document nose in collection red was not deleted",
@@ -135,7 +142,7 @@ describe("Snapshot", () => {
135142
name: "Santa",
136143
reindeer: ["Dasher", "Prancer", "etc."],
137144
}],
138-
}]);
145+
}], noBlobLoader);
139146

140147
expect(response).toEqual({
141148
"result": false,
@@ -165,6 +172,7 @@ describe("Snapshot", () => {
165172
await snapshot.record("red", cbl.DocID("nose"));
166173

167174
(nose.reindeer as string[])[2] = "Rudolph";
175+
await red.save(nose);
168176

169177
const response = await snapshot.verify([{
170178
collection: "red",
@@ -173,22 +181,41 @@ describe("Snapshot", () => {
173181
updatedProperties: [{
174182
"reindeer[2]": "Rudolph",
175183
}],
176-
}]);
184+
}], noBlobLoader);
177185

178-
expect(response).toEqual({
179-
"result": false,
180-
"description": "Document nose in collection red had unexpected properties at .reindeer[2]",
181-
"expected": "Rudolph",
182-
"actual": "etc.",
183-
"document": {
184-
"name": "Santa",
185-
"reindeer": [
186-
"Dasher",
187-
"Prancer",
188-
"etc.",
189-
],
190-
},
186+
expect(response).toEqual({"result": true});
187+
});
188+
189+
190+
test("doc added a blob", async () => {
191+
const nose = red.createDocument(cbl.DocID("nose"), {
192+
name: "Santa",
193+
reindeer: ["Dasher", "Prancer", "etc."],
191194
});
195+
await red.save(nose);
196+
197+
const snapshot = new Snapshot(db);
198+
await snapshot.record("red", cbl.DocID("nose"));
199+
200+
const hohoho = new TextEncoder().encode("Ho ho ho!");
201+
nose.hohoho = new cbl.NewBlob(hohoho, "text/plain");
202+
await red.save(nose);
203+
204+
const blobLoader = async (url: string): Promise<cbl.NewBlob> => {
205+
expect(url).toBe("x/y/hohoho.txt");
206+
return new cbl.NewBlob(hohoho, "text/plain");
207+
};
208+
209+
const response = await snapshot.verify([{
210+
collection: "red",
211+
documentID: cbl.DocID("nose"),
212+
type: 'UPDATE',
213+
updatedBlobs: {
214+
"hohoho": "x/y/hohoho.txt",
215+
},
216+
}], blobLoader);
217+
218+
expect(response).toEqual({"result": true});
192219
});
193220

194221

@@ -210,7 +237,7 @@ describe("Snapshot", () => {
210237
updatedProperties: [{
211238
"reindeer[2]": "Rudolph",
212239
}],
213-
}]);
240+
}], noBlobLoader);
214241

215242
expect(response).toEqual({
216243
"result": false,

servers/javascript/src/snapshot.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import type * as tdk from "./tdkSchema";
1616
import type * as cbl from "@couchbase/lite-js";
1717

1818

19+
export type BlobLoader = (blobURL: string) => Promise<cbl.NewBlob>;
20+
21+
1922
/** Creates and verifies TDK database snapshots. */
2023
export class Snapshot {
2124
constructor(readonly db: cbl.Database) { }
@@ -29,7 +32,8 @@ export class Snapshot {
2932

3033
/** Verifies the current database against the snapshot and a list of updates.
3134
* @warning This can only be called once, as it mutates the stored state. */
32-
async verify(changes: readonly tdk.DatabaseUpdateItem[]): Promise<tdk.VerifyDocumentsResponse> {
35+
async verify(changes: readonly tdk.DatabaseUpdateItem[],
36+
blobLoader: BlobLoader): Promise<tdk.VerifyDocumentsResponse> {
3337
// Index the updates in a map for quick lookup:
3438
const updates = new DocumentMap<tdk.DatabaseUpdateItem>();
3539
for (const u of changes) {
@@ -46,10 +50,13 @@ export class Snapshot {
4650
let expected: cbl.CBLDocument | undefined;
4751
// Update the old document with the changes listed in the DatabaseUpdateItem:
4852
if (oldDoc) {
49-
expected = update ? this.#applyUpdate(oldDoc, update) : oldDoc;
53+
if (update)
54+
expected = await this.#applyUpdate(oldDoc, update, blobLoader);
55+
else
56+
expected = oldDoc;
5057
} else {
5158
oldDoc = this.#getCollection(collection).createDocument(id);
52-
expected = this.#applyUpdate(oldDoc, update!!);
59+
expected = await this.#applyUpdate(oldDoc, update!!, blobLoader);
5360
}
5461
// Compare the updated oldDoc against the database's current document:
5562
const newDoc = await this.#getCollection(collection).getDocument(id);
@@ -79,31 +86,31 @@ export class Snapshot {
7986

8087

8188
/** Applies the changes described in a `DatabaseUpdateItem` to a `CBLDocument`. */
82-
#applyUpdate(doc: cbl.CBLDocument, update: tdk.DatabaseUpdateItem)
83-
: cbl.CBLDocument | undefined
89+
async #applyUpdate(doc: cbl.CBLDocument,
90+
update: tdk.DatabaseUpdateItem,
91+
blobLoader: BlobLoader) : Promise<cbl.CBLDocument | undefined>
8492
{
8593
if (update.type !== 'UPDATE')
8694
return undefined;
95+
96+
const patch = (key: string, value: cbl.CBLValue | undefined) => {
97+
if (!KeyPathCache.path(key).write(doc, value))
98+
throw new HTTPError(400, `Type mismatch traversing path ${key}`);
99+
};
100+
87101
if (update.updatedProperties !== undefined) {
88102
for (const updates of update.updatedProperties) {
89-
for (const key of Object.getOwnPropertyNames(updates)) {
90-
if (!KeyPathCache.path(key).write(doc, updates[key])) {
91-
throw new HTTPError(400, `Type mismatch traversing path ${key}`);
92-
}
93-
}
103+
for (const key of Object.getOwnPropertyNames(updates))
104+
patch(key, updates[key]);
94105
}
95106
}
96107
if (update.removedProperties !== undefined) {
97-
for (const key of update.removedProperties) {
98-
if (!KeyPathCache.path(key).write(doc, undefined)) {
99-
throw new HTTPError(400, `Type mismatch traversing path ${key}`);
100-
}
101-
}
108+
for (const key of update.removedProperties)
109+
patch(key, undefined);
102110
}
103111
if (update.updatedBlobs !== undefined) {
104-
for (const _key of Object.getOwnPropertyNames(update.updatedBlobs)) {
105-
throw new HTTPError(501, `updatedBlobs is not supported yet`);
106-
}
112+
for (const key of Object.getOwnPropertyNames(update.updatedBlobs))
113+
patch(key, await blobLoader(update.updatedBlobs[key]));
107114
}
108115
return doc;
109116
}

servers/javascript/src/tdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
332332
throw new HTTPError(400, `Snapshot is of a different database, ${db.name}`);
333333
this.#snapshots.delete(rq.snapshot);
334334

335-
return await snap.verify(rq.changes);
335+
return await snap.verify(rq.changes, this.#downloadBlob.bind(this));
336336
}
337337

338338

0 commit comments

Comments
 (0)