@peerbit/native-backbone 0.1.1 → 0.1.3

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.
@@ -13,6 +13,7 @@ use crate::documents::{
13
13
  DocumentContextFacts, DocumentIndexAppendCommit, DocumentIndexProjectionPlan,
14
14
  DocumentIndexValuePrefix, PreparedDocumentIndexAppendPut,
15
15
  };
16
+ use crate::error::BackboneError;
16
17
  use crate::js_interop::{
17
18
  array_from_value, ensure_same_len, hash_number_u64, number_to_row, numbers_to_rows,
18
19
  optional_bytes_from_js, string_field, strings_to_array,
@@ -43,8 +44,15 @@ struct LatestBatchPendingAppend {
43
44
  next_hashes: Vec<String>,
44
45
  meta_bytes: Vec<u8>,
45
46
  trim_hashes: Vec<String>,
46
- entry_row: Array,
47
- trim_rows: Array,
47
+ // Owned committed-append facts and resolved trimmed entries; the frozen JS
48
+ // `entry_row`/`trim_rows` layouts are rebuilt at the consume boundary so
49
+ // this pending state stays JS-free (no `js_sys::Array` captured in core
50
+ // state). `resolve_trimmed_entries` records which trim-row mode produced
51
+ // `trimmed_entries` so the empty-resolved and unresolved cases stay
52
+ // distinct, matching the pre-lift behaviour byte-for-byte.
53
+ entry_facts: NativeCommittedEntryFacts,
54
+ trimmed_entries: Vec<LogIndexEntry>,
55
+ resolve_trimmed_entries: bool,
48
56
  document_trimmed_heads_processed: bool,
49
57
  previous_document_context: Option<DocumentContextFacts>,
50
58
  }
@@ -183,10 +191,10 @@ fn ensure_batch_append_lens(
183
191
  wall_times: &BigUint64Array,
184
192
  logicals: &Uint32Array,
185
193
  gids: &Array,
186
- gids_label: &str,
194
+ gids_label: &'static str,
187
195
  meta_datas: &Array,
188
196
  document_keys: &Array,
189
- ) -> Result<(), JsValue> {
197
+ ) -> Result<(), BackboneError> {
190
198
  ensure_same_len(len, wall_times.length() as usize, "batch wall times")?;
191
199
  ensure_same_len(len, logicals.length() as usize, "batch logicals")?;
192
200
  ensure_same_len(len, gids.length() as usize, gids_label)?;
@@ -200,7 +208,7 @@ fn ensure_batch_projection_lens(
200
208
  plan_ids: &Uint32Array,
201
209
  encoded_documents: Option<&Array>,
202
210
  signers: &Array,
203
- ) -> Result<(), JsValue> {
211
+ ) -> Result<(), BackboneError> {
204
212
  ensure_same_len(
205
213
  len,
206
214
  plan_ids.length() as usize,
@@ -224,11 +232,11 @@ fn ensure_batch_projection_lens(
224
232
  fn required_projection_encoded_document(
225
233
  encoded_documents: &Array,
226
234
  index: u32,
227
- ) -> Result<JsValue, JsValue> {
235
+ ) -> Result<JsValue, BackboneError> {
228
236
  let encoded_document = encoded_documents.get(index);
229
237
  if encoded_document.is_undefined() || encoded_document.is_null() {
230
- return Err(JsValue::from_str(
231
- "Expected batch document projection encoded document",
238
+ return Err(BackboneError::Expected(
239
+ "batch document projection encoded document",
232
240
  ));
233
241
  }
234
242
  Ok(encoded_document)
@@ -239,21 +247,21 @@ impl NativePeerbitBackbone {
239
247
  &mut self,
240
248
  meta_data: JsValue,
241
249
  payload_data: &Uint8Array,
242
- ) -> (Option<Vec<u8>>, Vec<u8>) {
243
- let input_copy_started = self.append_profile_enabled.then(js_sys::Date::now);
244
- let meta_data = optional_bytes_from_js(meta_data);
250
+ ) -> Result<(Option<Vec<u8>>, Vec<u8>), BackboneError> {
251
+ let input_copy_started = self.append_profile_enabled.then(crate::time::now_ms);
252
+ let meta_data = optional_bytes_from_js(meta_data, "meta data")?;
245
253
  let payload_data = payload_data.to_vec();
246
254
  if let Some(started) = input_copy_started {
247
- self.append_profile.input_copy_ms += js_sys::Date::now() - started;
255
+ self.append_profile.input_copy_ms += crate::time::now_ms() - started;
248
256
  }
249
- (meta_data, payload_data)
257
+ Ok((meta_data, payload_data))
250
258
  }
251
259
 
252
- fn hash_number_profiled(&mut self, hash_digest_bytes: &[u8]) -> Result<u64, JsValue> {
253
- let hash_number_started = self.append_profile_enabled.then(js_sys::Date::now);
260
+ fn hash_number_profiled(&mut self, hash_digest_bytes: &[u8]) -> Result<u64, BackboneError> {
261
+ let hash_number_started = self.append_profile_enabled.then(crate::time::now_ms);
254
262
  let hash_number = hash_number_u64(&self.resolution, hash_digest_bytes)?;
255
263
  if let Some(started) = hash_number_started {
256
- self.append_profile.hash_number_ms += js_sys::Date::now() - started;
264
+ self.append_profile.hash_number_ms += crate::time::now_ms() - started;
257
265
  }
258
266
  Ok(hash_number)
259
267
  }
@@ -268,9 +276,9 @@ impl NativePeerbitBackbone {
268
276
  meta_data: Option<Vec<u8>>,
269
277
  payload_data: &[u8],
270
278
  trim_length_to: Option<usize>,
271
- ) -> Result<(NativeCommittedEntryFacts, Vec<String>), JsValue> {
279
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>), BackboneError> {
272
280
  let profile_enabled = self.append_profile_enabled;
273
- let log_started = profile_enabled.then(js_sys::Date::now);
281
+ let log_started = profile_enabled.then(crate::time::now_ms);
274
282
  let mut log_profile = NativeLogAppendProfile::default();
275
283
  let result = if let Some(trim_length_to) = trim_length_to {
276
284
  self.log
@@ -304,7 +312,7 @@ impl NativePeerbitBackbone {
304
312
  )
305
313
  };
306
314
  if let Some(started) = log_started {
307
- self.append_profile.log_total_ms += js_sys::Date::now() - started;
315
+ self.append_profile.log_total_ms += crate::time::now_ms() - started;
308
316
  self.append_profile.add_log_profile(&log_profile);
309
317
  }
310
318
  Ok(result)
@@ -321,9 +329,9 @@ impl NativePeerbitBackbone {
321
329
  meta_data: Option<Vec<u8>>,
322
330
  payload_data: &[u8],
323
331
  trim_length_to: Option<usize>,
324
- ) -> Result<(NativeCommittedEntryFacts, Vec<String>), JsValue> {
332
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>), BackboneError> {
325
333
  let profile_enabled = self.append_profile_enabled;
326
- let log_started = profile_enabled.then(js_sys::Date::now);
334
+ let log_started = profile_enabled.then(crate::time::now_ms);
327
335
  let mut log_profile = NativeLogAppendProfile::default();
328
336
  let result = self
329
337
  .log
@@ -341,14 +349,20 @@ impl NativePeerbitBackbone {
341
349
  profile_enabled.then_some(&mut log_profile),
342
350
  )?;
343
351
  if let Some(started) = log_started {
344
- self.append_profile.log_total_ms += js_sys::Date::now() - started;
352
+ self.append_profile.log_total_ms += crate::time::now_ms() - started;
345
353
  self.append_profile.add_log_profile(&log_profile);
346
354
  }
347
355
  Ok(result)
348
356
  }
349
357
 
358
+ /// JS-free committed-log append: performs the log append and (optionally)
359
+ /// resolves the trimmed entries, returning owned Rust data only. Callers
360
+ /// that need the frozen JS result-row layouts build them at their own
361
+ /// boundary via [`committed_entry_facts_to_row`] and
362
+ /// [`native_backbone_trim_entries_to_rows`], keeping the pending-append
363
+ /// state JS-free until the row is emitted.
350
364
  #[allow(clippy::too_many_arguments)]
351
- fn prepare_committed_log_append_rows_profiled(
365
+ fn prepare_committed_log_append_owned_profiled(
352
366
  &mut self,
353
367
  wall_time: u64,
354
368
  logical: u32,
@@ -359,9 +373,9 @@ impl NativePeerbitBackbone {
359
373
  payload_data: Vec<u8>,
360
374
  trim_length_to: Option<usize>,
361
375
  resolve_trimmed_entries: bool,
362
- ) -> Result<(NativeCommittedEntryFacts, Vec<String>, Array, Array), JsValue> {
376
+ ) -> Result<(NativeCommittedEntryFacts, Vec<LogIndexEntry>, Vec<String>), BackboneError> {
363
377
  let profile_enabled = self.append_profile_enabled;
364
- let log_started = profile_enabled.then(js_sys::Date::now);
378
+ let log_started = profile_enabled.then(crate::time::now_ms);
365
379
  let mut log_profile = NativeLogAppendProfile::default();
366
380
  let (entry_facts, trimmed_entries, trim_hashes) = if resolve_trimmed_entries {
367
381
  let (entry_facts, trimmed_entries) = self
@@ -403,23 +417,78 @@ impl NativePeerbitBackbone {
403
417
  (entry_facts, Vec::new(), trim_hashes)
404
418
  };
405
419
  if let Some(started) = log_started {
406
- self.append_profile.log_total_ms += js_sys::Date::now() - started;
420
+ self.append_profile.log_total_ms += crate::time::now_ms() - started;
407
421
  self.append_profile.add_log_profile(&log_profile);
408
422
  }
409
- let entry_row_started = profile_enabled.then(js_sys::Date::now);
410
- let entry_row = committed_entry_facts_to_row(&entry_facts, !entry_facts.next.is_empty());
423
+ Ok((entry_facts, trimmed_entries, trim_hashes))
424
+ }
425
+
426
+ /// Build the frozen committed-append entry row from owned facts, timing the
427
+ /// build against the `entry_row` profile counter exactly as the inline
428
+ /// row build did.
429
+ fn committed_entry_facts_to_row_profiled(
430
+ &mut self,
431
+ entry_facts: &NativeCommittedEntryFacts,
432
+ ) -> Array {
433
+ let entry_row_started = self.append_profile_enabled.then(crate::time::now_ms);
434
+ let entry_row = committed_entry_facts_to_row(entry_facts, !entry_facts.next.is_empty());
411
435
  if let Some(started) = entry_row_started {
412
- self.append_profile.entry_row_ms += js_sys::Date::now() - started;
436
+ self.append_profile.entry_row_ms += crate::time::now_ms() - started;
413
437
  }
414
- let trim_rows_started = profile_enabled.then(js_sys::Date::now);
438
+ entry_row
439
+ }
440
+
441
+ /// Build the frozen trimmed-entries rows from owned entries, timing the
442
+ /// build against the `trim_rows` profile counter exactly as the inline
443
+ /// row build did. `resolve_trimmed_entries` distinguishes an empty resolved
444
+ /// set from the unresolved mode (which always emits an empty array).
445
+ fn native_backbone_trim_entries_to_rows_profiled(
446
+ &mut self,
447
+ trimmed_entries: Vec<LogIndexEntry>,
448
+ resolve_trimmed_entries: bool,
449
+ ) -> Array {
450
+ let trim_rows_started = self.append_profile_enabled.then(crate::time::now_ms);
415
451
  let trim_rows = if resolve_trimmed_entries {
416
452
  native_backbone_trim_entries_to_rows(trimmed_entries)
417
453
  } else {
418
454
  Array::new()
419
455
  };
420
456
  if let Some(started) = trim_rows_started {
421
- self.append_profile.trim_rows_ms += js_sys::Date::now() - started;
457
+ self.append_profile.trim_rows_ms += crate::time::now_ms() - started;
422
458
  }
459
+ trim_rows
460
+ }
461
+
462
+ #[allow(clippy::too_many_arguments)]
463
+ fn prepare_committed_log_append_rows_profiled(
464
+ &mut self,
465
+ wall_time: u64,
466
+ logical: u32,
467
+ gid: String,
468
+ next_hashes: Vec<String>,
469
+ entry_type: u8,
470
+ meta_data: Option<Vec<u8>>,
471
+ payload_data: Vec<u8>,
472
+ trim_length_to: Option<usize>,
473
+ resolve_trimmed_entries: bool,
474
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>, Array, Array), BackboneError> {
475
+ let (entry_facts, trimmed_entries, trim_hashes) = self
476
+ .prepare_committed_log_append_owned_profiled(
477
+ wall_time,
478
+ logical,
479
+ gid,
480
+ next_hashes,
481
+ entry_type,
482
+ meta_data,
483
+ payload_data,
484
+ trim_length_to,
485
+ resolve_trimmed_entries,
486
+ )?;
487
+ let entry_row = self.committed_entry_facts_to_row_profiled(&entry_facts);
488
+ let trim_rows = self.native_backbone_trim_entries_to_rows_profiled(
489
+ trimmed_entries,
490
+ resolve_trimmed_entries,
491
+ );
423
492
  Ok((entry_facts, trim_hashes, entry_row, trim_rows))
424
493
  }
425
494
 
@@ -433,9 +502,9 @@ impl NativePeerbitBackbone {
433
502
  self_hash: &str,
434
503
  self_replicating: bool,
435
504
  expected_len: usize,
436
- mismatch_label: &str,
437
- ) -> Result<Vec<NativeLocalAppendCompactFacts>, JsValue> {
438
- let coordinate_plan_started = self.append_profile_enabled.then(js_sys::Date::now);
505
+ mismatch_label: &'static str,
506
+ ) -> Result<Vec<NativeLocalAppendCompactFacts>, BackboneError> {
507
+ let coordinate_plan_started = self.append_profile_enabled.then(crate::time::now_ms);
439
508
  let coordinate_facts = commit_local_appends_for_gids_compact_core(
440
509
  &mut self.shared_log,
441
510
  coordinate_inputs,
@@ -448,10 +517,12 @@ impl NativePeerbitBackbone {
448
517
  true,
449
518
  )?;
450
519
  if let Some(started) = coordinate_plan_started {
451
- self.append_profile.coordinate_plan_ms += js_sys::Date::now() - started;
520
+ self.append_profile.coordinate_plan_ms += crate::time::now_ms() - started;
452
521
  }
453
522
  if coordinate_facts.len() != expected_len {
454
- return Err(JsValue::from_str(mismatch_label));
523
+ return Err(BackboneError::MismatchedCompactCoordinateFacts(
524
+ mismatch_label,
525
+ ));
455
526
  }
456
527
  Ok(coordinate_facts)
457
528
  }
@@ -467,8 +538,8 @@ impl NativePeerbitBackbone {
467
538
  plain_put_payload_data: Option<&[u8]>,
468
539
  delete_trimmed_document_heads: bool,
469
540
  trim_hashes: &[String],
470
- ) -> Result<bool, JsValue> {
471
- let document_index_started = self.append_profile_enabled.then(js_sys::Date::now);
541
+ ) -> Result<bool, BackboneError> {
542
+ let document_index_started = self.append_profile_enabled.then(crate::time::now_ms);
472
543
  self.put_document_index_for_append_with_plain_put_payload(
473
544
  document_index_commit,
474
545
  wall_time,
@@ -480,7 +551,7 @@ impl NativePeerbitBackbone {
480
551
  let document_trimmed_heads_processed = delete_trimmed_document_heads
481
552
  && self.delete_documents_by_context_heads_profiled(trim_hashes);
482
553
  if let Some(started) = document_index_started {
483
- self.append_profile.document_index_commit_ms += js_sys::Date::now() - started;
554
+ self.append_profile.document_index_commit_ms += crate::time::now_ms() - started;
484
555
  }
485
556
  Ok(document_trimmed_heads_processed)
486
557
  }
@@ -489,7 +560,7 @@ impl NativePeerbitBackbone {
489
560
  &self,
490
561
  document_index_commit: &mut DocumentIndexAppendCommit,
491
562
  fallback_gid: String,
492
- ) -> Result<(Option<DocumentContextFacts>, String, Vec<String>), JsValue> {
563
+ ) -> Result<(Option<DocumentContextFacts>, String, Vec<String>), BackboneError> {
493
564
  let previous_context = self.document_context_facts_by_key(&document_index_commit.key)?;
494
565
  let known_existing = previous_context.is_some();
495
566
  let gid = previous_context
@@ -516,7 +587,7 @@ impl NativePeerbitBackbone {
516
587
  wall_time: u64,
517
588
  document_gid: &str,
518
589
  payload_size: u32,
519
- ) -> Result<(), JsValue> {
590
+ ) -> Result<(), BackboneError> {
520
591
  let entry_row = if row.length() == 2 && Array::is_array(&row.get(0)) {
521
592
  array_from_value(row.get(0), "native trim document index entry row")?
522
593
  } else {
@@ -539,7 +610,7 @@ impl NativePeerbitBackbone {
539
610
  hash: &str,
540
611
  gid: &str,
541
612
  payload_size: u32,
542
- ) -> Result<(), JsValue> {
613
+ ) -> Result<(), BackboneError> {
543
614
  self.put_document_index_for_append_with_plain_put_payload(
544
615
  document_index_commit,
545
616
  wall_time,
@@ -558,7 +629,7 @@ impl NativePeerbitBackbone {
558
629
  gid: &str,
559
630
  payload_size: u32,
560
631
  plain_put_payload_data: Option<&[u8]>,
561
- ) -> Result<(), JsValue> {
632
+ ) -> Result<(), BackboneError> {
562
633
  let Some(document_index_commit) = document_index_commit else {
563
634
  return Ok(());
564
635
  };
@@ -583,7 +654,7 @@ impl NativePeerbitBackbone {
583
654
  gid: &str,
584
655
  payload_size: u32,
585
656
  plain_put_payload_data: Option<&[u8]>,
586
- ) -> Result<PreparedDocumentIndexAppendPut, JsValue> {
657
+ ) -> Result<PreparedDocumentIndexAppendPut, BackboneError> {
587
658
  let record_previous_signer = document_index_commit
588
659
  .required_previous_signer_public_key
589
660
  .is_some();
@@ -595,7 +666,7 @@ impl NativePeerbitBackbone {
595
666
  let byte_element_index_limit = document_index_commit.byte_element_index_limit;
596
667
  let known_existing = document_index_commit.known_existing;
597
668
  let profile_enabled = self.append_profile_enabled;
598
- let context_started = profile_enabled.then(js_sys::Date::now);
669
+ let context_started = profile_enabled.then(crate::time::now_ms);
599
670
  let context_suffix = encode_document_context_suffix(
600
671
  document_index_commit.existing_created.unwrap_or(wall_time),
601
672
  wall_time,
@@ -604,7 +675,7 @@ impl NativePeerbitBackbone {
604
675
  payload_size,
605
676
  )?;
606
677
  if let Some(started) = context_started {
607
- self.append_profile.document_index_context_encode_ms += js_sys::Date::now() - started;
678
+ self.append_profile.document_index_context_encode_ms += crate::time::now_ms() - started;
608
679
  }
609
680
  let value_prefix_bytes = match document_index_commit.value_prefix {
610
681
  DocumentIndexValuePrefix::Bytes(bytes) => bytes,
@@ -626,9 +697,10 @@ impl NativePeerbitBackbone {
626
697
  )?
627
698
  }
628
699
  DocumentIndexProjectionPlan::Cached(index) => {
629
- let plan = self.document_projection_plans.get(index).ok_or_else(|| {
630
- JsValue::from_str("Missing cached document projection plan")
631
- })?;
700
+ let plan = self
701
+ .document_projection_plans
702
+ .get(index)
703
+ .ok_or(BackboneError::MissingCachedDocumentProjectionPlan)?;
632
704
  project_document_index_simple_bytes_with_plan(
633
705
  &encoded_document,
634
706
  plan,
@@ -644,15 +716,13 @@ impl NativePeerbitBackbone {
644
716
  DocumentIndexValuePrefix::PlainPutPayloadIdentity => plain_put_payload_data
645
717
  .map(plain_put_document_bytes_from_payload)
646
718
  .transpose()?
647
- .ok_or_else(|| JsValue::from_str("Missing plain put payload for document index"))?
719
+ .ok_or(BackboneError::MissingPlainPutPayloadForDocumentIndex)?
648
720
  .to_vec(),
649
721
  DocumentIndexValuePrefix::PlainPutPayloadProjection { plan, signer } => {
650
722
  let encoded_document = plain_put_payload_data
651
723
  .map(plain_put_document_bytes_from_payload)
652
724
  .transpose()?
653
- .ok_or_else(|| {
654
- JsValue::from_str("Missing plain put payload for document projection")
655
- })?;
725
+ .ok_or(BackboneError::MissingPlainPutPayloadForDocumentProjection)?;
656
726
  match plan {
657
727
  DocumentIndexProjectionPlan::Inline(plan) => {
658
728
  project_document_index_simple_bytes_with_plan(
@@ -667,9 +737,10 @@ impl NativePeerbitBackbone {
667
737
  )?
668
738
  }
669
739
  DocumentIndexProjectionPlan::Cached(index) => {
670
- let plan = self.document_projection_plans.get(index).ok_or_else(|| {
671
- JsValue::from_str("Missing cached document projection plan")
672
- })?;
740
+ let plan = self
741
+ .document_projection_plans
742
+ .get(index)
743
+ .ok_or(BackboneError::MissingCachedDocumentProjectionPlan)?;
673
744
  project_document_index_simple_bytes_with_plan(
674
745
  encoded_document,
675
746
  plan,
@@ -719,10 +790,10 @@ impl NativePeerbitBackbone {
719
790
  if heads.is_empty() {
720
791
  return false;
721
792
  }
722
- let started = self.append_profile_enabled.then(js_sys::Date::now);
793
+ let started = self.append_profile_enabled.then(crate::time::now_ms);
723
794
  let deleted = self.delete_documents_by_context_heads(heads);
724
795
  if let Some(started) = started {
725
- self.append_profile.document_index_trim_delete_ms += js_sys::Date::now() - started;
796
+ self.append_profile.document_index_trim_delete_ms += crate::time::now_ms() - started;
726
797
  }
727
798
  deleted
728
799
  }
@@ -749,3 +820,34 @@ impl NativePeerbitBackbone {
749
820
  true
750
821
  }
751
822
  }
823
+
824
+ #[cfg(test)]
825
+ mod tests {
826
+ use crate::error::BackboneError;
827
+
828
+ #[test]
829
+ fn compact_coordinate_mismatch_variant_renders_exact_message() {
830
+ for label in [
831
+ "Native no-next compact batch returned mismatched coordinate facts",
832
+ "Native latest batch returned mismatched coordinate facts",
833
+ "Native compact batch returned mismatched coordinate facts",
834
+ ] {
835
+ assert_eq!(
836
+ BackboneError::MismatchedCompactCoordinateFacts(label).to_string(),
837
+ label
838
+ );
839
+ }
840
+ }
841
+
842
+ #[test]
843
+ fn batch_gid_variants_render_exact_messages() {
844
+ assert_eq!(
845
+ BackboneError::ExpectedString("batch gid").to_string(),
846
+ "Expected batch gid string"
847
+ );
848
+ assert_eq!(
849
+ BackboneError::ExpectedString("batch fallback gid").to_string(),
850
+ "Expected batch fallback gid string"
851
+ );
852
+ }
853
+ }