cojson 0.8.11 → 0.8.16

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +94 -82
  2. package/dist/native/PeerKnownStates.js +1 -1
  3. package/dist/native/PeerKnownStates.js.map +1 -1
  4. package/dist/native/PeerState.js +4 -1
  5. package/dist/native/PeerState.js.map +1 -1
  6. package/dist/native/PriorityBasedMessageQueue.js +1 -10
  7. package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
  8. package/dist/native/base64url.js.map +1 -1
  9. package/dist/native/base64url.test.js +1 -1
  10. package/dist/native/base64url.test.js.map +1 -1
  11. package/dist/native/coValue.js.map +1 -1
  12. package/dist/native/coValueCore.js +141 -149
  13. package/dist/native/coValueCore.js.map +1 -1
  14. package/dist/native/coValueState.js.map +1 -1
  15. package/dist/native/coValues/account.js +6 -6
  16. package/dist/native/coValues/account.js.map +1 -1
  17. package/dist/native/coValues/coList.js +2 -3
  18. package/dist/native/coValues/coList.js.map +1 -1
  19. package/dist/native/coValues/coMap.js +1 -1
  20. package/dist/native/coValues/coMap.js.map +1 -1
  21. package/dist/native/coValues/coStream.js +3 -5
  22. package/dist/native/coValues/coStream.js.map +1 -1
  23. package/dist/native/coValues/group.js +11 -11
  24. package/dist/native/coValues/group.js.map +1 -1
  25. package/dist/native/coreToCoValue.js +2 -2
  26. package/dist/native/coreToCoValue.js.map +1 -1
  27. package/dist/native/crypto/PureJSCrypto.js +4 -4
  28. package/dist/native/crypto/PureJSCrypto.js.map +1 -1
  29. package/dist/native/crypto/crypto.js.map +1 -1
  30. package/dist/native/exports.js +12 -12
  31. package/dist/native/exports.js.map +1 -1
  32. package/dist/native/ids.js.map +1 -1
  33. package/dist/native/jsonStringify.js.map +1 -1
  34. package/dist/native/localNode.js +6 -8
  35. package/dist/native/localNode.js.map +1 -1
  36. package/dist/native/permissions.js +4 -7
  37. package/dist/native/permissions.js.map +1 -1
  38. package/dist/native/priority.js.map +1 -1
  39. package/dist/native/storage/FileSystem.js.map +1 -1
  40. package/dist/native/storage/chunksAndKnownStates.js +2 -4
  41. package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
  42. package/dist/native/storage/index.js +7 -16
  43. package/dist/native/storage/index.js.map +1 -1
  44. package/dist/native/streamUtils.js.map +1 -1
  45. package/dist/native/sync.js +6 -8
  46. package/dist/native/sync.js.map +1 -1
  47. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  48. package/dist/native/typeUtils/expectGroup.js.map +1 -1
  49. package/dist/native/typeUtils/isAccountID.js.map +1 -1
  50. package/dist/native/typeUtils/isCoValue.js +1 -1
  51. package/dist/native/typeUtils/isCoValue.js.map +1 -1
  52. package/dist/web/PeerKnownStates.js +1 -1
  53. package/dist/web/PeerKnownStates.js.map +1 -1
  54. package/dist/web/PeerState.js +4 -1
  55. package/dist/web/PeerState.js.map +1 -1
  56. package/dist/web/PriorityBasedMessageQueue.js +1 -10
  57. package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
  58. package/dist/web/base64url.js.map +1 -1
  59. package/dist/web/base64url.test.js +1 -1
  60. package/dist/web/base64url.test.js.map +1 -1
  61. package/dist/web/coValue.js.map +1 -1
  62. package/dist/web/coValueCore.js +141 -149
  63. package/dist/web/coValueCore.js.map +1 -1
  64. package/dist/web/coValueState.js.map +1 -1
  65. package/dist/web/coValues/account.js +6 -6
  66. package/dist/web/coValues/account.js.map +1 -1
  67. package/dist/web/coValues/coList.js +2 -3
  68. package/dist/web/coValues/coList.js.map +1 -1
  69. package/dist/web/coValues/coMap.js +1 -1
  70. package/dist/web/coValues/coMap.js.map +1 -1
  71. package/dist/web/coValues/coStream.js +3 -5
  72. package/dist/web/coValues/coStream.js.map +1 -1
  73. package/dist/web/coValues/group.js +11 -11
  74. package/dist/web/coValues/group.js.map +1 -1
  75. package/dist/web/coreToCoValue.js +2 -2
  76. package/dist/web/coreToCoValue.js.map +1 -1
  77. package/dist/web/crypto/PureJSCrypto.js +4 -4
  78. package/dist/web/crypto/PureJSCrypto.js.map +1 -1
  79. package/dist/web/crypto/WasmCrypto.js +5 -5
  80. package/dist/web/crypto/WasmCrypto.js.map +1 -1
  81. package/dist/web/crypto/crypto.js.map +1 -1
  82. package/dist/web/exports.js +12 -12
  83. package/dist/web/exports.js.map +1 -1
  84. package/dist/web/ids.js.map +1 -1
  85. package/dist/web/jsonStringify.js.map +1 -1
  86. package/dist/web/localNode.js +6 -8
  87. package/dist/web/localNode.js.map +1 -1
  88. package/dist/web/permissions.js +4 -7
  89. package/dist/web/permissions.js.map +1 -1
  90. package/dist/web/priority.js.map +1 -1
  91. package/dist/web/storage/FileSystem.js.map +1 -1
  92. package/dist/web/storage/chunksAndKnownStates.js +2 -4
  93. package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
  94. package/dist/web/storage/index.js +7 -16
  95. package/dist/web/storage/index.js.map +1 -1
  96. package/dist/web/streamUtils.js.map +1 -1
  97. package/dist/web/sync.js +6 -8
  98. package/dist/web/sync.js.map +1 -1
  99. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  100. package/dist/web/typeUtils/expectGroup.js.map +1 -1
  101. package/dist/web/typeUtils/isAccountID.js.map +1 -1
  102. package/dist/web/typeUtils/isCoValue.js +1 -1
  103. package/dist/web/typeUtils/isCoValue.js.map +1 -1
  104. package/package.json +4 -14
  105. package/src/PeerKnownStates.ts +91 -89
  106. package/src/PeerState.ts +72 -69
  107. package/src/PriorityBasedMessageQueue.ts +42 -49
  108. package/src/base64url.test.ts +24 -24
  109. package/src/base64url.ts +44 -45
  110. package/src/coValue.ts +45 -45
  111. package/src/coValueCore.ts +746 -785
  112. package/src/coValueState.ts +82 -72
  113. package/src/coValues/account.ts +143 -150
  114. package/src/coValues/coList.ts +520 -522
  115. package/src/coValues/coMap.ts +283 -285
  116. package/src/coValues/coStream.ts +320 -324
  117. package/src/coValues/group.ts +306 -305
  118. package/src/coreToCoValue.ts +28 -31
  119. package/src/crypto/PureJSCrypto.ts +188 -194
  120. package/src/crypto/WasmCrypto.ts +236 -254
  121. package/src/crypto/crypto.ts +302 -309
  122. package/src/exports.ts +116 -116
  123. package/src/ids.ts +9 -9
  124. package/src/jsonStringify.ts +46 -46
  125. package/src/jsonValue.ts +24 -10
  126. package/src/localNode.ts +635 -660
  127. package/src/media.ts +3 -3
  128. package/src/permissions.ts +272 -278
  129. package/src/priority.ts +21 -19
  130. package/src/storage/FileSystem.ts +91 -99
  131. package/src/storage/chunksAndKnownStates.ts +110 -115
  132. package/src/storage/index.ts +466 -497
  133. package/src/streamUtils.ts +60 -60
  134. package/src/sync.ts +593 -615
  135. package/src/tests/PeerKnownStates.test.ts +38 -34
  136. package/src/tests/PeerState.test.ts +101 -64
  137. package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
  138. package/src/tests/account.test.ts +59 -59
  139. package/src/tests/coList.test.ts +65 -65
  140. package/src/tests/coMap.test.ts +137 -137
  141. package/src/tests/coStream.test.ts +254 -257
  142. package/src/tests/coValueCore.test.ts +153 -156
  143. package/src/tests/crypto.test.ts +136 -144
  144. package/src/tests/cryptoImpl.test.ts +205 -197
  145. package/src/tests/group.test.ts +24 -24
  146. package/src/tests/permissions.test.ts +1306 -1371
  147. package/src/tests/priority.test.ts +65 -82
  148. package/src/tests/sync.test.ts +1300 -1291
  149. package/src/tests/testUtils.ts +52 -53
  150. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
  151. package/src/typeUtils/expectGroup.ts +9 -9
  152. package/src/typeUtils/isAccountID.ts +1 -1
  153. package/src/typeUtils/isCoValue.ts +9 -9
  154. package/tsconfig.json +4 -6
  155. package/tsconfig.native.json +9 -11
  156. package/tsconfig.web.json +4 -10
  157. package/.eslintrc.cjs +0 -25
  158. package/.prettierrc.js +0 -9
@@ -1,574 +1,543 @@
1
- import { RawCoID } from "../ids.js";
1
+ import { CoID, RawCoValue } from "../coValue.js";
2
2
  import { CoValueHeader, Transaction } from "../coValueCore.js";
3
3
  import { Signature } from "../crypto/crypto.js";
4
- import {
5
- CoValueKnownState,
6
- IncomingSyncStream,
7
- NewContentMessage,
8
- OutgoingSyncQueue,
9
- Peer,
10
- } from "../sync.js";
11
- import { CoID, RawCoValue } from "../coValue.js";
4
+ import { RawCoID } from "../ids.js";
12
5
  import { connectedPeers } from "../streamUtils.js";
13
6
  import {
14
- chunkToKnownState,
15
- contentSinceChunk,
16
- mergeChunks,
17
- } from "./chunksAndKnownStates.js";
7
+ CoValueKnownState,
8
+ IncomingSyncStream,
9
+ NewContentMessage,
10
+ OutgoingSyncQueue,
11
+ Peer,
12
+ } from "../sync.js";
18
13
  import {
19
- BlockFilename,
20
- FileSystem,
21
- WalEntry,
22
- WalFilename,
23
- readChunk,
24
- readHeader,
25
- textDecoder,
26
- writeBlock,
27
- writeToWal,
14
+ BlockFilename,
15
+ FileSystem,
16
+ WalEntry,
17
+ WalFilename,
18
+ readChunk,
19
+ readHeader,
20
+ textDecoder,
21
+ writeBlock,
22
+ writeToWal,
28
23
  } from "./FileSystem.js";
24
+ import {
25
+ chunkToKnownState,
26
+ contentSinceChunk,
27
+ mergeChunks,
28
+ } from "./chunksAndKnownStates.js";
29
29
  export type { BlockFilename, WalFilename } from "./FileSystem.js";
30
30
 
31
31
  const MAX_N_LEVELS = 3;
32
32
 
33
33
  export type CoValueChunk = {
34
- header?: CoValueHeader;
35
- sessionEntries: {
36
- [sessionID: string]: {
37
- after: number;
38
- lastSignature: Signature;
39
- transactions: Transaction[];
40
- }[];
41
- };
34
+ header?: CoValueHeader;
35
+ sessionEntries: {
36
+ [sessionID: string]: {
37
+ after: number;
38
+ lastSignature: Signature;
39
+ transactions: Transaction[];
40
+ }[];
41
+ };
42
42
  };
43
43
 
44
44
  export class LSMStorage<WH, RH, FS extends FileSystem<WH, RH>> {
45
- currentWal: WH | undefined;
46
- coValues: {
47
- [id: RawCoID]: CoValueChunk | undefined;
45
+ currentWal: WH | undefined;
46
+ coValues: {
47
+ [id: RawCoID]: CoValueChunk | undefined;
48
+ };
49
+ fileCache: string[] | undefined;
50
+ headerCache = new Map<
51
+ BlockFilename,
52
+ { [id: RawCoID]: { start: number; length: number } }
53
+ >();
54
+ blockFileHandles = new Map<
55
+ BlockFilename,
56
+ Promise<{ handle: RH; size: number }>
57
+ >();
58
+
59
+ constructor(
60
+ public fs: FS,
61
+ public fromLocalNode: IncomingSyncStream,
62
+ public toLocalNode: OutgoingSyncQueue,
63
+ ) {
64
+ this.coValues = {};
65
+ this.currentWal = undefined;
66
+
67
+ let nMsg = 0;
68
+
69
+ const processMessages = async () => {
70
+ for await (const msg of fromLocalNode) {
71
+ console.log("Storage msg start", nMsg);
72
+ try {
73
+ if (msg === "Disconnected" || msg === "PingTimeout") {
74
+ throw new Error("Unexpected Disconnected message");
75
+ }
76
+ if (msg.action === "done") {
77
+ return;
78
+ }
79
+
80
+ if (msg.action === "content") {
81
+ await this.handleNewContent(msg);
82
+ } else if (msg.action === "load" || msg.action === "known") {
83
+ await this.sendNewContent(msg.id, msg, undefined);
84
+ }
85
+ } catch (e) {
86
+ console.error(
87
+ new Error(
88
+ `Error reading from localNode, handling msg\n\n${JSON.stringify(
89
+ msg,
90
+ (k, v) =>
91
+ k === "changes" || k === "encryptedChanges"
92
+ ? v.slice(0, 20) + "..."
93
+ : v,
94
+ )}`,
95
+ { cause: e },
96
+ ),
97
+ );
98
+ }
99
+ console.log("Storage msg end", nMsg);
100
+ nMsg++;
101
+ }
48
102
  };
49
- fileCache: string[] | undefined;
50
- headerCache = new Map<
51
- BlockFilename,
52
- { [id: RawCoID]: { start: number; length: number } }
53
- >();
54
- blockFileHandles = new Map<
55
- BlockFilename,
56
- Promise<{ handle: RH; size: number }>
57
- >();
58
-
59
- constructor(
60
- public fs: FS,
61
- public fromLocalNode: IncomingSyncStream,
62
- public toLocalNode: OutgoingSyncQueue,
63
- ) {
64
- this.coValues = {};
65
- this.currentWal = undefined;
66
-
67
- let nMsg = 0;
68
-
69
- const processMessages = async () => {
70
- for await (const msg of fromLocalNode) {
71
- console.log("Storage msg start", nMsg);
72
- try {
73
- if (msg === "Disconnected" || msg === "PingTimeout") {
74
- throw new Error("Unexpected Disconnected message");
75
- }
76
- if (msg.action === "done") {
77
- return;
78
- }
79
-
80
- if (msg.action === "content") {
81
- await this.handleNewContent(msg);
82
- } else if (msg.action === 'load' || msg.action === 'known') {
83
- await this.sendNewContent(msg.id, msg, undefined);
84
- }
85
- } catch (e) {
86
- console.error(
87
- new Error(
88
- `Error reading from localNode, handling msg\n\n${JSON.stringify(
89
- msg,
90
- (k, v) =>
91
- k === "changes" || k === "encryptedChanges"
92
- ? v.slice(0, 20) + "..."
93
- : v,
94
- )}`,
95
- { cause: e },
96
- ),
97
- );
98
- }
99
- console.log("Storage msg end", nMsg);
100
- nMsg++;
101
- }
102
- };
103
-
104
- processMessages().catch((e) =>
105
- console.error("Error in processMessages in storage", e),
106
- );
107
103
 
108
- setTimeout(
109
- () =>
110
- this.compact().catch((e) => {
111
- console.error("Error while compacting", e);
112
- }),
113
- 20000,
114
- );
104
+ processMessages().catch((e) =>
105
+ console.error("Error in processMessages in storage", e),
106
+ );
107
+
108
+ setTimeout(
109
+ () =>
110
+ this.compact().catch((e) => {
111
+ console.error("Error while compacting", e);
112
+ }),
113
+ 20000,
114
+ );
115
+ }
116
+
117
+ async sendNewContent(
118
+ id: RawCoID,
119
+ known: CoValueKnownState | undefined,
120
+ asDependencyOf: RawCoID | undefined,
121
+ ) {
122
+ let coValue = this.coValues[id];
123
+
124
+ if (!coValue) {
125
+ coValue = await this.loadCoValue(id, this.fs);
115
126
  }
116
127
 
117
- async sendNewContent(
118
- id: RawCoID,
119
- known: CoValueKnownState | undefined,
120
- asDependencyOf: RawCoID | undefined,
121
- ) {
122
- let coValue = this.coValues[id];
123
-
124
- if (!coValue) {
125
- coValue = await this.loadCoValue(id, this.fs);
126
- }
127
-
128
- if (!coValue) {
129
- this.toLocalNode
130
- .push({
131
- id: id,
132
- action: "known",
133
- header: false,
134
- sessions: {},
135
- asDependencyOf,
136
- })
137
- .catch((e) => console.error("Error while pushing known", e));
138
-
139
- return;
140
- }
128
+ if (!coValue) {
129
+ this.toLocalNode
130
+ .push({
131
+ id: id,
132
+ action: "known",
133
+ header: false,
134
+ sessions: {},
135
+ asDependencyOf,
136
+ })
137
+ .catch((e) => console.error("Error while pushing known", e));
138
+
139
+ return;
140
+ }
141
141
 
142
- if (!known?.header && coValue.header?.ruleset.type === "ownedByGroup") {
143
- await this.sendNewContent(
144
- coValue.header.ruleset.group,
145
- undefined,
146
- asDependencyOf || id,
147
- );
148
- } else if (!known?.header && coValue.header?.ruleset.type === "group") {
149
- const dependedOnAccounts = new Set();
150
- for (const session of Object.values(coValue.sessionEntries)) {
151
- for (const entry of session) {
152
- for (const tx of entry.transactions) {
153
- if (tx.privacy === "trusting") {
154
- const parsedChanges = JSON.parse(tx.changes);
155
- for (const change of parsedChanges) {
156
- if (
157
- change.op === "set" &&
158
- change.key.startsWith("co_")
159
- ) {
160
- dependedOnAccounts.add(change.key);
161
- }
162
- }
163
- }
164
- }
142
+ if (!known?.header && coValue.header?.ruleset.type === "ownedByGroup") {
143
+ await this.sendNewContent(
144
+ coValue.header.ruleset.group,
145
+ undefined,
146
+ asDependencyOf || id,
147
+ );
148
+ } else if (!known?.header && coValue.header?.ruleset.type === "group") {
149
+ const dependedOnAccounts = new Set();
150
+ for (const session of Object.values(coValue.sessionEntries)) {
151
+ for (const entry of session) {
152
+ for (const tx of entry.transactions) {
153
+ if (tx.privacy === "trusting") {
154
+ const parsedChanges = JSON.parse(tx.changes);
155
+ for (const change of parsedChanges) {
156
+ if (change.op === "set" && change.key.startsWith("co_")) {
157
+ dependedOnAccounts.add(change.key);
165
158
  }
159
+ }
166
160
  }
167
- for (const account of dependedOnAccounts) {
168
- await this.sendNewContent(
169
- account as CoID<RawCoValue>,
170
- undefined,
171
- asDependencyOf || id,
172
- );
173
- }
161
+ }
174
162
  }
175
-
176
- const newContentMessages = contentSinceChunk(id, coValue, known).map(
177
- (message) => ({ ...message, asDependencyOf }),
163
+ }
164
+ for (const account of dependedOnAccounts) {
165
+ await this.sendNewContent(
166
+ account as CoID<RawCoValue>,
167
+ undefined,
168
+ asDependencyOf || id,
178
169
  );
179
-
180
- const ourKnown: CoValueKnownState = chunkToKnownState(id, coValue);
181
-
182
- this.toLocalNode
183
- .push({
184
- action: "known",
185
- ...ourKnown,
186
- asDependencyOf,
187
- })
188
- .catch((e) => console.error("Error while pushing known", e));
189
-
190
- for (const message of newContentMessages) {
191
- if (Object.keys(message.new).length === 0) continue;
192
- this.toLocalNode
193
- .push(message)
194
- .catch((e) =>
195
- console.error("Error while pushing new content", e),
196
- );
197
- }
198
-
199
- this.coValues[id] = coValue;
170
+ }
200
171
  }
201
172
 
202
- async withWAL(handler: (wal: WH) => Promise<void>) {
203
- if (!this.currentWal) {
204
- this.currentWal = await this.fs.createFile(
205
- `wal-${Date.now()}-${Math.random()
206
- .toString(36)
207
- .slice(2)}.jsonl`,
208
- );
209
- }
210
- await handler(this.currentWal);
173
+ const newContentMessages = contentSinceChunk(id, coValue, known).map(
174
+ (message) => ({ ...message, asDependencyOf }),
175
+ );
176
+
177
+ const ourKnown: CoValueKnownState = chunkToKnownState(id, coValue);
178
+
179
+ this.toLocalNode
180
+ .push({
181
+ action: "known",
182
+ ...ourKnown,
183
+ asDependencyOf,
184
+ })
185
+ .catch((e) => console.error("Error while pushing known", e));
186
+
187
+ for (const message of newContentMessages) {
188
+ if (Object.keys(message.new).length === 0) continue;
189
+ this.toLocalNode
190
+ .push(message)
191
+ .catch((e) => console.error("Error while pushing new content", e));
211
192
  }
212
193
 
213
- async handleNewContent(newContent: NewContentMessage) {
214
- const coValue = this.coValues[newContent.id];
215
-
216
- const newContentAsChunk: CoValueChunk = {
217
- header: newContent.header,
218
- sessionEntries: Object.fromEntries(
219
- Object.entries(newContent.new).map(
220
- ([sessionID, newInSession]) => [
221
- sessionID,
222
- [
223
- {
224
- after: newInSession.after,
225
- lastSignature: newInSession.lastSignature,
226
- transactions: newInSession.newTransactions,
227
- },
228
- ],
229
- ],
230
- ),
231
- ),
232
- };
194
+ this.coValues[id] = coValue;
195
+ }
233
196
 
234
- if (!coValue) {
235
- if (newContent.header) {
236
- // console.log("Creating in WAL", newContent.id);
237
- await this.withWAL((wal) =>
238
- writeToWal(wal, this.fs, newContent.id, newContentAsChunk),
239
- );
197
+ async withWAL(handler: (wal: WH) => Promise<void>) {
198
+ if (!this.currentWal) {
199
+ this.currentWal = await this.fs.createFile(
200
+ `wal-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`,
201
+ );
202
+ }
203
+ await handler(this.currentWal);
204
+ }
205
+
206
+ async handleNewContent(newContent: NewContentMessage) {
207
+ const coValue = this.coValues[newContent.id];
208
+
209
+ const newContentAsChunk: CoValueChunk = {
210
+ header: newContent.header,
211
+ sessionEntries: Object.fromEntries(
212
+ Object.entries(newContent.new).map(([sessionID, newInSession]) => [
213
+ sessionID,
214
+ [
215
+ {
216
+ after: newInSession.after,
217
+ lastSignature: newInSession.lastSignature,
218
+ transactions: newInSession.newTransactions,
219
+ },
220
+ ],
221
+ ]),
222
+ ),
223
+ };
240
224
 
241
- this.coValues[newContent.id] = newContentAsChunk;
242
- } else {
243
- console.warn(
244
- "Incontiguous incoming update for " + newContent.id,
245
- );
246
- return;
247
- }
248
- } else {
249
- const merged = mergeChunks(coValue, newContentAsChunk);
250
- if (merged === "nonContigous") {
251
- console.warn(
252
- "Non-contigous new content for " + newContent.id,
253
- Object.entries(coValue.sessionEntries).map(
254
- ([session, entries]) =>
255
- entries.map((entry) => ({
256
- session: session,
257
- after: entry.after,
258
- length: entry.transactions.length,
259
- })),
260
- ),
261
- Object.entries(newContentAsChunk.sessionEntries).map(
262
- ([session, entries]) =>
263
- entries.map((entry) => ({
264
- session: session,
265
- after: entry.after,
266
- length: entry.transactions.length,
267
- })),
268
- ),
269
- );
270
- } else {
271
- // console.log("Appending to WAL", newContent.id);
272
- await this.withWAL((wal) =>
273
- writeToWal(wal, this.fs, newContent.id, newContentAsChunk),
274
- );
225
+ if (!coValue) {
226
+ if (newContent.header) {
227
+ // console.log("Creating in WAL", newContent.id);
228
+ await this.withWAL((wal) =>
229
+ writeToWal(wal, this.fs, newContent.id, newContentAsChunk),
230
+ );
275
231
 
276
- this.coValues[newContent.id] = merged;
277
- }
278
- }
232
+ this.coValues[newContent.id] = newContentAsChunk;
233
+ } else {
234
+ console.warn("Incontiguous incoming update for " + newContent.id);
235
+ return;
236
+ }
237
+ } else {
238
+ const merged = mergeChunks(coValue, newContentAsChunk);
239
+ if (merged === "nonContigous") {
240
+ console.warn(
241
+ "Non-contigous new content for " + newContent.id,
242
+ Object.entries(coValue.sessionEntries).map(([session, entries]) =>
243
+ entries.map((entry) => ({
244
+ session: session,
245
+ after: entry.after,
246
+ length: entry.transactions.length,
247
+ })),
248
+ ),
249
+ Object.entries(newContentAsChunk.sessionEntries).map(
250
+ ([session, entries]) =>
251
+ entries.map((entry) => ({
252
+ session: session,
253
+ after: entry.after,
254
+ length: entry.transactions.length,
255
+ })),
256
+ ),
257
+ );
258
+ } else {
259
+ // console.log("Appending to WAL", newContent.id);
260
+ await this.withWAL((wal) =>
261
+ writeToWal(wal, this.fs, newContent.id, newContentAsChunk),
262
+ );
263
+
264
+ this.coValues[newContent.id] = merged;
265
+ }
266
+ }
267
+ }
268
+
269
+ async getBlockHandle(
270
+ blockFile: BlockFilename,
271
+ fs: FS,
272
+ ): Promise<{ handle: RH; size: number }> {
273
+ if (!this.blockFileHandles.has(blockFile)) {
274
+ this.blockFileHandles.set(blockFile, fs.openToRead(blockFile));
279
275
  }
280
276
 
281
- async getBlockHandle(
282
- blockFile: BlockFilename,
283
- fs: FS,
284
- ): Promise<{ handle: RH; size: number }> {
285
- if (!this.blockFileHandles.has(blockFile)) {
286
- this.blockFileHandles.set(blockFile, fs.openToRead(blockFile));
287
- }
277
+ return this.blockFileHandles.get(blockFile)!;
278
+ }
288
279
 
289
- return this.blockFileHandles.get(blockFile)!;
290
- }
280
+ async loadCoValue(id: RawCoID, fs: FS): Promise<CoValueChunk | undefined> {
281
+ const files = this.fileCache || (await fs.listFiles());
282
+ this.fileCache = files;
283
+ const blockFiles = (
284
+ files.filter((name) => name.startsWith("L")) as BlockFilename[]
285
+ ).sort();
291
286
 
292
- async loadCoValue(id: RawCoID, fs: FS): Promise<CoValueChunk | undefined> {
293
- const files = this.fileCache || (await fs.listFiles());
294
- this.fileCache = files;
295
- const blockFiles = (
296
- files.filter((name) => name.startsWith("L")) as BlockFilename[]
297
- ).sort();
287
+ let result;
298
288
 
299
- let result;
289
+ for (const blockFile of blockFiles) {
290
+ let cachedHeader:
291
+ | { [id: RawCoID]: { start: number; length: number } }
292
+ | undefined = this.headerCache.get(blockFile);
300
293
 
301
- for (const blockFile of blockFiles) {
302
- let cachedHeader:
303
- | { [id: RawCoID]: { start: number; length: number } }
304
- | undefined = this.headerCache.get(blockFile);
294
+ const { handle, size } = await this.getBlockHandle(blockFile, fs);
305
295
 
306
- const { handle, size } = await this.getBlockHandle(blockFile, fs);
296
+ // console.log("Attempting to load", id, blockFile);
307
297
 
308
- // console.log("Attempting to load", id, blockFile);
298
+ if (!cachedHeader) {
299
+ cachedHeader = {};
300
+ const header = await readHeader(blockFile, handle, size, fs);
301
+ for (const entry of header) {
302
+ cachedHeader[entry.id] = {
303
+ start: entry.start,
304
+ length: entry.length,
305
+ };
306
+ }
309
307
 
310
- if (!cachedHeader) {
311
- cachedHeader = {};
312
- const header = await readHeader(blockFile, handle, size, fs);
313
- for (const entry of header) {
314
- cachedHeader[entry.id] = {
315
- start: entry.start,
316
- length: entry.length,
317
- };
318
- }
308
+ this.headerCache.set(blockFile, cachedHeader);
309
+ }
310
+ const headerEntry = cachedHeader[id];
319
311
 
320
- this.headerCache.set(blockFile, cachedHeader);
321
- }
322
- const headerEntry = cachedHeader[id];
323
-
324
- // console.log("Header entry", id, headerEntry);
325
-
326
- if (headerEntry) {
327
- const nextChunk = await readChunk(handle, headerEntry, fs);
328
- if (result) {
329
- const merged = mergeChunks(result, nextChunk);
330
-
331
- if (merged === "nonContigous") {
332
- console.warn(
333
- "Non-contigous chunks while loading " + id,
334
- result,
335
- nextChunk,
336
- );
337
- } else {
338
- result = merged;
339
- }
340
- } else {
341
- result = nextChunk;
342
- }
343
- }
312
+ // console.log("Header entry", id, headerEntry);
344
313
 
345
- // await fs.close(handle);
314
+ if (headerEntry) {
315
+ const nextChunk = await readChunk(handle, headerEntry, fs);
316
+ if (result) {
317
+ const merged = mergeChunks(result, nextChunk);
318
+
319
+ if (merged === "nonContigous") {
320
+ console.warn(
321
+ "Non-contigous chunks while loading " + id,
322
+ result,
323
+ nextChunk,
324
+ );
325
+ } else {
326
+ result = merged;
327
+ }
328
+ } else {
329
+ result = nextChunk;
346
330
  }
331
+ }
347
332
 
348
- return result;
333
+ // await fs.close(handle);
349
334
  }
350
335
 
351
- async compact() {
352
- const fileNames = await this.fs.listFiles();
336
+ return result;
337
+ }
353
338
 
354
- const walFiles = fileNames.filter((name) =>
355
- name.startsWith("wal-"),
356
- ) as WalFilename[];
357
- walFiles.sort();
339
+ async compact() {
340
+ const fileNames = await this.fs.listFiles();
358
341
 
359
- const coValues = new Map<RawCoID, CoValueChunk>();
342
+ const walFiles = fileNames.filter((name) =>
343
+ name.startsWith("wal-"),
344
+ ) as WalFilename[];
345
+ walFiles.sort();
360
346
 
361
- console.log("Compacting WAL files", walFiles);
362
- if (walFiles.length === 0) return;
347
+ const coValues = new Map<RawCoID, CoValueChunk>();
363
348
 
364
- const oldWal = this.currentWal;
365
- this.currentWal = undefined;
349
+ console.log("Compacting WAL files", walFiles);
350
+ if (walFiles.length === 0) return;
351
+
352
+ const oldWal = this.currentWal;
353
+ this.currentWal = undefined;
354
+
355
+ if (oldWal) {
356
+ await this.fs.close(oldWal);
357
+ }
366
358
 
367
- if (oldWal) {
368
- await this.fs.close(oldWal);
359
+ for (const fileName of walFiles) {
360
+ const { handle, size }: { handle: RH; size: number } =
361
+ await this.fs.openToRead(fileName);
362
+ if (size === 0) {
363
+ await this.fs.close(handle);
364
+ continue;
365
+ }
366
+ const bytes = await this.fs.read(handle, 0, size);
367
+
368
+ const decoded = textDecoder.decode(bytes);
369
+ const lines = decoded.split("\n");
370
+
371
+ for (const line of lines) {
372
+ if (line.length === 0) continue;
373
+ const chunk = JSON.parse(line) as WalEntry;
374
+
375
+ const existingChunk = coValues.get(chunk.id);
376
+
377
+ if (existingChunk) {
378
+ const merged = mergeChunks(existingChunk, chunk);
379
+ if (merged === "nonContigous") {
380
+ console.log(
381
+ "Non-contigous chunks in " + chunk.id + ", " + fileName,
382
+ existingChunk,
383
+ chunk,
384
+ );
385
+ } else {
386
+ coValues.set(chunk.id, merged);
387
+ }
388
+ } else {
389
+ coValues.set(chunk.id, chunk);
369
390
  }
391
+ }
370
392
 
371
- for (const fileName of walFiles) {
372
- const { handle, size }: { handle: RH; size: number } =
373
- await this.fs.openToRead(fileName);
374
- if (size === 0) {
375
- await this.fs.close(handle);
376
- continue;
377
- }
378
- const bytes = await this.fs.read(handle, 0, size);
379
-
380
- const decoded = textDecoder.decode(bytes);
381
- const lines = decoded.split("\n");
382
-
383
- for (const line of lines) {
384
- if (line.length === 0) continue;
385
- const chunk = JSON.parse(line) as WalEntry;
386
-
387
- const existingChunk = coValues.get(chunk.id);
388
-
389
- if (existingChunk) {
390
- const merged = mergeChunks(existingChunk, chunk);
391
- if (merged === "nonContigous") {
392
- console.log(
393
- "Non-contigous chunks in " +
394
- chunk.id +
395
- ", " +
396
- fileName,
397
- existingChunk,
398
- chunk,
399
- );
400
- } else {
401
- coValues.set(chunk.id, merged);
402
- }
403
- } else {
404
- coValues.set(chunk.id, chunk);
405
- }
406
- }
393
+ await this.fs.close(handle);
394
+ }
407
395
 
408
- await this.fs.close(handle);
396
+ const highestBlockNumber = fileNames.reduce((acc, name) => {
397
+ if (name.startsWith("L" + MAX_N_LEVELS)) {
398
+ const num = parseInt(name.split("-")[1]!);
399
+ if (num > acc) {
400
+ return num;
409
401
  }
402
+ }
403
+ return acc;
404
+ }, 0);
410
405
 
411
- const highestBlockNumber = fileNames.reduce((acc, name) => {
412
- if (name.startsWith("L" + MAX_N_LEVELS)) {
413
- const num = parseInt(name.split("-")[1]!);
414
- if (num > acc) {
415
- return num;
416
- }
417
- }
418
- return acc;
419
- }, 0);
406
+ console.log([...coValues.keys()], fileNames, highestBlockNumber);
420
407
 
421
- console.log([...coValues.keys()], fileNames, highestBlockNumber);
408
+ await writeBlock(coValues, MAX_N_LEVELS, highestBlockNumber + 1, this.fs);
422
409
 
423
- await writeBlock(
424
- coValues,
425
- MAX_N_LEVELS,
426
- highestBlockNumber + 1,
427
- this.fs,
428
- );
410
+ for (const walFile of walFiles) {
411
+ await this.fs.removeFile(walFile);
412
+ }
413
+ this.fileCache = undefined;
429
414
 
430
- for (const walFile of walFiles) {
431
- await this.fs.removeFile(walFile);
432
- }
433
- this.fileCache = undefined;
415
+ const fileNames2 = await this.fs.listFiles();
434
416
 
435
- const fileNames2 = await this.fs.listFiles();
417
+ const blockFiles = (
418
+ fileNames2.filter((name) => name.startsWith("L")) as BlockFilename[]
419
+ ).sort();
436
420
 
437
- const blockFiles = (
438
- fileNames2.filter((name) => name.startsWith("L")) as BlockFilename[]
439
- ).sort();
421
+ const blockFilesByLevelInOrder: {
422
+ [level: number]: BlockFilename[];
423
+ } = {};
440
424
 
441
- const blockFilesByLevelInOrder: {
442
- [level: number]: BlockFilename[];
443
- } = {};
425
+ for (const blockFile of blockFiles) {
426
+ const level = parseInt(blockFile.split("-")[0]!.slice(1));
427
+ if (!blockFilesByLevelInOrder[level]) {
428
+ blockFilesByLevelInOrder[level] = [];
429
+ }
430
+ blockFilesByLevelInOrder[level]!.push(blockFile);
431
+ }
444
432
 
445
- for (const blockFile of blockFiles) {
446
- const level = parseInt(blockFile.split("-")[0]!.slice(1));
447
- if (!blockFilesByLevelInOrder[level]) {
448
- blockFilesByLevelInOrder[level] = [];
449
- }
450
- blockFilesByLevelInOrder[level]!.push(blockFile);
451
- }
433
+ console.log(blockFilesByLevelInOrder);
452
434
 
453
- console.log(blockFilesByLevelInOrder);
454
-
455
- for (let level = MAX_N_LEVELS; level > 0; level--) {
456
- const nBlocksDesired = Math.pow(2, level);
457
- const blocksInLevel = blockFilesByLevelInOrder[level];
458
-
459
- if (blocksInLevel && blocksInLevel.length > nBlocksDesired) {
460
- console.log("Compacting blocks in level", level, blocksInLevel);
461
-
462
- const coValues = new Map<RawCoID, CoValueChunk>();
463
-
464
- for (const blockFile of blocksInLevel) {
465
- const { handle, size }: { handle: RH; size: number } =
466
- await this.getBlockHandle(blockFile, this.fs);
467
-
468
- if (size === 0) {
469
- continue;
470
- }
471
- const header = await readHeader(
472
- blockFile,
473
- handle,
474
- size,
475
- this.fs,
476
- );
477
- for (const entry of header) {
478
- const chunk = await readChunk(handle, entry, this.fs);
479
-
480
- const existingChunk = coValues.get(entry.id);
481
-
482
- if (existingChunk) {
483
- const merged = mergeChunks(existingChunk, chunk);
484
- if (merged === "nonContigous") {
485
- console.log(
486
- "Non-contigous chunks in " +
487
- entry.id +
488
- ", " +
489
- blockFile,
490
- existingChunk,
491
- chunk,
492
- );
493
- } else {
494
- coValues.set(entry.id, merged);
495
- }
496
- } else {
497
- coValues.set(entry.id, chunk);
498
- }
499
- }
500
- }
435
+ for (let level = MAX_N_LEVELS; level > 0; level--) {
436
+ const nBlocksDesired = Math.pow(2, level);
437
+ const blocksInLevel = blockFilesByLevelInOrder[level];
501
438
 
502
- let levelBelow = blockFilesByLevelInOrder[level - 1];
503
- if (!levelBelow) {
504
- levelBelow = [];
505
- blockFilesByLevelInOrder[level - 1] = levelBelow;
506
- }
439
+ if (blocksInLevel && blocksInLevel.length > nBlocksDesired) {
440
+ console.log("Compacting blocks in level", level, blocksInLevel);
507
441
 
508
- const highestBlockNumberInLevelBelow = levelBelow.reduce(
509
- (acc, name) => {
510
- const num = parseInt(name.split("-")[1]!);
511
- if (num > acc) {
512
- return num;
513
- }
514
- return acc;
515
- },
516
- 0,
517
- );
442
+ const coValues = new Map<RawCoID, CoValueChunk>();
518
443
 
519
- const newBlockName = await writeBlock(
520
- coValues,
521
- level - 1,
522
- highestBlockNumberInLevelBelow + 1,
523
- this.fs,
444
+ for (const blockFile of blocksInLevel) {
445
+ const { handle, size }: { handle: RH; size: number } =
446
+ await this.getBlockHandle(blockFile, this.fs);
447
+
448
+ if (size === 0) {
449
+ continue;
450
+ }
451
+ const header = await readHeader(blockFile, handle, size, this.fs);
452
+ for (const entry of header) {
453
+ const chunk = await readChunk(handle, entry, this.fs);
454
+
455
+ const existingChunk = coValues.get(entry.id);
456
+
457
+ if (existingChunk) {
458
+ const merged = mergeChunks(existingChunk, chunk);
459
+ if (merged === "nonContigous") {
460
+ console.log(
461
+ "Non-contigous chunks in " + entry.id + ", " + blockFile,
462
+ existingChunk,
463
+ chunk,
524
464
  );
525
- levelBelow.push(newBlockName);
526
-
527
- // delete blocks that went into this one
528
- for (const blockFile of blocksInLevel) {
529
- const handle = await this.getBlockHandle(
530
- blockFile,
531
- this.fs,
532
- );
533
- await this.fs.close(handle.handle);
534
- await this.fs.removeFile(blockFile);
535
- this.blockFileHandles.delete(blockFile);
536
- }
465
+ } else {
466
+ coValues.set(entry.id, merged);
467
+ }
468
+ } else {
469
+ coValues.set(entry.id, chunk);
537
470
  }
471
+ }
472
+ }
473
+
474
+ let levelBelow = blockFilesByLevelInOrder[level - 1];
475
+ if (!levelBelow) {
476
+ levelBelow = [];
477
+ blockFilesByLevelInOrder[level - 1] = levelBelow;
538
478
  }
539
479
 
540
- setTimeout(
541
- () =>
542
- this.compact().catch((e) => {
543
- console.error("Error while compacting", e);
544
- }),
545
- 5000,
480
+ const highestBlockNumberInLevelBelow = levelBelow.reduce(
481
+ (acc, name) => {
482
+ const num = parseInt(name.split("-")[1]!);
483
+ if (num > acc) {
484
+ return num;
485
+ }
486
+ return acc;
487
+ },
488
+ 0,
546
489
  );
490
+
491
+ const newBlockName = await writeBlock(
492
+ coValues,
493
+ level - 1,
494
+ highestBlockNumberInLevelBelow + 1,
495
+ this.fs,
496
+ );
497
+ levelBelow.push(newBlockName);
498
+
499
+ // delete blocks that went into this one
500
+ for (const blockFile of blocksInLevel) {
501
+ const handle = await this.getBlockHandle(blockFile, this.fs);
502
+ await this.fs.close(handle.handle);
503
+ await this.fs.removeFile(blockFile);
504
+ this.blockFileHandles.delete(blockFile);
505
+ }
506
+ }
547
507
  }
548
508
 
549
- static asPeer<WH, RH, FS extends FileSystem<WH, RH>>({
550
- fs,
509
+ setTimeout(
510
+ () =>
511
+ this.compact().catch((e) => {
512
+ console.error("Error while compacting", e);
513
+ }),
514
+ 5000,
515
+ );
516
+ }
517
+
518
+ static asPeer<WH, RH, FS extends FileSystem<WH, RH>>({
519
+ fs,
520
+ trace,
521
+ localNodeName = "local",
522
+ }: {
523
+ fs: FS;
524
+ trace?: boolean;
525
+ localNodeName?: string;
526
+ }): Peer {
527
+ const [localNodeAsPeer, storageAsPeer] = connectedPeers(
528
+ localNodeName,
529
+ "storage",
530
+ {
531
+ peer1role: "client",
532
+ peer2role: "storage",
551
533
  trace,
552
- localNodeName = "local",
553
- }: {
554
- fs: FS;
555
- trace?: boolean;
556
- localNodeName?: string;
557
- }): Peer {
558
- const [localNodeAsPeer, storageAsPeer] = connectedPeers(
559
- localNodeName,
560
- "storage",
561
- {
562
- peer1role: "client",
563
- peer2role: "server",
564
- trace,
565
- crashOnClose: true,
566
- },
567
- );
534
+ crashOnClose: true,
535
+ },
536
+ );
568
537
 
569
- new LSMStorage(fs, localNodeAsPeer.incoming, localNodeAsPeer.outgoing);
538
+ new LSMStorage(fs, localNodeAsPeer.incoming, localNodeAsPeer.outgoing);
570
539
 
571
- // return { ...storageAsPeer, priority: 200 };
572
- return storageAsPeer;
573
- }
540
+ // return { ...storageAsPeer, priority: 200 };
541
+ return storageAsPeer;
542
+ }
574
543
  }