cojson-storage-indexeddb 0.8.34 → 0.8.36
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/CHANGELOG.md +22 -0
- package/dist/idbClient.js +101 -0
- package/dist/idbClient.js.map +1 -0
- package/dist/idbNode.js +78 -0
- package/dist/idbNode.js.map +1 -0
- package/dist/index.js +1 -410
- package/dist/index.js.map +1 -1
- package/dist/tests/{index.test.js → idbNode.test.js} +3 -3
- package/dist/tests/idbNode.test.js.map +1 -0
- package/package.json +3 -2
- package/src/idbClient.ts +224 -0
- package/src/idbNode.ts +123 -0
- package/src/index.ts +1 -733
- package/src/tests/{index.test.ts → idbNode.test.ts} +2 -2
- package/dist/tests/index.test.js.map +0 -1
|
@@ -3,14 +3,14 @@ import { ControlledAgent, LocalNode, WasmCrypto } from "cojson";
|
|
|
3
3
|
import { expect, test } from "vitest";
|
|
4
4
|
import { IDBStorage } from "../index.js";
|
|
5
5
|
const Crypto = await WasmCrypto.create();
|
|
6
|
-
test
|
|
6
|
+
test("Should be able to initialize and load from empty DB", async () => {
|
|
7
7
|
const agentSecret = Crypto.newRandomAgentSecret();
|
|
8
8
|
const node = new LocalNode(new ControlledAgent(agentSecret, Crypto), Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)), Crypto);
|
|
9
9
|
node.syncManager.addPeer(await IDBStorage.asPeer({ trace: true }));
|
|
10
10
|
console.log("yay!");
|
|
11
11
|
const _group = node.createGroup();
|
|
12
12
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
13
|
-
expect(node.syncManager.peers["
|
|
13
|
+
expect(node.syncManager.peers["indexedDB"]).toBeDefined();
|
|
14
14
|
});
|
|
15
15
|
test("Should be able to sync data to database and then load that from a new node", async () => {
|
|
16
16
|
const agentSecret = Crypto.newRandomAgentSecret();
|
|
@@ -29,4 +29,4 @@ test("Should be able to sync data to database and then load that from a new node
|
|
|
29
29
|
}
|
|
30
30
|
expect(map2.get("hello")).toBe("world");
|
|
31
31
|
});
|
|
32
|
-
//# sourceMappingURL=
|
|
32
|
+
//# sourceMappingURL=idbNode.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idbNode.test.js","sourceRoot":"","sources":["../../src/tests/idbNode.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cojson-storage-indexeddb",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.36",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"cojson": "0.8.
|
|
9
|
+
"cojson": "0.8.36",
|
|
10
|
+
"cojson-storage": "0.8.36"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
13
|
"@vitest/browser": "^0.34.1",
|
package/src/idbClient.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { CojsonInternalTypes } from "cojson";
|
|
2
|
+
import { SyncPromise } from "./syncPromises";
|
|
3
|
+
import RawCoID = CojsonInternalTypes.RawCoID;
|
|
4
|
+
import Transaction = CojsonInternalTypes.Transaction;
|
|
5
|
+
import Signature = CojsonInternalTypes.Signature;
|
|
6
|
+
import {
|
|
7
|
+
CoValueRow,
|
|
8
|
+
DBClientInterface,
|
|
9
|
+
SessionRow,
|
|
10
|
+
SignatureAfterRow,
|
|
11
|
+
StoredCoValueRow,
|
|
12
|
+
StoredSessionRow,
|
|
13
|
+
TransactionRow,
|
|
14
|
+
} from "cojson-storage";
|
|
15
|
+
|
|
16
|
+
export class IDBClient implements DBClientInterface {
|
|
17
|
+
private db;
|
|
18
|
+
|
|
19
|
+
currentTx:
|
|
20
|
+
| {
|
|
21
|
+
id: number;
|
|
22
|
+
tx: IDBTransaction;
|
|
23
|
+
stores: {
|
|
24
|
+
coValues: IDBObjectStore;
|
|
25
|
+
sessions: IDBObjectStore;
|
|
26
|
+
transactions: IDBObjectStore;
|
|
27
|
+
signatureAfter: IDBObjectStore;
|
|
28
|
+
};
|
|
29
|
+
startedAt: number;
|
|
30
|
+
pendingRequests: ((txEntry: {
|
|
31
|
+
stores: {
|
|
32
|
+
coValues: IDBObjectStore;
|
|
33
|
+
sessions: IDBObjectStore;
|
|
34
|
+
transactions: IDBObjectStore;
|
|
35
|
+
signatureAfter: IDBObjectStore;
|
|
36
|
+
};
|
|
37
|
+
}) => void)[];
|
|
38
|
+
}
|
|
39
|
+
| undefined;
|
|
40
|
+
|
|
41
|
+
currentTxID = 0;
|
|
42
|
+
|
|
43
|
+
constructor(db: IDBDatabase) {
|
|
44
|
+
this.db = db;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
makeRequest<T>(
|
|
48
|
+
handler: (stores: {
|
|
49
|
+
coValues: IDBObjectStore;
|
|
50
|
+
sessions: IDBObjectStore;
|
|
51
|
+
transactions: IDBObjectStore;
|
|
52
|
+
signatureAfter: IDBObjectStore;
|
|
53
|
+
}) => IDBRequest,
|
|
54
|
+
): SyncPromise<T> {
|
|
55
|
+
return new SyncPromise((resolve, reject) => {
|
|
56
|
+
let txEntry = this.currentTx;
|
|
57
|
+
|
|
58
|
+
const requestEntry = ({
|
|
59
|
+
stores,
|
|
60
|
+
}: {
|
|
61
|
+
stores: {
|
|
62
|
+
coValues: IDBObjectStore;
|
|
63
|
+
sessions: IDBObjectStore;
|
|
64
|
+
transactions: IDBObjectStore;
|
|
65
|
+
signatureAfter: IDBObjectStore;
|
|
66
|
+
};
|
|
67
|
+
}) => {
|
|
68
|
+
const request = handler(stores);
|
|
69
|
+
request.onerror = () => {
|
|
70
|
+
console.error("Error in request", request.error);
|
|
71
|
+
this.currentTx = undefined;
|
|
72
|
+
reject(request.error);
|
|
73
|
+
};
|
|
74
|
+
request.onsuccess = () => {
|
|
75
|
+
const value = request.result as T;
|
|
76
|
+
resolve(value);
|
|
77
|
+
|
|
78
|
+
const next = txEntry!.pendingRequests.shift();
|
|
79
|
+
|
|
80
|
+
if (next) {
|
|
81
|
+
next({ stores });
|
|
82
|
+
} else {
|
|
83
|
+
if (this.currentTx === txEntry) {
|
|
84
|
+
this.currentTx = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Transaction batching
|
|
91
|
+
if (!txEntry || performance.now() - txEntry.startedAt > 20) {
|
|
92
|
+
const tx = this.db.transaction(
|
|
93
|
+
["coValues", "sessions", "transactions", "signatureAfter"],
|
|
94
|
+
"readwrite",
|
|
95
|
+
);
|
|
96
|
+
txEntry = {
|
|
97
|
+
id: this.currentTxID++,
|
|
98
|
+
tx,
|
|
99
|
+
stores: {
|
|
100
|
+
coValues: tx.objectStore("coValues"),
|
|
101
|
+
sessions: tx.objectStore("sessions"),
|
|
102
|
+
transactions: tx.objectStore("transactions"),
|
|
103
|
+
signatureAfter: tx.objectStore("signatureAfter"),
|
|
104
|
+
},
|
|
105
|
+
startedAt: performance.now(),
|
|
106
|
+
pendingRequests: [],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.currentTx = txEntry;
|
|
110
|
+
|
|
111
|
+
requestEntry(txEntry);
|
|
112
|
+
} else {
|
|
113
|
+
txEntry.pendingRequests.push(requestEntry);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getCoValue(coValueId: RawCoID): Promise<StoredCoValueRow | undefined> {
|
|
119
|
+
return this.makeRequest<StoredCoValueRow | undefined>(({ coValues }) =>
|
|
120
|
+
coValues.index("coValuesById").get(coValueId),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]> {
|
|
125
|
+
return this.makeRequest<StoredSessionRow[]>(({ sessions }) =>
|
|
126
|
+
sessions.index("sessionsByCoValue").getAll(coValueRowId),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getNewTransactionInSession(
|
|
131
|
+
sessionRowId: number,
|
|
132
|
+
firstNewTxIdx: number,
|
|
133
|
+
): Promise<TransactionRow[]> {
|
|
134
|
+
return this.makeRequest<TransactionRow[]>(({ transactions }) =>
|
|
135
|
+
transactions.getAll(
|
|
136
|
+
IDBKeyRange.bound(
|
|
137
|
+
[sessionRowId, firstNewTxIdx],
|
|
138
|
+
[sessionRowId, Infinity],
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getSignatures(
|
|
145
|
+
sessionRowId: number,
|
|
146
|
+
firstNewTxIdx: number,
|
|
147
|
+
): Promise<SignatureAfterRow[]> {
|
|
148
|
+
return this.makeRequest<SignatureAfterRow[]>(
|
|
149
|
+
({ signatureAfter }: { signatureAfter: IDBObjectStore }) =>
|
|
150
|
+
signatureAfter.getAll(
|
|
151
|
+
IDBKeyRange.bound(
|
|
152
|
+
[sessionRowId, firstNewTxIdx],
|
|
153
|
+
[sessionRowId, Infinity],
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async addCoValue(
|
|
160
|
+
msg: CojsonInternalTypes.NewContentMessage,
|
|
161
|
+
): Promise<number> {
|
|
162
|
+
if (!msg.header) {
|
|
163
|
+
throw new Error("Header is required, coId: " + msg.id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (await this.makeRequest<IDBValidKey>(({ coValues }) =>
|
|
167
|
+
coValues.put({
|
|
168
|
+
id: msg.id,
|
|
169
|
+
header: msg.header!,
|
|
170
|
+
} satisfies CoValueRow),
|
|
171
|
+
)) as number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async addSessionUpdate({
|
|
175
|
+
sessionUpdate,
|
|
176
|
+
sessionRow,
|
|
177
|
+
}: {
|
|
178
|
+
sessionUpdate: SessionRow;
|
|
179
|
+
sessionRow?: StoredSessionRow;
|
|
180
|
+
}): Promise<number> {
|
|
181
|
+
return this.makeRequest<number>(({ sessions }) =>
|
|
182
|
+
sessions.put(
|
|
183
|
+
sessionRow?.rowID
|
|
184
|
+
? {
|
|
185
|
+
rowID: sessionRow.rowID,
|
|
186
|
+
...sessionUpdate,
|
|
187
|
+
}
|
|
188
|
+
: sessionUpdate,
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
addTransaction(
|
|
194
|
+
sessionRowID: number,
|
|
195
|
+
idx: number,
|
|
196
|
+
newTransaction: Transaction,
|
|
197
|
+
) {
|
|
198
|
+
return this.makeRequest(({ transactions }) =>
|
|
199
|
+
transactions.add({
|
|
200
|
+
ses: sessionRowID,
|
|
201
|
+
idx,
|
|
202
|
+
tx: newTransaction,
|
|
203
|
+
} satisfies TransactionRow),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async addSignatureAfter({
|
|
208
|
+
sessionRowID,
|
|
209
|
+
idx,
|
|
210
|
+
signature,
|
|
211
|
+
}: { sessionRowID: number; idx: number; signature: Signature }) {
|
|
212
|
+
return this.makeRequest(({ signatureAfter }) =>
|
|
213
|
+
signatureAfter.put({
|
|
214
|
+
ses: sessionRowID,
|
|
215
|
+
idx,
|
|
216
|
+
signature,
|
|
217
|
+
} satisfies SignatureAfterRow),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async unitOfWork(operationsCallback: () => unknown[]) {
|
|
222
|
+
return Promise.all(operationsCallback());
|
|
223
|
+
}
|
|
224
|
+
}
|
package/src/idbNode.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IncomingSyncStream,
|
|
3
|
+
OutgoingSyncQueue,
|
|
4
|
+
Peer,
|
|
5
|
+
cojsonInternals,
|
|
6
|
+
} from "cojson";
|
|
7
|
+
import { SyncManager } from "cojson-storage";
|
|
8
|
+
import { IDBClient } from "./idbClient.js";
|
|
9
|
+
|
|
10
|
+
export class IDBNode {
|
|
11
|
+
private readonly dbClient: IDBClient;
|
|
12
|
+
private readonly syncManager: SyncManager;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
db: IDBDatabase,
|
|
16
|
+
fromLocalNode: IncomingSyncStream,
|
|
17
|
+
toLocalNode: OutgoingSyncQueue,
|
|
18
|
+
) {
|
|
19
|
+
this.dbClient = new IDBClient(db);
|
|
20
|
+
this.syncManager = new SyncManager(this.dbClient, toLocalNode);
|
|
21
|
+
|
|
22
|
+
const processMessages = async () => {
|
|
23
|
+
for await (const msg of fromLocalNode) {
|
|
24
|
+
try {
|
|
25
|
+
if (msg === "Disconnected" || msg === "PingTimeout") {
|
|
26
|
+
throw new Error("Unexpected Disconnected message");
|
|
27
|
+
}
|
|
28
|
+
await this.syncManager.handleSyncMessage(msg);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(
|
|
31
|
+
new Error(
|
|
32
|
+
`Error reading from localNode, handling msg\n\n${JSON.stringify(
|
|
33
|
+
msg,
|
|
34
|
+
(k, v) =>
|
|
35
|
+
k === "changes" || k === "encryptedChanges"
|
|
36
|
+
? v.slice(0, 20) + "..."
|
|
37
|
+
: v,
|
|
38
|
+
)}`,
|
|
39
|
+
{ cause: e },
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
processMessages().catch((e) =>
|
|
47
|
+
console.error("Error in processMessages in IndexedDB", e),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static async asPeer(
|
|
52
|
+
{
|
|
53
|
+
trace,
|
|
54
|
+
localNodeName = "local",
|
|
55
|
+
}: { trace?: boolean; localNodeName?: string } | undefined = {
|
|
56
|
+
localNodeName: "local",
|
|
57
|
+
},
|
|
58
|
+
): Promise<Peer> {
|
|
59
|
+
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
|
|
60
|
+
localNodeName,
|
|
61
|
+
"indexedDB",
|
|
62
|
+
{
|
|
63
|
+
peer1role: "client",
|
|
64
|
+
peer2role: "storage",
|
|
65
|
+
trace,
|
|
66
|
+
crashOnClose: true,
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await IDBNode.open(localNodeAsPeer.incoming, localNodeAsPeer.outgoing);
|
|
71
|
+
|
|
72
|
+
return { ...storageAsPeer, priority: 100 };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static async open(
|
|
76
|
+
fromLocalNode: IncomingSyncStream,
|
|
77
|
+
toLocalNode: OutgoingSyncQueue,
|
|
78
|
+
) {
|
|
79
|
+
const dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
|
|
80
|
+
const request = indexedDB.open("jazz-storage", 4);
|
|
81
|
+
request.onerror = () => {
|
|
82
|
+
reject(request.error);
|
|
83
|
+
};
|
|
84
|
+
request.onsuccess = () => {
|
|
85
|
+
resolve(request.result);
|
|
86
|
+
};
|
|
87
|
+
request.onupgradeneeded = async (ev) => {
|
|
88
|
+
const db = request.result;
|
|
89
|
+
if (ev.oldVersion === 0) {
|
|
90
|
+
const coValues = db.createObjectStore("coValues", {
|
|
91
|
+
autoIncrement: true,
|
|
92
|
+
keyPath: "rowID",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
coValues.createIndex("coValuesById", "id", {
|
|
96
|
+
unique: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const sessions = db.createObjectStore("sessions", {
|
|
100
|
+
autoIncrement: true,
|
|
101
|
+
keyPath: "rowID",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
sessions.createIndex("sessionsByCoValue", "coValue");
|
|
105
|
+
sessions.createIndex("uniqueSessions", ["coValue", "sessionID"], {
|
|
106
|
+
unique: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
db.createObjectStore("transactions", {
|
|
110
|
+
keyPath: ["ses", "idx"],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (ev.oldVersion <= 1) {
|
|
114
|
+
db.createObjectStore("signatureAfter", {
|
|
115
|
+
keyPath: ["ses", "idx"],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return new IDBNode(await dbPromise, fromLocalNode, toLocalNode);
|
|
122
|
+
}
|
|
123
|
+
}
|