@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.
@@ -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
+ }