cojson-storage-do-sqlite 0.18.28
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +9 -0
- package/LICENSE.txt +19 -0
- package/README.md +4 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/dosqlite.d.ts +89 -0
- package/dist/tests/dosqlite.d.ts.map +1 -0
- package/dist/tests/dosqlite.js +489 -0
- package/dist/tests/dosqlite.js.map +1 -0
- package/dist/tests/dosqlite.test.d.ts +2 -0
- package/dist/tests/dosqlite.test.d.ts.map +1 -0
- package/dist/tests/dosqlite.test.js +284 -0
- package/dist/tests/dosqlite.test.js.map +1 -0
- package/dist/tests/messagesTestUtils.d.ts +10 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/testUtils.d.ts +10 -0
- package/dist/tests/testUtils.d.ts.map +1 -0
- package/dist/tests/testUtils.js +88 -0
- package/dist/tests/testUtils.js.map +1 -0
- package/dist/tests/vitest.config.d.ts +3 -0
- package/dist/tests/vitest.config.d.ts.map +1 -0
- package/dist/tests/vitest.config.js +8 -0
- package/dist/tests/vitest.config.js.map +1 -0
- package/package.json +28 -0
- package/src/index.ts +60 -0
- package/src/tests/dosqlite.test.ts +338 -0
- package/src/tests/dosqlite.ts +771 -0
- package/src/tests/messagesTestUtils.ts +72 -0
- package/src/tests/testUtils.ts +112 -0
- package/src/tests/tsconfig.json +43 -0
- package/src/tests/vitest.config.ts +8 -0
- package/src/tests/worker-configuration.d.ts +9681 -0
- package/src/tests/wrangler.jsonc +20 -0
- package/tsconfig.json +17 -0
package/CHANGELOG.md
ADDED
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright 2025, Garden Computing, Inc.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SQLiteDatabaseDriver } from "cojson";
|
|
2
|
+
import type { DurableObjectStorage } from "@cloudflare/workers-types";
|
|
3
|
+
export declare class DurableObjectSqlDriver implements SQLiteDatabaseDriver {
|
|
4
|
+
private readonly storage;
|
|
5
|
+
constructor(durableObjectStorage: DurableObjectStorage);
|
|
6
|
+
run(sql: string, params: unknown[]): void;
|
|
7
|
+
query<T>(sql: string, params: unknown[]): T[];
|
|
8
|
+
get<T>(sql: string, params: unknown[]): T | undefined;
|
|
9
|
+
transaction(callback: () => unknown): unknown;
|
|
10
|
+
closeDb(): void;
|
|
11
|
+
getMigrationVersion(): number;
|
|
12
|
+
saveMigrationVersion(version: number): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function getDurableObjectSqlStorage(durableObjectStorage: DurableObjectStorage): import("cojson").StorageApiSync;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAEnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,qBAAa,sBAAuB,YAAW,oBAAoB;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;gBAEnC,oBAAoB,EAAE,oBAAoB;IAItD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;IAIlC,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE;IAI7C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,SAAS;IAQrD,WAAW,CAAC,QAAQ,EAAE,MAAM,OAAO;IAInC,OAAO;IAIP,mBAAmB,IAAI,MAAM;IAa7B,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5C;AAED,wBAAgB,0BAA0B,CACxC,oBAAoB,EAAE,oBAAoB,mCAK3C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getSqliteStorage } from "cojson";
|
|
2
|
+
export class DurableObjectSqlDriver {
|
|
3
|
+
constructor(durableObjectStorage) {
|
|
4
|
+
this.storage = durableObjectStorage;
|
|
5
|
+
}
|
|
6
|
+
run(sql, params) {
|
|
7
|
+
this.storage.sql.exec(sql, ...params);
|
|
8
|
+
}
|
|
9
|
+
query(sql, params) {
|
|
10
|
+
return this.storage.sql.exec(sql, ...params).toArray();
|
|
11
|
+
}
|
|
12
|
+
get(sql, params) {
|
|
13
|
+
const res = this.storage.sql.exec(sql, ...params);
|
|
14
|
+
if (res.rowsRead === 0) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return res.one();
|
|
18
|
+
}
|
|
19
|
+
transaction(callback) {
|
|
20
|
+
return this.storage.transactionSync(callback);
|
|
21
|
+
}
|
|
22
|
+
closeDb() {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
getMigrationVersion() {
|
|
26
|
+
this.run("CREATE TABLE IF NOT EXISTS _migration_version (version INTEGER);", []);
|
|
27
|
+
return (this.get("SELECT max(version) as version FROM _migration_version;", [])?.version ?? 0);
|
|
28
|
+
}
|
|
29
|
+
saveMigrationVersion(version) {
|
|
30
|
+
this.run("INSERT INTO _migration_version (version) VALUES (?);", [version]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getDurableObjectSqlStorage(durableObjectStorage) {
|
|
34
|
+
const db = new DurableObjectSqlDriver(durableObjectStorage);
|
|
35
|
+
return getSqliteStorage(db);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAG1C,MAAM,OAAO,sBAAsB;IAGjC,YAAY,oBAA0C;QACpD,IAAI,CAAC,OAAO,GAAG,oBAAoB,CAAC;IACtC,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,MAAiB;QAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAI,GAAW,EAAE,MAAiB;QACrC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,OAAO,EAAS,CAAC;IAChE,CAAC;IAED,GAAG,CAAI,GAAW,EAAE,MAAiB;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;QAClD,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,CAAC,GAAG,EAAO,CAAC;IACxB,CAAC;IAED,WAAW,CAAC,QAAuB;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,OAAO;QACL,OAAO;IACT,CAAC;IAED,mBAAmB;QACjB,IAAI,CAAC,GAAG,CACN,kEAAkE,EAClE,EAAE,CACH,CAAC;QACF,OAAO,CACL,IAAI,CAAC,GAAG,CACN,yDAAyD,EACzD,EAAE,CACH,EAAE,OAAO,IAAI,CAAC,CAChB,CAAC;IACJ,CAAC;IAED,oBAAoB,CAAC,OAAe;QAClC,IAAI,CAAC,GAAG,CAAC,sDAAsD,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9E,CAAC;CACF;AAED,MAAM,UAAU,0BAA0B,CACxC,oBAA0C;IAE1C,MAAM,EAAE,GAAG,IAAI,sBAAsB,CAAC,oBAAoB,CAAC,CAAC;IAE5D,OAAO,gBAAgB,CAAC,EAAE,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import "jazz-tools/load-edge-wasm";
|
|
2
|
+
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
import { SyncMessage } from "cojson";
|
|
4
|
+
export declare function trackMessages(): {
|
|
5
|
+
messages: {
|
|
6
|
+
from: "client" | "server" | "storage";
|
|
7
|
+
msg: SyncMessage;
|
|
8
|
+
}[];
|
|
9
|
+
restore: () => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Due to issues with @cloudflare/vitest-pool-workers and loading wasm modules
|
|
13
|
+
* implementing the test logic in the durable object and running full server
|
|
14
|
+
* with wrangler.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
export declare class DoSqlStoreTest extends DurableObject {
|
|
18
|
+
/**
|
|
19
|
+
* Test case from cojson-storage-sqlite: "should sync and load data from storage"
|
|
20
|
+
*/
|
|
21
|
+
storeLoadTest(): Promise<{
|
|
22
|
+
messageRes1: string[];
|
|
23
|
+
messageRes2: string[];
|
|
24
|
+
map2Hello: import("cojson").JsonValue | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Test case from cojson-storage-sqlite: "should send an empty content message if there is no content"
|
|
28
|
+
*/
|
|
29
|
+
storeLoadEmptyTest(): Promise<{
|
|
30
|
+
messageRes1: string[];
|
|
31
|
+
messageRes2: string[];
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Test case from cojson-storage-sqlite: "should load dependencies correctly (group inheritance)"
|
|
35
|
+
*/
|
|
36
|
+
loadDependenciesTest(): Promise<{
|
|
37
|
+
messageRes1: string[];
|
|
38
|
+
messageRes2: string[];
|
|
39
|
+
mapLoaded: boolean;
|
|
40
|
+
groupLoaded: boolean;
|
|
41
|
+
parentGroupLoaded: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Test case from cojson-storage-sqlite: "should not send the same dependency value twice"
|
|
45
|
+
*/
|
|
46
|
+
duplicateDependencyTest(): Promise<{
|
|
47
|
+
messageRes1: string[];
|
|
48
|
+
mapLoaded: boolean;
|
|
49
|
+
mapFromParentLoaded: boolean;
|
|
50
|
+
groupLoaded: boolean;
|
|
51
|
+
parentGroupLoaded: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Test case from cojson-storage-sqlite: "should recover from data loss"
|
|
55
|
+
*/
|
|
56
|
+
dataLossRecoverTest(): Promise<{
|
|
57
|
+
messageRes1: string[];
|
|
58
|
+
messageRes2: string[];
|
|
59
|
+
mapContent: {
|
|
60
|
+
[x: string]: import("cojson").JsonValue | undefined;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Test case from cojson-storage-sqlite: "should recover missing dependencies from storage"
|
|
65
|
+
*/
|
|
66
|
+
recoverMissingDependenciesTest(): Promise<{
|
|
67
|
+
mapContent: {
|
|
68
|
+
[x: string]: import("cojson").JsonValue | undefined;
|
|
69
|
+
};
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Test case from cojson-storage-sqlite: "should sync multiple sessions in a single content message"
|
|
73
|
+
*/
|
|
74
|
+
multipleSessionsSingleContentMessageTest(): Promise<{
|
|
75
|
+
messageRes1: string[];
|
|
76
|
+
map2Hello: import("cojson").JsonValue | undefined;
|
|
77
|
+
map3Hello: import("cojson").JsonValue | undefined;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Test case from cojson-storage-sqlite: "large coValue upload streaming"
|
|
81
|
+
*/
|
|
82
|
+
largeCoValueUploadTest(): Promise<{
|
|
83
|
+
messageRes1: string[];
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
export default class EntryPoint extends WorkerEntrypoint {
|
|
87
|
+
fetch(request: Request): Promise<Response>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=dosqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dosqlite.d.ts","sourceRoot":"","sources":["../../src/tests/dosqlite.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC;AACnC,OAAO,EAAO,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAIL,WAAW,EAEZ,MAAM,QAAQ,CAAC;AAShB,wBAAgB,aAAa;;cAEnB,QAAQ,GAAG,QAAQ,GAAG,SAAS;aAChC,WAAW;;;EAuEnB;AAgCD;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C;;OAEG;IACG,aAAa;;;;;IA8DnB;;OAEG;IACG,kBAAkB;;;;IA2DxB;;OAEG;IACG,oBAAoB;;;;;;;IAoE1B;;OAEG;IACG,uBAAuB;;;;;;;IAkE7B;;OAEG;IACG,mBAAmB;;;;;;;IA8EzB;;OAEG;IACG,8BAA8B;;;;;IAmFpC;;OAEG;IACG,wCAAwC;;;;;IA8E9C;;OAEG;IACG,sBAAsB;;;CAqE7B;AAED,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,gBAAgB;IAChD,KAAK,CAAC,OAAO,EAAE,OAAO;CAkD7B"}
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import "jazz-tools/load-edge-wasm";
|
|
2
|
+
import { env, DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
import { LocalNode, StorageApiSync, cojsonInternals, } from "cojson";
|
|
4
|
+
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
5
|
+
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
6
|
+
import { getDurableObjectSqlStorage } from "../index.js";
|
|
7
|
+
const Crypto = await WasmCrypto.create();
|
|
8
|
+
// Adopted from cojson-storage-sqlite/src/tests/testUtils.js
|
|
9
|
+
export function trackMessages() {
|
|
10
|
+
const messages = [];
|
|
11
|
+
const originalLoad = StorageApiSync.prototype.load;
|
|
12
|
+
const originalStore = StorageApiSync.prototype.store;
|
|
13
|
+
StorageApiSync.prototype.load = async function (id, callback, done) {
|
|
14
|
+
messages.push({
|
|
15
|
+
from: "client",
|
|
16
|
+
msg: {
|
|
17
|
+
action: "load",
|
|
18
|
+
id: id,
|
|
19
|
+
header: false,
|
|
20
|
+
sessions: {},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return originalLoad.call(this, id, (msg) => {
|
|
24
|
+
messages.push({
|
|
25
|
+
from: "storage",
|
|
26
|
+
msg,
|
|
27
|
+
});
|
|
28
|
+
callback(msg);
|
|
29
|
+
}, done);
|
|
30
|
+
};
|
|
31
|
+
StorageApiSync.prototype.store = function (data, correctionCallback) {
|
|
32
|
+
messages.push({
|
|
33
|
+
from: "client",
|
|
34
|
+
msg: data,
|
|
35
|
+
});
|
|
36
|
+
return originalStore.call(this, data, (msg) => {
|
|
37
|
+
messages.push({
|
|
38
|
+
from: "storage",
|
|
39
|
+
msg: {
|
|
40
|
+
action: "known",
|
|
41
|
+
isCorrection: true,
|
|
42
|
+
...msg,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const correctionMessages = correctionCallback(msg);
|
|
46
|
+
if (correctionMessages) {
|
|
47
|
+
for (const msg of correctionMessages) {
|
|
48
|
+
messages.push({
|
|
49
|
+
from: "client",
|
|
50
|
+
msg,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return correctionMessages;
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const restore = () => {
|
|
58
|
+
StorageApiSync.prototype.load = originalLoad;
|
|
59
|
+
StorageApiSync.prototype.store = originalStore;
|
|
60
|
+
messages.length = 0;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
messages,
|
|
64
|
+
restore,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function waitFor(callback) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const checkPassed = async () => {
|
|
70
|
+
try {
|
|
71
|
+
return { ok: await callback(), error: null };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return { ok: false, error };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
let retries = 0;
|
|
78
|
+
const interval = setInterval(async () => {
|
|
79
|
+
const { ok, error } = await checkPassed();
|
|
80
|
+
if (ok !== false) {
|
|
81
|
+
clearInterval(interval);
|
|
82
|
+
resolve();
|
|
83
|
+
}
|
|
84
|
+
if (++retries > 10) {
|
|
85
|
+
clearInterval(interval);
|
|
86
|
+
reject(error);
|
|
87
|
+
}
|
|
88
|
+
}, 100);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Due to issues with @cloudflare/vitest-pool-workers and loading wasm modules
|
|
93
|
+
* implementing the test logic in the durable object and running full server
|
|
94
|
+
* with wrangler.
|
|
95
|
+
*
|
|
96
|
+
*/
|
|
97
|
+
export class DoSqlStoreTest extends DurableObject {
|
|
98
|
+
/**
|
|
99
|
+
* Test case from cojson-storage-sqlite: "should sync and load data from storage"
|
|
100
|
+
*/
|
|
101
|
+
async storeLoadTest() {
|
|
102
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
103
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
104
|
+
const node1Sync = trackMessages();
|
|
105
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
106
|
+
node1.setStorage(storage1);
|
|
107
|
+
const group = node1.createGroup();
|
|
108
|
+
const map = group.createMap();
|
|
109
|
+
map.set("hello", "world");
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
111
|
+
const messageRes1 = toSimplifiedMessages({
|
|
112
|
+
Map: map.core,
|
|
113
|
+
Group: group.core,
|
|
114
|
+
}, node1Sync.messages);
|
|
115
|
+
node1Sync.restore();
|
|
116
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
117
|
+
const node2Sync = trackMessages();
|
|
118
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
119
|
+
node2.setStorage(storage2);
|
|
120
|
+
const map2 = await node2.load(map.id);
|
|
121
|
+
if (map2 === "unavailable") {
|
|
122
|
+
throw new Error("Map is unavailable");
|
|
123
|
+
}
|
|
124
|
+
const messageRes2 = toSimplifiedMessages({
|
|
125
|
+
Map: map.core,
|
|
126
|
+
Group: group.core,
|
|
127
|
+
}, node2Sync.messages);
|
|
128
|
+
node2Sync.restore();
|
|
129
|
+
return {
|
|
130
|
+
messageRes1,
|
|
131
|
+
messageRes2,
|
|
132
|
+
map2Hello: map2.get("hello"),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Test case from cojson-storage-sqlite: "should send an empty content message if there is no content"
|
|
137
|
+
*/
|
|
138
|
+
async storeLoadEmptyTest() {
|
|
139
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
140
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
141
|
+
const node1Sync = trackMessages();
|
|
142
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
143
|
+
node1.setStorage(storage1);
|
|
144
|
+
const group = node1.createGroup();
|
|
145
|
+
const map = group.createMap();
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
147
|
+
const messageRes1 = toSimplifiedMessages({
|
|
148
|
+
Map: map.core,
|
|
149
|
+
Group: group.core,
|
|
150
|
+
}, node1Sync.messages);
|
|
151
|
+
node1Sync.restore();
|
|
152
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
153
|
+
const node2Sync = trackMessages();
|
|
154
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
155
|
+
node2.setStorage(storage2);
|
|
156
|
+
const map2 = await node2.load(map.id);
|
|
157
|
+
if (map2 === "unavailable") {
|
|
158
|
+
throw new Error("Map is unavailable");
|
|
159
|
+
}
|
|
160
|
+
const messageRes2 = toSimplifiedMessages({
|
|
161
|
+
Map: map.core,
|
|
162
|
+
Group: group.core,
|
|
163
|
+
}, node2Sync.messages);
|
|
164
|
+
node2Sync.restore();
|
|
165
|
+
return {
|
|
166
|
+
messageRes1,
|
|
167
|
+
messageRes2,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Test case from cojson-storage-sqlite: "should load dependencies correctly (group inheritance)"
|
|
172
|
+
*/
|
|
173
|
+
async loadDependenciesTest() {
|
|
174
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
175
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
176
|
+
const node1Sync = trackMessages();
|
|
177
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
178
|
+
node1.setStorage(storage1);
|
|
179
|
+
const group = node1.createGroup();
|
|
180
|
+
const parentGroup = node1.createGroup();
|
|
181
|
+
group.extend(parentGroup);
|
|
182
|
+
const map = group.createMap();
|
|
183
|
+
map.set("hello", "world");
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
185
|
+
const messageRes1 = toSimplifiedMessages({
|
|
186
|
+
Map: map.core,
|
|
187
|
+
Group: group.core,
|
|
188
|
+
ParentGroup: parentGroup.core,
|
|
189
|
+
}, node1Sync.messages);
|
|
190
|
+
node1Sync.restore();
|
|
191
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
192
|
+
const node2Sync = trackMessages();
|
|
193
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
194
|
+
node2.setStorage(storage2);
|
|
195
|
+
await node2.load(map.id);
|
|
196
|
+
const mapLoaded = !!node2.expectCoValueLoaded(map.id);
|
|
197
|
+
const groupLoaded = !!node2.expectCoValueLoaded(group.id);
|
|
198
|
+
const parentGroupLoaded = !!node2.expectCoValueLoaded(parentGroup.id);
|
|
199
|
+
const messageRes2 = toSimplifiedMessages({
|
|
200
|
+
Map: map.core,
|
|
201
|
+
Group: group.core,
|
|
202
|
+
ParentGroup: parentGroup.core,
|
|
203
|
+
}, node2Sync.messages);
|
|
204
|
+
return {
|
|
205
|
+
messageRes1,
|
|
206
|
+
messageRes2,
|
|
207
|
+
mapLoaded,
|
|
208
|
+
groupLoaded,
|
|
209
|
+
parentGroupLoaded,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Test case from cojson-storage-sqlite: "should not send the same dependency value twice"
|
|
214
|
+
*/
|
|
215
|
+
async duplicateDependencyTest() {
|
|
216
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
217
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
218
|
+
const node1Sync = trackMessages();
|
|
219
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
220
|
+
node1.setStorage(storage1);
|
|
221
|
+
const group = node1.createGroup();
|
|
222
|
+
const parentGroup = node1.createGroup();
|
|
223
|
+
group.extend(parentGroup);
|
|
224
|
+
const mapFromParent = parentGroup.createMap();
|
|
225
|
+
const map = group.createMap();
|
|
226
|
+
map.set("hello", "world");
|
|
227
|
+
mapFromParent.set("hello", "world");
|
|
228
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
229
|
+
node1Sync.restore();
|
|
230
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
231
|
+
const node2Sync = trackMessages();
|
|
232
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
233
|
+
node2.setStorage(storage2);
|
|
234
|
+
await node2.load(map.id);
|
|
235
|
+
await node2.load(mapFromParent.id);
|
|
236
|
+
const mapLoaded = !!node2.expectCoValueLoaded(map.id);
|
|
237
|
+
const mapFromParentLoaded = !!node2.expectCoValueLoaded(mapFromParent.id);
|
|
238
|
+
const groupLoaded = !!node2.expectCoValueLoaded(group.id);
|
|
239
|
+
const parentGroupLoaded = !!node2.expectCoValueLoaded(parentGroup.id);
|
|
240
|
+
const messageRes1 = toSimplifiedMessages({
|
|
241
|
+
Map: map.core,
|
|
242
|
+
Group: group.core,
|
|
243
|
+
ParentGroup: parentGroup.core,
|
|
244
|
+
MapFromParent: mapFromParent.core,
|
|
245
|
+
}, node2Sync.messages);
|
|
246
|
+
return {
|
|
247
|
+
messageRes1,
|
|
248
|
+
mapLoaded,
|
|
249
|
+
mapFromParentLoaded,
|
|
250
|
+
groupLoaded,
|
|
251
|
+
parentGroupLoaded,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Test case from cojson-storage-sqlite: "should recover from data loss"
|
|
256
|
+
*/
|
|
257
|
+
async dataLossRecoverTest() {
|
|
258
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
259
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
260
|
+
const node1Sync = trackMessages();
|
|
261
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
262
|
+
node1.setStorage(storage1);
|
|
263
|
+
const group = node1.createGroup();
|
|
264
|
+
const map = group.createMap();
|
|
265
|
+
map.set("0", 0);
|
|
266
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
267
|
+
const originalStore = StorageApiSync.prototype.store;
|
|
268
|
+
StorageApiSync.prototype.store = () => false;
|
|
269
|
+
map.set("1", 1);
|
|
270
|
+
map.set("2", 2);
|
|
271
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
272
|
+
StorageApiSync.prototype.store = originalStore;
|
|
273
|
+
map.set("3", 3);
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
275
|
+
const messageRes1 = toSimplifiedMessages({
|
|
276
|
+
Map: map.core,
|
|
277
|
+
Group: group.core,
|
|
278
|
+
}, node1Sync.messages);
|
|
279
|
+
node1Sync.restore();
|
|
280
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
281
|
+
const node2Sync = trackMessages();
|
|
282
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
283
|
+
node2.setStorage(storage2);
|
|
284
|
+
const map2 = await node2.load(map.id);
|
|
285
|
+
if (map2 === "unavailable") {
|
|
286
|
+
throw new Error("Map is unavailable");
|
|
287
|
+
}
|
|
288
|
+
const mapContent = map2.toJSON();
|
|
289
|
+
const messageRes2 = toSimplifiedMessages({
|
|
290
|
+
Map: map.core,
|
|
291
|
+
Group: group.core,
|
|
292
|
+
}, node2Sync.messages);
|
|
293
|
+
return {
|
|
294
|
+
messageRes1,
|
|
295
|
+
messageRes2,
|
|
296
|
+
mapContent,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Test case from cojson-storage-sqlite: "should recover missing dependencies from storage"
|
|
301
|
+
*/
|
|
302
|
+
async recoverMissingDependenciesTest() {
|
|
303
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
304
|
+
const account = LocalNode.internalCreateAccount({
|
|
305
|
+
crypto: Crypto,
|
|
306
|
+
});
|
|
307
|
+
const node1 = account.core.node;
|
|
308
|
+
const serverNode = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
309
|
+
const [serverPeer, clientPeer] = cojsonInternals.connectedPeers(node1.agentSecret, serverNode.agentSecret, {
|
|
310
|
+
peer1role: "server",
|
|
311
|
+
peer2role: "client",
|
|
312
|
+
});
|
|
313
|
+
node1.syncManager.addPeer(serverPeer);
|
|
314
|
+
serverNode.syncManager.addPeer(clientPeer);
|
|
315
|
+
// manual mock
|
|
316
|
+
const originalStore = StorageApiSync.prototype.store;
|
|
317
|
+
StorageApiSync.prototype.store = function (data, correctionCallback) {
|
|
318
|
+
if ([group.core.id, account.core.id].includes(data.id)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return originalStore.call(this, data, correctionCallback);
|
|
322
|
+
};
|
|
323
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
324
|
+
node1.setStorage(storage1);
|
|
325
|
+
const group = node1.createGroup();
|
|
326
|
+
group.addMember("everyone", "writer");
|
|
327
|
+
const map = group.createMap();
|
|
328
|
+
map.set("0", 0);
|
|
329
|
+
StorageApiSync.prototype.store = originalStore;
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
331
|
+
const node2 = new LocalNode(Crypto.newRandomAgentSecret(), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
332
|
+
const [serverPeer2, clientPeer2] = cojsonInternals.connectedPeers(node1.agentSecret, serverNode.agentSecret, {
|
|
333
|
+
peer1role: "server",
|
|
334
|
+
peer2role: "client",
|
|
335
|
+
});
|
|
336
|
+
node2.syncManager.addPeer(serverPeer2);
|
|
337
|
+
serverNode.syncManager.addPeer(clientPeer2);
|
|
338
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
339
|
+
node2.setStorage(storage2);
|
|
340
|
+
const map2 = await node2.load(map.id);
|
|
341
|
+
if (map2 === "unavailable") {
|
|
342
|
+
throw new Error("Map is unavailable");
|
|
343
|
+
}
|
|
344
|
+
const mapContent = map2.toJSON();
|
|
345
|
+
return {
|
|
346
|
+
mapContent,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Test case from cojson-storage-sqlite: "should sync multiple sessions in a single content message"
|
|
351
|
+
*/
|
|
352
|
+
async multipleSessionsSingleContentMessageTest() {
|
|
353
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
354
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
355
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
356
|
+
node1.setStorage(storage1);
|
|
357
|
+
const group = node1.createGroup();
|
|
358
|
+
const map = group.createMap();
|
|
359
|
+
map.set("hello", "world");
|
|
360
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
361
|
+
node1.gracefulShutdown();
|
|
362
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
363
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
364
|
+
node2.setStorage(storage2);
|
|
365
|
+
const map2 = await node2.load(map.id);
|
|
366
|
+
if (map2 === "unavailable") {
|
|
367
|
+
throw new Error("Map is unavailable");
|
|
368
|
+
}
|
|
369
|
+
const map2Hello = map2.get("hello");
|
|
370
|
+
map2.set("hello", "world2");
|
|
371
|
+
await map2.core.waitForSync();
|
|
372
|
+
node2.gracefulShutdown();
|
|
373
|
+
const node3 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
374
|
+
const node3Sync = trackMessages();
|
|
375
|
+
const storage3 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
376
|
+
node3.setStorage(storage3);
|
|
377
|
+
const map3 = await node3.load(map.id);
|
|
378
|
+
if (map3 === "unavailable") {
|
|
379
|
+
throw new Error("Map is unavailable");
|
|
380
|
+
}
|
|
381
|
+
const map3Hello = map3.get("hello");
|
|
382
|
+
const messageRes1 = toSimplifiedMessages({
|
|
383
|
+
Map: map.core,
|
|
384
|
+
Group: group.core,
|
|
385
|
+
}, node3Sync.messages);
|
|
386
|
+
node3Sync.restore();
|
|
387
|
+
return {
|
|
388
|
+
messageRes1,
|
|
389
|
+
map2Hello,
|
|
390
|
+
map3Hello,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Test case from cojson-storage-sqlite: "large coValue upload streaming"
|
|
395
|
+
*/
|
|
396
|
+
async largeCoValueUploadTest() {
|
|
397
|
+
const agentSecret = Crypto.newRandomAgentSecret();
|
|
398
|
+
const node1 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
399
|
+
const storage1 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
400
|
+
node1.setStorage(storage1);
|
|
401
|
+
const group = node1.createGroup();
|
|
402
|
+
const largeMap = group.createMap();
|
|
403
|
+
const dataSize = 1 * 1024 * 200;
|
|
404
|
+
const chunkSize = 1024; // 1KB chunks
|
|
405
|
+
const chunks = dataSize / chunkSize;
|
|
406
|
+
const value = "a".repeat(chunkSize);
|
|
407
|
+
for (let i = 0; i < chunks; i++) {
|
|
408
|
+
const key = `key${i}`;
|
|
409
|
+
largeMap.set(key, value, "trusting");
|
|
410
|
+
}
|
|
411
|
+
await largeMap.core.waitForSync();
|
|
412
|
+
node1.gracefulShutdown();
|
|
413
|
+
const node2 = new LocalNode(agentSecret, Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
414
|
+
const node2Sync = trackMessages();
|
|
415
|
+
const storage2 = getDurableObjectSqlStorage(this.ctx.storage);
|
|
416
|
+
node2.setStorage(storage2);
|
|
417
|
+
const largeMapOnNode2 = await node2.load(largeMap.id);
|
|
418
|
+
if (largeMapOnNode2 === "unavailable") {
|
|
419
|
+
throw new Error("Map is unavailable");
|
|
420
|
+
}
|
|
421
|
+
await waitFor(() => {
|
|
422
|
+
if (JSON.stringify(largeMapOnNode2.core.knownState()) !==
|
|
423
|
+
JSON.stringify(largeMap.core.knownState())) {
|
|
424
|
+
throw new Error("Map states are not equal");
|
|
425
|
+
}
|
|
426
|
+
return true;
|
|
427
|
+
});
|
|
428
|
+
const messageRes1 = toSimplifiedMessages({
|
|
429
|
+
Map: largeMap.core,
|
|
430
|
+
Group: group.core,
|
|
431
|
+
}, node2Sync.messages);
|
|
432
|
+
return {
|
|
433
|
+
messageRes1,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
export default class EntryPoint extends WorkerEntrypoint {
|
|
438
|
+
async fetch(request) {
|
|
439
|
+
if (request.method !== "POST") {
|
|
440
|
+
return new Response("Method not allowed", { status: 405 });
|
|
441
|
+
}
|
|
442
|
+
const path = new URL(request.url).pathname;
|
|
443
|
+
const body = await request.json();
|
|
444
|
+
if (!(typeof body === "object" &&
|
|
445
|
+
body !== null &&
|
|
446
|
+
"doId" in body &&
|
|
447
|
+
typeof body.doId === "string")) {
|
|
448
|
+
throw new Error("Invalid request body");
|
|
449
|
+
}
|
|
450
|
+
const stub = env.DoSqlStoreTest.getByName(body.doId);
|
|
451
|
+
if (path === "/sync-and-load") {
|
|
452
|
+
const res = await stub.storeLoadTest();
|
|
453
|
+
// @ts-expect-error ts2589 the type initiation is too deep
|
|
454
|
+
return Response.json(res);
|
|
455
|
+
}
|
|
456
|
+
else if (path === "/sync-and-load-empty") {
|
|
457
|
+
const res = await stub.storeLoadEmptyTest();
|
|
458
|
+
return Response.json(res);
|
|
459
|
+
}
|
|
460
|
+
else if (path === "/group-load") {
|
|
461
|
+
const res = await stub.loadDependenciesTest();
|
|
462
|
+
return Response.json(res);
|
|
463
|
+
}
|
|
464
|
+
else if (path === "/group-load-duplicate") {
|
|
465
|
+
const res = await stub.duplicateDependencyTest();
|
|
466
|
+
return Response.json(res);
|
|
467
|
+
}
|
|
468
|
+
else if (path === "/data-loss-recovery") {
|
|
469
|
+
const res = await stub.dataLossRecoverTest();
|
|
470
|
+
return Response.json(res);
|
|
471
|
+
}
|
|
472
|
+
else if (path === "/missing-dependency-recovery") {
|
|
473
|
+
const res = await stub.recoverMissingDependenciesTest();
|
|
474
|
+
return Response.json(res);
|
|
475
|
+
}
|
|
476
|
+
else if (path === "/multiple-sessions") {
|
|
477
|
+
const res = await stub.multipleSessionsSingleContentMessageTest();
|
|
478
|
+
return Response.json(res);
|
|
479
|
+
}
|
|
480
|
+
else if (path === "/large-covalue-upload") {
|
|
481
|
+
const res = await stub.largeCoValueUploadTest();
|
|
482
|
+
return Response.json(res);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
return new Response("Invalid test method", { status: 400 });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=dosqlite.js.map
|