@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
package/src/documents.rs
ADDED
|
@@ -0,0 +1,1698 @@
|
|
|
1
|
+
use js_sys::{Array, Uint8Array};
|
|
2
|
+
use peerbit_indexer_core::codec::{decode_query, decode_sort};
|
|
3
|
+
use peerbit_indexer_core::persistence::{
|
|
4
|
+
decode_journal, decode_key_value_snapshot, encode_key_value_snapshot, JournalRecord,
|
|
5
|
+
JOURNAL_MAGIC,
|
|
6
|
+
};
|
|
7
|
+
use peerbit_indexer_core::planner::{DocumentFields, FieldPath, FieldValue, SumResult};
|
|
8
|
+
use peerbit_indexer_core::schema::{
|
|
9
|
+
decode_native_schema_ir, extract_encoded_document_fields_from_parts_with_byte_limits,
|
|
10
|
+
};
|
|
11
|
+
use peerbit_indexer_core::storage::ByteStorage;
|
|
12
|
+
use peerbit_log_rust::entry_v0_signature_public_key_from_storage_bytes;
|
|
13
|
+
use std::collections::HashMap;
|
|
14
|
+
use wasm_bindgen::prelude::*;
|
|
15
|
+
|
|
16
|
+
use crate::js_interop::{
|
|
17
|
+
append_journal_delete_record, append_journal_put_record, array_strings, bytes_vec_from_array,
|
|
18
|
+
clear_journal_prefix, decode_error, ensure_same_len, js_error, js_get, optional_string,
|
|
19
|
+
parse_optional_u64_string, parse_u64_string, read_bytes, read_encoded_string, read_u32,
|
|
20
|
+
read_u64, strings_from_array, write_bool, write_bytes, write_string, write_u32, write_u64,
|
|
21
|
+
write_u8,
|
|
22
|
+
};
|
|
23
|
+
use crate::{NativePeerbitBackbone, NATIVE_BACKBONE_BYTE_EXACT_INDEX_LIMIT};
|
|
24
|
+
|
|
25
|
+
pub(crate) struct DocumentIndexAppendCommit {
|
|
26
|
+
pub(crate) key: String,
|
|
27
|
+
pub(crate) value_prefix: DocumentIndexValuePrefix,
|
|
28
|
+
pub(crate) existing_created: Option<u64>,
|
|
29
|
+
pub(crate) byte_element_index_limit: usize,
|
|
30
|
+
pub(crate) delete_trimmed_heads: bool,
|
|
31
|
+
pub(crate) previous_context: Option<DocumentContextFacts>,
|
|
32
|
+
pub(crate) known_existing: bool,
|
|
33
|
+
pub(crate) required_previous_signer_public_key: Option<Vec<u8>>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[derive(Clone, Copy)]
|
|
37
|
+
pub(crate) struct DocumentContextFields {
|
|
38
|
+
created: u32,
|
|
39
|
+
modified: u32,
|
|
40
|
+
head: u32,
|
|
41
|
+
gid: u32,
|
|
42
|
+
size: u32,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[derive(Clone)]
|
|
46
|
+
pub(crate) struct DocumentContextFacts {
|
|
47
|
+
pub(crate) created: u64,
|
|
48
|
+
pub(crate) modified: u64,
|
|
49
|
+
pub(crate) head: String,
|
|
50
|
+
pub(crate) gid: String,
|
|
51
|
+
pub(crate) size: u32,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[derive(Clone)]
|
|
55
|
+
pub(crate) struct DocumentPreviousSignerFact {
|
|
56
|
+
head: String,
|
|
57
|
+
public_key: Vec<u8>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub(crate) struct PreparedDocumentEncodedPartsPut {
|
|
61
|
+
pub(crate) key: String,
|
|
62
|
+
pub(crate) value_bytes: Vec<u8>,
|
|
63
|
+
pub(crate) fields: DocumentFields,
|
|
64
|
+
pub(crate) known_existing: bool,
|
|
65
|
+
pub(crate) new_head: Option<String>,
|
|
66
|
+
pub(crate) previous_head: Option<String>,
|
|
67
|
+
pub(crate) record_document_journal: bool,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub(crate) struct PreparedDocumentIndexAppendPut {
|
|
71
|
+
pub(crate) parts: PreparedDocumentEncodedPartsPut,
|
|
72
|
+
pub(crate) previous_signer_head: Option<String>,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub(crate) struct ParsedProjectionPlan {
|
|
76
|
+
document_variant_type: Option<String>,
|
|
77
|
+
document_variant_value: Option<String>,
|
|
78
|
+
output_variant_type: Option<String>,
|
|
79
|
+
output_variant_value: Option<String>,
|
|
80
|
+
document_field_names: Vec<String>,
|
|
81
|
+
document_field_types: Vec<String>,
|
|
82
|
+
output_field_types: Vec<String>,
|
|
83
|
+
source_kinds: Vec<String>,
|
|
84
|
+
source_values: Vec<String>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub(crate) enum DocumentIndexValuePrefix {
|
|
88
|
+
Bytes(Vec<u8>),
|
|
89
|
+
Projection {
|
|
90
|
+
encoded_document: Vec<u8>,
|
|
91
|
+
plan: DocumentIndexProjectionPlan,
|
|
92
|
+
signer: Option<Vec<u8>>,
|
|
93
|
+
},
|
|
94
|
+
PlainPutPayloadIdentity,
|
|
95
|
+
PlainPutPayloadProjection {
|
|
96
|
+
plan: DocumentIndexProjectionPlan,
|
|
97
|
+
signer: Option<Vec<u8>>,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub(crate) enum DocumentIndexProjectionPlan {
|
|
102
|
+
Inline(ParsedProjectionPlan),
|
|
103
|
+
Cached(usize),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub(crate) fn document_index_append_commit(
|
|
107
|
+
key: String,
|
|
108
|
+
value_prefix_bytes: Vec<u8>,
|
|
109
|
+
existing_created: String,
|
|
110
|
+
byte_element_index_limit: usize,
|
|
111
|
+
delete_trimmed_heads: bool,
|
|
112
|
+
projection_plan: JsValue,
|
|
113
|
+
projection_encoded_document: JsValue,
|
|
114
|
+
projection_signer: JsValue,
|
|
115
|
+
) -> Result<DocumentIndexAppendCommit, JsValue> {
|
|
116
|
+
let value_prefix = if projection_plan.is_null() || projection_plan.is_undefined() {
|
|
117
|
+
DocumentIndexValuePrefix::Bytes(value_prefix_bytes)
|
|
118
|
+
} else {
|
|
119
|
+
DocumentIndexValuePrefix::Projection {
|
|
120
|
+
encoded_document: Uint8Array::new(&projection_encoded_document).to_vec(),
|
|
121
|
+
plan: DocumentIndexProjectionPlan::Inline(parse_projection_plan(&projection_plan)?),
|
|
122
|
+
signer: if projection_signer.is_null() || projection_signer.is_undefined() {
|
|
123
|
+
None
|
|
124
|
+
} else {
|
|
125
|
+
Some(Uint8Array::new(&projection_signer).to_vec())
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
Ok(DocumentIndexAppendCommit {
|
|
130
|
+
key,
|
|
131
|
+
value_prefix,
|
|
132
|
+
existing_created: parse_optional_u64_string(
|
|
133
|
+
&existing_created,
|
|
134
|
+
"document existing created",
|
|
135
|
+
)?,
|
|
136
|
+
byte_element_index_limit,
|
|
137
|
+
delete_trimmed_heads,
|
|
138
|
+
previous_context: None,
|
|
139
|
+
known_existing: false,
|
|
140
|
+
required_previous_signer_public_key: None,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub(crate) fn document_index_cached_projection_append_commit(
|
|
145
|
+
key: String,
|
|
146
|
+
existing_created: String,
|
|
147
|
+
byte_element_index_limit: usize,
|
|
148
|
+
delete_trimmed_heads: bool,
|
|
149
|
+
projection_plan_id: u32,
|
|
150
|
+
projection_encoded_document: JsValue,
|
|
151
|
+
projection_signer: JsValue,
|
|
152
|
+
) -> Result<DocumentIndexAppendCommit, JsValue> {
|
|
153
|
+
Ok(DocumentIndexAppendCommit {
|
|
154
|
+
key,
|
|
155
|
+
value_prefix: DocumentIndexValuePrefix::Projection {
|
|
156
|
+
encoded_document: Uint8Array::new(&projection_encoded_document).to_vec(),
|
|
157
|
+
plan: DocumentIndexProjectionPlan::Cached(projection_plan_id as usize),
|
|
158
|
+
signer: if projection_signer.is_null() || projection_signer.is_undefined() {
|
|
159
|
+
None
|
|
160
|
+
} else {
|
|
161
|
+
Some(Uint8Array::new(&projection_signer).to_vec())
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
existing_created: parse_optional_u64_string(
|
|
165
|
+
&existing_created,
|
|
166
|
+
"document existing created",
|
|
167
|
+
)?,
|
|
168
|
+
byte_element_index_limit,
|
|
169
|
+
delete_trimmed_heads,
|
|
170
|
+
previous_context: None,
|
|
171
|
+
known_existing: false,
|
|
172
|
+
required_previous_signer_public_key: None,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pub(crate) fn document_index_plain_put_payload_append_commit(
|
|
177
|
+
key: String,
|
|
178
|
+
existing_created: String,
|
|
179
|
+
byte_element_index_limit: usize,
|
|
180
|
+
delete_trimmed_heads: bool,
|
|
181
|
+
) -> Result<DocumentIndexAppendCommit, JsValue> {
|
|
182
|
+
Ok(DocumentIndexAppendCommit {
|
|
183
|
+
key,
|
|
184
|
+
value_prefix: DocumentIndexValuePrefix::PlainPutPayloadIdentity,
|
|
185
|
+
existing_created: parse_optional_u64_string(
|
|
186
|
+
&existing_created,
|
|
187
|
+
"document existing created",
|
|
188
|
+
)?,
|
|
189
|
+
byte_element_index_limit,
|
|
190
|
+
delete_trimmed_heads,
|
|
191
|
+
previous_context: None,
|
|
192
|
+
known_existing: false,
|
|
193
|
+
required_previous_signer_public_key: None,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pub(crate) fn document_index_cached_projection_plain_put_payload_append_commit(
|
|
198
|
+
key: String,
|
|
199
|
+
existing_created: String,
|
|
200
|
+
byte_element_index_limit: usize,
|
|
201
|
+
delete_trimmed_heads: bool,
|
|
202
|
+
projection_plan_id: u32,
|
|
203
|
+
projection_signer: JsValue,
|
|
204
|
+
) -> Result<DocumentIndexAppendCommit, JsValue> {
|
|
205
|
+
Ok(DocumentIndexAppendCommit {
|
|
206
|
+
key,
|
|
207
|
+
value_prefix: DocumentIndexValuePrefix::PlainPutPayloadProjection {
|
|
208
|
+
plan: DocumentIndexProjectionPlan::Cached(projection_plan_id as usize),
|
|
209
|
+
signer: if projection_signer.is_null() || projection_signer.is_undefined() {
|
|
210
|
+
None
|
|
211
|
+
} else {
|
|
212
|
+
Some(Uint8Array::new(&projection_signer).to_vec())
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
existing_created: parse_optional_u64_string(
|
|
216
|
+
&existing_created,
|
|
217
|
+
"document existing created",
|
|
218
|
+
)?,
|
|
219
|
+
byte_element_index_limit,
|
|
220
|
+
delete_trimmed_heads,
|
|
221
|
+
previous_context: None,
|
|
222
|
+
known_existing: false,
|
|
223
|
+
required_previous_signer_public_key: None,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
pub(crate) fn plain_put_document_bytes_from_payload(payload_data: &[u8]) -> Result<&[u8], JsValue> {
|
|
228
|
+
const PLAIN_PUT_OPERATION_PREFIX_LENGTH: usize = 6;
|
|
229
|
+
if payload_data.len() < PLAIN_PUT_OPERATION_PREFIX_LENGTH {
|
|
230
|
+
return Err(JsValue::from_str("Plain put payload is too short"));
|
|
231
|
+
}
|
|
232
|
+
if payload_data[0] != 0 || payload_data[1] != 3 {
|
|
233
|
+
return Err(JsValue::from_str("Expected native plain put payload"));
|
|
234
|
+
}
|
|
235
|
+
let declared_len = u32::from_le_bytes([
|
|
236
|
+
payload_data[2],
|
|
237
|
+
payload_data[3],
|
|
238
|
+
payload_data[4],
|
|
239
|
+
payload_data[5],
|
|
240
|
+
]) as usize;
|
|
241
|
+
let document_bytes = &payload_data[PLAIN_PUT_OPERATION_PREFIX_LENGTH..];
|
|
242
|
+
if document_bytes.len() != declared_len {
|
|
243
|
+
return Err(JsValue::from_str("Plain put payload length mismatch"));
|
|
244
|
+
}
|
|
245
|
+
Ok(document_bytes)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fn document_entry_to_row(key: &str, value: &[u8]) -> Array {
|
|
249
|
+
let row = Array::new();
|
|
250
|
+
row.push(&JsValue::from_str(key));
|
|
251
|
+
row.push(&Uint8Array::from(value));
|
|
252
|
+
row
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pub(crate) fn document_context_facts_to_row(context: &DocumentContextFacts) -> Array {
|
|
256
|
+
let row = Array::new();
|
|
257
|
+
row.push(&JsValue::from_str(&context.created.to_string()));
|
|
258
|
+
row.push(&JsValue::from_str(&context.modified.to_string()));
|
|
259
|
+
row.push(&JsValue::from_str(&context.head));
|
|
260
|
+
row.push(&JsValue::from_str(&context.gid));
|
|
261
|
+
row.push(&JsValue::from_f64(context.size as f64));
|
|
262
|
+
row
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn document_u64_field(fields: &DocumentFields, field: u32) -> Option<u64> {
|
|
266
|
+
match fields.scalar_values(&FieldPath::Id(field))?.first()? {
|
|
267
|
+
FieldValue::U64(value) => Some(*value),
|
|
268
|
+
FieldValue::I64(value) if *value >= 0 => Some(*value as u64),
|
|
269
|
+
_ => None,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fn document_string_field(fields: &DocumentFields, field: u32) -> Option<String> {
|
|
274
|
+
match fields.scalar_values(&FieldPath::Id(field))?.first()? {
|
|
275
|
+
FieldValue::String(value) => Some(value.to_string()),
|
|
276
|
+
_ => None,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fn field_value_to_row(value: &FieldValue) -> JsValue {
|
|
281
|
+
let row = Array::new_with_length(2);
|
|
282
|
+
match value {
|
|
283
|
+
FieldValue::Bool(value) => {
|
|
284
|
+
row.set(0, JsValue::from_str("bool"));
|
|
285
|
+
row.set(1, JsValue::from_bool(*value));
|
|
286
|
+
}
|
|
287
|
+
FieldValue::I64(value) => {
|
|
288
|
+
row.set(0, JsValue::from_str("i64"));
|
|
289
|
+
row.set(1, JsValue::from_str(&value.to_string()));
|
|
290
|
+
}
|
|
291
|
+
FieldValue::U64(value) => {
|
|
292
|
+
row.set(0, JsValue::from_str("u64"));
|
|
293
|
+
row.set(1, JsValue::from_str(&value.to_string()));
|
|
294
|
+
}
|
|
295
|
+
FieldValue::String(value) => {
|
|
296
|
+
row.set(0, JsValue::from_str("string"));
|
|
297
|
+
row.set(1, JsValue::from_str(value));
|
|
298
|
+
}
|
|
299
|
+
FieldValue::Bytes(value) => {
|
|
300
|
+
row.set(0, JsValue::from_str("bytes"));
|
|
301
|
+
row.set(1, Uint8Array::from(value.as_ref()).into());
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
row.into()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fn sum_to_js(sum: SumResult) -> Array {
|
|
308
|
+
let out = Array::new();
|
|
309
|
+
match sum {
|
|
310
|
+
SumResult::None => {
|
|
311
|
+
out.push(&JsValue::from_str("none"));
|
|
312
|
+
out.push(&JsValue::from_str("0"));
|
|
313
|
+
}
|
|
314
|
+
SumResult::I64(value) => {
|
|
315
|
+
out.push(&JsValue::from_str("i64"));
|
|
316
|
+
out.push(&JsValue::from_str(&value.to_string()));
|
|
317
|
+
}
|
|
318
|
+
SumResult::U64(value) => {
|
|
319
|
+
out.push(&JsValue::from_str("u64"));
|
|
320
|
+
out.push(&JsValue::from_str(&value.to_string()));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
out
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fn encode_document_signer_fact(fact: &DocumentPreviousSignerFact) -> Vec<u8> {
|
|
327
|
+
let mut out = Vec::with_capacity(8 + fact.head.len() + fact.public_key.len());
|
|
328
|
+
write_string(&mut out, &fact.head);
|
|
329
|
+
write_bytes(&mut out, &fact.public_key);
|
|
330
|
+
out
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
fn decode_document_signer_fact(bytes: &[u8]) -> Result<DocumentPreviousSignerFact, JsValue> {
|
|
334
|
+
let mut offset = 0usize;
|
|
335
|
+
let head = read_encoded_string(bytes, &mut offset, "document signer head")?;
|
|
336
|
+
let public_key = read_bytes(bytes, &mut offset, "document signer public key")?;
|
|
337
|
+
if offset != bytes.len() {
|
|
338
|
+
return Err(JsValue::from_str("Trailing document signer fact bytes"));
|
|
339
|
+
}
|
|
340
|
+
Ok(DocumentPreviousSignerFact { head, public_key })
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
pub(crate) fn encode_document_context_suffix(
|
|
344
|
+
created: u64,
|
|
345
|
+
modified: u64,
|
|
346
|
+
head: &str,
|
|
347
|
+
gid: &str,
|
|
348
|
+
size: u32,
|
|
349
|
+
) -> Result<Vec<u8>, JsValue> {
|
|
350
|
+
let capacity = 1usize
|
|
351
|
+
.checked_add(8)
|
|
352
|
+
.and_then(|value| value.checked_add(8))
|
|
353
|
+
.and_then(|value| value.checked_add(4))
|
|
354
|
+
.and_then(|value| value.checked_add(head.len()))
|
|
355
|
+
.and_then(|value| value.checked_add(4))
|
|
356
|
+
.and_then(|value| value.checked_add(gid.len()))
|
|
357
|
+
.and_then(|value| value.checked_add(4))
|
|
358
|
+
.ok_or_else(|| JsValue::from_str("Document context suffix capacity overflow"))?;
|
|
359
|
+
let mut out = Vec::with_capacity(capacity);
|
|
360
|
+
// Context is @variant(0); keep this byte-for-byte aligned with Borsh.
|
|
361
|
+
out.push(0);
|
|
362
|
+
write_u64(&mut out, created);
|
|
363
|
+
write_u64(&mut out, modified);
|
|
364
|
+
write_string(&mut out, head);
|
|
365
|
+
write_string(&mut out, gid);
|
|
366
|
+
write_u32(&mut out, size);
|
|
367
|
+
Ok(out)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#[derive(Clone, Debug)]
|
|
371
|
+
enum ProjectionValue {
|
|
372
|
+
String(String),
|
|
373
|
+
U64(u64),
|
|
374
|
+
Bool(bool),
|
|
375
|
+
Bytes(Vec<u8>),
|
|
376
|
+
None,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
fn read_u8_projection(bytes: &[u8], offset: &mut usize, label: &str) -> Result<u8, JsValue> {
|
|
380
|
+
if *offset >= bytes.len() {
|
|
381
|
+
return Err(JsValue::from_str(&format!("Truncated {label}")));
|
|
382
|
+
}
|
|
383
|
+
let value = bytes[*offset];
|
|
384
|
+
*offset += 1;
|
|
385
|
+
Ok(value)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn read_bool_projection(bytes: &[u8], offset: &mut usize, label: &str) -> Result<bool, JsValue> {
|
|
389
|
+
match read_u8_projection(bytes, offset, label)? {
|
|
390
|
+
0 => Ok(false),
|
|
391
|
+
1 => Ok(true),
|
|
392
|
+
_ => Err(JsValue::from_str(&format!("Invalid bool {label}"))),
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fn skip_projection_value(bytes: &[u8], offset: &mut usize, kind: &str) -> Result<(), JsValue> {
|
|
397
|
+
match kind {
|
|
398
|
+
"string" => {
|
|
399
|
+
read_encoded_string(bytes, offset, "projected string")?;
|
|
400
|
+
}
|
|
401
|
+
"u8" => {
|
|
402
|
+
read_u8_projection(bytes, offset, "projected u8")?;
|
|
403
|
+
}
|
|
404
|
+
"u32" => {
|
|
405
|
+
read_u32(bytes, offset, "projected u32")?;
|
|
406
|
+
}
|
|
407
|
+
"u64" => {
|
|
408
|
+
read_u64(bytes, offset, "projected u64")?;
|
|
409
|
+
}
|
|
410
|
+
"bool" => {
|
|
411
|
+
read_bool_projection(bytes, offset, "projected bool")?;
|
|
412
|
+
}
|
|
413
|
+
"bytes" => {
|
|
414
|
+
read_bytes(bytes, offset, "projected bytes")?;
|
|
415
|
+
}
|
|
416
|
+
"option:string" | "option:u8" | "option:u32" | "option:u64" | "option:bool"
|
|
417
|
+
| "option:bytes" => {
|
|
418
|
+
let has_value = read_u8_projection(bytes, offset, "projected option")?;
|
|
419
|
+
if has_value == 1 {
|
|
420
|
+
skip_projection_value(bytes, offset, &kind["option:".len()..])?;
|
|
421
|
+
} else if has_value != 0 {
|
|
422
|
+
return Err(JsValue::from_str("Invalid projection option marker"));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
"vec:string" => {
|
|
426
|
+
let len = read_u32(bytes, offset, "projected string vec length")?;
|
|
427
|
+
for _ in 0..len {
|
|
428
|
+
read_encoded_string(bytes, offset, "projected string vec item")?;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
"vec:bytes" => {
|
|
432
|
+
let len = read_u32(bytes, offset, "projected bytes vec length")?;
|
|
433
|
+
for _ in 0..len {
|
|
434
|
+
read_bytes(bytes, offset, "projected bytes vec item")?;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
_ => {
|
|
438
|
+
return Err(JsValue::from_str(
|
|
439
|
+
"Unsupported document projection field type",
|
|
440
|
+
))
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
Ok(())
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
fn read_projection_value(
|
|
447
|
+
bytes: &[u8],
|
|
448
|
+
offset: &mut usize,
|
|
449
|
+
kind: &str,
|
|
450
|
+
) -> Result<ProjectionValue, JsValue> {
|
|
451
|
+
match kind {
|
|
452
|
+
"string" => Ok(ProjectionValue::String(read_encoded_string(
|
|
453
|
+
bytes,
|
|
454
|
+
offset,
|
|
455
|
+
"projection string",
|
|
456
|
+
)?)),
|
|
457
|
+
"u8" => Ok(ProjectionValue::U64(
|
|
458
|
+
read_u8_projection(bytes, offset, "projection u8")? as u64,
|
|
459
|
+
)),
|
|
460
|
+
"u32" => Ok(ProjectionValue::U64(
|
|
461
|
+
read_u32(bytes, offset, "projection u32")? as u64,
|
|
462
|
+
)),
|
|
463
|
+
"u64" => Ok(ProjectionValue::U64(read_u64(
|
|
464
|
+
bytes,
|
|
465
|
+
offset,
|
|
466
|
+
"projection u64",
|
|
467
|
+
)?)),
|
|
468
|
+
"bool" => Ok(ProjectionValue::Bool(read_bool_projection(
|
|
469
|
+
bytes,
|
|
470
|
+
offset,
|
|
471
|
+
"projection bool",
|
|
472
|
+
)?)),
|
|
473
|
+
"bytes" => Ok(ProjectionValue::Bytes(read_bytes(
|
|
474
|
+
bytes,
|
|
475
|
+
offset,
|
|
476
|
+
"projection bytes",
|
|
477
|
+
)?)),
|
|
478
|
+
"option:string" => {
|
|
479
|
+
let has_value = read_u8_projection(bytes, offset, "projection option string")?;
|
|
480
|
+
if has_value == 0 {
|
|
481
|
+
Ok(ProjectionValue::None)
|
|
482
|
+
} else if has_value == 1 {
|
|
483
|
+
Ok(ProjectionValue::String(read_encoded_string(
|
|
484
|
+
bytes,
|
|
485
|
+
offset,
|
|
486
|
+
"projection option string",
|
|
487
|
+
)?))
|
|
488
|
+
} else {
|
|
489
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
"option:u8" => {
|
|
493
|
+
let has_value = read_u8_projection(bytes, offset, "projection option u8")?;
|
|
494
|
+
if has_value == 0 {
|
|
495
|
+
Ok(ProjectionValue::None)
|
|
496
|
+
} else if has_value == 1 {
|
|
497
|
+
Ok(ProjectionValue::U64(
|
|
498
|
+
read_u8_projection(bytes, offset, "projection option u8")? as u64,
|
|
499
|
+
))
|
|
500
|
+
} else {
|
|
501
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
"option:u32" => {
|
|
505
|
+
let has_value = read_u8_projection(bytes, offset, "projection option u32")?;
|
|
506
|
+
if has_value == 0 {
|
|
507
|
+
Ok(ProjectionValue::None)
|
|
508
|
+
} else if has_value == 1 {
|
|
509
|
+
Ok(ProjectionValue::U64(
|
|
510
|
+
read_u32(bytes, offset, "projection option u32")? as u64,
|
|
511
|
+
))
|
|
512
|
+
} else {
|
|
513
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
"option:u64" => {
|
|
517
|
+
let has_value = read_u8_projection(bytes, offset, "projection option u64")?;
|
|
518
|
+
if has_value == 0 {
|
|
519
|
+
Ok(ProjectionValue::None)
|
|
520
|
+
} else if has_value == 1 {
|
|
521
|
+
Ok(ProjectionValue::U64(read_u64(
|
|
522
|
+
bytes,
|
|
523
|
+
offset,
|
|
524
|
+
"projection option u64",
|
|
525
|
+
)?))
|
|
526
|
+
} else {
|
|
527
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
"option:bool" => {
|
|
531
|
+
let has_value = read_u8_projection(bytes, offset, "projection option bool")?;
|
|
532
|
+
if has_value == 0 {
|
|
533
|
+
Ok(ProjectionValue::None)
|
|
534
|
+
} else if has_value == 1 {
|
|
535
|
+
Ok(ProjectionValue::Bool(read_bool_projection(
|
|
536
|
+
bytes,
|
|
537
|
+
offset,
|
|
538
|
+
"projection option bool",
|
|
539
|
+
)?))
|
|
540
|
+
} else {
|
|
541
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
"option:bytes" => {
|
|
545
|
+
let has_value = read_u8_projection(bytes, offset, "projection option bytes")?;
|
|
546
|
+
if has_value == 0 {
|
|
547
|
+
Ok(ProjectionValue::None)
|
|
548
|
+
} else if has_value == 1 {
|
|
549
|
+
Ok(ProjectionValue::Bytes(read_bytes(
|
|
550
|
+
bytes,
|
|
551
|
+
offset,
|
|
552
|
+
"projection option bytes",
|
|
553
|
+
)?))
|
|
554
|
+
} else {
|
|
555
|
+
Err(JsValue::from_str("Invalid projection option marker"))
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
_ => Err(JsValue::from_str(
|
|
559
|
+
"Unsupported projected document field type",
|
|
560
|
+
)),
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
fn write_projection_value(
|
|
565
|
+
out: &mut Vec<u8>,
|
|
566
|
+
kind: &str,
|
|
567
|
+
value: &ProjectionValue,
|
|
568
|
+
) -> Result<(), JsValue> {
|
|
569
|
+
match (kind, value) {
|
|
570
|
+
("string", ProjectionValue::String(value)) => write_string(out, value),
|
|
571
|
+
("u8", ProjectionValue::U64(value)) => write_u8(out, *value as u8),
|
|
572
|
+
("u32", ProjectionValue::U64(value)) => write_u32(out, *value as u32),
|
|
573
|
+
("u64", ProjectionValue::U64(value)) => write_u64(out, *value),
|
|
574
|
+
("bool", ProjectionValue::Bool(value)) => write_bool(out, *value),
|
|
575
|
+
("bytes", ProjectionValue::Bytes(value)) => write_bytes(out, value),
|
|
576
|
+
("option:string", ProjectionValue::None)
|
|
577
|
+
| ("option:u8", ProjectionValue::None)
|
|
578
|
+
| ("option:u32", ProjectionValue::None)
|
|
579
|
+
| ("option:u64", ProjectionValue::None)
|
|
580
|
+
| ("option:bool", ProjectionValue::None)
|
|
581
|
+
| ("option:bytes", ProjectionValue::None) => write_u8(out, 0),
|
|
582
|
+
("option:string", ProjectionValue::String(value)) => {
|
|
583
|
+
write_u8(out, 1);
|
|
584
|
+
write_string(out, value);
|
|
585
|
+
}
|
|
586
|
+
("option:u8", ProjectionValue::U64(value)) => {
|
|
587
|
+
write_u8(out, 1);
|
|
588
|
+
write_u8(out, *value as u8);
|
|
589
|
+
}
|
|
590
|
+
("option:u32", ProjectionValue::U64(value)) => {
|
|
591
|
+
write_u8(out, 1);
|
|
592
|
+
write_u32(out, *value as u32);
|
|
593
|
+
}
|
|
594
|
+
("option:u64", ProjectionValue::U64(value)) => {
|
|
595
|
+
write_u8(out, 1);
|
|
596
|
+
write_u64(out, *value);
|
|
597
|
+
}
|
|
598
|
+
("option:bool", ProjectionValue::Bool(value)) => {
|
|
599
|
+
write_u8(out, 1);
|
|
600
|
+
write_bool(out, *value);
|
|
601
|
+
}
|
|
602
|
+
("option:bytes", ProjectionValue::Bytes(value)) => {
|
|
603
|
+
write_u8(out, 1);
|
|
604
|
+
write_bytes(out, value);
|
|
605
|
+
}
|
|
606
|
+
_ => {
|
|
607
|
+
return Err(JsValue::from_str(
|
|
608
|
+
"Projection value does not match output type",
|
|
609
|
+
))
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
Ok(())
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fn read_projected_document_fields(
|
|
616
|
+
encoded_document: &[u8],
|
|
617
|
+
variant_type: Option<&str>,
|
|
618
|
+
variant_value: Option<&str>,
|
|
619
|
+
names: &[String],
|
|
620
|
+
types: &[String],
|
|
621
|
+
) -> Result<HashMap<String, ProjectionValue>, JsValue> {
|
|
622
|
+
if names.len() != types.len() {
|
|
623
|
+
return Err(JsValue::from_str(
|
|
624
|
+
"Document projection plan length mismatch",
|
|
625
|
+
));
|
|
626
|
+
}
|
|
627
|
+
let mut offset = 0usize;
|
|
628
|
+
match variant_type {
|
|
629
|
+
Some("u8") => {
|
|
630
|
+
let expected = variant_value
|
|
631
|
+
.ok_or_else(|| JsValue::from_str("Missing document variant"))?
|
|
632
|
+
.parse::<u8>()
|
|
633
|
+
.map_err(|_| JsValue::from_str("Invalid document variant"))?;
|
|
634
|
+
if read_u8_projection(encoded_document, &mut offset, "document variant")? != expected {
|
|
635
|
+
return Err(JsValue::from_str("Document variant mismatch"));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
Some("string") => {
|
|
639
|
+
let expected =
|
|
640
|
+
variant_value.ok_or_else(|| JsValue::from_str("Missing document variant"))?;
|
|
641
|
+
if read_encoded_string(encoded_document, &mut offset, "document variant")? != expected {
|
|
642
|
+
return Err(JsValue::from_str("Document variant mismatch"));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
Some("") | None => {}
|
|
646
|
+
_ => return Err(JsValue::from_str("Unsupported document variant type")),
|
|
647
|
+
}
|
|
648
|
+
let mut out = HashMap::with_capacity(names.len());
|
|
649
|
+
for (name, kind) in names.iter().zip(types.iter()) {
|
|
650
|
+
let before = offset;
|
|
651
|
+
let value = read_projection_value(encoded_document, &mut offset, kind);
|
|
652
|
+
match value {
|
|
653
|
+
Ok(value) => {
|
|
654
|
+
out.insert(name.clone(), value);
|
|
655
|
+
}
|
|
656
|
+
Err(_) => {
|
|
657
|
+
offset = before;
|
|
658
|
+
skip_projection_value(encoded_document, &mut offset, kind)?;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
Ok(out)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
fn write_projection_variant(
|
|
666
|
+
out: &mut Vec<u8>,
|
|
667
|
+
variant_type: Option<&str>,
|
|
668
|
+
variant_value: Option<&str>,
|
|
669
|
+
) -> Result<(), JsValue> {
|
|
670
|
+
match variant_type {
|
|
671
|
+
Some("u8") => {
|
|
672
|
+
let value = variant_value
|
|
673
|
+
.ok_or_else(|| JsValue::from_str("Missing output variant"))?
|
|
674
|
+
.parse::<u8>()
|
|
675
|
+
.map_err(|_| JsValue::from_str("Invalid output variant"))?;
|
|
676
|
+
write_u8(out, value);
|
|
677
|
+
}
|
|
678
|
+
Some("string") => {
|
|
679
|
+
let value = variant_value.ok_or_else(|| JsValue::from_str("Missing output variant"))?;
|
|
680
|
+
write_string(out, value);
|
|
681
|
+
}
|
|
682
|
+
Some("") | None => {}
|
|
683
|
+
_ => return Err(JsValue::from_str("Unsupported output variant type")),
|
|
684
|
+
}
|
|
685
|
+
Ok(())
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
fn parse_projection_plan(plan: &JsValue) -> Result<ParsedProjectionPlan, JsValue> {
|
|
689
|
+
let document_field_names =
|
|
690
|
+
array_strings(js_get(plan, "documentFieldNames"), "documentFieldNames")?;
|
|
691
|
+
let document_field_types =
|
|
692
|
+
array_strings(js_get(plan, "documentFieldTypes"), "documentFieldTypes")?;
|
|
693
|
+
let output_field_types = array_strings(js_get(plan, "outputFieldTypes"), "outputFieldTypes")?;
|
|
694
|
+
let source_kinds = array_strings(js_get(plan, "sourceKinds"), "sourceKinds")?;
|
|
695
|
+
let source_values = array_strings(js_get(plan, "sourceValues"), "sourceValues")?;
|
|
696
|
+
if output_field_types.len() != source_kinds.len() || source_kinds.len() != source_values.len() {
|
|
697
|
+
return Err(JsValue::from_str("Projection plan length mismatch"));
|
|
698
|
+
}
|
|
699
|
+
Ok(ParsedProjectionPlan {
|
|
700
|
+
document_variant_type: optional_string(js_get(plan, "documentVariantType")),
|
|
701
|
+
document_variant_value: optional_string(js_get(plan, "documentVariantValue")),
|
|
702
|
+
output_variant_type: optional_string(js_get(plan, "outputVariantType")),
|
|
703
|
+
output_variant_value: optional_string(js_get(plan, "outputVariantValue")),
|
|
704
|
+
document_field_names,
|
|
705
|
+
document_field_types,
|
|
706
|
+
output_field_types,
|
|
707
|
+
source_kinds,
|
|
708
|
+
source_values,
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
pub(crate) fn project_document_index_simple_bytes_with_plan(
|
|
713
|
+
encoded_document: &[u8],
|
|
714
|
+
plan: &ParsedProjectionPlan,
|
|
715
|
+
created: u64,
|
|
716
|
+
modified: u64,
|
|
717
|
+
head: &str,
|
|
718
|
+
gid: &str,
|
|
719
|
+
size: u32,
|
|
720
|
+
signer: Option<&[u8]>,
|
|
721
|
+
) -> Result<Vec<u8>, JsValue> {
|
|
722
|
+
let document_values = read_projected_document_fields(
|
|
723
|
+
encoded_document,
|
|
724
|
+
plan.document_variant_type.as_deref(),
|
|
725
|
+
plan.document_variant_value.as_deref(),
|
|
726
|
+
&plan.document_field_names,
|
|
727
|
+
&plan.document_field_types,
|
|
728
|
+
)?;
|
|
729
|
+
|
|
730
|
+
let mut out = Vec::new();
|
|
731
|
+
write_projection_variant(
|
|
732
|
+
&mut out,
|
|
733
|
+
plan.output_variant_type.as_deref(),
|
|
734
|
+
plan.output_variant_value.as_deref(),
|
|
735
|
+
)?;
|
|
736
|
+
|
|
737
|
+
for index in 0..plan.output_field_types.len() {
|
|
738
|
+
let value = match plan.source_kinds[index].as_str() {
|
|
739
|
+
"field" => document_values
|
|
740
|
+
.get(&plan.source_values[index])
|
|
741
|
+
.cloned()
|
|
742
|
+
.unwrap_or(ProjectionValue::None),
|
|
743
|
+
"context" => match plan.source_values[index].as_str() {
|
|
744
|
+
"created" => ProjectionValue::U64(created),
|
|
745
|
+
"modified" => ProjectionValue::U64(modified),
|
|
746
|
+
"head" => ProjectionValue::String(head.to_string()),
|
|
747
|
+
"gid" => ProjectionValue::String(gid.to_string()),
|
|
748
|
+
"size" => ProjectionValue::U64(size as u64),
|
|
749
|
+
_ => return Err(JsValue::from_str("Unsupported context projection source")),
|
|
750
|
+
},
|
|
751
|
+
"entryFirstSignerPublicKey" => signer
|
|
752
|
+
.map(|bytes| ProjectionValue::Bytes(bytes.to_vec()))
|
|
753
|
+
.unwrap_or(ProjectionValue::None),
|
|
754
|
+
_ => return Err(JsValue::from_str("Unsupported projection source kind")),
|
|
755
|
+
};
|
|
756
|
+
write_projection_value(&mut out, &plan.output_field_types[index], &value)?;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
Ok(out)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
fn project_document_index_simple_bytes(
|
|
763
|
+
encoded_document: &[u8],
|
|
764
|
+
plan: &JsValue,
|
|
765
|
+
created: &str,
|
|
766
|
+
modified: &str,
|
|
767
|
+
head: &str,
|
|
768
|
+
gid: &str,
|
|
769
|
+
size: u32,
|
|
770
|
+
signer: JsValue,
|
|
771
|
+
) -> Result<Vec<u8>, JsValue> {
|
|
772
|
+
let plan = parse_projection_plan(plan)?;
|
|
773
|
+
let created = parse_u64_string(created, "created")?;
|
|
774
|
+
let modified = parse_u64_string(modified, "modified")?;
|
|
775
|
+
let signer = if signer.is_null() || signer.is_undefined() {
|
|
776
|
+
None
|
|
777
|
+
} else {
|
|
778
|
+
Some(Uint8Array::new(&signer).to_vec())
|
|
779
|
+
};
|
|
780
|
+
project_document_index_simple_bytes_with_plan(
|
|
781
|
+
encoded_document,
|
|
782
|
+
&plan,
|
|
783
|
+
created,
|
|
784
|
+
modified,
|
|
785
|
+
head,
|
|
786
|
+
gid,
|
|
787
|
+
size,
|
|
788
|
+
signer.as_deref(),
|
|
789
|
+
)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
#[wasm_bindgen]
|
|
793
|
+
impl NativePeerbitBackbone {
|
|
794
|
+
pub fn configure_document_schema_ir(
|
|
795
|
+
&mut self,
|
|
796
|
+
schema_ir_bytes: Vec<u8>,
|
|
797
|
+
) -> Result<Array, JsValue> {
|
|
798
|
+
let schema_ir = decode_native_schema_ir(&schema_ir_bytes).map_err(js_error)?;
|
|
799
|
+
let stats = schema_ir.stats();
|
|
800
|
+
self.document_schema_ir = Some(schema_ir);
|
|
801
|
+
self.rebuild_document_index_from_values()?;
|
|
802
|
+
let out = Array::new();
|
|
803
|
+
out.push(&JsValue::from_f64(stats.root_fields as f64));
|
|
804
|
+
out.push(&JsValue::from_f64(stats.node_count as f64));
|
|
805
|
+
out.push(&JsValue::from_f64(stats.generic_nodes as f64));
|
|
806
|
+
Ok(out)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
pub fn set_document_byte_element_index_limit(&mut self, limit: usize) -> Result<(), JsValue> {
|
|
810
|
+
if self.document_byte_element_index_limit == limit {
|
|
811
|
+
return Ok(());
|
|
812
|
+
}
|
|
813
|
+
self.document_byte_element_index_limit = limit;
|
|
814
|
+
self.rebuild_document_index_from_values()
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
pub fn set_document_context_head_field(&mut self, field: u32) {
|
|
818
|
+
self.document_context_head_field = Some(field);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
pub fn set_document_context_fields(
|
|
822
|
+
&mut self,
|
|
823
|
+
created: u32,
|
|
824
|
+
modified: u32,
|
|
825
|
+
head: u32,
|
|
826
|
+
gid: u32,
|
|
827
|
+
size: u32,
|
|
828
|
+
) {
|
|
829
|
+
self.document_context_head_field = Some(head);
|
|
830
|
+
self.document_context_fields = Some(DocumentContextFields {
|
|
831
|
+
created,
|
|
832
|
+
modified,
|
|
833
|
+
head,
|
|
834
|
+
gid,
|
|
835
|
+
size,
|
|
836
|
+
});
|
|
837
|
+
self.rebuild_document_head_keys();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
pub fn register_document_projection_plan(&mut self, plan: JsValue) -> Result<u32, JsValue> {
|
|
841
|
+
let id = self.document_projection_plans.len();
|
|
842
|
+
if id > u32::MAX as usize {
|
|
843
|
+
return Err(JsValue::from_str("Too many document projection plans"));
|
|
844
|
+
}
|
|
845
|
+
self.document_projection_plans
|
|
846
|
+
.push(parse_projection_plan(&plan)?);
|
|
847
|
+
Ok(id as u32)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
pub fn project_document_index_simple(
|
|
851
|
+
&self,
|
|
852
|
+
encoded_document: Uint8Array,
|
|
853
|
+
plan: JsValue,
|
|
854
|
+
created: &str,
|
|
855
|
+
modified: &str,
|
|
856
|
+
head: &str,
|
|
857
|
+
gid: &str,
|
|
858
|
+
size: u32,
|
|
859
|
+
signer: JsValue,
|
|
860
|
+
) -> Result<Uint8Array, JsValue> {
|
|
861
|
+
let bytes = project_document_index_simple_bytes(
|
|
862
|
+
&encoded_document.to_vec(),
|
|
863
|
+
&plan,
|
|
864
|
+
created,
|
|
865
|
+
modified,
|
|
866
|
+
head,
|
|
867
|
+
gid,
|
|
868
|
+
size,
|
|
869
|
+
signer,
|
|
870
|
+
)?;
|
|
871
|
+
Ok(Uint8Array::from(bytes.as_slice()))
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
pub fn document_index_len(&self) -> usize {
|
|
875
|
+
self.document_index.len()
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
pub fn document_value_len(&self) -> usize {
|
|
879
|
+
self.document_values.len()
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
pub fn document_exact_string_first_key(&self, field: u32, value: String) -> JsValue {
|
|
883
|
+
self.document_index
|
|
884
|
+
.exact_first(&FieldPath::Id(field), &FieldValue::from(value))
|
|
885
|
+
.map(|key| JsValue::from_str(&key))
|
|
886
|
+
.unwrap_or(JsValue::UNDEFINED)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
pub fn document_value_bytes(&self, key: &str) -> JsValue {
|
|
890
|
+
self.document_values
|
|
891
|
+
.get(key)
|
|
892
|
+
.map(|value| Uint8Array::from(value).into())
|
|
893
|
+
.unwrap_or(JsValue::UNDEFINED)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
pub fn document_entry(&self, key: &str) -> JsValue {
|
|
897
|
+
self.document_values
|
|
898
|
+
.get(key)
|
|
899
|
+
.map(|value| document_entry_to_row(key, value).into())
|
|
900
|
+
.unwrap_or(JsValue::UNDEFINED)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
pub fn document_keys_exist(&self, keys: Vec<String>) -> Uint8Array {
|
|
904
|
+
let mut out = Vec::with_capacity(keys.len());
|
|
905
|
+
for key in keys {
|
|
906
|
+
out.push(u8::from(self.document_values.get(&key).is_some()));
|
|
907
|
+
}
|
|
908
|
+
Uint8Array::from(out.as_slice())
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
pub fn document_field_value(&self, key: &str, field: u32) -> JsValue {
|
|
912
|
+
self.document_index
|
|
913
|
+
.document_fields_by_id(key)
|
|
914
|
+
.and_then(|fields| fields.scalar_values(&FieldPath::Id(field)))
|
|
915
|
+
.and_then(|values| values.first())
|
|
916
|
+
.map(field_value_to_row)
|
|
917
|
+
.unwrap_or(JsValue::UNDEFINED)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
pub fn document_context(&self, key: &str) -> Result<JsValue, JsValue> {
|
|
921
|
+
Ok(self
|
|
922
|
+
.document_context_facts_by_key(key)?
|
|
923
|
+
.map(|context| document_context_facts_to_row(&context).into())
|
|
924
|
+
.unwrap_or(JsValue::UNDEFINED))
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
pub fn document_context_batch(&self, keys: Vec<String>) -> Result<Array, JsValue> {
|
|
928
|
+
let rows = Array::new_with_length(keys.len() as u32);
|
|
929
|
+
for (index, key) in keys.iter().enumerate() {
|
|
930
|
+
let value = self
|
|
931
|
+
.document_context_facts_by_key(key)?
|
|
932
|
+
.map(|context| document_context_facts_to_row(&context).into())
|
|
933
|
+
.unwrap_or(JsValue::UNDEFINED);
|
|
934
|
+
rows.set(index as u32, value);
|
|
935
|
+
}
|
|
936
|
+
Ok(rows)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
pub fn document_previous_signature_public_key(&self, key: &str) -> Result<Array, JsValue> {
|
|
940
|
+
let row = Array::new();
|
|
941
|
+
let Some(context) = self.document_context_facts_by_key(key)? else {
|
|
942
|
+
row.push(&JsValue::from_bool(false));
|
|
943
|
+
return Ok(row);
|
|
944
|
+
};
|
|
945
|
+
row.push(&JsValue::from_bool(true));
|
|
946
|
+
match self.document_previous_signer_public_key(key, &context) {
|
|
947
|
+
Some(public_key) => row.push(&Uint8Array::from(public_key.as_slice())),
|
|
948
|
+
None => row.push(&JsValue::UNDEFINED),
|
|
949
|
+
};
|
|
950
|
+
Ok(row)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
pub fn document_context_previous_signature_public_key_batch(
|
|
954
|
+
&self,
|
|
955
|
+
keys: Vec<String>,
|
|
956
|
+
) -> Result<Array, JsValue> {
|
|
957
|
+
let rows = Array::new_with_length(keys.len() as u32);
|
|
958
|
+
for (index, key) in keys.iter().enumerate() {
|
|
959
|
+
let row = Array::new();
|
|
960
|
+
match self.document_context_facts_by_key(key)? {
|
|
961
|
+
Some(context) => {
|
|
962
|
+
row.push(&document_context_facts_to_row(&context));
|
|
963
|
+
match self.document_previous_signer_public_key(key, &context) {
|
|
964
|
+
Some(public_key) => row.push(&Uint8Array::from(public_key.as_slice())),
|
|
965
|
+
None => row.push(&JsValue::UNDEFINED),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
None => {
|
|
969
|
+
row.push(&JsValue::UNDEFINED);
|
|
970
|
+
row.push(&JsValue::UNDEFINED);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
rows.set(index as u32, row.into());
|
|
974
|
+
}
|
|
975
|
+
Ok(rows)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
pub fn document_query(
|
|
979
|
+
&self,
|
|
980
|
+
query_bytes: Vec<u8>,
|
|
981
|
+
sort_bytes: Vec<u8>,
|
|
982
|
+
) -> Result<Array, JsValue> {
|
|
983
|
+
let query = decode_query(&query_bytes).map_err(js_error)?;
|
|
984
|
+
let sort = decode_sort(&sort_bytes).map_err(js_error)?;
|
|
985
|
+
let keys = self.document_index.search(&query, &sort, None);
|
|
986
|
+
Ok(self.document_entries_for_keys(&keys))
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
pub fn document_query_page(
|
|
990
|
+
&self,
|
|
991
|
+
query_bytes: Vec<u8>,
|
|
992
|
+
sort_bytes: Vec<u8>,
|
|
993
|
+
offset: usize,
|
|
994
|
+
limit: usize,
|
|
995
|
+
) -> Result<Array, JsValue> {
|
|
996
|
+
let query = decode_query(&query_bytes).map_err(js_error)?;
|
|
997
|
+
let sort = decode_sort(&sort_bytes).map_err(js_error)?;
|
|
998
|
+
let keys = self
|
|
999
|
+
.document_index
|
|
1000
|
+
.search_page(&query, &sort, offset, Some(limit));
|
|
1001
|
+
Ok(self.document_entries_for_keys(&keys))
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
pub fn document_count(&self, query_bytes: Vec<u8>) -> Result<usize, JsValue> {
|
|
1005
|
+
let query = decode_query(&query_bytes).map_err(js_error)?;
|
|
1006
|
+
Ok(self.document_index.count(&query) as usize)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
pub fn document_sum(&self, query_bytes: Vec<u8>, field: u32) -> Result<Array, JsValue> {
|
|
1010
|
+
let query = decode_query(&query_bytes).map_err(js_error)?;
|
|
1011
|
+
let sum = self
|
|
1012
|
+
.document_index
|
|
1013
|
+
.sum(&query, FieldPath::Id(field))
|
|
1014
|
+
.map_err(js_error)?;
|
|
1015
|
+
Ok(sum_to_js(sum))
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
pub fn put_document_encoded_parts_stored(
|
|
1019
|
+
&mut self,
|
|
1020
|
+
key: String,
|
|
1021
|
+
value_prefix_bytes: Vec<u8>,
|
|
1022
|
+
value_suffix_bytes: Vec<u8>,
|
|
1023
|
+
byte_element_index_limit: usize,
|
|
1024
|
+
) -> Result<(), JsValue> {
|
|
1025
|
+
let stored_key = key.clone();
|
|
1026
|
+
self.put_document_encoded_parts_stored_inner(
|
|
1027
|
+
key,
|
|
1028
|
+
value_prefix_bytes,
|
|
1029
|
+
value_suffix_bytes,
|
|
1030
|
+
byte_element_index_limit,
|
|
1031
|
+
false,
|
|
1032
|
+
None,
|
|
1033
|
+
None,
|
|
1034
|
+
true,
|
|
1035
|
+
)?;
|
|
1036
|
+
self.refresh_document_previous_signer_fact(&stored_key)?;
|
|
1037
|
+
Ok(())
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
pub fn put_document_encoded_parts_stored_batch(
|
|
1041
|
+
&mut self,
|
|
1042
|
+
keys: Array,
|
|
1043
|
+
value_prefix_bytes: Array,
|
|
1044
|
+
value_suffix_bytes: Array,
|
|
1045
|
+
byte_element_index_limit: usize,
|
|
1046
|
+
) -> Result<(), JsValue> {
|
|
1047
|
+
let keys = strings_from_array(keys)?;
|
|
1048
|
+
let value_prefix_bytes = bytes_vec_from_array(value_prefix_bytes)?;
|
|
1049
|
+
let value_suffix_bytes = bytes_vec_from_array(value_suffix_bytes)?;
|
|
1050
|
+
ensure_same_len(
|
|
1051
|
+
keys.len(),
|
|
1052
|
+
value_prefix_bytes.len(),
|
|
1053
|
+
"document value prefix",
|
|
1054
|
+
)?;
|
|
1055
|
+
ensure_same_len(
|
|
1056
|
+
keys.len(),
|
|
1057
|
+
value_suffix_bytes.len(),
|
|
1058
|
+
"document value suffix",
|
|
1059
|
+
)?;
|
|
1060
|
+
self.document_values.reserve(keys.len());
|
|
1061
|
+
self.document_index.reserve_documents(keys.len());
|
|
1062
|
+
|
|
1063
|
+
for ((key, prefix), suffix) in keys
|
|
1064
|
+
.into_iter()
|
|
1065
|
+
.zip(value_prefix_bytes.into_iter())
|
|
1066
|
+
.zip(value_suffix_bytes.into_iter())
|
|
1067
|
+
{
|
|
1068
|
+
let stored_key = key.clone();
|
|
1069
|
+
self.put_document_encoded_parts_stored_inner(
|
|
1070
|
+
key,
|
|
1071
|
+
prefix,
|
|
1072
|
+
suffix,
|
|
1073
|
+
byte_element_index_limit,
|
|
1074
|
+
false,
|
|
1075
|
+
None,
|
|
1076
|
+
None,
|
|
1077
|
+
true,
|
|
1078
|
+
)?;
|
|
1079
|
+
self.refresh_document_previous_signer_fact(&stored_key)?;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
Ok(())
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
pub fn delete_document(&mut self, key: &str) -> bool {
|
|
1086
|
+
self.delete_document_inner(key, true)
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
pub fn delete_documents(&mut self, keys: Array) -> Result<u32, JsValue> {
|
|
1090
|
+
let keys = strings_from_array(keys)?;
|
|
1091
|
+
let mut deleted = 0u32;
|
|
1092
|
+
for key in keys {
|
|
1093
|
+
if self.delete_document_inner(&key, true) {
|
|
1094
|
+
deleted += 1;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
Ok(deleted)
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
pub fn delete_documents_result(&mut self, keys: Array) -> Result<Uint8Array, JsValue> {
|
|
1101
|
+
let keys = strings_from_array(keys)?;
|
|
1102
|
+
let mut deleted = Vec::with_capacity(keys.len());
|
|
1103
|
+
for key in keys {
|
|
1104
|
+
deleted.push(u8::from(self.delete_document_inner(&key, true)));
|
|
1105
|
+
}
|
|
1106
|
+
Ok(Uint8Array::from(deleted.as_slice()))
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
pub fn clear_document_index(&mut self) {
|
|
1110
|
+
if self.document_journal_enabled {
|
|
1111
|
+
let keys: Vec<String> = self
|
|
1112
|
+
.document_values
|
|
1113
|
+
.entries()
|
|
1114
|
+
.into_iter()
|
|
1115
|
+
.map(|(key, _)| key.to_string())
|
|
1116
|
+
.collect();
|
|
1117
|
+
for key in keys {
|
|
1118
|
+
self.push_document_journal_delete(&key);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
self.document_index.clear();
|
|
1122
|
+
self.document_values.clear();
|
|
1123
|
+
self.document_key_by_head.clear();
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
pub fn document_signer_journal_header(&self) -> Vec<u8> {
|
|
1127
|
+
JOURNAL_MAGIC.to_vec()
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
pub fn document_journal_header(&self) -> Vec<u8> {
|
|
1131
|
+
JOURNAL_MAGIC.to_vec()
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
pub fn document_pending_journal_len(&self) -> usize {
|
|
1135
|
+
self.document_journal_record_count
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
pub fn document_pending_journal_byte_len(&self) -> usize {
|
|
1139
|
+
self.document_journal.len()
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
pub fn document_journal_enabled(&self) -> bool {
|
|
1143
|
+
self.document_journal_enabled
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
pub fn set_document_journal_enabled(&mut self, enabled: bool) {
|
|
1147
|
+
self.document_journal_enabled = enabled;
|
|
1148
|
+
if !enabled {
|
|
1149
|
+
self.document_journal.clear();
|
|
1150
|
+
self.document_journal_record_count = 0;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
pub fn document_journal(&self) -> Vec<u8> {
|
|
1155
|
+
self.document_journal.clone()
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
pub fn clear_document_journal(&mut self) {
|
|
1159
|
+
self.document_journal.clear();
|
|
1160
|
+
self.document_journal_record_count = 0;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
pub fn clear_document_journal_prefix(&mut self, byte_len: usize, record_count: usize) {
|
|
1164
|
+
clear_journal_prefix(
|
|
1165
|
+
&mut self.document_journal,
|
|
1166
|
+
&mut self.document_journal_record_count,
|
|
1167
|
+
byte_len,
|
|
1168
|
+
record_count,
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
pub fn document_snapshot(&self) -> Vec<u8> {
|
|
1173
|
+
encode_key_value_snapshot(self.document_values.entries())
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
pub fn load_document_snapshot_and_journal(
|
|
1177
|
+
&mut self,
|
|
1178
|
+
snapshot: Uint8Array,
|
|
1179
|
+
journal: Uint8Array,
|
|
1180
|
+
) -> Result<usize, JsValue> {
|
|
1181
|
+
let mut entries = if snapshot.length() == 0 {
|
|
1182
|
+
Default::default()
|
|
1183
|
+
} else {
|
|
1184
|
+
decode_key_value_snapshot(&snapshot.to_vec()).map_err(decode_error)?
|
|
1185
|
+
};
|
|
1186
|
+
let journal_records = if journal.length() == 0 {
|
|
1187
|
+
Vec::new()
|
|
1188
|
+
} else {
|
|
1189
|
+
decode_journal(&journal.to_vec()).map_err(decode_error)?
|
|
1190
|
+
};
|
|
1191
|
+
let operations = journal_records.len();
|
|
1192
|
+
for record in journal_records {
|
|
1193
|
+
match record {
|
|
1194
|
+
JournalRecord {
|
|
1195
|
+
key,
|
|
1196
|
+
value: Some(value),
|
|
1197
|
+
..
|
|
1198
|
+
} => {
|
|
1199
|
+
entries.insert(key, value);
|
|
1200
|
+
}
|
|
1201
|
+
JournalRecord { key, .. } => {
|
|
1202
|
+
entries.shift_remove(&key);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
self.document_values.clear();
|
|
1208
|
+
for (key, value) in entries {
|
|
1209
|
+
self.document_values.put(key, value);
|
|
1210
|
+
}
|
|
1211
|
+
self.rebuild_document_index_from_values()?;
|
|
1212
|
+
self.document_journal.clear();
|
|
1213
|
+
self.document_journal_record_count = 0;
|
|
1214
|
+
Ok(operations)
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
pub fn document_signer_pending_journal_len(&self) -> usize {
|
|
1218
|
+
self.document_signer_journal_record_count
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
pub fn document_signer_pending_journal_byte_len(&self) -> usize {
|
|
1222
|
+
self.document_signer_journal.len()
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
pub fn document_signer_journal_enabled(&self) -> bool {
|
|
1226
|
+
self.document_signer_journal_enabled
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
pub fn set_document_signer_journal_enabled(&mut self, enabled: bool) {
|
|
1230
|
+
self.document_signer_journal_enabled = enabled;
|
|
1231
|
+
if !enabled {
|
|
1232
|
+
self.document_signer_journal.clear();
|
|
1233
|
+
self.document_signer_journal_record_count = 0;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
pub fn document_signer_journal(&self) -> Vec<u8> {
|
|
1238
|
+
self.document_signer_journal.clone()
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
pub fn clear_document_signer_journal(&mut self) {
|
|
1242
|
+
self.document_signer_journal.clear();
|
|
1243
|
+
self.document_signer_journal_record_count = 0;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
pub fn clear_document_signer_journal_prefix(&mut self, byte_len: usize, record_count: usize) {
|
|
1247
|
+
clear_journal_prefix(
|
|
1248
|
+
&mut self.document_signer_journal,
|
|
1249
|
+
&mut self.document_signer_journal_record_count,
|
|
1250
|
+
byte_len,
|
|
1251
|
+
record_count,
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
pub fn document_signer_snapshot(&self) -> Vec<u8> {
|
|
1256
|
+
encode_key_value_snapshot(
|
|
1257
|
+
self.document_previous_signer_by_key
|
|
1258
|
+
.iter()
|
|
1259
|
+
.map(|(key, fact)| (key.clone(), encode_document_signer_fact(fact))),
|
|
1260
|
+
)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
pub fn load_document_signer_snapshot_and_journal(
|
|
1264
|
+
&mut self,
|
|
1265
|
+
snapshot: Uint8Array,
|
|
1266
|
+
journal: Uint8Array,
|
|
1267
|
+
) -> Result<usize, JsValue> {
|
|
1268
|
+
let mut entries = if snapshot.length() == 0 {
|
|
1269
|
+
Default::default()
|
|
1270
|
+
} else {
|
|
1271
|
+
decode_key_value_snapshot(&snapshot.to_vec()).map_err(decode_error)?
|
|
1272
|
+
};
|
|
1273
|
+
let journal_records = if journal.length() == 0 {
|
|
1274
|
+
Vec::new()
|
|
1275
|
+
} else {
|
|
1276
|
+
decode_journal(&journal.to_vec()).map_err(decode_error)?
|
|
1277
|
+
};
|
|
1278
|
+
let operations = journal_records.len();
|
|
1279
|
+
for record in journal_records {
|
|
1280
|
+
match record {
|
|
1281
|
+
JournalRecord {
|
|
1282
|
+
key,
|
|
1283
|
+
value: Some(value),
|
|
1284
|
+
..
|
|
1285
|
+
} => {
|
|
1286
|
+
entries.insert(key, value);
|
|
1287
|
+
}
|
|
1288
|
+
JournalRecord { key, .. } => {
|
|
1289
|
+
entries.shift_remove(&key);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
self.document_previous_signer_by_key.clear();
|
|
1295
|
+
for (key, value) in entries {
|
|
1296
|
+
self.document_previous_signer_by_key
|
|
1297
|
+
.insert(key, decode_document_signer_fact(&value)?);
|
|
1298
|
+
}
|
|
1299
|
+
self.document_signer_journal.clear();
|
|
1300
|
+
self.document_signer_journal_record_count = 0;
|
|
1301
|
+
Ok(operations)
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
impl NativePeerbitBackbone {
|
|
1306
|
+
fn document_previous_signer_public_key(
|
|
1307
|
+
&self,
|
|
1308
|
+
key: &str,
|
|
1309
|
+
context: &DocumentContextFacts,
|
|
1310
|
+
) -> Option<Vec<u8>> {
|
|
1311
|
+
self.blocks
|
|
1312
|
+
.get_ref(&context.head)
|
|
1313
|
+
.and_then(|bytes| entry_v0_signature_public_key_from_storage_bytes(bytes).ok())
|
|
1314
|
+
.or_else(|| {
|
|
1315
|
+
self.document_previous_signer_by_key
|
|
1316
|
+
.get(key)
|
|
1317
|
+
.filter(|fact| fact.head == context.head)
|
|
1318
|
+
.map(|fact| fact.public_key.clone())
|
|
1319
|
+
})
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
pub(crate) fn document_context_facts_by_key(
|
|
1323
|
+
&self,
|
|
1324
|
+
key: &str,
|
|
1325
|
+
) -> Result<Option<DocumentContextFacts>, JsValue> {
|
|
1326
|
+
let Some(document_fields) = self.document_index.document_fields_by_id(key) else {
|
|
1327
|
+
return Ok(None);
|
|
1328
|
+
};
|
|
1329
|
+
self.document_context_facts_from_fields(document_fields)
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
fn document_context_facts_from_fields(
|
|
1333
|
+
&self,
|
|
1334
|
+
document_fields: &DocumentFields,
|
|
1335
|
+
) -> Result<Option<DocumentContextFacts>, JsValue> {
|
|
1336
|
+
let Some(fields) = self.document_context_fields else {
|
|
1337
|
+
return Ok(None);
|
|
1338
|
+
};
|
|
1339
|
+
let created = document_u64_field(document_fields, fields.created)
|
|
1340
|
+
.ok_or_else(|| JsValue::from_str("Missing document context created field"))?;
|
|
1341
|
+
let modified = document_u64_field(document_fields, fields.modified)
|
|
1342
|
+
.ok_or_else(|| JsValue::from_str("Missing document context modified field"))?;
|
|
1343
|
+
let head = document_string_field(document_fields, fields.head)
|
|
1344
|
+
.ok_or_else(|| JsValue::from_str("Missing document context head field"))?;
|
|
1345
|
+
let gid = document_string_field(document_fields, fields.gid)
|
|
1346
|
+
.ok_or_else(|| JsValue::from_str("Missing document context gid field"))?;
|
|
1347
|
+
let size = document_u64_field(document_fields, fields.size)
|
|
1348
|
+
.and_then(|value| u32::try_from(value).ok())
|
|
1349
|
+
.ok_or_else(|| JsValue::from_str("Missing document context size field"))?;
|
|
1350
|
+
Ok(Some(DocumentContextFacts {
|
|
1351
|
+
created,
|
|
1352
|
+
modified,
|
|
1353
|
+
head,
|
|
1354
|
+
gid,
|
|
1355
|
+
size,
|
|
1356
|
+
}))
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
pub(crate) fn validate_document_index_required_previous_signer(
|
|
1360
|
+
&self,
|
|
1361
|
+
document_index_commit: &DocumentIndexAppendCommit,
|
|
1362
|
+
) -> Result<(), JsValue> {
|
|
1363
|
+
let Some(required_public_key) = document_index_commit
|
|
1364
|
+
.required_previous_signer_public_key
|
|
1365
|
+
.as_ref()
|
|
1366
|
+
else {
|
|
1367
|
+
return Ok(());
|
|
1368
|
+
};
|
|
1369
|
+
let Some(previous_context) = document_index_commit.previous_context.as_ref() else {
|
|
1370
|
+
return Ok(());
|
|
1371
|
+
};
|
|
1372
|
+
let previous_public_key = self
|
|
1373
|
+
.document_previous_signer_public_key(&document_index_commit.key, previous_context)
|
|
1374
|
+
.ok_or_else(|| JsValue::from_str("Previous document signer public key unavailable"))?;
|
|
1375
|
+
if previous_public_key.as_slice() != required_public_key.as_slice() {
|
|
1376
|
+
return Err(JsValue::from_str(
|
|
1377
|
+
"Previous document signer public key did not match native policy",
|
|
1378
|
+
));
|
|
1379
|
+
}
|
|
1380
|
+
Ok(())
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
fn put_document_encoded_parts_stored_inner(
|
|
1384
|
+
&mut self,
|
|
1385
|
+
key: String,
|
|
1386
|
+
value_prefix_bytes: Vec<u8>,
|
|
1387
|
+
value_suffix_bytes: Vec<u8>,
|
|
1388
|
+
byte_element_index_limit: usize,
|
|
1389
|
+
known_existing: bool,
|
|
1390
|
+
new_head: Option<&str>,
|
|
1391
|
+
previous_head: Option<&str>,
|
|
1392
|
+
record_document_journal: bool,
|
|
1393
|
+
) -> Result<(), JsValue> {
|
|
1394
|
+
let prepared = self.prepare_document_encoded_parts_put(
|
|
1395
|
+
key,
|
|
1396
|
+
value_prefix_bytes,
|
|
1397
|
+
value_suffix_bytes,
|
|
1398
|
+
byte_element_index_limit,
|
|
1399
|
+
known_existing,
|
|
1400
|
+
new_head,
|
|
1401
|
+
previous_head,
|
|
1402
|
+
record_document_journal,
|
|
1403
|
+
)?;
|
|
1404
|
+
self.commit_prepared_document_encoded_parts_put(prepared);
|
|
1405
|
+
Ok(())
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
#[allow(clippy::too_many_arguments)]
|
|
1409
|
+
pub(crate) fn prepare_document_encoded_parts_put(
|
|
1410
|
+
&mut self,
|
|
1411
|
+
key: String,
|
|
1412
|
+
mut value_prefix_bytes: Vec<u8>,
|
|
1413
|
+
value_suffix_bytes: Vec<u8>,
|
|
1414
|
+
byte_element_index_limit: usize,
|
|
1415
|
+
known_existing: bool,
|
|
1416
|
+
new_head: Option<&str>,
|
|
1417
|
+
previous_head: Option<&str>,
|
|
1418
|
+
record_document_journal: bool,
|
|
1419
|
+
) -> Result<PreparedDocumentEncodedPartsPut, JsValue> {
|
|
1420
|
+
self.document_byte_element_index_limit = byte_element_index_limit;
|
|
1421
|
+
let profile_enabled = self.append_profile_enabled;
|
|
1422
|
+
let extract_started = profile_enabled.then(js_sys::Date::now);
|
|
1423
|
+
let fields = {
|
|
1424
|
+
let schema_ir = self.document_schema_ir.as_ref().ok_or_else(|| {
|
|
1425
|
+
js_error("Native backbone document schema IR has not been configured")
|
|
1426
|
+
})?;
|
|
1427
|
+
extract_encoded_document_fields_from_parts_with_byte_limits(
|
|
1428
|
+
&schema_ir,
|
|
1429
|
+
&value_prefix_bytes,
|
|
1430
|
+
&value_suffix_bytes,
|
|
1431
|
+
byte_element_index_limit,
|
|
1432
|
+
NATIVE_BACKBONE_BYTE_EXACT_INDEX_LIMIT,
|
|
1433
|
+
)
|
|
1434
|
+
.map_err(js_error)?
|
|
1435
|
+
};
|
|
1436
|
+
if let Some(started) = extract_started {
|
|
1437
|
+
self.append_profile.document_index_extract_ms += js_sys::Date::now() - started;
|
|
1438
|
+
}
|
|
1439
|
+
let value_build_started = profile_enabled.then(js_sys::Date::now);
|
|
1440
|
+
value_prefix_bytes.reserve(value_suffix_bytes.len());
|
|
1441
|
+
value_prefix_bytes.extend_from_slice(&value_suffix_bytes);
|
|
1442
|
+
if let Some(started) = value_build_started {
|
|
1443
|
+
self.append_profile.document_index_value_build_ms += js_sys::Date::now() - started;
|
|
1444
|
+
}
|
|
1445
|
+
Ok(PreparedDocumentEncodedPartsPut {
|
|
1446
|
+
key,
|
|
1447
|
+
value_bytes: value_prefix_bytes,
|
|
1448
|
+
fields,
|
|
1449
|
+
known_existing,
|
|
1450
|
+
new_head: new_head.map(str::to_string),
|
|
1451
|
+
previous_head: previous_head.map(str::to_string),
|
|
1452
|
+
record_document_journal,
|
|
1453
|
+
})
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
pub(crate) fn commit_prepared_document_encoded_parts_put(
|
|
1457
|
+
&mut self,
|
|
1458
|
+
prepared: PreparedDocumentEncodedPartsPut,
|
|
1459
|
+
) {
|
|
1460
|
+
let PreparedDocumentEncodedPartsPut {
|
|
1461
|
+
key,
|
|
1462
|
+
value_bytes,
|
|
1463
|
+
fields,
|
|
1464
|
+
known_existing,
|
|
1465
|
+
new_head,
|
|
1466
|
+
previous_head,
|
|
1467
|
+
record_document_journal,
|
|
1468
|
+
} = prepared;
|
|
1469
|
+
let profile_enabled = self.append_profile_enabled;
|
|
1470
|
+
let should_record_document_journal =
|
|
1471
|
+
record_document_journal && self.document_journal_enabled;
|
|
1472
|
+
if should_record_document_journal {
|
|
1473
|
+
self.push_document_journal_put(&key, &value_bytes);
|
|
1474
|
+
}
|
|
1475
|
+
let value_put_started = profile_enabled.then(js_sys::Date::now);
|
|
1476
|
+
let was_existing = if known_existing {
|
|
1477
|
+
self.document_values.put(key.clone(), value_bytes);
|
|
1478
|
+
true
|
|
1479
|
+
} else {
|
|
1480
|
+
self.document_values
|
|
1481
|
+
.put_return_previous(key.clone(), value_bytes)
|
|
1482
|
+
.is_some()
|
|
1483
|
+
};
|
|
1484
|
+
if let Some(started) = value_put_started {
|
|
1485
|
+
self.append_profile.document_value_put_ms += js_sys::Date::now() - started;
|
|
1486
|
+
}
|
|
1487
|
+
let index_put_started = profile_enabled.then(js_sys::Date::now);
|
|
1488
|
+
if known_existing || was_existing {
|
|
1489
|
+
self.document_index.put(&key, fields);
|
|
1490
|
+
} else {
|
|
1491
|
+
self.document_index.put_new_unchecked(&key, fields);
|
|
1492
|
+
}
|
|
1493
|
+
if let Some(started) = index_put_started {
|
|
1494
|
+
self.append_profile.document_index_put_ms += js_sys::Date::now() - started;
|
|
1495
|
+
}
|
|
1496
|
+
self.update_document_head_key(
|
|
1497
|
+
&key,
|
|
1498
|
+
new_head.as_deref(),
|
|
1499
|
+
previous_head.as_deref(),
|
|
1500
|
+
was_existing,
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
fn update_document_head_key(
|
|
1505
|
+
&mut self,
|
|
1506
|
+
key: &str,
|
|
1507
|
+
new_head: Option<&str>,
|
|
1508
|
+
previous_head: Option<&str>,
|
|
1509
|
+
was_existing: bool,
|
|
1510
|
+
) {
|
|
1511
|
+
let Some(new_head) = new_head else {
|
|
1512
|
+
return;
|
|
1513
|
+
};
|
|
1514
|
+
if self.document_context_head_field.is_none() {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if let Some(previous_head) = previous_head {
|
|
1518
|
+
if previous_head != new_head {
|
|
1519
|
+
self.document_key_by_head.remove(previous_head);
|
|
1520
|
+
}
|
|
1521
|
+
} else if was_existing {
|
|
1522
|
+
self.document_key_by_head
|
|
1523
|
+
.retain(|_, existing_key| existing_key != key);
|
|
1524
|
+
}
|
|
1525
|
+
self.document_key_by_head
|
|
1526
|
+
.insert(new_head.to_string(), key.to_string());
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
fn refresh_document_previous_signer_fact(&mut self, key: &str) -> Result<(), JsValue> {
|
|
1530
|
+
let Some(context) = self.document_context_facts_by_key(key)? else {
|
|
1531
|
+
self.delete_document_previous_signer_fact(key, true);
|
|
1532
|
+
return Ok(());
|
|
1533
|
+
};
|
|
1534
|
+
if let Some(public_key) = self
|
|
1535
|
+
.blocks
|
|
1536
|
+
.get_ref(&context.head)
|
|
1537
|
+
.and_then(|bytes| entry_v0_signature_public_key_from_storage_bytes(bytes).ok())
|
|
1538
|
+
{
|
|
1539
|
+
self.put_document_previous_signer_fact(key.to_string(), context.head, public_key, true);
|
|
1540
|
+
return Ok(());
|
|
1541
|
+
}
|
|
1542
|
+
// Rebuilding the document index can observe context state before the
|
|
1543
|
+
// corresponding block is resident. Keep durable signer facts in that
|
|
1544
|
+
// case; lookups filter by the current head so stale facts are inert,
|
|
1545
|
+
// while deleting them here would make strict native same-signer
|
|
1546
|
+
// policies lose their persisted proof across reopen.
|
|
1547
|
+
Ok(())
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
pub(crate) fn delete_document_inner(
|
|
1551
|
+
&mut self,
|
|
1552
|
+
key: &str,
|
|
1553
|
+
record_document_journal: bool,
|
|
1554
|
+
) -> bool {
|
|
1555
|
+
if let Ok(Some(context)) = self.document_context_facts_by_key(key) {
|
|
1556
|
+
self.document_key_by_head.remove(&context.head);
|
|
1557
|
+
} else {
|
|
1558
|
+
self.document_key_by_head
|
|
1559
|
+
.retain(|_, existing_key| existing_key != key);
|
|
1560
|
+
}
|
|
1561
|
+
self.document_index.delete_id(key);
|
|
1562
|
+
self.delete_document_previous_signer_fact(key, true);
|
|
1563
|
+
let deleted = self.document_values.delete(key);
|
|
1564
|
+
if deleted && record_document_journal && self.document_journal_enabled {
|
|
1565
|
+
self.push_document_journal_delete(key);
|
|
1566
|
+
}
|
|
1567
|
+
deleted
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
pub(crate) fn clear_document_core(&mut self) {
|
|
1571
|
+
self.document_index.clear();
|
|
1572
|
+
self.document_values.clear();
|
|
1573
|
+
self.document_journal.clear();
|
|
1574
|
+
self.document_journal_record_count = 0;
|
|
1575
|
+
self.document_key_by_head.clear();
|
|
1576
|
+
self.document_previous_signer_by_key.clear();
|
|
1577
|
+
self.document_signer_journal.clear();
|
|
1578
|
+
self.document_signer_journal_record_count = 0;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
fn rebuild_document_index_from_values(&mut self) -> Result<(), JsValue> {
|
|
1582
|
+
let Some(schema_ir) = self.document_schema_ir.clone() else {
|
|
1583
|
+
self.document_index.clear();
|
|
1584
|
+
self.document_key_by_head.clear();
|
|
1585
|
+
return Ok(());
|
|
1586
|
+
};
|
|
1587
|
+
let values: Vec<(String, Vec<u8>)> = self
|
|
1588
|
+
.document_values
|
|
1589
|
+
.entries()
|
|
1590
|
+
.into_iter()
|
|
1591
|
+
.map(|(key, value)| (key.to_string(), value.to_vec()))
|
|
1592
|
+
.collect();
|
|
1593
|
+
self.document_index.clear();
|
|
1594
|
+
self.document_key_by_head.clear();
|
|
1595
|
+
self.document_index.reserve_documents(values.len());
|
|
1596
|
+
for (key, value) in values {
|
|
1597
|
+
let fields = extract_encoded_document_fields_from_parts_with_byte_limits(
|
|
1598
|
+
&schema_ir,
|
|
1599
|
+
&value,
|
|
1600
|
+
&[],
|
|
1601
|
+
self.document_byte_element_index_limit,
|
|
1602
|
+
NATIVE_BACKBONE_BYTE_EXACT_INDEX_LIMIT,
|
|
1603
|
+
)
|
|
1604
|
+
.map_err(js_error)?;
|
|
1605
|
+
if let Some(context) = self.document_context_facts_from_fields(&fields)? {
|
|
1606
|
+
self.document_key_by_head.insert(context.head, key.clone());
|
|
1607
|
+
}
|
|
1608
|
+
self.document_index.put_new_unchecked(key, fields);
|
|
1609
|
+
}
|
|
1610
|
+
Ok(())
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
fn rebuild_document_head_keys(&mut self) {
|
|
1614
|
+
let Some(fields) = self.document_context_fields else {
|
|
1615
|
+
self.document_key_by_head.clear();
|
|
1616
|
+
return;
|
|
1617
|
+
};
|
|
1618
|
+
let values: Vec<(String, Vec<u8>)> = self
|
|
1619
|
+
.document_values
|
|
1620
|
+
.entries()
|
|
1621
|
+
.into_iter()
|
|
1622
|
+
.map(|(key, value)| (key.to_string(), value.to_vec()))
|
|
1623
|
+
.collect();
|
|
1624
|
+
self.document_key_by_head.clear();
|
|
1625
|
+
for (key, _) in values {
|
|
1626
|
+
let Some(document_fields) = self.document_index.document_fields_by_id(&key) else {
|
|
1627
|
+
continue;
|
|
1628
|
+
};
|
|
1629
|
+
let Some(head) = document_string_field(document_fields, fields.head) else {
|
|
1630
|
+
continue;
|
|
1631
|
+
};
|
|
1632
|
+
self.document_key_by_head.insert(head, key);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
pub(crate) fn put_document_previous_signer_fact(
|
|
1637
|
+
&mut self,
|
|
1638
|
+
key: String,
|
|
1639
|
+
head: String,
|
|
1640
|
+
public_key: Vec<u8>,
|
|
1641
|
+
record_journal: bool,
|
|
1642
|
+
) {
|
|
1643
|
+
let should_record = record_journal
|
|
1644
|
+
&& self.document_signer_journal_enabled
|
|
1645
|
+
&& !self
|
|
1646
|
+
.document_previous_signer_by_key
|
|
1647
|
+
.get(&key)
|
|
1648
|
+
.is_some_and(|fact| fact.head == head && fact.public_key == public_key);
|
|
1649
|
+
let fact = DocumentPreviousSignerFact { head, public_key };
|
|
1650
|
+
if should_record {
|
|
1651
|
+
self.push_document_signer_journal_put(&key, &encode_document_signer_fact(&fact));
|
|
1652
|
+
}
|
|
1653
|
+
self.document_previous_signer_by_key.insert(key, fact);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
fn delete_document_previous_signer_fact(&mut self, key: &str, record_journal: bool) {
|
|
1657
|
+
let existed = self.document_previous_signer_by_key.remove(key).is_some();
|
|
1658
|
+
if existed && record_journal && self.document_signer_journal_enabled {
|
|
1659
|
+
self.push_document_signer_journal_delete(key);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
fn push_document_signer_journal_put(&mut self, key: &str, value: &[u8]) {
|
|
1664
|
+
append_journal_put_record(&mut self.document_signer_journal, key, value);
|
|
1665
|
+
self.document_signer_journal_record_count += 1;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
fn push_document_signer_journal_delete(&mut self, key: &str) {
|
|
1669
|
+
append_journal_delete_record(&mut self.document_signer_journal, key);
|
|
1670
|
+
self.document_signer_journal_record_count += 1;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
fn push_document_journal_put(&mut self, key: &str, value: &[u8]) {
|
|
1674
|
+
append_journal_put_record(&mut self.document_journal, key, value);
|
|
1675
|
+
self.document_journal_record_count += 1;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
fn push_document_journal_delete(&mut self, key: &str) {
|
|
1679
|
+
append_journal_delete_record(&mut self.document_journal, key);
|
|
1680
|
+
self.document_journal_record_count += 1;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
fn document_entries_for_keys(&self, keys: &[String]) -> Array {
|
|
1684
|
+
let out = Array::new();
|
|
1685
|
+
for key in keys {
|
|
1686
|
+
if let Some(value) = self.document_values.get(key) {
|
|
1687
|
+
out.push(&document_entry_to_row(key, value));
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
out
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
pub(crate) fn reserve_document_batch(&mut self, batch_len: usize) {
|
|
1694
|
+
self.document_values.reserve(batch_len);
|
|
1695
|
+
self.document_index.reserve_documents(batch_len);
|
|
1696
|
+
self.document_key_by_head.reserve(batch_len);
|
|
1697
|
+
}
|
|
1698
|
+
}
|