cojson 0.15.7 → 0.15.9
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 +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/IncomingMessagesQueue.d.ts +27 -0
- package/dist/IncomingMessagesQueue.d.ts.map +1 -0
- package/dist/IncomingMessagesQueue.js +114 -0
- package/dist/IncomingMessagesQueue.js.map +1 -0
- package/dist/PeerState.d.ts +2 -10
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +9 -90
- package/dist/PeerState.js.map +1 -1
- package/dist/PriorityBasedMessageQueue.d.ts +2 -1
- package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
- package/dist/PriorityBasedMessageQueue.js +9 -6
- package/dist/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/SyncStateManager.d.ts +1 -0
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +1 -1
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueCore/coValueCore.d.ts +9 -17
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +75 -50
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +10 -3
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +73 -14
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coStream.d.ts +2 -2
- package/dist/coValues/group.d.ts +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +2 -4
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +2 -1
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/exports.d.ts +18 -7
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +11 -8
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +8 -2
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +19 -12
- package/dist/localNode.js.map +1 -1
- package/dist/storage/StoreQueue.d.ts +15 -0
- package/dist/storage/StoreQueue.d.ts.map +1 -0
- package/dist/storage/StoreQueue.js +35 -0
- package/dist/storage/StoreQueue.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/knownState.d.ts +18 -0
- package/dist/storage/knownState.d.ts.map +1 -0
- package/dist/storage/knownState.js +63 -0
- package/dist/storage/knownState.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +37 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -0
- package/dist/storage/sqlite/client.js +89 -0
- package/dist/storage/sqlite/client.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +5 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +13 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/sqliteMigrations.d.ts +3 -0
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -0
- package/dist/storage/sqlite/sqliteMigrations.js +44 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -0
- package/dist/storage/sqlite/types.d.ts +8 -0
- package/dist/storage/sqlite/types.d.ts.map +1 -0
- package/dist/storage/sqlite/types.js +2 -0
- package/dist/storage/sqlite/types.js.map +1 -0
- package/dist/storage/sqliteAsync/client.d.ts +37 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/client.js +88 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -0
- package/dist/storage/sqliteAsync/index.d.ts +6 -0
- package/dist/storage/sqliteAsync/index.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/index.js +15 -0
- package/dist/storage/sqliteAsync/index.js.map +1 -0
- package/dist/storage/sqliteAsync/types.d.ts +9 -0
- package/dist/storage/sqliteAsync/types.d.ts.map +1 -0
- package/dist/storage/sqliteAsync/types.js +2 -0
- package/dist/storage/sqliteAsync/types.js.map +1 -0
- package/dist/storage/storageAsync.d.ts +22 -0
- package/dist/storage/storageAsync.d.ts.map +1 -0
- package/dist/storage/storageAsync.js +214 -0
- package/dist/storage/storageAsync.js.map +1 -0
- package/dist/storage/storageSync.d.ts +21 -0
- package/dist/storage/storageSync.d.ts.map +1 -0
- package/dist/storage/storageSync.js +206 -0
- package/dist/storage/storageSync.js.map +1 -0
- package/dist/storage/syncUtils.d.ts +13 -0
- package/dist/storage/syncUtils.d.ts.map +1 -0
- package/dist/storage/syncUtils.js +25 -0
- package/dist/storage/syncUtils.js.map +1 -0
- package/dist/storage/types.d.ts +82 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/streamUtils.d.ts +13 -9
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +46 -13
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +22 -14
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +143 -125
- package/dist/sync.js.map +1 -1
- package/dist/tests/IncomingMessagesQueue.test.d.ts +2 -0
- package/dist/tests/IncomingMessagesQueue.test.d.ts.map +1 -0
- package/dist/tests/IncomingMessagesQueue.test.js +437 -0
- package/dist/tests/IncomingMessagesQueue.test.js.map +1 -0
- package/dist/tests/PeerState.test.js +6 -94
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/PriorityBasedMessageQueue.test.js +14 -14
- package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
- package/dist/tests/StoreQueue.test.d.ts +2 -0
- package/dist/tests/StoreQueue.test.d.ts.map +1 -0
- package/dist/tests/StoreQueue.test.js +208 -0
- package/dist/tests/StoreQueue.test.js.map +1 -0
- package/dist/tests/SyncStateManager.test.js +3 -1
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +9 -9
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/coStream.test.js +1 -1
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +208 -1
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.js +2 -2
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
- package/dist/tests/group.addMember.test.js.map +1 -1
- package/dist/tests/group.removeMember.test.js +1 -1
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/sync.auth.test.js +23 -15
- package/dist/tests/sync.auth.test.js.map +1 -1
- package/dist/tests/sync.invite.test.js +10 -16
- package/dist/tests/sync.invite.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +52 -50
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +173 -56
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +42 -32
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +162 -62
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.d.ts +2 -0
- package/dist/tests/sync.storageAsync.test.d.ts.map +1 -0
- package/dist/tests/sync.storageAsync.test.js +361 -0
- package/dist/tests/sync.storageAsync.test.js.map +1 -0
- package/dist/tests/sync.test.js +16 -21
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +28 -25
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +12 -0
- package/dist/tests/testStorage.d.ts.map +1 -0
- package/dist/tests/testStorage.js +151 -0
- package/dist/tests/testStorage.js.map +1 -0
- package/dist/tests/testUtils.d.ts +20 -15
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +79 -45
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/IncomingMessagesQueue.ts +142 -0
- package/src/PeerState.ts +11 -110
- package/src/PriorityBasedMessageQueue.ts +13 -5
- package/src/SyncStateManager.ts +1 -1
- package/src/coValueCore/coValueCore.ts +100 -66
- package/src/coValueCore/verifiedState.ts +91 -21
- package/src/coValues/group.ts +2 -4
- package/src/config.ts +26 -0
- package/src/crypto/WasmCrypto.ts +3 -1
- package/src/exports.ts +20 -27
- package/src/localNode.ts +27 -12
- package/src/storage/StoreQueue.ts +56 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/knownState.ts +88 -0
- package/src/storage/sqlite/client.ts +180 -0
- package/src/storage/sqlite/index.ts +19 -0
- package/src/storage/sqlite/sqliteMigrations.ts +44 -0
- package/src/storage/sqlite/types.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +179 -0
- package/src/storage/sqliteAsync/index.ts +25 -0
- package/src/storage/sqliteAsync/types.ts +8 -0
- package/src/storage/storageAsync.ts +367 -0
- package/src/storage/storageSync.ts +343 -0
- package/src/storage/syncUtils.ts +50 -0
- package/src/storage/types.ts +162 -0
- package/src/streamUtils.ts +61 -19
- package/src/sync.ts +191 -160
- package/src/tests/IncomingMessagesQueue.test.ts +626 -0
- package/src/tests/PeerState.test.ts +6 -118
- package/src/tests/PriorityBasedMessageQueue.test.ts +18 -14
- package/src/tests/StoreQueue.test.ts +283 -0
- package/src/tests/SyncStateManager.test.ts +4 -1
- package/src/tests/account.test.ts +11 -12
- package/src/tests/coStream.test.ts +1 -3
- package/src/tests/coValueCore.test.ts +270 -1
- package/src/tests/coValueCoreLoadingState.test.ts +2 -2
- package/src/tests/group.addMember.test.ts +1 -0
- package/src/tests/group.removeMember.test.ts +2 -8
- package/src/tests/messagesTestUtils.ts +2 -2
- package/src/tests/sync.auth.test.ts +24 -14
- package/src/tests/sync.invite.test.ts +11 -17
- package/src/tests/sync.load.test.ts +53 -49
- package/src/tests/sync.mesh.test.ts +198 -56
- package/src/tests/sync.peerReconciliation.test.ts +44 -34
- package/src/tests/sync.storage.test.ts +231 -64
- package/src/tests/sync.storageAsync.test.ts +486 -0
- package/src/tests/sync.test.ts +17 -23
- package/src/tests/sync.upload.test.ts +29 -24
- package/src/tests/testStorage.ts +216 -0
- package/src/tests/testUtils.ts +89 -54
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CoValueHeader,
|
|
3
|
+
Transaction,
|
|
4
|
+
} from "../../coValueCore/verifiedState.js";
|
|
5
|
+
import type { Signature } from "../../crypto/crypto.js";
|
|
6
|
+
import type { RawCoID, SessionID } from "../../exports.js";
|
|
7
|
+
import { logger } from "../../logger.js";
|
|
8
|
+
import type { NewContentMessage } from "../../sync.js";
|
|
9
|
+
import type {
|
|
10
|
+
DBClientInterfaceAsync,
|
|
11
|
+
SessionRow,
|
|
12
|
+
SignatureAfterRow,
|
|
13
|
+
StoredCoValueRow,
|
|
14
|
+
StoredSessionRow,
|
|
15
|
+
TransactionRow,
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
import type { SQLiteDatabaseDriverAsync } from "./types.js";
|
|
18
|
+
|
|
19
|
+
export type RawCoValueRow = {
|
|
20
|
+
id: RawCoID;
|
|
21
|
+
header: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RawTransactionRow = {
|
|
25
|
+
ses: number;
|
|
26
|
+
idx: number;
|
|
27
|
+
tx: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function getErrorMessage(error: unknown) {
|
|
31
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class SQLiteClientAsync implements DBClientInterfaceAsync {
|
|
35
|
+
private readonly db: SQLiteDatabaseDriverAsync;
|
|
36
|
+
|
|
37
|
+
constructor(db: SQLiteDatabaseDriverAsync) {
|
|
38
|
+
this.db = db;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getCoValue(coValueId: RawCoID): Promise<StoredCoValueRow | undefined> {
|
|
42
|
+
const coValueRow = await this.db.get<RawCoValueRow & { rowID: number }>(
|
|
43
|
+
"SELECT * FROM coValues WHERE id = ?",
|
|
44
|
+
[coValueId],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!coValueRow) return;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const parsedHeader = (coValueRow?.header &&
|
|
51
|
+
JSON.parse(coValueRow.header)) as CoValueHeader;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...coValueRow,
|
|
55
|
+
header: parsedHeader,
|
|
56
|
+
};
|
|
57
|
+
} catch (e) {
|
|
58
|
+
const headerValue = coValueRow?.header ?? "";
|
|
59
|
+
logger.warn(`Invalid JSON in header: ${headerValue}`, {
|
|
60
|
+
id: coValueId,
|
|
61
|
+
err: e,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]> {
|
|
68
|
+
return this.db.query<StoredSessionRow>(
|
|
69
|
+
"SELECT * FROM sessions WHERE coValue = ?",
|
|
70
|
+
[coValueRowId],
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getSingleCoValueSession(
|
|
75
|
+
coValueRowId: number,
|
|
76
|
+
sessionID: SessionID,
|
|
77
|
+
): Promise<StoredSessionRow | undefined> {
|
|
78
|
+
return this.db.get<StoredSessionRow>(
|
|
79
|
+
"SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?",
|
|
80
|
+
[coValueRowId, sessionID],
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getNewTransactionInSession(
|
|
85
|
+
sessionRowId: number,
|
|
86
|
+
fromIdx: number,
|
|
87
|
+
toIdx: number,
|
|
88
|
+
): Promise<TransactionRow[]> {
|
|
89
|
+
const txs = await this.db.query<RawTransactionRow>(
|
|
90
|
+
"SELECT * FROM transactions WHERE ses = ? AND idx >= ? AND idx <= ?",
|
|
91
|
+
[sessionRowId, fromIdx, toIdx],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
return txs.map((transactionRow) => ({
|
|
96
|
+
...transactionRow,
|
|
97
|
+
tx: JSON.parse(transactionRow.tx) as Transaction,
|
|
98
|
+
}));
|
|
99
|
+
} catch (e) {
|
|
100
|
+
logger.warn("Invalid JSON in transaction", { err: e });
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getSignatures(
|
|
106
|
+
sessionRowId: number,
|
|
107
|
+
firstNewTxIdx: number,
|
|
108
|
+
): Promise<SignatureAfterRow[]> {
|
|
109
|
+
return this.db.query<SignatureAfterRow>(
|
|
110
|
+
"SELECT * FROM signatureAfter WHERE ses = ? AND idx >= ?",
|
|
111
|
+
[sessionRowId, firstNewTxIdx],
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async addCoValue(msg: NewContentMessage): Promise<number> {
|
|
116
|
+
const result = await this.db.get<{ rowID: number }>(
|
|
117
|
+
"INSERT INTO coValues (id, header) VALUES (?, ?) RETURNING rowID",
|
|
118
|
+
[msg.id, JSON.stringify(msg.header)],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!result) {
|
|
122
|
+
throw new Error("Failed to add coValue");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result.rowID;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async addSessionUpdate({
|
|
129
|
+
sessionUpdate,
|
|
130
|
+
}: {
|
|
131
|
+
sessionUpdate: SessionRow;
|
|
132
|
+
}): Promise<number> {
|
|
133
|
+
const result = await this.db.get<{ rowID: number }>(
|
|
134
|
+
`INSERT INTO sessions (coValue, sessionID, lastIdx, lastSignature, bytesSinceLastSignature) VALUES (?, ?, ?, ?, ?)
|
|
135
|
+
ON CONFLICT(coValue, sessionID) DO UPDATE SET lastIdx=excluded.lastIdx, lastSignature=excluded.lastSignature, bytesSinceLastSignature=excluded.bytesSinceLastSignature
|
|
136
|
+
RETURNING rowID`,
|
|
137
|
+
[
|
|
138
|
+
sessionUpdate.coValue,
|
|
139
|
+
sessionUpdate.sessionID,
|
|
140
|
+
sessionUpdate.lastIdx,
|
|
141
|
+
sessionUpdate.lastSignature,
|
|
142
|
+
sessionUpdate.bytesSinceLastSignature,
|
|
143
|
+
],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (!result) {
|
|
147
|
+
throw new Error("Failed to add session update");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result.rowID;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addTransaction(
|
|
154
|
+
sessionRowID: number,
|
|
155
|
+
nextIdx: number,
|
|
156
|
+
newTransaction: Transaction,
|
|
157
|
+
) {
|
|
158
|
+
this.db.run("INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)", [
|
|
159
|
+
sessionRowID,
|
|
160
|
+
nextIdx,
|
|
161
|
+
JSON.stringify(newTransaction),
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async addSignatureAfter({
|
|
166
|
+
sessionRowID,
|
|
167
|
+
idx,
|
|
168
|
+
signature,
|
|
169
|
+
}: { sessionRowID: number; idx: number; signature: Signature }) {
|
|
170
|
+
this.db.run(
|
|
171
|
+
"INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)",
|
|
172
|
+
[sessionRowID, idx, signature],
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async transaction(operationsCallback: () => unknown) {
|
|
177
|
+
return this.db.transaction(operationsCallback);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { SQLiteClientAsync } from "./client.js";
|
|
2
|
+
export type { SQLiteDatabaseDriverAsync } from "./types.js";
|
|
3
|
+
|
|
4
|
+
import { getSQLiteMigrationQueries } from "../sqlite/sqliteMigrations.js";
|
|
5
|
+
import { StorageApiAsync } from "../storageAsync.js";
|
|
6
|
+
import { SQLiteClientAsync } from "./client.js";
|
|
7
|
+
import type { SQLiteDatabaseDriverAsync } from "./types.js";
|
|
8
|
+
|
|
9
|
+
export async function getSqliteStorageAsync(db: SQLiteDatabaseDriverAsync) {
|
|
10
|
+
await db.initialize();
|
|
11
|
+
|
|
12
|
+
const rows = await db.query<{ user_version: string }>(
|
|
13
|
+
"PRAGMA user_version",
|
|
14
|
+
[],
|
|
15
|
+
);
|
|
16
|
+
const userVersion = Number(rows[0]?.user_version) ?? 0;
|
|
17
|
+
|
|
18
|
+
const migrations = getSQLiteMigrationQueries(userVersion);
|
|
19
|
+
|
|
20
|
+
for (const migration of migrations) {
|
|
21
|
+
await db.run(migration, []);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return new StorageApiAsync(new SQLiteClientAsync(db));
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SQLiteDatabaseDriverAsync {
|
|
2
|
+
initialize(): Promise<void>;
|
|
3
|
+
run(sql: string, params: unknown[]): Promise<void>;
|
|
4
|
+
query<T>(sql: string, params: unknown[]): Promise<T[]>;
|
|
5
|
+
get<T>(sql: string, params: unknown[]): Promise<T | undefined>;
|
|
6
|
+
transaction(callback: () => unknown): Promise<unknown>;
|
|
7
|
+
closeDb(): Promise<unknown>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { LinkedList } from "../PriorityBasedMessageQueue.js";
|
|
2
|
+
import {
|
|
3
|
+
type CoValueCore,
|
|
4
|
+
MAX_RECOMMENDED_TX_SIZE,
|
|
5
|
+
type RawCoID,
|
|
6
|
+
type SessionID,
|
|
7
|
+
type StorageAPI,
|
|
8
|
+
} from "../exports.js";
|
|
9
|
+
import { getPriorityFromHeader } from "../priority.js";
|
|
10
|
+
import {
|
|
11
|
+
CoValueKnownState,
|
|
12
|
+
NewContentMessage,
|
|
13
|
+
emptyKnownState,
|
|
14
|
+
} from "../sync.js";
|
|
15
|
+
import { StoreQueue } from "./StoreQueue.js";
|
|
16
|
+
import { StorageKnownState } from "./knownState.js";
|
|
17
|
+
import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
|
|
18
|
+
import type {
|
|
19
|
+
DBClientInterfaceAsync,
|
|
20
|
+
SignatureAfterRow,
|
|
21
|
+
StoredCoValueRow,
|
|
22
|
+
StoredSessionRow,
|
|
23
|
+
} from "./types.js";
|
|
24
|
+
|
|
25
|
+
export class StorageApiAsync implements StorageAPI {
|
|
26
|
+
private readonly dbClient: DBClientInterfaceAsync;
|
|
27
|
+
|
|
28
|
+
private loadedCoValues = new Set<RawCoID>();
|
|
29
|
+
|
|
30
|
+
constructor(dbClient: DBClientInterfaceAsync) {
|
|
31
|
+
this.dbClient = dbClient;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
knwonStates = new StorageKnownState();
|
|
35
|
+
|
|
36
|
+
getKnownState(id: string): CoValueKnownState {
|
|
37
|
+
return this.knwonStates.getKnownState(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async load(
|
|
41
|
+
id: string,
|
|
42
|
+
callback: (data: NewContentMessage) => void,
|
|
43
|
+
done: (found: boolean) => void,
|
|
44
|
+
) {
|
|
45
|
+
await this.loadCoValue(id, callback, done);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async loadCoValue(
|
|
49
|
+
id: string,
|
|
50
|
+
callback: (data: NewContentMessage) => void,
|
|
51
|
+
done: (found: boolean) => void,
|
|
52
|
+
) {
|
|
53
|
+
const coValueRow = await this.dbClient.getCoValue(id);
|
|
54
|
+
|
|
55
|
+
if (!coValueRow) {
|
|
56
|
+
done?.(false);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const allCoValueSessions = await this.dbClient.getCoValueSessions(
|
|
61
|
+
coValueRow.rowID,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const signaturesBySession = new Map<
|
|
65
|
+
SessionID,
|
|
66
|
+
Pick<SignatureAfterRow, "idx" | "signature">[]
|
|
67
|
+
>();
|
|
68
|
+
|
|
69
|
+
let contentStreaming = false;
|
|
70
|
+
|
|
71
|
+
await Promise.all(
|
|
72
|
+
allCoValueSessions.map(async (sessionRow) => {
|
|
73
|
+
const signatures = await this.dbClient.getSignatures(
|
|
74
|
+
sessionRow.rowID,
|
|
75
|
+
0,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (signatures.length > 0) {
|
|
79
|
+
contentStreaming = true;
|
|
80
|
+
signaturesBySession.set(sessionRow.sessionID, signatures);
|
|
81
|
+
}
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const knownState = this.knwonStates.getKnownState(coValueRow.id);
|
|
86
|
+
|
|
87
|
+
for (const sessionRow of allCoValueSessions) {
|
|
88
|
+
knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.loadedCoValues.add(coValueRow.id);
|
|
92
|
+
|
|
93
|
+
let contentMessage = {
|
|
94
|
+
action: "content",
|
|
95
|
+
id: coValueRow.id,
|
|
96
|
+
header: coValueRow.header,
|
|
97
|
+
new: {},
|
|
98
|
+
priority: getPriorityFromHeader(coValueRow.header),
|
|
99
|
+
} as NewContentMessage;
|
|
100
|
+
|
|
101
|
+
if (contentStreaming) {
|
|
102
|
+
contentMessage.expectContentUntil = knownState["sessions"];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const sessionRow of allCoValueSessions) {
|
|
106
|
+
const signatures = signaturesBySession.get(sessionRow.sessionID) || [];
|
|
107
|
+
|
|
108
|
+
let idx = 0;
|
|
109
|
+
|
|
110
|
+
signatures.push({
|
|
111
|
+
idx: sessionRow.lastIdx,
|
|
112
|
+
signature: sessionRow.lastSignature,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for (const signature of signatures) {
|
|
116
|
+
const newTxsInSession = await this.dbClient.getNewTransactionInSession(
|
|
117
|
+
sessionRow.rowID,
|
|
118
|
+
idx,
|
|
119
|
+
signature.idx,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
collectNewTxs({
|
|
123
|
+
newTxsInSession,
|
|
124
|
+
contentMessage,
|
|
125
|
+
sessionRow,
|
|
126
|
+
firstNewTxIdx: idx,
|
|
127
|
+
signature: signature.signature,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
idx = signature.idx + 1;
|
|
131
|
+
|
|
132
|
+
if (signatures.length > 1) {
|
|
133
|
+
// Having more than one signature means that the content needs streaming
|
|
134
|
+
// So we start pushing the content to the client, and start a new content message
|
|
135
|
+
await this.pushContentWithDependencies(
|
|
136
|
+
coValueRow,
|
|
137
|
+
contentMessage,
|
|
138
|
+
callback,
|
|
139
|
+
);
|
|
140
|
+
contentMessage = {
|
|
141
|
+
action: "content",
|
|
142
|
+
id: coValueRow.id,
|
|
143
|
+
header: coValueRow.header,
|
|
144
|
+
new: {},
|
|
145
|
+
priority: getPriorityFromHeader(coValueRow.header),
|
|
146
|
+
} satisfies NewContentMessage;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hasNewContent = Object.keys(contentMessage.new).length > 0;
|
|
152
|
+
|
|
153
|
+
// If there is no new content but steaming is not active, it's the case for a coValue with the header but no transactions
|
|
154
|
+
// For streaming the push has already been done in the loop above
|
|
155
|
+
if (hasNewContent || !contentStreaming) {
|
|
156
|
+
await this.pushContentWithDependencies(
|
|
157
|
+
coValueRow,
|
|
158
|
+
contentMessage,
|
|
159
|
+
callback,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.knwonStates.handleUpdate(coValueRow.id, knownState);
|
|
164
|
+
done?.(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async pushContentWithDependencies(
|
|
168
|
+
coValueRow: StoredCoValueRow,
|
|
169
|
+
contentMessage: NewContentMessage,
|
|
170
|
+
pushCallback: (data: NewContentMessage) => void,
|
|
171
|
+
) {
|
|
172
|
+
const dependedOnCoValuesList = getDependedOnCoValues(
|
|
173
|
+
coValueRow.header,
|
|
174
|
+
contentMessage,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const promises = [];
|
|
178
|
+
|
|
179
|
+
for (const dependedOnCoValue of dependedOnCoValuesList) {
|
|
180
|
+
if (this.loadedCoValues.has(dependedOnCoValue)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
promises.push(
|
|
185
|
+
new Promise((resolve) => {
|
|
186
|
+
this.loadCoValue(dependedOnCoValue, pushCallback, resolve);
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await Promise.all(promises);
|
|
192
|
+
|
|
193
|
+
pushCallback(contentMessage);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
storeQueue = new StoreQueue();
|
|
197
|
+
|
|
198
|
+
async store(
|
|
199
|
+
msgs: NewContentMessage[],
|
|
200
|
+
correctionCallback: (data: CoValueKnownState) => void,
|
|
201
|
+
) {
|
|
202
|
+
/**
|
|
203
|
+
* The store operations must be done one by one, because we can't start a new transaction when there
|
|
204
|
+
* is already a transaction open.
|
|
205
|
+
*/
|
|
206
|
+
this.storeQueue.push(msgs, correctionCallback);
|
|
207
|
+
|
|
208
|
+
this.storeQueue.processQueue(async (data, correctionCallback) => {
|
|
209
|
+
for (const msg of data) {
|
|
210
|
+
const success = await this.storeSingle(msg, correctionCallback);
|
|
211
|
+
|
|
212
|
+
if (!success) {
|
|
213
|
+
// Stop processing the messages for this entry, because the data is out of sync with storage
|
|
214
|
+
// and the other transactions will be rejected anyway.
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async storeSingle(
|
|
222
|
+
msg: NewContentMessage,
|
|
223
|
+
correctionCallback: (data: CoValueKnownState) => void,
|
|
224
|
+
): Promise<boolean> {
|
|
225
|
+
const id = msg.id;
|
|
226
|
+
const coValueRow = await this.dbClient.getCoValue(id);
|
|
227
|
+
|
|
228
|
+
// We have no info about coValue header
|
|
229
|
+
const invalidAssumptionOnHeaderPresence = !msg.header && !coValueRow;
|
|
230
|
+
|
|
231
|
+
if (invalidAssumptionOnHeaderPresence) {
|
|
232
|
+
const knownState = emptyKnownState(id as RawCoID);
|
|
233
|
+
this.knwonStates.setKnownState(id, knownState);
|
|
234
|
+
|
|
235
|
+
correctionCallback(knownState);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const storedCoValueRowID: number = coValueRow
|
|
240
|
+
? coValueRow.rowID
|
|
241
|
+
: await this.dbClient.addCoValue(msg);
|
|
242
|
+
|
|
243
|
+
const knownState = this.knwonStates.getKnownState(id);
|
|
244
|
+
knownState.header = true;
|
|
245
|
+
|
|
246
|
+
let invalidAssumptions = false;
|
|
247
|
+
|
|
248
|
+
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
|
|
249
|
+
await this.dbClient.transaction(async () => {
|
|
250
|
+
const sessionRow = await this.dbClient.getSingleCoValueSession(
|
|
251
|
+
storedCoValueRowID,
|
|
252
|
+
sessionID,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (sessionRow) {
|
|
256
|
+
knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const lastIdx = sessionRow?.lastIdx || 0;
|
|
260
|
+
const after = msg.new[sessionID]?.after || 0;
|
|
261
|
+
|
|
262
|
+
if (lastIdx < after) {
|
|
263
|
+
knownState.sessions[sessionID] = lastIdx;
|
|
264
|
+
invalidAssumptions = true;
|
|
265
|
+
} else {
|
|
266
|
+
const newLastIdx = await this.putNewTxs(
|
|
267
|
+
msg,
|
|
268
|
+
sessionID,
|
|
269
|
+
sessionRow,
|
|
270
|
+
storedCoValueRowID,
|
|
271
|
+
);
|
|
272
|
+
knownState.sessions[sessionID] = newLastIdx;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.knwonStates.handleUpdate(id, knownState);
|
|
278
|
+
|
|
279
|
+
if (invalidAssumptions) {
|
|
280
|
+
correctionCallback(knownState);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async putNewTxs(
|
|
288
|
+
msg: NewContentMessage,
|
|
289
|
+
sessionID: SessionID,
|
|
290
|
+
sessionRow: StoredSessionRow | undefined,
|
|
291
|
+
storedCoValueRowID: number,
|
|
292
|
+
) {
|
|
293
|
+
const newTransactions = msg.new[sessionID]?.newTransactions || [];
|
|
294
|
+
|
|
295
|
+
const actuallyNewOffset =
|
|
296
|
+
(sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
|
|
297
|
+
|
|
298
|
+
const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
|
|
299
|
+
|
|
300
|
+
if (actuallyNewTransactions.length === 0) {
|
|
301
|
+
return sessionRow?.lastIdx || 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let newBytesSinceLastSignature =
|
|
305
|
+
(sessionRow?.bytesSinceLastSignature || 0) +
|
|
306
|
+
actuallyNewTransactions.reduce(
|
|
307
|
+
(sum, tx) =>
|
|
308
|
+
sum +
|
|
309
|
+
(tx.privacy === "private"
|
|
310
|
+
? tx.encryptedChanges.length
|
|
311
|
+
: tx.changes.length),
|
|
312
|
+
0,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const newLastIdx =
|
|
316
|
+
(sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
|
|
317
|
+
|
|
318
|
+
let shouldWriteSignature = false;
|
|
319
|
+
|
|
320
|
+
if (newBytesSinceLastSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
321
|
+
shouldWriteSignature = true;
|
|
322
|
+
newBytesSinceLastSignature = 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const nextIdx = sessionRow?.lastIdx || 0;
|
|
326
|
+
|
|
327
|
+
if (!msg.new[sessionID]) throw new Error("Session ID not found");
|
|
328
|
+
|
|
329
|
+
const sessionUpdate = {
|
|
330
|
+
coValue: storedCoValueRowID,
|
|
331
|
+
sessionID,
|
|
332
|
+
lastIdx: newLastIdx,
|
|
333
|
+
lastSignature: msg.new[sessionID].lastSignature,
|
|
334
|
+
bytesSinceLastSignature: newBytesSinceLastSignature,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const sessionRowID: number = await this.dbClient.addSessionUpdate({
|
|
338
|
+
sessionUpdate,
|
|
339
|
+
sessionRow,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (shouldWriteSignature) {
|
|
343
|
+
await this.dbClient.addSignatureAfter({
|
|
344
|
+
sessionRowID,
|
|
345
|
+
idx: newLastIdx - 1,
|
|
346
|
+
signature: msg.new[sessionID].lastSignature,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await Promise.all(
|
|
351
|
+
actuallyNewTransactions.map((newTransaction, i) =>
|
|
352
|
+
this.dbClient.addTransaction(sessionRowID, nextIdx + i, newTransaction),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return newLastIdx;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
waitForSync(id: string, coValue: CoValueCore) {
|
|
360
|
+
return this.knwonStates.waitForSync(id, coValue);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
close() {
|
|
364
|
+
// Drain the store queue
|
|
365
|
+
this.storeQueue.drain();
|
|
366
|
+
}
|
|
367
|
+
}
|