cojson 0.16.5 → 0.16.7
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 +16 -0
- package/dist/GarbageCollector.d.ts +12 -0
- package/dist/GarbageCollector.d.ts.map +1 -0
- package/dist/GarbageCollector.js +37 -0
- package/dist/GarbageCollector.js.map +1 -0
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueContentMessage.d.ts +1 -0
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +11 -3
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +2 -1
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +15 -0
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/utils.d.ts.map +1 -1
- package/dist/coValueCore/utils.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +1 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coPlainText.d.ts +1 -0
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +27 -8
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +2 -2
- package/dist/coValues/group.d.ts +1 -1
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +11 -4
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +8 -3
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +3 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +11 -0
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -1
- package/dist/permissions.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/queue/StoreQueue.d.ts +7 -1
- package/dist/queue/StoreQueue.d.ts.map +1 -1
- package/dist/queue/StoreQueue.js +35 -13
- package/dist/queue/StoreQueue.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +4 -4
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +13 -4
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -3
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +12 -3
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +2 -7
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +2 -7
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +2 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +17 -3
- package/dist/sync.js.map +1 -1
- package/dist/tests/GarbageCollector.test.d.ts +2 -0
- package/dist/tests/GarbageCollector.test.d.ts.map +1 -0
- package/dist/tests/GarbageCollector.test.js +85 -0
- package/dist/tests/GarbageCollector.test.js.map +1 -0
- package/dist/tests/coPlainText.test.js +142 -4
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +3 -3
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/sync.garbageCollection.test.d.ts +2 -0
- package/dist/tests/sync.garbageCollection.test.d.ts.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js +133 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +48 -34
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +31 -21
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +76 -29
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +1 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +1 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +1 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +1 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/GarbageCollector.ts +48 -0
- package/src/coValueContentMessage.ts +16 -3
- package/src/coValueCore/coValueCore.ts +27 -10
- package/src/coValueCore/utils.ts +1 -0
- package/src/coValueCore/verifiedState.ts +1 -0
- package/src/coValues/coPlainText.ts +40 -8
- package/src/config.ts +20 -1
- package/src/exports.ts +13 -5
- package/src/localNode.ts +15 -1
- package/src/permissions.ts +3 -1
- package/src/queue/LocalTransactionsSyncQueue.ts +1 -1
- package/src/queue/StoreQueue.ts +45 -12
- package/src/storage/sqlite/client.ts +24 -10
- package/src/storage/sqliteAsync/client.ts +26 -5
- package/src/storage/storageAsync.ts +5 -9
- package/src/storage/storageSync.ts +2 -9
- package/src/storage/types.ts +7 -4
- package/src/sync.ts +19 -3
- package/src/tests/GarbageCollector.test.ts +127 -0
- package/src/tests/coPlainText.test.ts +176 -4
- package/src/tests/coStream.test.ts +7 -3
- package/src/tests/sync.garbageCollection.test.ts +178 -0
- package/src/tests/sync.mesh.test.ts +49 -34
- package/src/tests/sync.storage.test.ts +31 -21
- package/src/tests/sync.storageAsync.test.ts +81 -29
- package/src/tests/testStorage.ts +11 -3
- package/src/tests/testUtils.ts +4 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CoValueCore } from "./coValueCore/coValueCore.js";
|
|
2
|
+
import { GARBAGE_COLLECTOR_CONFIG } from "./config.js";
|
|
3
|
+
import { RawCoID } from "./ids.js";
|
|
4
|
+
|
|
5
|
+
export class GarbageCollector {
|
|
6
|
+
private readonly interval: ReturnType<typeof setInterval>;
|
|
7
|
+
|
|
8
|
+
constructor(private readonly coValues: Map<RawCoID, CoValueCore>) {
|
|
9
|
+
this.interval = setInterval(() => {
|
|
10
|
+
this.collect();
|
|
11
|
+
}, GARBAGE_COLLECTOR_CONFIG.INTERVAL);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getCurrentTime() {
|
|
15
|
+
return performance.now();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
trackCoValueAccess({ verified }: CoValueCore) {
|
|
19
|
+
if (verified) {
|
|
20
|
+
verified.lastAccessed = this.getCurrentTime();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
collect() {
|
|
25
|
+
const currentTime = this.getCurrentTime();
|
|
26
|
+
for (const coValue of this.coValues.values()) {
|
|
27
|
+
const { verified } = coValue;
|
|
28
|
+
|
|
29
|
+
if (!verified?.lastAccessed) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const timeSinceLastAccessed = currentTime - verified.lastAccessed;
|
|
34
|
+
|
|
35
|
+
if (timeSinceLastAccessed > GARBAGE_COLLECTOR_CONFIG.MAX_AGE) {
|
|
36
|
+
const unmounted = coValue.unmount();
|
|
37
|
+
|
|
38
|
+
if (unmounted) {
|
|
39
|
+
this.coValues.delete(coValue.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
stop() {
|
|
46
|
+
clearInterval(this.interval);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
Transaction,
|
|
4
4
|
VerifiedState,
|
|
5
5
|
} from "./coValueCore/verifiedState.js";
|
|
6
|
-
import {
|
|
6
|
+
import { TRANSACTION_CONFIG } from "./config.js";
|
|
7
7
|
import { Signature } from "./crypto/crypto.js";
|
|
8
8
|
import { RawCoID, SessionID } from "./ids.js";
|
|
9
9
|
import { getPriorityFromHeader } from "./priority.js";
|
|
@@ -55,10 +55,12 @@ export function exceedsRecommendedSize(
|
|
|
55
55
|
transactionSize?: number,
|
|
56
56
|
) {
|
|
57
57
|
if (transactionSize === undefined) {
|
|
58
|
-
return baseSize > MAX_RECOMMENDED_TX_SIZE;
|
|
58
|
+
return baseSize > TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
return
|
|
61
|
+
return (
|
|
62
|
+
baseSize + transactionSize > TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE
|
|
63
|
+
);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export function knownStateFromContent(content: NewContentMessage) {
|
|
@@ -71,3 +73,14 @@ export function knownStateFromContent(content: NewContentMessage) {
|
|
|
71
73
|
|
|
72
74
|
return knownState;
|
|
73
75
|
}
|
|
76
|
+
|
|
77
|
+
export function getContentMessageSize(msg: NewContentMessage) {
|
|
78
|
+
return Object.values(msg.new).reduce((acc, sessionNewContent) => {
|
|
79
|
+
return (
|
|
80
|
+
acc +
|
|
81
|
+
sessionNewContent.newTransactions.reduce((acc, tx) => {
|
|
82
|
+
return acc + getTransactionSize(tx);
|
|
83
|
+
}, 0)
|
|
84
|
+
);
|
|
85
|
+
}, 0);
|
|
86
|
+
}
|
|
@@ -69,7 +69,9 @@ export class CoValueCore {
|
|
|
69
69
|
}
|
|
70
70
|
private readonly peers = new Map<
|
|
71
71
|
PeerID,
|
|
72
|
-
| {
|
|
72
|
+
| {
|
|
73
|
+
type: "unknown" | "pending" | "available" | "unavailable";
|
|
74
|
+
}
|
|
73
75
|
| {
|
|
74
76
|
type: "errored";
|
|
75
77
|
error: TryAddTransactionsError;
|
|
@@ -78,9 +80,8 @@ export class CoValueCore {
|
|
|
78
80
|
|
|
79
81
|
// cached state and listeners
|
|
80
82
|
private _cachedContent?: RawCoValue;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
> = new Set();
|
|
83
|
+
readonly listeners: Set<(core: CoValueCore, unsub: () => void) => void> =
|
|
84
|
+
new Set();
|
|
84
85
|
private readonly _decryptionCache: {
|
|
85
86
|
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
|
|
86
87
|
} = {};
|
|
@@ -201,6 +202,26 @@ export class CoValueCore {
|
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
unmount() {
|
|
206
|
+
// For simplicity, we don't unmount groups and accounts
|
|
207
|
+
if (this.verified?.header.ruleset.type === "group") {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (this.listeners.size > 0) {
|
|
212
|
+
return false; // The coValue is still in use
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.counter.add(-1, { state: this.loadingState });
|
|
216
|
+
|
|
217
|
+
if (this.groupInvalidationSubscription) {
|
|
218
|
+
this.groupInvalidationSubscription();
|
|
219
|
+
this.groupInvalidationSubscription = undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
markNotFoundInPeer(peerId: PeerID) {
|
|
205
226
|
const previousState = this.loadingState;
|
|
206
227
|
this.peers.set(peerId, { type: "unavailable" });
|
|
@@ -609,9 +630,7 @@ export class CoValueCore {
|
|
|
609
630
|
return success;
|
|
610
631
|
}
|
|
611
632
|
|
|
612
|
-
getCurrentContent(options?: {
|
|
613
|
-
ignorePrivateTransactions: true;
|
|
614
|
-
}): RawCoValue {
|
|
633
|
+
getCurrentContent(options?: { ignorePrivateTransactions: true }): RawCoValue {
|
|
615
634
|
if (!this.verified) {
|
|
616
635
|
throw new Error(
|
|
617
636
|
"CoValueCore: getCurrentContent called on coValue without verified state",
|
|
@@ -851,9 +870,7 @@ export class CoValueCore {
|
|
|
851
870
|
}
|
|
852
871
|
}
|
|
853
872
|
|
|
854
|
-
waitForSync(options?: {
|
|
855
|
-
timeout?: number;
|
|
856
|
-
}) {
|
|
873
|
+
waitForSync(options?: { timeout?: number }) {
|
|
857
874
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
858
875
|
}
|
|
859
876
|
|
package/src/coValueCore/utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { getGroupDependentKey } from "../ids.js";
|
|
|
2
2
|
import { RawCoID, SessionID } from "../ids.js";
|
|
3
3
|
import { Stringified, parseJSON } from "../jsonStringify.js";
|
|
4
4
|
import { JsonValue } from "../jsonValue.js";
|
|
5
|
+
import { NewContentMessage } from "../sync.js";
|
|
5
6
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
6
7
|
import { isAccountID } from "../typeUtils/isAccountID.js";
|
|
7
8
|
import { CoValueHeader, Transaction } from "./verifiedState.js";
|
|
@@ -65,6 +65,7 @@ export class VerifiedState {
|
|
|
65
65
|
private _cachedKnownState?: CoValueKnownState;
|
|
66
66
|
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
67
67
|
private streamingKnownState?: CoValueKnownState["sessions"];
|
|
68
|
+
public lastAccessed: number | undefined;
|
|
68
69
|
|
|
69
70
|
constructor(
|
|
70
71
|
id: RawCoID,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { splitGraphemes } from "unicode-segmenter/grapheme";
|
|
2
2
|
import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
|
|
3
|
+
import { TRANSACTION_CONFIG } from "../config.js";
|
|
3
4
|
import { JsonObject } from "../jsonValue.js";
|
|
4
5
|
import { DeletionOpPayload, OpID, RawCoList } from "./coList.js";
|
|
5
6
|
|
|
@@ -110,16 +111,34 @@ export class RawCoPlainText<
|
|
|
110
111
|
text: string,
|
|
111
112
|
privacy: "private" | "trusting" = "private",
|
|
112
113
|
) {
|
|
113
|
-
const graphemes =
|
|
114
|
+
const graphemes = Array.from(splitGraphemes(text));
|
|
114
115
|
|
|
115
116
|
if (idx === 0) {
|
|
116
|
-
// For insertions at start, prepend
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// For insertions at start, prepend the first char and append the rest
|
|
118
|
+
const firstChar = graphemes[0];
|
|
119
|
+
|
|
120
|
+
if (firstChar) {
|
|
121
|
+
this.prepend(firstChar, 0, privacy);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (graphemes.length > 1) {
|
|
125
|
+
this.appendChars(graphemes.slice(1), 0, privacy);
|
|
119
126
|
}
|
|
120
127
|
} else {
|
|
121
128
|
// For other insertions, append after the previous character
|
|
122
|
-
this.
|
|
129
|
+
this.appendChars(graphemes, idx - 1, privacy);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
appendChars(
|
|
134
|
+
text: string[],
|
|
135
|
+
position: number,
|
|
136
|
+
privacy: "private" | "trusting" = "private",
|
|
137
|
+
) {
|
|
138
|
+
const chunks = splitIntoChunks(text);
|
|
139
|
+
for (const chunk of chunks) {
|
|
140
|
+
this.appendItems(chunk, position, privacy);
|
|
141
|
+
position += chunk.length;
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
144
|
|
|
@@ -136,11 +155,12 @@ export class RawCoPlainText<
|
|
|
136
155
|
text: string,
|
|
137
156
|
privacy: "private" | "trusting" = "private",
|
|
138
157
|
) {
|
|
139
|
-
const graphemes =
|
|
158
|
+
const graphemes = Array.from(splitGraphemes(text));
|
|
159
|
+
|
|
140
160
|
if (idx >= this.entries().length) {
|
|
141
|
-
this.
|
|
161
|
+
this.appendChars(graphemes, idx - 1, privacy);
|
|
142
162
|
} else {
|
|
143
|
-
this.
|
|
163
|
+
this.appendChars(graphemes, idx, privacy);
|
|
144
164
|
}
|
|
145
165
|
}
|
|
146
166
|
|
|
@@ -178,3 +198,15 @@ export class RawCoPlainText<
|
|
|
178
198
|
return graphemes.join("");
|
|
179
199
|
}
|
|
180
200
|
}
|
|
201
|
+
|
|
202
|
+
function splitIntoChunks(text: string[]) {
|
|
203
|
+
const chunks: string[][] = [];
|
|
204
|
+
for (
|
|
205
|
+
let i = 0;
|
|
206
|
+
i < text.length;
|
|
207
|
+
i += TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE
|
|
208
|
+
) {
|
|
209
|
+
chunks.push(text.slice(i, i + TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE));
|
|
210
|
+
}
|
|
211
|
+
return chunks;
|
|
212
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
This also means that we want to keep signatures roughly after each MAX_RECOMMENDED_TX size chunk,
|
|
6
6
|
to be able to verify partially loaded CoValues or CoValues that are still being created (like a video live stream).
|
|
7
7
|
**/
|
|
8
|
-
export const
|
|
8
|
+
export const TRANSACTION_CONFIG = {
|
|
9
|
+
MAX_RECOMMENDED_TX_SIZE: 100 * 1024,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function setMaxRecommendedTxSize(size: number) {
|
|
13
|
+
TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE = size;
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
export const CO_VALUE_LOADING_CONFIG = {
|
|
11
17
|
MAX_RETRIES: 1,
|
|
@@ -32,3 +38,16 @@ export const SYNC_SCHEDULER_CONFIG = {
|
|
|
32
38
|
export function setIncomingMessagesTimeBudget(budget: number) {
|
|
33
39
|
SYNC_SCHEDULER_CONFIG.INCOMING_MESSAGES_TIME_BUDGET = budget;
|
|
34
40
|
}
|
|
41
|
+
|
|
42
|
+
export const GARBAGE_COLLECTOR_CONFIG = {
|
|
43
|
+
MAX_AGE: 1000 * 60 * 10, // 10 minutes
|
|
44
|
+
INTERVAL: 1000 * 60 * 5, // 5 minutes
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function setGarbageCollectorMaxAge(maxAge: number) {
|
|
48
|
+
GARBAGE_COLLECTOR_CONFIG.MAX_AGE = maxAge;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function setGarbageCollectorInterval(interval: number) {
|
|
52
|
+
GARBAGE_COLLECTOR_CONFIG.INTERVAL = interval;
|
|
53
|
+
}
|
package/src/exports.ts
CHANGED
|
@@ -61,20 +61,25 @@ import { disablePermissionErrors } from "./permissions.js";
|
|
|
61
61
|
import type { Peer, SyncMessage } from "./sync.js";
|
|
62
62
|
import { DisconnectedError, SyncManager, emptyKnownState } from "./sync.js";
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
import {
|
|
65
|
+
getContentMessageSize,
|
|
66
|
+
getTransactionSize,
|
|
67
|
+
} from "./coValueContentMessage.js";
|
|
67
68
|
import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
|
|
68
69
|
import {
|
|
69
70
|
CO_VALUE_LOADING_CONFIG,
|
|
70
|
-
|
|
71
|
+
TRANSACTION_CONFIG,
|
|
71
72
|
setCoValueLoadingRetryDelay,
|
|
72
73
|
setIncomingMessagesTimeBudget,
|
|
74
|
+
setMaxRecommendedTxSize,
|
|
73
75
|
} from "./config.js";
|
|
74
76
|
import { LogLevel, logger } from "./logger.js";
|
|
75
77
|
import { CO_VALUE_PRIORITY, getPriorityFromHeader } from "./priority.js";
|
|
76
78
|
import { getDependedOnCoValues } from "./storage/syncUtils.js";
|
|
77
79
|
|
|
80
|
+
type Value = JsonValue | AnyRawCoValue;
|
|
81
|
+
|
|
82
|
+
export { PriorityBasedMessageQueue } from "./queue/PriorityBasedMessageQueue.js";
|
|
78
83
|
/** @hidden */
|
|
79
84
|
export const cojsonInternals = {
|
|
80
85
|
connectedPeers,
|
|
@@ -106,6 +111,10 @@ export const cojsonInternals = {
|
|
|
106
111
|
ConnectedPeerChannel,
|
|
107
112
|
textEncoder,
|
|
108
113
|
textDecoder,
|
|
114
|
+
getTransactionSize,
|
|
115
|
+
getContentMessageSize,
|
|
116
|
+
TRANSACTION_CONFIG,
|
|
117
|
+
setMaxRecommendedTxSize,
|
|
109
118
|
};
|
|
110
119
|
|
|
111
120
|
export {
|
|
@@ -132,7 +141,6 @@ export {
|
|
|
132
141
|
Media,
|
|
133
142
|
CoValueCore,
|
|
134
143
|
ControlledAgent,
|
|
135
|
-
MAX_RECOMMENDED_TX_SIZE,
|
|
136
144
|
JsonObject,
|
|
137
145
|
JsonValue,
|
|
138
146
|
Peer,
|
package/src/localNode.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Result, err, ok } from "neverthrow";
|
|
2
|
+
import { GarbageCollector } from "./GarbageCollector.js";
|
|
2
3
|
import type { CoID } from "./coValue.js";
|
|
3
4
|
import type { RawCoValue } from "./coValue.js";
|
|
4
5
|
import {
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
type RawGroup,
|
|
31
32
|
secretSeedFromInviteSecret,
|
|
32
33
|
} from "./coValues/group.js";
|
|
33
|
-
import { CO_VALUE_LOADING_CONFIG } from "./config.js";
|
|
34
|
+
import { CO_VALUE_LOADING_CONFIG, GARBAGE_COLLECTOR_CONFIG } from "./config.js";
|
|
34
35
|
import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
|
|
35
36
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
36
37
|
import { logger } from "./logger.js";
|
|
@@ -63,6 +64,7 @@ export class LocalNode {
|
|
|
63
64
|
/** @category 3. Low-level */
|
|
64
65
|
syncManager = new SyncManager(this);
|
|
65
66
|
|
|
67
|
+
garbageCollector: GarbageCollector | undefined = undefined;
|
|
66
68
|
crashed: Error | undefined = undefined;
|
|
67
69
|
|
|
68
70
|
storage?: StorageAPI;
|
|
@@ -78,6 +80,14 @@ export class LocalNode {
|
|
|
78
80
|
this.crypto = crypto;
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
enableGarbageCollector() {
|
|
84
|
+
if (this.garbageCollector) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.garbageCollector = new GarbageCollector(this.coValues);
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
setStorage(storage: StorageAPI) {
|
|
82
92
|
this.storage = storage;
|
|
83
93
|
}
|
|
@@ -95,6 +105,8 @@ export class LocalNode {
|
|
|
95
105
|
this.coValues.set(id, entry);
|
|
96
106
|
}
|
|
97
107
|
|
|
108
|
+
this.garbageCollector?.trackCoValueAccess(entry);
|
|
109
|
+
|
|
98
110
|
return entry;
|
|
99
111
|
}
|
|
100
112
|
|
|
@@ -351,6 +363,7 @@ export class LocalNode {
|
|
|
351
363
|
new VerifiedState(id, this.crypto, header, new Map()),
|
|
352
364
|
);
|
|
353
365
|
|
|
366
|
+
this.garbageCollector?.trackCoValueAccess(coValue);
|
|
354
367
|
this.syncManager.syncHeader(coValue.verified);
|
|
355
368
|
|
|
356
369
|
return coValue;
|
|
@@ -745,6 +758,7 @@ export class LocalNode {
|
|
|
745
758
|
*/
|
|
746
759
|
gracefulShutdown(): Promise<unknown> | undefined {
|
|
747
760
|
this.syncManager.gracefulShutdown();
|
|
761
|
+
this.garbageCollector?.stop();
|
|
748
762
|
return this.storage?.close();
|
|
749
763
|
}
|
|
750
764
|
}
|
package/src/permissions.ts
CHANGED
|
@@ -434,7 +434,9 @@ function determineValidTransactionsForGroup(
|
|
|
434
434
|
const currentAccountId = coValue.node.getCurrentAccountOrAgentID();
|
|
435
435
|
|
|
436
436
|
const isSelfRevoke =
|
|
437
|
-
currentAccountId === change.key &&
|
|
437
|
+
currentAccountId === change.key &&
|
|
438
|
+
transactor === currentAccountId &&
|
|
439
|
+
change.value === "revoked";
|
|
438
440
|
|
|
439
441
|
if (!isFirstSelfAppointment && !isSelfRevoke) {
|
|
440
442
|
if (memberState[transactor] === "admin") {
|
|
@@ -42,7 +42,7 @@ export class LocalTransactionsSyncQueue {
|
|
|
42
42
|
const lastPendingSync = this.queue.tail?.value;
|
|
43
43
|
const lastSignatureIdx = coValue.getLastSignatureCheckpoint(sessionID);
|
|
44
44
|
const isSignatureCheckpoint =
|
|
45
|
-
lastSignatureIdx > -1 && lastSignatureIdx === txIdx
|
|
45
|
+
lastSignatureIdx > -1 && lastSignatureIdx === txIdx;
|
|
46
46
|
|
|
47
47
|
if (lastPendingSync?.id === coValue.id && !isSignatureCheckpoint) {
|
|
48
48
|
addTransactionToContentMessage(
|
package/src/queue/StoreQueue.ts
CHANGED
|
@@ -8,7 +8,38 @@ type StoreQueueEntry = {
|
|
|
8
8
|
correctionCallback: CorrectionCallback;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
class StoreQueueManager {
|
|
12
|
+
private backlog = new LinkedList<{
|
|
13
|
+
queue: StoreQueue;
|
|
14
|
+
callback: () => Promise<unknown>;
|
|
15
|
+
}>();
|
|
16
|
+
|
|
17
|
+
private processing = false;
|
|
18
|
+
|
|
19
|
+
async schedule(queue: StoreQueue, callback: () => Promise<unknown>) {
|
|
20
|
+
this.backlog.push({ queue, callback });
|
|
21
|
+
|
|
22
|
+
if (this.processing) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.processing = true;
|
|
27
|
+
|
|
28
|
+
while (this.backlog.head) {
|
|
29
|
+
const entry = this.backlog.head;
|
|
30
|
+
|
|
31
|
+
await entry.value.callback();
|
|
32
|
+
|
|
33
|
+
this.backlog.shift();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.processing = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
11
40
|
export class StoreQueue {
|
|
41
|
+
static manager = new StoreQueueManager();
|
|
42
|
+
|
|
12
43
|
private queue = new LinkedList<StoreQueueEntry>();
|
|
13
44
|
closed = false;
|
|
14
45
|
|
|
@@ -27,7 +58,7 @@ export class StoreQueue {
|
|
|
27
58
|
processing = false;
|
|
28
59
|
lastCallback: Promise<unknown> | undefined;
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
processQueue(
|
|
31
62
|
callback: (
|
|
32
63
|
data: NewContentMessage,
|
|
33
64
|
correctionCallback: CorrectionCallback,
|
|
@@ -39,21 +70,23 @@ export class StoreQueue {
|
|
|
39
70
|
|
|
40
71
|
this.processing = true;
|
|
41
72
|
|
|
42
|
-
|
|
73
|
+
return StoreQueue.manager.schedule(this, async () => {
|
|
74
|
+
let entry: StoreQueueEntry | undefined;
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
while ((entry = this.pull())) {
|
|
77
|
+
const { data, correctionCallback } = entry;
|
|
46
78
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
try {
|
|
80
|
+
this.lastCallback = callback(data, correctionCallback);
|
|
81
|
+
await this.lastCallback;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.error("Error processing message in store queue", { err });
|
|
84
|
+
}
|
|
52
85
|
}
|
|
53
|
-
}
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
this.lastCallback = undefined;
|
|
88
|
+
this.processing = false;
|
|
89
|
+
});
|
|
57
90
|
}
|
|
58
91
|
|
|
59
92
|
close() {
|
|
@@ -112,24 +112,34 @@ export class SQLiteClient implements DBClientInterfaceSync {
|
|
|
112
112
|
) as SignatureAfterRow[];
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
getCoValueRowID(id: RawCoID): number | undefined {
|
|
116
|
+
const row = this.db.get<{ rowID: number }>(
|
|
117
|
+
"SELECT rowID FROM coValues WHERE id = ?",
|
|
118
|
+
[id],
|
|
119
|
+
);
|
|
120
|
+
return row?.rowID;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
upsertCoValue(id: RawCoID, header?: CoValueHeader): number | undefined {
|
|
124
|
+
if (!header) {
|
|
125
|
+
return this.getCoValueRowID(id);
|
|
126
|
+
}
|
|
127
|
+
|
|
116
128
|
const result = this.db.get<{ rowID: number }>(
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
`INSERT INTO coValues (id, header) VALUES (?, ?)
|
|
130
|
+
ON CONFLICT(id) DO NOTHING
|
|
131
|
+
RETURNING rowID`,
|
|
132
|
+
[id, JSON.stringify(header)],
|
|
119
133
|
);
|
|
120
134
|
|
|
121
135
|
if (!result) {
|
|
122
|
-
|
|
136
|
+
return this.getCoValueRowID(id);
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
return result.rowID;
|
|
126
140
|
}
|
|
127
141
|
|
|
128
|
-
addSessionUpdate({
|
|
129
|
-
sessionUpdate,
|
|
130
|
-
}: {
|
|
131
|
-
sessionUpdate: SessionRow;
|
|
132
|
-
}): number {
|
|
142
|
+
addSessionUpdate({ sessionUpdate }: { sessionUpdate: SessionRow }): number {
|
|
133
143
|
const result = this.db.get<{ rowID: number }>(
|
|
134
144
|
`INSERT INTO sessions (coValue, sessionID, lastIdx, lastSignature, bytesSinceLastSignature) VALUES (?, ?, ?, ?, ?)
|
|
135
145
|
ON CONFLICT(coValue, sessionID) DO UPDATE SET lastIdx=excluded.lastIdx, lastSignature=excluded.lastSignature, bytesSinceLastSignature=excluded.bytesSinceLastSignature
|
|
@@ -166,7 +176,11 @@ export class SQLiteClient implements DBClientInterfaceSync {
|
|
|
166
176
|
sessionRowID,
|
|
167
177
|
idx,
|
|
168
178
|
signature,
|
|
169
|
-
}: {
|
|
179
|
+
}: {
|
|
180
|
+
sessionRowID: number;
|
|
181
|
+
idx: number;
|
|
182
|
+
signature: Signature;
|
|
183
|
+
}) {
|
|
170
184
|
this.db.run(
|
|
171
185
|
"INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)",
|
|
172
186
|
[sessionRowID, idx, signature],
|
|
@@ -112,14 +112,31 @@ export class SQLiteClientAsync implements DBClientInterfaceAsync {
|
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
async
|
|
115
|
+
async getCoValueRowID(id: RawCoID): Promise<number | undefined> {
|
|
116
|
+
const row = await this.db.get<{ rowID: number }>(
|
|
117
|
+
"SELECT rowID FROM coValues WHERE id = ?",
|
|
118
|
+
[id],
|
|
119
|
+
);
|
|
120
|
+
return row?.rowID;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async upsertCoValue(
|
|
124
|
+
id: RawCoID,
|
|
125
|
+
header?: CoValueHeader,
|
|
126
|
+
): Promise<number | undefined> {
|
|
127
|
+
if (!header) {
|
|
128
|
+
return this.getCoValueRowID(id);
|
|
129
|
+
}
|
|
130
|
+
|
|
116
131
|
const result = await this.db.get<{ rowID: number }>(
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
`INSERT INTO coValues (id, header) VALUES (?, ?)
|
|
133
|
+
ON CONFLICT(id) DO NOTHING
|
|
134
|
+
RETURNING rowID`,
|
|
135
|
+
[id, JSON.stringify(header)],
|
|
119
136
|
);
|
|
120
137
|
|
|
121
138
|
if (!result) {
|
|
122
|
-
|
|
139
|
+
return this.getCoValueRowID(id);
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
return result.rowID;
|
|
@@ -166,7 +183,11 @@ export class SQLiteClientAsync implements DBClientInterfaceAsync {
|
|
|
166
183
|
sessionRowID,
|
|
167
184
|
idx,
|
|
168
185
|
signature,
|
|
169
|
-
}: {
|
|
186
|
+
}: {
|
|
187
|
+
sessionRowID: number;
|
|
188
|
+
idx: number;
|
|
189
|
+
signature: Signature;
|
|
190
|
+
}) {
|
|
170
191
|
this.db.run(
|
|
171
192
|
"INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)",
|
|
172
193
|
[sessionRowID, idx, signature],
|
|
@@ -254,22 +254,18 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
const id = msg.id;
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
const storedCoValueRowID = await this.dbClient.upsertCoValue(
|
|
258
|
+
id,
|
|
259
|
+
msg.header,
|
|
260
|
+
);
|
|
261
261
|
|
|
262
|
-
if (
|
|
262
|
+
if (!storedCoValueRowID) {
|
|
263
263
|
const knownState = emptyKnownState(id as RawCoID);
|
|
264
264
|
this.knwonStates.setKnownState(id, knownState);
|
|
265
265
|
|
|
266
266
|
return this.handleCorrection(knownState, correctionCallback);
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
const storedCoValueRowID: number = coValueRow
|
|
270
|
-
? coValueRow.rowID
|
|
271
|
-
: await this.dbClient.addCoValue(msg);
|
|
272
|
-
|
|
273
269
|
const knownState = this.knwonStates.getKnownState(id);
|
|
274
270
|
knownState.header = true;
|
|
275
271
|
|
|
@@ -235,22 +235,15 @@ export class StorageApiSync implements StorageAPI {
|
|
|
235
235
|
correctionCallback: CorrectionCallback,
|
|
236
236
|
): boolean {
|
|
237
237
|
const id = msg.id;
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
// We have no info about coValue header
|
|
241
|
-
const invalidAssumptionOnHeaderPresence = !msg.header && !coValueRow;
|
|
238
|
+
const storedCoValueRowID = this.dbClient.upsertCoValue(id, msg.header);
|
|
242
239
|
|
|
243
|
-
if (
|
|
240
|
+
if (!storedCoValueRowID) {
|
|
244
241
|
const knownState = emptyKnownState(id as RawCoID);
|
|
245
242
|
this.knwonStates.setKnownState(id, knownState);
|
|
246
243
|
|
|
247
244
|
return this.handleCorrection(knownState, correctionCallback);
|
|
248
245
|
}
|
|
249
246
|
|
|
250
|
-
const storedCoValueRowID: number = coValueRow
|
|
251
|
-
? coValueRow.rowID
|
|
252
|
-
: this.dbClient.addCoValue(msg);
|
|
253
|
-
|
|
254
247
|
const knownState = this.knwonStates.getKnownState(id);
|
|
255
248
|
knownState.header = true;
|
|
256
249
|
|