@comapeo/core 1.0.0
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/LICENSE.md +9 -0
- package/README.md +31 -0
- package/dist/blob-api.d.ts +92 -0
- package/dist/blob-api.d.ts.map +1 -0
- package/dist/blob-store/index.d.ts +163 -0
- package/dist/blob-store/index.d.ts.map +1 -0
- package/dist/blob-store/live-download.d.ts +107 -0
- package/dist/blob-store/live-download.d.ts.map +1 -0
- package/dist/config-import.d.ts +74 -0
- package/dist/config-import.d.ts.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core-manager/bitfield-rle.d.ts +25 -0
- package/dist/core-manager/bitfield-rle.d.ts.map +1 -0
- package/dist/core-manager/core-index.d.ts +56 -0
- package/dist/core-manager/core-index.d.ts.map +1 -0
- package/dist/core-manager/index.d.ts +125 -0
- package/dist/core-manager/index.d.ts.map +1 -0
- package/dist/core-manager/random-access-file-pool.d.ts +17 -0
- package/dist/core-manager/random-access-file-pool.d.ts.map +1 -0
- package/dist/core-manager/remote-bitfield.d.ts +146 -0
- package/dist/core-manager/remote-bitfield.d.ts.map +1 -0
- package/dist/core-ownership.d.ts +112 -0
- package/dist/core-ownership.d.ts.map +1 -0
- package/dist/datastore/index.d.ts +91 -0
- package/dist/datastore/index.d.ts.map +1 -0
- package/dist/datatype/index.d.ts +108 -0
- package/dist/discovery/local-discovery.d.ts +64 -0
- package/dist/discovery/local-discovery.d.ts.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fastify-controller.d.ts +27 -0
- package/dist/fastify-controller.d.ts.map +1 -0
- package/dist/fastify-plugins/blobs.d.ts +6 -0
- package/dist/fastify-plugins/blobs.d.ts.map +1 -0
- package/dist/fastify-plugins/constants.d.ts +3 -0
- package/dist/fastify-plugins/constants.d.ts.map +1 -0
- package/dist/fastify-plugins/icons.d.ts +6 -0
- package/dist/fastify-plugins/icons.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/index.d.ts +11 -0
- package/dist/fastify-plugins/maps/index.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts +12 -0
- package/dist/fastify-plugins/maps/offline-fallback-map.d.ts.map +1 -0
- package/dist/fastify-plugins/maps/static-maps.d.ts +11 -0
- package/dist/fastify-plugins/maps/static-maps.d.ts.map +1 -0
- package/dist/fastify-plugins/utils.d.ts +23 -0
- package/dist/fastify-plugins/utils.d.ts.map +1 -0
- package/dist/generated/extensions.d.ts +44 -0
- package/dist/generated/extensions.d.ts.map +1 -0
- package/dist/generated/keys.d.ts +36 -0
- package/dist/generated/keys.d.ts.map +1 -0
- package/dist/generated/rpc.d.ts +87 -0
- package/dist/generated/rpc.d.ts.map +1 -0
- package/dist/icon-api.d.ts +109 -0
- package/dist/icon-api.d.ts.map +1 -0
- package/dist/index-writer/index.d.ts +51 -0
- package/dist/index-writer/index.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/invite-api.d.ts +70 -0
- package/dist/invite-api.d.ts.map +1 -0
- package/dist/lib/hashmap.d.ts +62 -0
- package/dist/lib/hashmap.d.ts.map +1 -0
- package/dist/lib/hypercore-helpers.d.ts +6 -0
- package/dist/lib/hypercore-helpers.d.ts.map +1 -0
- package/dist/lib/noise-secret-stream-helpers.d.ts +45 -0
- package/dist/lib/noise-secret-stream-helpers.d.ts.map +1 -0
- package/dist/lib/ponyfills.d.ts +10 -0
- package/dist/lib/ponyfills.d.ts.map +1 -0
- package/dist/lib/string.d.ts +2 -0
- package/dist/lib/string.d.ts.map +1 -0
- package/dist/lib/timing-safe-equal.d.ts +15 -0
- package/dist/lib/timing-safe-equal.d.ts.map +1 -0
- package/dist/local-peers.d.ts +151 -0
- package/dist/local-peers.d.ts.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/mapeo-manager.d.ts +178 -0
- package/dist/mapeo-manager.d.ts.map +1 -0
- package/dist/mapeo-project.d.ts +3233 -0
- package/dist/mapeo-project.d.ts.map +1 -0
- package/dist/member-api.d.ts +114 -0
- package/dist/member-api.d.ts.map +1 -0
- package/dist/roles.d.ts +157 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/schema/client.d.ts +284 -0
- package/dist/schema/client.d.ts.map +1 -0
- package/dist/schema/project.d.ts +1812 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/schema-to-drizzle.d.ts +20 -0
- package/dist/schema/schema-to-drizzle.d.ts.map +1 -0
- package/dist/schema/types.d.ts +98 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/utils.d.ts +55 -0
- package/dist/schema/utils.d.ts.map +1 -0
- package/dist/sync/core-sync-state.d.ts +252 -0
- package/dist/sync/core-sync-state.d.ts.map +1 -0
- package/dist/sync/namespace-sync-state.d.ts +47 -0
- package/dist/sync/namespace-sync-state.d.ts.map +1 -0
- package/dist/sync/peer-sync-controller.d.ts +44 -0
- package/dist/sync/peer-sync-controller.d.ts.map +1 -0
- package/dist/sync/sync-api.d.ts +158 -0
- package/dist/sync/sync-api.d.ts.map +1 -0
- package/dist/sync/sync-state.d.ts +40 -0
- package/dist/sync/sync-state.d.ts.map +1 -0
- package/dist/translation-api.d.ts +288 -0
- package/dist/translation-api.d.ts.map +1 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +115 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils_types.d.ts +14 -0
- package/drizzle/client/0000_bumpy_carnage.sql +33 -0
- package/drizzle/client/meta/0000_snapshot.json +199 -0
- package/drizzle/client/meta/_journal.json +13 -0
- package/drizzle/project/0000_spooky_lady_ursula.sql +192 -0
- package/drizzle/project/meta/0000_snapshot.json +1137 -0
- package/drizzle/project/meta/_journal.json +13 -0
- package/package.json +202 -0
- package/src/blob-api.js +139 -0
- package/src/blob-store/index.js +325 -0
- package/src/blob-store/live-download.js +373 -0
- package/src/config-import.js +604 -0
- package/src/constants.js +34 -0
- package/src/core-manager/bitfield-rle.js +235 -0
- package/src/core-manager/core-index.js +87 -0
- package/src/core-manager/index.js +504 -0
- package/src/core-manager/random-access-file-pool.js +30 -0
- package/src/core-manager/remote-bitfield.js +416 -0
- package/src/core-ownership.js +235 -0
- package/src/datastore/README.md +46 -0
- package/src/datastore/index.js +234 -0
- package/src/datatype/README.md +33 -0
- package/src/datatype/index.d.ts +108 -0
- package/src/datatype/index.js +358 -0
- package/src/discovery/local-discovery.js +303 -0
- package/src/errors.js +5 -0
- package/src/fastify-controller.js +84 -0
- package/src/fastify-plugins/blobs.js +139 -0
- package/src/fastify-plugins/constants.js +5 -0
- package/src/fastify-plugins/icons.js +158 -0
- package/src/fastify-plugins/maps/index.js +173 -0
- package/src/fastify-plugins/maps/offline-fallback-map.js +114 -0
- package/src/fastify-plugins/maps/static-maps.js +271 -0
- package/src/fastify-plugins/utils.js +52 -0
- package/src/generated/README.md +3 -0
- package/src/generated/extensions.d.ts +44 -0
- package/src/generated/extensions.js +196 -0
- package/src/generated/extensions.ts +237 -0
- package/src/generated/keys.d.ts +36 -0
- package/src/generated/keys.js +148 -0
- package/src/generated/keys.ts +185 -0
- package/src/generated/rpc.d.ts +87 -0
- package/src/generated/rpc.js +389 -0
- package/src/generated/rpc.ts +463 -0
- package/src/icon-api.js +282 -0
- package/src/index-writer/README.md +38 -0
- package/src/index-writer/index.js +124 -0
- package/src/index.js +16 -0
- package/src/invite-api.js +450 -0
- package/src/lib/hashmap.js +91 -0
- package/src/lib/hypercore-helpers.js +18 -0
- package/src/lib/noise-secret-stream-helpers.js +37 -0
- package/src/lib/ponyfills.js +25 -0
- package/src/lib/string.js +7 -0
- package/src/lib/timing-safe-equal.js +34 -0
- package/src/local-peers.js +737 -0
- package/src/logger.js +99 -0
- package/src/mapeo-manager.js +914 -0
- package/src/mapeo-project.js +980 -0
- package/src/member-api.js +319 -0
- package/src/roles.js +412 -0
- package/src/schema/client.js +55 -0
- package/src/schema/project.js +44 -0
- package/src/schema/schema-to-drizzle.js +118 -0
- package/src/schema/types.ts +153 -0
- package/src/schema/utils.js +51 -0
- package/src/sync/core-sync-state.js +440 -0
- package/src/sync/namespace-sync-state.js +193 -0
- package/src/sync/peer-sync-controller.js +332 -0
- package/src/sync/sync-api.js +588 -0
- package/src/sync/sync-state.js +63 -0
- package/src/translation-api.js +141 -0
- package/src/types.ts +149 -0
- package/src/utils.js +210 -0
- package/src/utils_types.d.ts +14 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// Thanks to:
|
|
2
|
+
// https://github.com/holepunchto/hypercore/blob/c572826/lib/remote-bitfield.js
|
|
3
|
+
// With added JSDoc types
|
|
4
|
+
|
|
5
|
+
import BigSparseArray from 'big-sparse-array'
|
|
6
|
+
import quickbit from 'quickbit-universal'
|
|
7
|
+
|
|
8
|
+
export const BITS_PER_PAGE = 32768
|
|
9
|
+
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
|
|
10
|
+
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
|
|
11
|
+
const BITS_PER_SEGMENT = 2097152
|
|
12
|
+
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
|
|
13
|
+
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
|
|
14
|
+
|
|
15
|
+
class RemoteBitfieldPage {
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @param {number} index
|
|
19
|
+
* @param {Uint32Array} bitfield
|
|
20
|
+
* @param {RemoteBitfieldSegment} segment
|
|
21
|
+
*/
|
|
22
|
+
constructor(index, bitfield, segment) {
|
|
23
|
+
/** @type {typeof index} */
|
|
24
|
+
this.index = index
|
|
25
|
+
/** @type {number} */
|
|
26
|
+
this.offset = index * BYTES_PER_PAGE - segment.offset
|
|
27
|
+
/** @type {typeof bitfield} */
|
|
28
|
+
this.bitfield = bitfield
|
|
29
|
+
/** @type {typeof segment} */
|
|
30
|
+
this.segment = segment
|
|
31
|
+
|
|
32
|
+
segment.add(this)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get tree() {
|
|
36
|
+
return this.segment.tree
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {number} index
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
get(index) {
|
|
45
|
+
return quickbit.get(this.bitfield, index)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {number} index
|
|
51
|
+
* @param {boolean} val
|
|
52
|
+
*/
|
|
53
|
+
set(index, val) {
|
|
54
|
+
if (quickbit.set(this.bitfield, index, val)) {
|
|
55
|
+
this.tree.update(this.offset * 8 + index)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param {number} start
|
|
62
|
+
* @param {number} length
|
|
63
|
+
* @param {boolean} val
|
|
64
|
+
*/
|
|
65
|
+
setRange(start, length, val) {
|
|
66
|
+
quickbit.fill(this.bitfield, val, start, start + length)
|
|
67
|
+
|
|
68
|
+
let i = Math.floor(start / 128)
|
|
69
|
+
const n = i + Math.ceil(length / 128)
|
|
70
|
+
|
|
71
|
+
while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @param {boolean} val
|
|
76
|
+
* @param {number} position
|
|
77
|
+
*/
|
|
78
|
+
findFirst(val, position) {
|
|
79
|
+
return quickbit.findFirst(this.bitfield, val, position)
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param {boolean} val
|
|
83
|
+
* @param {number} position
|
|
84
|
+
*/
|
|
85
|
+
findLast(val, position) {
|
|
86
|
+
return quickbit.findLast(this.bitfield, val, position)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param {number} start
|
|
92
|
+
* @param {Uint32Array} bitfield
|
|
93
|
+
*/
|
|
94
|
+
insert(start, bitfield) {
|
|
95
|
+
this.bitfield.set(bitfield, start / 32)
|
|
96
|
+
this.segment.refresh()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class RemoteBitfieldSegment {
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param {number} index
|
|
104
|
+
*/
|
|
105
|
+
constructor(index) {
|
|
106
|
+
this.index = index
|
|
107
|
+
this.offset = index * BYTES_PER_SEGMENT
|
|
108
|
+
this.tree = /** @type {import('quickbit-universal').SparseIndex} */ (
|
|
109
|
+
quickbit.Index.from([], BYTES_PER_SEGMENT)
|
|
110
|
+
)
|
|
111
|
+
this.pages = new Array(PAGES_PER_SEGMENT)
|
|
112
|
+
this.pagesLength = 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get chunks() {
|
|
116
|
+
return this.tree.chunks
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
refresh() {
|
|
120
|
+
this.tree = /** @type {import('quickbit-universal').SparseIndex} */ (
|
|
121
|
+
quickbit.Index.from(this.tree.chunks, BYTES_PER_SEGMENT)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {RemoteBitfieldPage} page
|
|
127
|
+
*/
|
|
128
|
+
add(page) {
|
|
129
|
+
const pageIndex = page.index - this.index * PAGES_PER_SEGMENT
|
|
130
|
+
if (pageIndex >= this.pagesLength) this.pagesLength = pageIndex + 1
|
|
131
|
+
|
|
132
|
+
this.pages[pageIndex] = page
|
|
133
|
+
|
|
134
|
+
const chunk = { field: page.bitfield, offset: page.offset }
|
|
135
|
+
|
|
136
|
+
this.chunks.push(chunk)
|
|
137
|
+
|
|
138
|
+
for (let i = this.chunks.length - 2; i >= 0; i--) {
|
|
139
|
+
const prev = this.chunks[i]
|
|
140
|
+
if (prev.offset <= chunk.offset) break
|
|
141
|
+
this.chunks[i] = chunk
|
|
142
|
+
this.chunks[i + 1] = prev
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
* @param {boolean} val
|
|
149
|
+
* @param {number} position
|
|
150
|
+
*/
|
|
151
|
+
findFirst(val, position) {
|
|
152
|
+
position = this.tree.skipFirst(!val, position)
|
|
153
|
+
|
|
154
|
+
let j = position & (BITS_PER_PAGE - 1)
|
|
155
|
+
let i = (position - j) / BITS_PER_PAGE
|
|
156
|
+
|
|
157
|
+
if (i >= PAGES_PER_SEGMENT) return -1
|
|
158
|
+
|
|
159
|
+
while (i < this.pagesLength) {
|
|
160
|
+
const p = this.pages[i]
|
|
161
|
+
|
|
162
|
+
let index = -1
|
|
163
|
+
|
|
164
|
+
if (p) index = p.findFirst(val, j)
|
|
165
|
+
else if (!val) index = j
|
|
166
|
+
|
|
167
|
+
if (index !== -1) return i * BITS_PER_PAGE + index
|
|
168
|
+
|
|
169
|
+
j = 0
|
|
170
|
+
i++
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return -1
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {boolean} val
|
|
178
|
+
* @param {number} position
|
|
179
|
+
*/
|
|
180
|
+
findLast(val, position) {
|
|
181
|
+
position = this.tree.skipLast(!val, position)
|
|
182
|
+
|
|
183
|
+
let j = position & (BITS_PER_PAGE - 1)
|
|
184
|
+
let i = (position - j) / BITS_PER_PAGE
|
|
185
|
+
|
|
186
|
+
if (i >= PAGES_PER_SEGMENT) return -1
|
|
187
|
+
|
|
188
|
+
while (i >= 0) {
|
|
189
|
+
const p = this.pages[i]
|
|
190
|
+
|
|
191
|
+
let index = -1
|
|
192
|
+
|
|
193
|
+
if (p) index = p.findLast(val, j)
|
|
194
|
+
else if (!val) index = j
|
|
195
|
+
|
|
196
|
+
if (index !== -1) return i * BITS_PER_PAGE + index
|
|
197
|
+
|
|
198
|
+
j = BITS_PER_PAGE - 1
|
|
199
|
+
i--
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return -1
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default class RemoteBitfield {
|
|
207
|
+
constructor() {
|
|
208
|
+
/** @type {BigSparseArray<RemoteBitfieldPage>} */
|
|
209
|
+
this._pages = new BigSparseArray()
|
|
210
|
+
/** @type {BigSparseArray<RemoteBitfieldSegment>} */
|
|
211
|
+
this._segments = new BigSparseArray()
|
|
212
|
+
this._maxSegments = 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {number} index
|
|
217
|
+
*/
|
|
218
|
+
get(index) {
|
|
219
|
+
const j = index & (BITS_PER_PAGE - 1)
|
|
220
|
+
const i = (index - j) / BITS_PER_PAGE
|
|
221
|
+
|
|
222
|
+
const p = this._pages.get(i)
|
|
223
|
+
|
|
224
|
+
return p ? p.get(j) : false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @param {number} index
|
|
229
|
+
*/
|
|
230
|
+
getBitfield(index) {
|
|
231
|
+
const j = index & (BITS_PER_PAGE - 1)
|
|
232
|
+
const i = (index - j) / BITS_PER_PAGE
|
|
233
|
+
|
|
234
|
+
const p = this._pages.get(i)
|
|
235
|
+
return p || null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {number} index
|
|
240
|
+
* @param {boolean} val
|
|
241
|
+
*/
|
|
242
|
+
set(index, val) {
|
|
243
|
+
const j = index & (BITS_PER_PAGE - 1)
|
|
244
|
+
const i = (index - j) / BITS_PER_PAGE
|
|
245
|
+
|
|
246
|
+
let p = this._pages.get(i)
|
|
247
|
+
|
|
248
|
+
if (!p && val) {
|
|
249
|
+
const k = Math.floor(i / PAGES_PER_SEGMENT)
|
|
250
|
+
const s =
|
|
251
|
+
this._segments.get(k) ||
|
|
252
|
+
this._segments.set(k, new RemoteBitfieldSegment(k))
|
|
253
|
+
if (this._maxSegments <= k) this._maxSegments = k + 1
|
|
254
|
+
|
|
255
|
+
p = this._pages.set(
|
|
256
|
+
i,
|
|
257
|
+
new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s)
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (p) p.set(j, val)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {number} start
|
|
266
|
+
* @param {number} length
|
|
267
|
+
* @param {boolean} val
|
|
268
|
+
*/
|
|
269
|
+
setRange(start, length, val) {
|
|
270
|
+
let j = start & (BITS_PER_PAGE - 1)
|
|
271
|
+
let i = (start - j) / BITS_PER_PAGE
|
|
272
|
+
|
|
273
|
+
while (length > 0) {
|
|
274
|
+
let p = this._pages.get(i)
|
|
275
|
+
|
|
276
|
+
if (!p && val) {
|
|
277
|
+
const k = Math.floor(i / PAGES_PER_SEGMENT)
|
|
278
|
+
const s =
|
|
279
|
+
this._segments.get(k) ||
|
|
280
|
+
this._segments.set(k, new RemoteBitfieldSegment(k))
|
|
281
|
+
if (this._maxSegments <= k) this._maxSegments = k + 1
|
|
282
|
+
|
|
283
|
+
p = this._pages.set(
|
|
284
|
+
i,
|
|
285
|
+
new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s)
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const end = Math.min(j + length, BITS_PER_PAGE)
|
|
290
|
+
const range = end - j
|
|
291
|
+
|
|
292
|
+
if (p) p.setRange(j, range, val)
|
|
293
|
+
|
|
294
|
+
j = 0
|
|
295
|
+
i++
|
|
296
|
+
length -= range
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {boolean} val
|
|
302
|
+
* @param {number} position
|
|
303
|
+
*/
|
|
304
|
+
findFirst(val, position) {
|
|
305
|
+
let j = position & (BITS_PER_SEGMENT - 1)
|
|
306
|
+
let i = (position - j) / BITS_PER_SEGMENT
|
|
307
|
+
|
|
308
|
+
while (i < this._maxSegments) {
|
|
309
|
+
const s = this._segments.get(i)
|
|
310
|
+
|
|
311
|
+
let index = -1
|
|
312
|
+
|
|
313
|
+
if (s) index = s.findFirst(val, j)
|
|
314
|
+
else if (!val) index = j
|
|
315
|
+
|
|
316
|
+
if (index !== -1) return i * BITS_PER_SEGMENT + index
|
|
317
|
+
|
|
318
|
+
j = 0
|
|
319
|
+
i++
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return val ? -1 : position
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* @param {number} position
|
|
326
|
+
*/
|
|
327
|
+
firstSet(position) {
|
|
328
|
+
return this.findFirst(true, position)
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* @param {number} position
|
|
332
|
+
*/
|
|
333
|
+
firstUnset(position) {
|
|
334
|
+
return this.findFirst(false, position)
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @param {boolean} val
|
|
338
|
+
* @param {number} position
|
|
339
|
+
*/
|
|
340
|
+
findLast(val, position) {
|
|
341
|
+
let j = position & (BITS_PER_SEGMENT - 1)
|
|
342
|
+
let i = (position - j) / BITS_PER_SEGMENT
|
|
343
|
+
|
|
344
|
+
while (i >= 0) {
|
|
345
|
+
const s = this._segments.get(i)
|
|
346
|
+
|
|
347
|
+
let index = -1
|
|
348
|
+
|
|
349
|
+
if (s) index = s.findLast(val, j)
|
|
350
|
+
else if (!val) index = j
|
|
351
|
+
|
|
352
|
+
if (index !== -1) return i * BITS_PER_SEGMENT + index
|
|
353
|
+
|
|
354
|
+
j = BITS_PER_SEGMENT - 1
|
|
355
|
+
i--
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return -1
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @param {number} position
|
|
363
|
+
*/
|
|
364
|
+
lastSet(position) {
|
|
365
|
+
return this.findLast(true, position)
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* @param {number} position
|
|
369
|
+
*/
|
|
370
|
+
lastUnset(position) {
|
|
371
|
+
return this.findLast(false, position)
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* @param {number} start
|
|
375
|
+
* @param {Uint32Array} bitfield
|
|
376
|
+
* @returns {boolean}
|
|
377
|
+
*/
|
|
378
|
+
insert(start, bitfield) {
|
|
379
|
+
if (start % 32 !== 0) return false
|
|
380
|
+
|
|
381
|
+
let length = bitfield.byteLength * 8
|
|
382
|
+
|
|
383
|
+
let j = start & (BITS_PER_PAGE - 1)
|
|
384
|
+
let i = (start - j) / BITS_PER_PAGE
|
|
385
|
+
|
|
386
|
+
while (length > 0) {
|
|
387
|
+
let p = this._pages.get(i)
|
|
388
|
+
|
|
389
|
+
if (!p) {
|
|
390
|
+
const k = Math.floor(i / PAGES_PER_SEGMENT)
|
|
391
|
+
const s =
|
|
392
|
+
this._segments.get(k) ||
|
|
393
|
+
this._segments.set(k, new RemoteBitfieldSegment(k))
|
|
394
|
+
if (this._maxSegments <= k) this._maxSegments = k + 1
|
|
395
|
+
|
|
396
|
+
p = this._pages.set(
|
|
397
|
+
i,
|
|
398
|
+
new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s)
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const end = Math.min(j + length, BITS_PER_PAGE)
|
|
403
|
+
const range = end - j
|
|
404
|
+
|
|
405
|
+
p.insert(j, bitfield.subarray(0, range / 32))
|
|
406
|
+
|
|
407
|
+
bitfield = bitfield.subarray(range / 32)
|
|
408
|
+
|
|
409
|
+
j = 0
|
|
410
|
+
i++
|
|
411
|
+
length -= range
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return true
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { verifySignature, sign } from '@mapeo/crypto'
|
|
2
|
+
import { parseVersionId } from '@comapeo/schema'
|
|
3
|
+
import { defaultGetWinner } from '@mapeo/sqlite-indexer'
|
|
4
|
+
import assert from 'node:assert/strict'
|
|
5
|
+
import sodium from 'sodium-universal'
|
|
6
|
+
import {
|
|
7
|
+
kTable,
|
|
8
|
+
kSelect,
|
|
9
|
+
kCreateWithDocId,
|
|
10
|
+
kDataStore,
|
|
11
|
+
} from './datatype/index.js'
|
|
12
|
+
import { eq, or } from 'drizzle-orm'
|
|
13
|
+
import mapObject from 'map-obj'
|
|
14
|
+
import { discoveryKey } from 'hypercore-crypto'
|
|
15
|
+
import pDefer from 'p-defer'
|
|
16
|
+
import { NAMESPACES } from './constants.js'
|
|
17
|
+
import { TypedEmitter } from 'tiny-typed-emitter'
|
|
18
|
+
/**
|
|
19
|
+
* @import {
|
|
20
|
+
* CoreOwnershipWithSignatures,
|
|
21
|
+
* CoreOwnershipWithSignaturesValue,
|
|
22
|
+
* KeyPair,
|
|
23
|
+
* Namespace
|
|
24
|
+
* } from './types.js'
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} CoreOwnershipEvents
|
|
29
|
+
* @property {(docIds: Set<string>) => void} update Emitted when new coreOwnership records are indexed
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @extends {TypedEmitter<CoreOwnershipEvents>}
|
|
34
|
+
*/
|
|
35
|
+
export class CoreOwnership extends TypedEmitter {
|
|
36
|
+
#dataType
|
|
37
|
+
#ownershipWriteDone
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {import('./datatype/index.js').DataType<
|
|
42
|
+
* import('./datastore/index.js').DataStore<'auth'>,
|
|
43
|
+
* typeof import('./schema/project.js').coreOwnershipTable,
|
|
44
|
+
* 'coreOwnership',
|
|
45
|
+
* import('@comapeo/schema').CoreOwnership,
|
|
46
|
+
* import('@comapeo/schema').CoreOwnershipValue
|
|
47
|
+
* >} opts.dataType
|
|
48
|
+
* @param {Record<Namespace, KeyPair>} opts.coreKeypairs
|
|
49
|
+
* @param {KeyPair} opts.identityKeypair
|
|
50
|
+
*/
|
|
51
|
+
constructor({ dataType, coreKeypairs, identityKeypair }) {
|
|
52
|
+
super()
|
|
53
|
+
this.#dataType = dataType
|
|
54
|
+
const authWriterCore = dataType[kDataStore].writerCore
|
|
55
|
+
const deferred = pDefer()
|
|
56
|
+
this.#ownershipWriteDone = deferred.promise
|
|
57
|
+
|
|
58
|
+
const writeOwnership = () => {
|
|
59
|
+
if (authWriterCore.length > 0) {
|
|
60
|
+
deferred.resolve()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
this.#writeOwnership(identityKeypair, coreKeypairs)
|
|
64
|
+
.then(deferred.resolve)
|
|
65
|
+
.catch(deferred.reject)
|
|
66
|
+
}
|
|
67
|
+
// @ts-ignore - opened missing from types
|
|
68
|
+
if (authWriterCore.opened) {
|
|
69
|
+
writeOwnership()
|
|
70
|
+
} else {
|
|
71
|
+
authWriterCore.once('ready', writeOwnership)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
dataType[kDataStore].on('coreOwnership', this.emit.bind(this, 'update'))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} coreId
|
|
79
|
+
* @returns {Promise<string>} deviceId of device that owns the core
|
|
80
|
+
*/
|
|
81
|
+
async getOwner(coreId) {
|
|
82
|
+
await this.#ownershipWriteDone
|
|
83
|
+
const table = this.#dataType[kTable]
|
|
84
|
+
const expressions = []
|
|
85
|
+
for (const namespace of NAMESPACES) {
|
|
86
|
+
expressions.push(eq(table[`${namespace}CoreId`], coreId))
|
|
87
|
+
}
|
|
88
|
+
// prettier-ignore
|
|
89
|
+
const result = (await this.#dataType[kSelect]())
|
|
90
|
+
.where(or.apply(null, expressions))
|
|
91
|
+
.get()
|
|
92
|
+
if (!result) {
|
|
93
|
+
throw new Error('NotFound')
|
|
94
|
+
}
|
|
95
|
+
return result.docId
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
*
|
|
100
|
+
* @param {string} deviceId
|
|
101
|
+
* @param {Namespace} namespace
|
|
102
|
+
* @returns {Promise<string>} coreId of core belonging to `deviceId` for `namespace`
|
|
103
|
+
*/
|
|
104
|
+
async getCoreId(deviceId, namespace) {
|
|
105
|
+
const result = await this.get(deviceId)
|
|
106
|
+
return result[`${namespace}CoreId`]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get capabilities for a given deviceId
|
|
111
|
+
*
|
|
112
|
+
* @param {string} deviceId
|
|
113
|
+
*/
|
|
114
|
+
async get(deviceId) {
|
|
115
|
+
await this.#ownershipWriteDone
|
|
116
|
+
return this.#dataType.getByDocId(deviceId)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async getAll() {
|
|
120
|
+
await this.#ownershipWriteDone
|
|
121
|
+
return this.#dataType.getMany()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
* @param {KeyPair} identityKeypair
|
|
127
|
+
* @param {Record<Namespace, KeyPair>} coreKeypairs
|
|
128
|
+
*/
|
|
129
|
+
async #writeOwnership(identityKeypair, coreKeypairs) {
|
|
130
|
+
/** @type {CoreOwnershipWithSignaturesValue} */
|
|
131
|
+
const docValue = {
|
|
132
|
+
schemaName: 'coreOwnership',
|
|
133
|
+
...mapObject(coreKeypairs, (key, value) => {
|
|
134
|
+
return [`${key}CoreId`, value.publicKey.toString('hex')]
|
|
135
|
+
}),
|
|
136
|
+
identitySignature: sign(
|
|
137
|
+
identityKeypair.publicKey,
|
|
138
|
+
identityKeypair.secretKey
|
|
139
|
+
),
|
|
140
|
+
coreSignatures: mapObject(coreKeypairs, (key, value) => {
|
|
141
|
+
return [key, sign(value.publicKey, value.secretKey)]
|
|
142
|
+
}),
|
|
143
|
+
}
|
|
144
|
+
const docId = identityKeypair.publicKey.toString('hex')
|
|
145
|
+
await this.#dataType[kCreateWithDocId](docId, docValue)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* - Validate that the doc is written to the core identified by doc.authCoreId
|
|
151
|
+
* - Verify the signatures
|
|
152
|
+
* - Remove the signatures (we don't add them to the indexer)
|
|
153
|
+
* - Set doc.links to an empty array - this forces the indexer to treat every
|
|
154
|
+
* document as a fork, so getWinner is called for every doc, which resolves to
|
|
155
|
+
* the doc with the lowest index (e.g. the first)
|
|
156
|
+
*
|
|
157
|
+
* @param {CoreOwnershipWithSignatures} doc
|
|
158
|
+
* @param {import('@comapeo/schema').VersionIdObject} version
|
|
159
|
+
* @returns {import('@comapeo/schema').CoreOwnership}
|
|
160
|
+
*/
|
|
161
|
+
export function mapAndValidateCoreOwnership(doc, { coreDiscoveryKey }) {
|
|
162
|
+
if (
|
|
163
|
+
!coreDiscoveryKey.equals(discoveryKey(Buffer.from(doc.authCoreId, 'hex')))
|
|
164
|
+
) {
|
|
165
|
+
throw new Error('Invalid coreOwnership record: mismatched authCoreId')
|
|
166
|
+
}
|
|
167
|
+
if (!verifyCoreOwnership(doc)) {
|
|
168
|
+
throw new Error('Invalid coreOwnership record: signatures are invalid')
|
|
169
|
+
}
|
|
170
|
+
// eslint-disable-next-line no-unused-vars
|
|
171
|
+
const { identitySignature, coreSignatures, ...docWithoutSignatures } = doc
|
|
172
|
+
docWithoutSignatures.links = []
|
|
173
|
+
return docWithoutSignatures
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Verify the signatures of a coreOwnership record, which verify that the device
|
|
178
|
+
* with the identityKey matching the docIds does own (e.g. can write to) cores
|
|
179
|
+
* with the given core IDs
|
|
180
|
+
*
|
|
181
|
+
* @param {CoreOwnershipWithSignatures} doc
|
|
182
|
+
* @returns {boolean}
|
|
183
|
+
*/
|
|
184
|
+
function verifyCoreOwnership(doc) {
|
|
185
|
+
const { coreSignatures, identitySignature } = doc
|
|
186
|
+
for (const namespace of NAMESPACES) {
|
|
187
|
+
const signature = coreSignatures[namespace]
|
|
188
|
+
const coreKey = Buffer.from(doc[`${namespace}CoreId`], 'hex')
|
|
189
|
+
assert.equal(
|
|
190
|
+
signature.length,
|
|
191
|
+
sodium.crypto_sign_BYTES,
|
|
192
|
+
'Invalid core ownership signature'
|
|
193
|
+
)
|
|
194
|
+
assert.equal(
|
|
195
|
+
coreKey.length,
|
|
196
|
+
sodium.crypto_sign_PUBLICKEYBYTES,
|
|
197
|
+
'Invalid core ownership coreId'
|
|
198
|
+
)
|
|
199
|
+
const isValidSignature = verifySignature(coreKey, signature, coreKey)
|
|
200
|
+
if (!isValidSignature) return false
|
|
201
|
+
}
|
|
202
|
+
const identityPublicKey = Buffer.from(doc.docId, 'hex')
|
|
203
|
+
assert.equal(identitySignature.length, sodium.crypto_sign_BYTES)
|
|
204
|
+
assert.equal(identityPublicKey.length, sodium.crypto_sign_PUBLICKEYBYTES)
|
|
205
|
+
const isValidIdentitySignature = verifySignature(
|
|
206
|
+
identityPublicKey,
|
|
207
|
+
identitySignature,
|
|
208
|
+
identityPublicKey
|
|
209
|
+
)
|
|
210
|
+
if (!isValidIdentitySignature) return false
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* For coreOwnership records, we only trust the first record written to the core.
|
|
216
|
+
*
|
|
217
|
+
* @type {NonNullable<ConstructorParameters<typeof import('./index-writer/index.js').IndexWriter>[0]['getWinner']>}
|
|
218
|
+
*/
|
|
219
|
+
export function getWinner(docA, docB) {
|
|
220
|
+
if (
|
|
221
|
+
'schemaName' in docA &&
|
|
222
|
+
docA.schemaName === 'coreOwnership' &&
|
|
223
|
+
'schemaName' in docB &&
|
|
224
|
+
docB.schemaName === 'coreOwnership'
|
|
225
|
+
) {
|
|
226
|
+
// Assumes docA and docB have same coreKey, so we choose the first one
|
|
227
|
+
// written to the core
|
|
228
|
+
const docAindex = parseVersionId(docA.versionId).index
|
|
229
|
+
const docBindex = parseVersionId(docB.versionId).index
|
|
230
|
+
if (docAindex < docBindex) return docA
|
|
231
|
+
return docB
|
|
232
|
+
} else {
|
|
233
|
+
return defaultGetWinner(docA, docB)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# DataStore
|
|
2
|
+
|
|
3
|
+
> Manage reading cores for indexing, and reading and writing documents to cores.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The `DataStore` class is an API over a CoreManager namespace, responsible for reading blocks for indexing from all cores in a namespace; writing new documents to the namespace writer core, and reading existing documents from any core in the namespace based on the `versionId`. `DataStore` does not write documents to an index, it only reads them for indexing - it will call the `batch()` constructor option with entries that are read from cores in the namespace that the datastore manages. Writes will only resolve once `batch()` resolves (e.g. once a document has been written to the SQLite index tables).
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
The `DataStore` class is used internally by the [`DataType`](../datatype/) class.
|
|
12
|
+
|
|
13
|
+
An example of `DataStore` usage taken from the [datastore tests](../../tests/datastore.js):
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const datastore = new DataStore({
|
|
17
|
+
coreManager,
|
|
18
|
+
batch: async (entries) => {
|
|
19
|
+
// Process entries here using an indexer...
|
|
20
|
+
},
|
|
21
|
+
namespace: 'data',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
/** @type {MapeoDoc} */
|
|
25
|
+
const newObservation = await datastore.write(observationValue)
|
|
26
|
+
/** @type {MapeoDoc} */
|
|
27
|
+
const existingObservation = await datastore.read(versionId)
|
|
28
|
+
|
|
29
|
+
datastore.on('index-state', ({ current, remaining, entriesPerSecond }) => {
|
|
30
|
+
if (current === 'idle') {
|
|
31
|
+
// indexing done for now
|
|
32
|
+
} else if (current === 'indexing') {
|
|
33
|
+
// show state to user that indexing is happening
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const { current, remaining, entriesPerSecond } = datastore.getIndexState()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API docs
|
|
41
|
+
|
|
42
|
+
TODO!
|
|
43
|
+
|
|
44
|
+
## Tests
|
|
45
|
+
|
|
46
|
+
Tests for this module are in [tests/datastore.js](../../tests/datastore.js)
|