@peerbit/native-backbone 0.1.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 +202 -0
- package/README.md +27 -0
- package/dist/src/benchmark.d.ts +19 -0
- package/dist/src/benchmark.d.ts.map +1 -0
- package/dist/src/benchmark.js +12 -0
- package/dist/src/benchmark.js.map +1 -0
- package/dist/src/index.d.ts +1561 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3406 -0
- package/dist/src/index.js.map +1 -0
- package/dist/wasm/README.md +27 -0
- package/dist/wasm/native_backbone.d.ts +1609 -0
- package/dist/wasm/native_backbone.js +10221 -0
- package/dist/wasm/native_backbone_bg.wasm +0 -0
- package/dist/wasm/native_backbone_bg.wasm.d.ts +625 -0
- package/package.json +64 -0
- package/src/append_tx/committed_latest.rs +2063 -0
- package/src/append_tx/committed_no_next.rs +1165 -0
- package/src/append_tx/facts.rs +791 -0
- package/src/append_tx/mod.rs +751 -0
- package/src/append_tx/storage.rs +599 -0
- package/src/benchmark.ts +58 -0
- package/src/coordinates.rs +422 -0
- package/src/documents.rs +1698 -0
- package/src/graph_blocks.rs +362 -0
- package/src/index.ts +9134 -0
- package/src/js_interop.rs +448 -0
- package/src/lib.rs +144 -0
- package/src/profile.rs +164 -0
- package/src/raw_receive.rs +1724 -0
- package/src/shared_log_plan.rs +1464 -0
- package/src/sync_send.rs +180 -0
- package/src/wire_sync.rs +727 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
use js_sys::{Array, Reflect, Uint8Array};
|
|
2
|
+
use peerbit_indexer_core::wire::{self, WireError};
|
|
3
|
+
use std::collections::HashSet;
|
|
4
|
+
use wasm_bindgen::prelude::*;
|
|
5
|
+
use wasm_bindgen::JsCast;
|
|
6
|
+
|
|
7
|
+
/// Drops only the first `byte_len` bytes / `record_count` records so records
|
|
8
|
+
/// appended while a flush was awaiting its disk write are kept for the next
|
|
9
|
+
/// flush instead of being discarded.
|
|
10
|
+
pub(crate) fn clear_journal_prefix(
|
|
11
|
+
journal: &mut Vec<u8>,
|
|
12
|
+
journal_record_count: &mut usize,
|
|
13
|
+
byte_len: usize,
|
|
14
|
+
record_count: usize,
|
|
15
|
+
) {
|
|
16
|
+
if byte_len >= journal.len() {
|
|
17
|
+
journal.clear();
|
|
18
|
+
*journal_record_count = 0;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
journal.drain(..byte_len);
|
|
22
|
+
*journal_record_count = journal_record_count.saturating_sub(record_count);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub(crate) fn array_from_value(value: JsValue, label: &str) -> Result<Array, JsValue> {
|
|
26
|
+
value
|
|
27
|
+
.dyn_into::<Array>()
|
|
28
|
+
.map_err(|_| JsValue::from_str(&format!("Expected {label} array")))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub(crate) fn string_field(row: &Array, index: u32, label: &str) -> Result<String, JsValue> {
|
|
32
|
+
row.get(index)
|
|
33
|
+
.as_string()
|
|
34
|
+
.ok_or_else(|| JsValue::from_str(&format!("Expected {label} string")))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(crate) fn stringish_field(row: &Array, index: u32, label: &str) -> Result<String, JsValue> {
|
|
38
|
+
let value = row.get(index);
|
|
39
|
+
if let Some(value) = value.as_string() {
|
|
40
|
+
return Ok(value);
|
|
41
|
+
}
|
|
42
|
+
if let Some(value) = value.as_f64() {
|
|
43
|
+
return Ok((value as u64).to_string());
|
|
44
|
+
}
|
|
45
|
+
Err(JsValue::from_str(&format!("Expected {label} string")))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub(crate) fn bool_field(row: &Array, index: u32, label: &str) -> Result<bool, JsValue> {
|
|
49
|
+
row.get(index)
|
|
50
|
+
.as_bool()
|
|
51
|
+
.ok_or_else(|| JsValue::from_str(&format!("Expected {label} boolean")))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub(crate) fn usize_field(row: &Array, index: u32, label: &str) -> Result<usize, JsValue> {
|
|
55
|
+
row.get(index)
|
|
56
|
+
.as_f64()
|
|
57
|
+
.map(|value| value as usize)
|
|
58
|
+
.ok_or_else(|| JsValue::from_str(&format!("Expected {label} number")))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(crate) fn bytes_field(row: &Array, index: u32, label: &str) -> Result<Vec<u8>, JsValue> {
|
|
62
|
+
let value = row.get(index);
|
|
63
|
+
if value.is_undefined() || value.is_null() {
|
|
64
|
+
return Err(JsValue::from_str(&format!("Expected {label} bytes")));
|
|
65
|
+
}
|
|
66
|
+
Ok(Uint8Array::new(&value).to_vec())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub(crate) fn trim_hashes_vec(trim_rows: &Array) -> Result<Vec<String>, JsValue> {
|
|
70
|
+
let mut hashes = Vec::with_capacity(trim_rows.length() as usize);
|
|
71
|
+
for index in 0..trim_rows.length() {
|
|
72
|
+
let row = array_from_value(trim_rows.get(index), "trim row")?;
|
|
73
|
+
hashes.push(string_field(&row, 0, "trim hash")?);
|
|
74
|
+
}
|
|
75
|
+
Ok(hashes)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub(crate) fn numbers_to_rows(resolution: &str, values: &[u64]) -> Array {
|
|
79
|
+
let out = Array::new();
|
|
80
|
+
for value in values {
|
|
81
|
+
out.push(&number_to_row(resolution, *value));
|
|
82
|
+
}
|
|
83
|
+
out
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub(crate) fn number_to_row(resolution: &str, value: u64) -> JsValue {
|
|
87
|
+
match resolution {
|
|
88
|
+
"u64" => JsValue::from_str(&value.to_string()),
|
|
89
|
+
_ => JsValue::from_f64(value as f64),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub(crate) fn strings_to_array(values: Vec<String>) -> Array {
|
|
94
|
+
let out = Array::new();
|
|
95
|
+
for value in values {
|
|
96
|
+
out.push(&JsValue::from_str(&value));
|
|
97
|
+
}
|
|
98
|
+
out
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub(crate) fn has_duplicate_strings(values: &[String]) -> bool {
|
|
102
|
+
let mut seen = HashSet::with_capacity(values.len());
|
|
103
|
+
values.iter().any(|value| !seen.insert(value))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub(crate) fn strings_slice_to_array(values: &[String]) -> Array {
|
|
107
|
+
let out = Array::new();
|
|
108
|
+
for value in values {
|
|
109
|
+
out.push(&JsValue::from_str(value));
|
|
110
|
+
}
|
|
111
|
+
out
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub(crate) fn strings_from_array(values: Array) -> Result<Vec<String>, JsValue> {
|
|
115
|
+
let mut out = Vec::with_capacity(values.length() as usize);
|
|
116
|
+
for index in 0..values.length() {
|
|
117
|
+
out.push(
|
|
118
|
+
values
|
|
119
|
+
.get(index)
|
|
120
|
+
.as_string()
|
|
121
|
+
.ok_or_else(|| JsValue::from_str("Expected string array"))?,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
Ok(out)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pub(crate) fn bytes_vec_from_array(values: Array) -> Result<Vec<Vec<u8>>, JsValue> {
|
|
128
|
+
let mut out = Vec::with_capacity(values.length() as usize);
|
|
129
|
+
for index in 0..values.length() {
|
|
130
|
+
let value = values.get(index);
|
|
131
|
+
if value.is_undefined() || value.is_null() {
|
|
132
|
+
return Err(JsValue::from_str("Expected bytes array"));
|
|
133
|
+
}
|
|
134
|
+
out.push(Uint8Array::new(&value).to_vec());
|
|
135
|
+
}
|
|
136
|
+
Ok(out)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub(crate) fn required_bytes_from_array(
|
|
140
|
+
values: &Array,
|
|
141
|
+
index: u32,
|
|
142
|
+
field: &str,
|
|
143
|
+
) -> Result<Uint8Array, JsValue> {
|
|
144
|
+
let value = values.get(index);
|
|
145
|
+
if value.is_undefined() || value.is_null() {
|
|
146
|
+
return Err(JsValue::from_str(&format!("Expected {field} bytes")));
|
|
147
|
+
}
|
|
148
|
+
Ok(Uint8Array::new(&value))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub(crate) fn string_batches_from_array(
|
|
152
|
+
values: Array,
|
|
153
|
+
label: &str,
|
|
154
|
+
) -> Result<Vec<Vec<String>>, JsValue> {
|
|
155
|
+
let mut out = Vec::with_capacity(values.length() as usize);
|
|
156
|
+
for index in 0..values.length() {
|
|
157
|
+
let value = values.get(index);
|
|
158
|
+
if !Array::is_array(&value) {
|
|
159
|
+
return Err(JsValue::from_str(&format!("Expected {label}")));
|
|
160
|
+
}
|
|
161
|
+
out.push(strings_from_array(Array::from(&value))?);
|
|
162
|
+
}
|
|
163
|
+
Ok(out)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pub(crate) fn usize_values_from_array(values: Array) -> Result<Vec<usize>, JsValue> {
|
|
167
|
+
let mut out = Vec::with_capacity(values.length() as usize);
|
|
168
|
+
for index in 0..values.length() {
|
|
169
|
+
let value = values
|
|
170
|
+
.get(index)
|
|
171
|
+
.as_f64()
|
|
172
|
+
.ok_or_else(|| JsValue::from_str("Expected unsigned integer array"))?;
|
|
173
|
+
if !value.is_finite() || value < 0.0 || value.fract() != 0.0 {
|
|
174
|
+
return Err(JsValue::from_str("Expected unsigned integer array"));
|
|
175
|
+
}
|
|
176
|
+
out.push(value as usize);
|
|
177
|
+
}
|
|
178
|
+
Ok(out)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pub(crate) fn ensure_same_len(left: usize, right: usize, label: &str) -> Result<(), JsValue> {
|
|
182
|
+
if left == right {
|
|
183
|
+
Ok(())
|
|
184
|
+
} else {
|
|
185
|
+
Err(JsValue::from_str(&format!(
|
|
186
|
+
"Mismatched {label} input lengths"
|
|
187
|
+
)))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub(crate) fn optional_bytes_from_js(value: JsValue) -> Option<Vec<u8>> {
|
|
192
|
+
if value.is_undefined() || value.is_null() {
|
|
193
|
+
return None;
|
|
194
|
+
}
|
|
195
|
+
Some(Uint8Array::new(&value).to_vec())
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pub(crate) fn optional_usize_from_js(
|
|
199
|
+
value: JsValue,
|
|
200
|
+
label: &str,
|
|
201
|
+
) -> Result<Option<usize>, JsValue> {
|
|
202
|
+
if value.is_undefined() || value.is_null() {
|
|
203
|
+
return Ok(None);
|
|
204
|
+
}
|
|
205
|
+
value
|
|
206
|
+
.as_f64()
|
|
207
|
+
.map(|value| Some(value as usize))
|
|
208
|
+
.ok_or_else(|| JsValue::from_str(&format!("{label} must be a number")))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub(crate) fn number_strings_to_array(values: &[u64]) -> Array {
|
|
212
|
+
let out = Array::new();
|
|
213
|
+
for value in values {
|
|
214
|
+
out.push(&JsValue::from_str(&value.to_string()));
|
|
215
|
+
}
|
|
216
|
+
out
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
pub(crate) fn parse_u64_string(value: &str, label: &str) -> Result<u64, JsValue> {
|
|
220
|
+
value
|
|
221
|
+
.parse::<u64>()
|
|
222
|
+
.map_err(|_| JsValue::from_str(&format!("Expected {label} u64 string")))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
pub(crate) fn parse_optional_u64_string(value: &str, label: &str) -> Result<Option<u64>, JsValue> {
|
|
226
|
+
if value.is_empty() {
|
|
227
|
+
Ok(None)
|
|
228
|
+
} else {
|
|
229
|
+
parse_u64_string(value, label).map(Some)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const JOURNAL_PUT_OPERATION: u8 = 1;
|
|
234
|
+
const JOURNAL_DELETE_OPERATION: u8 = 2;
|
|
235
|
+
|
|
236
|
+
/// Writes the same wire format as
|
|
237
|
+
/// `peerbit_indexer_core::persistence::encode_journal_put_record`, but
|
|
238
|
+
/// directly into `out` — the journal push runs once per append transaction,
|
|
239
|
+
/// so the intermediate payload/record allocations are avoided on purpose
|
|
240
|
+
/// (see the `journal record encoding matches indexer core` parity test).
|
|
241
|
+
pub(crate) fn append_journal_put_record(out: &mut Vec<u8>, key: &str, value: &[u8]) {
|
|
242
|
+
let payload_len = 1usize + 4 + key.len() + 4 + value.len();
|
|
243
|
+
let key_len = (key.len() as u32).to_le_bytes();
|
|
244
|
+
let value_len = (value.len() as u32).to_le_bytes();
|
|
245
|
+
let checksum = fnv1a_parts([
|
|
246
|
+
&[JOURNAL_PUT_OPERATION][..],
|
|
247
|
+
key_len.as_slice(),
|
|
248
|
+
key.as_bytes(),
|
|
249
|
+
value_len.as_slice(),
|
|
250
|
+
value,
|
|
251
|
+
]);
|
|
252
|
+
out.reserve(8 + payload_len);
|
|
253
|
+
out.extend_from_slice(&(payload_len as u32).to_le_bytes());
|
|
254
|
+
out.extend_from_slice(&checksum.to_le_bytes());
|
|
255
|
+
out.push(JOURNAL_PUT_OPERATION);
|
|
256
|
+
out.extend_from_slice(&key_len);
|
|
257
|
+
out.extend_from_slice(key.as_bytes());
|
|
258
|
+
out.extend_from_slice(&value_len);
|
|
259
|
+
out.extend_from_slice(value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Allocation-free equivalent of
|
|
263
|
+
/// `peerbit_indexer_core::persistence::encode_journal_delete_record`.
|
|
264
|
+
pub(crate) fn append_journal_delete_record(out: &mut Vec<u8>, key: &str) {
|
|
265
|
+
let payload_len = 1usize + 4 + key.len();
|
|
266
|
+
let key_len = (key.len() as u32).to_le_bytes();
|
|
267
|
+
let checksum = fnv1a_parts([
|
|
268
|
+
&[JOURNAL_DELETE_OPERATION][..],
|
|
269
|
+
key_len.as_slice(),
|
|
270
|
+
key.as_bytes(),
|
|
271
|
+
]);
|
|
272
|
+
out.reserve(8 + payload_len);
|
|
273
|
+
out.extend_from_slice(&(payload_len as u32).to_le_bytes());
|
|
274
|
+
out.extend_from_slice(&checksum.to_le_bytes());
|
|
275
|
+
out.push(JOURNAL_DELETE_OPERATION);
|
|
276
|
+
out.extend_from_slice(&key_len);
|
|
277
|
+
out.extend_from_slice(key.as_bytes());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// FNV-1a over concatenated parts; must stay identical to
|
|
281
|
+
/// `peerbit_indexer_core::persistence::fnv1a` over the joined bytes.
|
|
282
|
+
fn fnv1a_parts<const N: usize>(parts: [&[u8]; N]) -> u32 {
|
|
283
|
+
let mut hash = 0x811c9dc5u32;
|
|
284
|
+
for part in parts {
|
|
285
|
+
for byte in part {
|
|
286
|
+
hash ^= u32::from(*byte);
|
|
287
|
+
hash = hash.wrapping_mul(0x01000193);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
hash
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
pub(crate) fn write_string(out: &mut Vec<u8>, value: &str) {
|
|
294
|
+
out.extend_from_slice(&(value.len() as u32).to_le_bytes());
|
|
295
|
+
out.extend_from_slice(value.as_bytes());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
pub(crate) fn write_u64(out: &mut Vec<u8>, value: u64) {
|
|
299
|
+
out.extend_from_slice(&value.to_le_bytes());
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
pub(crate) fn write_u32(out: &mut Vec<u8>, value: u32) {
|
|
303
|
+
out.extend_from_slice(&value.to_le_bytes());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
pub(crate) fn write_bytes(out: &mut Vec<u8>, value: &[u8]) {
|
|
307
|
+
out.extend_from_slice(&(value.len() as u32).to_le_bytes());
|
|
308
|
+
out.extend_from_slice(value);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
pub(crate) fn wire_error_to_js(error: WireError) -> JsValue {
|
|
312
|
+
JsValue::from_str(&error.to_string())
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
pub(crate) fn read_u32(
|
|
316
|
+
bytes: &[u8],
|
|
317
|
+
offset: &mut usize,
|
|
318
|
+
label: &'static str,
|
|
319
|
+
) -> Result<u32, JsValue> {
|
|
320
|
+
wire::read_u32(bytes, offset, label).map_err(wire_error_to_js)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
pub(crate) fn read_u64(
|
|
324
|
+
bytes: &[u8],
|
|
325
|
+
offset: &mut usize,
|
|
326
|
+
label: &'static str,
|
|
327
|
+
) -> Result<u64, JsValue> {
|
|
328
|
+
wire::read_u64(bytes, offset, label).map_err(wire_error_to_js)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
pub(crate) fn read_encoded_string(
|
|
332
|
+
bytes: &[u8],
|
|
333
|
+
offset: &mut usize,
|
|
334
|
+
label: &'static str,
|
|
335
|
+
) -> Result<String, JsValue> {
|
|
336
|
+
wire::read_encoded_string(bytes, offset, label).map_err(wire_error_to_js)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
pub(crate) fn read_bytes(
|
|
340
|
+
bytes: &[u8],
|
|
341
|
+
offset: &mut usize,
|
|
342
|
+
label: &'static str,
|
|
343
|
+
) -> Result<Vec<u8>, JsValue> {
|
|
344
|
+
wire::read_bytes(bytes, offset, label).map_err(wire_error_to_js)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
pub(crate) fn js_get(value: &JsValue, key: &str) -> JsValue {
|
|
348
|
+
Reflect::get(value, &JsValue::from_str(key)).unwrap_or(JsValue::UNDEFINED)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
fn js_string(value: JsValue, field: &str) -> Result<String, JsValue> {
|
|
352
|
+
value
|
|
353
|
+
.as_string()
|
|
354
|
+
.ok_or_else(|| JsValue::from_str(&format!("Missing or invalid {field}")))
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
pub(crate) fn array_strings(value: JsValue, field: &str) -> Result<Vec<String>, JsValue> {
|
|
358
|
+
if !Array::is_array(&value) {
|
|
359
|
+
return Err(JsValue::from_str(&format!("{field} must be an array")));
|
|
360
|
+
}
|
|
361
|
+
let array = Array::from(&value);
|
|
362
|
+
let mut out = Vec::with_capacity(array.length() as usize);
|
|
363
|
+
for index in 0..array.length() {
|
|
364
|
+
out.push(js_string(array.get(index), field)?);
|
|
365
|
+
}
|
|
366
|
+
Ok(out)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
pub(crate) fn optional_string(value: JsValue) -> Option<String> {
|
|
370
|
+
if value.is_null() || value.is_undefined() {
|
|
371
|
+
None
|
|
372
|
+
} else {
|
|
373
|
+
value.as_string()
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
pub(crate) fn write_u8(out: &mut Vec<u8>, value: u8) {
|
|
378
|
+
out.push(value);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
pub(crate) fn write_bool(out: &mut Vec<u8>, value: bool) {
|
|
382
|
+
out.push(if value { 1 } else { 0 });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
pub(crate) fn js_error(error: impl std::fmt::Display) -> JsValue {
|
|
386
|
+
JsValue::from_str(&error.to_string())
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
pub(crate) fn decode_error(error: impl std::fmt::Display) -> JsValue {
|
|
390
|
+
JsValue::from_str(&error.to_string())
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
pub(crate) fn hash_number_u64(resolution: &str, digest: &[u8]) -> Result<u64, JsValue> {
|
|
394
|
+
match resolution {
|
|
395
|
+
"u32" => {
|
|
396
|
+
if digest.len() < 4 {
|
|
397
|
+
return Err(JsValue::from_str("hash digest must have at least 4 bytes"));
|
|
398
|
+
}
|
|
399
|
+
Ok(u32::from_le_bytes(digest[0..4].try_into().unwrap()) as u64)
|
|
400
|
+
}
|
|
401
|
+
"u64" => {
|
|
402
|
+
if digest.len() < 8 {
|
|
403
|
+
return Err(JsValue::from_str("hash digest must have at least 8 bytes"));
|
|
404
|
+
}
|
|
405
|
+
Ok(u64::from_le_bytes(digest[0..8].try_into().unwrap()))
|
|
406
|
+
}
|
|
407
|
+
_ => Err(JsValue::from_str("resolution must be u32 or u64")),
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
#[cfg(test)]
|
|
412
|
+
mod tests {
|
|
413
|
+
use super::{append_journal_delete_record, append_journal_put_record, hash_number_u64};
|
|
414
|
+
use peerbit_indexer_core::persistence::{
|
|
415
|
+
encode_journal_delete_record, encode_journal_put_record,
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
#[test]
|
|
419
|
+
fn decodes_hash_numbers_like_shared_log_integer_helpers() {
|
|
420
|
+
let bytes = [1, 0, 0, 0, 2, 0, 0, 0];
|
|
421
|
+
assert_eq!(hash_number_u64("u32", &bytes).unwrap(), 1);
|
|
422
|
+
assert_eq!(hash_number_u64("u64", &bytes).unwrap(), 8_589_934_593);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
#[test]
|
|
426
|
+
fn journal_record_encoding_matches_indexer_core() {
|
|
427
|
+
for (key, value) in [
|
|
428
|
+
("", &[][..]),
|
|
429
|
+
("k", &[0u8][..]),
|
|
430
|
+
("some-journal-key", &[1u8, 2, 3, 255, 0, 42][..]),
|
|
431
|
+
("zAbc123", &[7u8; 1200][..]),
|
|
432
|
+
] {
|
|
433
|
+
let mut direct = vec![9u8, 9, 9];
|
|
434
|
+
append_journal_put_record(&mut direct, key, value);
|
|
435
|
+
let mut expected = vec![9u8, 9, 9];
|
|
436
|
+
expected.extend_from_slice(&encode_journal_put_record(key, value));
|
|
437
|
+
assert_eq!(direct, expected, "put record for key {key:?}");
|
|
438
|
+
|
|
439
|
+
let mut direct = Vec::new();
|
|
440
|
+
append_journal_delete_record(&mut direct, key);
|
|
441
|
+
assert_eq!(
|
|
442
|
+
direct,
|
|
443
|
+
encode_journal_delete_record(key),
|
|
444
|
+
"delete record for key {key:?}"
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
use js_sys::{Array, Uint8Array};
|
|
2
|
+
use peerbit_indexer_core::planner::NativeQueryIndex;
|
|
3
|
+
use peerbit_indexer_core::schema::NativeSchemaIr;
|
|
4
|
+
use peerbit_indexer_core::storage::MemoryByteStorage;
|
|
5
|
+
use peerbit_log_rust::{NativeEntryV0PlainBuilder, NativeLogBlockStore, NativeLogIndex};
|
|
6
|
+
use peerbit_shared_log_rust::NativeSharedLogState;
|
|
7
|
+
use std::collections::{HashMap, HashSet};
|
|
8
|
+
use wasm_bindgen::prelude::*;
|
|
9
|
+
|
|
10
|
+
mod append_tx;
|
|
11
|
+
mod coordinates;
|
|
12
|
+
mod documents;
|
|
13
|
+
mod graph_blocks;
|
|
14
|
+
mod js_interop;
|
|
15
|
+
mod profile;
|
|
16
|
+
mod raw_receive;
|
|
17
|
+
mod shared_log_plan;
|
|
18
|
+
mod sync_send;
|
|
19
|
+
mod wire_sync;
|
|
20
|
+
|
|
21
|
+
use crate::documents::{DocumentContextFields, DocumentPreviousSignerFact, ParsedProjectionPlan};
|
|
22
|
+
use crate::profile::NativeBackboneAppendProfile;
|
|
23
|
+
use crate::raw_receive::PendingRawReceiveEntry;
|
|
24
|
+
|
|
25
|
+
// Native-backbone is optimized for document-store hot paths where large byte
|
|
26
|
+
// fields are payloads, not query keys. Standalone indexer-rust keeps exact large
|
|
27
|
+
// byte matching through the compatibility extractor.
|
|
28
|
+
const NATIVE_BACKBONE_BYTE_EXACT_INDEX_LIMIT: usize = 128;
|
|
29
|
+
|
|
30
|
+
#[wasm_bindgen]
|
|
31
|
+
pub struct NativePeerbitBackbone {
|
|
32
|
+
resolution: String,
|
|
33
|
+
log: NativeLogIndex,
|
|
34
|
+
blocks: NativeLogBlockStore,
|
|
35
|
+
shared_log: NativeSharedLogState,
|
|
36
|
+
coordinate_index: HashSet<String>,
|
|
37
|
+
coordinate_values: MemoryByteStorage,
|
|
38
|
+
coordinate_journal: Vec<u8>,
|
|
39
|
+
coordinate_journal_record_count: usize,
|
|
40
|
+
coordinate_journal_enabled: bool,
|
|
41
|
+
document_index: NativeQueryIndex,
|
|
42
|
+
document_values: MemoryByteStorage,
|
|
43
|
+
document_journal: Vec<u8>,
|
|
44
|
+
document_journal_record_count: usize,
|
|
45
|
+
document_journal_enabled: bool,
|
|
46
|
+
document_byte_element_index_limit: usize,
|
|
47
|
+
document_key_by_head: HashMap<String, String>,
|
|
48
|
+
document_previous_signer_by_key: HashMap<String, DocumentPreviousSignerFact>,
|
|
49
|
+
document_signer_journal: Vec<u8>,
|
|
50
|
+
document_signer_journal_record_count: usize,
|
|
51
|
+
document_signer_journal_enabled: bool,
|
|
52
|
+
document_schema_ir: Option<NativeSchemaIr>,
|
|
53
|
+
document_context_head_field: Option<u32>,
|
|
54
|
+
document_context_fields: Option<DocumentContextFields>,
|
|
55
|
+
document_projection_plans: Vec<ParsedProjectionPlan>,
|
|
56
|
+
local_public_key: Vec<u8>,
|
|
57
|
+
builder: NativeEntryV0PlainBuilder,
|
|
58
|
+
pending_raw_receive_entries: HashMap<String, PendingRawReceiveEntry>,
|
|
59
|
+
append_profile_enabled: bool,
|
|
60
|
+
append_profile: NativeBackboneAppendProfile,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[wasm_bindgen]
|
|
64
|
+
impl NativePeerbitBackbone {
|
|
65
|
+
#[wasm_bindgen(constructor)]
|
|
66
|
+
pub fn new(
|
|
67
|
+
resolution: String,
|
|
68
|
+
clock_id: Uint8Array,
|
|
69
|
+
private_key: Uint8Array,
|
|
70
|
+
public_key: Uint8Array,
|
|
71
|
+
) -> Result<Self, JsValue> {
|
|
72
|
+
if resolution != "u32" && resolution != "u64" {
|
|
73
|
+
return Err(JsValue::from_str("resolution must be u32 or u64"));
|
|
74
|
+
}
|
|
75
|
+
let local_public_key = public_key.to_vec();
|
|
76
|
+
Ok(Self {
|
|
77
|
+
resolution: resolution.clone(),
|
|
78
|
+
log: NativeLogIndex::new(),
|
|
79
|
+
blocks: NativeLogBlockStore::new(),
|
|
80
|
+
shared_log: NativeSharedLogState::new(resolution),
|
|
81
|
+
coordinate_index: HashSet::new(),
|
|
82
|
+
coordinate_values: MemoryByteStorage::new(),
|
|
83
|
+
coordinate_journal: Vec::new(),
|
|
84
|
+
coordinate_journal_record_count: 0,
|
|
85
|
+
coordinate_journal_enabled: false,
|
|
86
|
+
document_index: NativeQueryIndex::new(),
|
|
87
|
+
document_values: MemoryByteStorage::new(),
|
|
88
|
+
document_journal: Vec::new(),
|
|
89
|
+
document_journal_record_count: 0,
|
|
90
|
+
document_journal_enabled: false,
|
|
91
|
+
document_byte_element_index_limit: 0,
|
|
92
|
+
document_key_by_head: HashMap::new(),
|
|
93
|
+
document_previous_signer_by_key: HashMap::new(),
|
|
94
|
+
document_signer_journal: Vec::new(),
|
|
95
|
+
document_signer_journal_record_count: 0,
|
|
96
|
+
document_signer_journal_enabled: false,
|
|
97
|
+
document_schema_ir: None,
|
|
98
|
+
document_context_head_field: None,
|
|
99
|
+
document_context_fields: None,
|
|
100
|
+
document_projection_plans: Vec::new(),
|
|
101
|
+
local_public_key,
|
|
102
|
+
builder: NativeEntryV0PlainBuilder::new(clock_id, private_key, public_key)?,
|
|
103
|
+
pending_raw_receive_entries: HashMap::new(),
|
|
104
|
+
append_profile_enabled: false,
|
|
105
|
+
append_profile: NativeBackboneAppendProfile::default(),
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn set_append_profile_enabled(&mut self, enabled: bool) {
|
|
110
|
+
self.append_profile_enabled = enabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn reset_append_profile(&mut self) {
|
|
114
|
+
self.append_profile = NativeBackboneAppendProfile::default();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub fn append_profile(&self) -> Array {
|
|
118
|
+
self.append_profile.to_row()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub fn log_len(&self) -> usize {
|
|
122
|
+
self.log.len()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub fn block_len(&self) -> usize {
|
|
126
|
+
self.blocks.len()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub fn has_log_entry(&self, hash: &str) -> bool {
|
|
130
|
+
self.log.has(hash)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
pub fn has_block(&self, hash: &str) -> bool {
|
|
134
|
+
self.blocks.has(hash)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub fn clear(&mut self) {
|
|
138
|
+
self.log.clear();
|
|
139
|
+
self.blocks.clear();
|
|
140
|
+
self.shared_log.clear();
|
|
141
|
+
self.clear_coordinate_core();
|
|
142
|
+
self.clear_document_core();
|
|
143
|
+
}
|
|
144
|
+
}
|