@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,751 @@
1
+ use js_sys::{Array, BigUint64Array, Uint32Array, Uint8Array};
2
+ use peerbit_indexer_core::planner::{FieldPath, FieldValue};
3
+ use peerbit_log_rust::{LogIndexEntry, NativeCommittedEntryFacts, NativeLogAppendProfile};
4
+ use peerbit_shared_log_rust::{
5
+ commit_local_appends_for_gids_compact_core, NativeLocalAppendCompactFacts,
6
+ NativeLocalAppendCompactInput,
7
+ };
8
+ use wasm_bindgen::prelude::*;
9
+
10
+ use crate::documents::{
11
+ document_context_facts_to_row, encode_document_context_suffix,
12
+ plain_put_document_bytes_from_payload, project_document_index_simple_bytes_with_plan,
13
+ DocumentContextFacts, DocumentIndexAppendCommit, DocumentIndexProjectionPlan,
14
+ DocumentIndexValuePrefix, PreparedDocumentIndexAppendPut,
15
+ };
16
+ use crate::js_interop::{
17
+ array_from_value, ensure_same_len, hash_number_u64, number_to_row, numbers_to_rows,
18
+ optional_bytes_from_js, string_field, strings_to_array,
19
+ };
20
+ use crate::shared_log_plan::leader_samples_to_optional_rows;
21
+ use crate::NativePeerbitBackbone;
22
+
23
+ mod committed_latest;
24
+ mod committed_no_next;
25
+ mod facts;
26
+ mod storage;
27
+
28
+ struct LatestCompactBatchPendingAppend {
29
+ wall_time: u64,
30
+ payload_size: u32,
31
+ gid: String,
32
+ entry_facts: NativeCommittedEntryFacts,
33
+ trim_hashes: Vec<String>,
34
+ document_index_commit: DocumentIndexAppendCommit,
35
+ previous_document_context: Option<DocumentContextFacts>,
36
+ delete_trimmed_document_heads: bool,
37
+ plain_put_payload_data: Option<Vec<u8>>,
38
+ materialization_bytes: Option<Uint8Array>,
39
+ }
40
+
41
+ struct LatestBatchPendingAppend {
42
+ wall_time: u64,
43
+ next_hashes: Vec<String>,
44
+ meta_bytes: Vec<u8>,
45
+ trim_hashes: Vec<String>,
46
+ entry_row: Array,
47
+ trim_rows: Array,
48
+ document_trimmed_heads_processed: bool,
49
+ previous_document_context: Option<DocumentContextFacts>,
50
+ }
51
+
52
+ fn committed_entry_facts_to_row(entry: &NativeCommittedEntryFacts, include_next: bool) -> Array {
53
+ let row = Array::new();
54
+ row.push(&JsValue::from_str(&entry.hash));
55
+ if include_next {
56
+ row.push(&strings_to_array(entry.next.clone()));
57
+ }
58
+ row.push(&Uint8Array::from(entry.meta_bytes.as_slice()));
59
+ row.push(&JsValue::from_f64(entry.byte_length as f64));
60
+ row.push(&Uint8Array::from(entry.hash_digest_bytes.as_slice()));
61
+ row
62
+ }
63
+
64
+ fn compact_committed_entry_facts_trim_hashes_to_row(
65
+ entry: &NativeCommittedEntryFacts,
66
+ trim_hashes: Vec<String>,
67
+ document_trimmed_heads_processed: bool,
68
+ ) -> Array {
69
+ let row = Array::new();
70
+ row.push(&JsValue::from_str(&entry.hash));
71
+ row.push(&JsValue::from_f64(entry.byte_length as f64));
72
+ row.push(&Uint8Array::from(entry.meta_bytes.as_slice()));
73
+ row.push(&Uint8Array::from(entry.hash_digest_bytes.as_slice()));
74
+ row.push(&strings_to_array(trim_hashes));
75
+ row.push(&JsValue::from_bool(document_trimmed_heads_processed));
76
+ row
77
+ }
78
+
79
+ fn no_next_compact_entry_row(
80
+ resolution: &str,
81
+ entry_facts: &NativeCommittedEntryFacts,
82
+ coordinate_facts: &NativeLocalAppendCompactFacts,
83
+ ) -> Array {
84
+ let row = Array::new();
85
+ row.push(&JsValue::from_str(&entry_facts.hash));
86
+ row.push(&JsValue::from_f64(entry_facts.byte_length as f64));
87
+ row.push(&Uint8Array::from(entry_facts.meta_bytes.as_slice()));
88
+ row.push(&number_to_row(
89
+ resolution,
90
+ coordinate_facts.coordinate.hash_number,
91
+ ));
92
+ row.push(&JsValue::from_str(&coordinate_facts.coordinate.gid));
93
+ row.push(&numbers_to_rows(
94
+ resolution,
95
+ &coordinate_facts.coordinate.coordinates,
96
+ ));
97
+ row.push(&JsValue::from_bool(
98
+ coordinate_facts.coordinate.assigned_to_range_boundary,
99
+ ));
100
+ row.push(&JsValue::from_f64(
101
+ coordinate_facts.coordinate.requested_replicas as f64,
102
+ ));
103
+ row.push(&leader_samples_to_optional_rows(&coordinate_facts.leaders));
104
+ row.push(&JsValue::from_bool(coordinate_facts.is_leader));
105
+ row
106
+ }
107
+
108
+ fn latest_compact_entry_row(
109
+ resolution: &str,
110
+ entry_facts: NativeCommittedEntryFacts,
111
+ coordinate_facts: &NativeLocalAppendCompactFacts,
112
+ trim_hashes: Vec<String>,
113
+ document_trimmed_heads_processed: bool,
114
+ previous_document_context: Option<&DocumentContextFacts>,
115
+ ) -> Array {
116
+ let row = Array::new();
117
+ row.push(&JsValue::from_str(&entry_facts.hash));
118
+ row.push(&JsValue::from_f64(entry_facts.byte_length as f64));
119
+ row.push(&Uint8Array::from(entry_facts.meta_bytes.as_slice()));
120
+ row.push(&Uint8Array::from(entry_facts.hash_digest_bytes.as_slice()));
121
+ row.push(&strings_to_array(entry_facts.next));
122
+ row.push(&coordinate_plan_to_row(resolution, coordinate_facts));
123
+ row.push(&leader_samples_to_optional_rows(&coordinate_facts.leaders));
124
+ row.push(&JsValue::from_bool(coordinate_facts.is_leader));
125
+ row.push(&strings_to_array(trim_hashes));
126
+ row.push(&JsValue::from_bool(document_trimmed_heads_processed));
127
+ row.push(
128
+ &previous_document_context
129
+ .map(|context| document_context_facts_to_row(context).into())
130
+ .unwrap_or(JsValue::UNDEFINED),
131
+ );
132
+ row
133
+ }
134
+
135
+ fn native_backbone_trim_entries_to_rows(values: Vec<LogIndexEntry>) -> Array {
136
+ let out = Array::new();
137
+ for entry in values {
138
+ let row = Array::new();
139
+ row.push(&JsValue::from_str(&entry.hash));
140
+ row.push(&JsValue::from_str(&entry.gid));
141
+ row.push(&strings_to_array(entry.next));
142
+ row.push(&JsValue::from_f64(entry.entry_type as f64));
143
+ row.push(&JsValue::from_str(&entry.wall_time.to_string()));
144
+ row.push(&JsValue::from_f64(entry.logical as f64));
145
+ row.push(&JsValue::from_f64(entry.payload_size as f64));
146
+ match entry.data {
147
+ Some(data) => row.push(&Uint8Array::from(data.as_slice())),
148
+ None => row.push(&JsValue::UNDEFINED),
149
+ };
150
+ out.push(&row);
151
+ }
152
+ out
153
+ }
154
+
155
+ fn coordinate_plan_to_row(resolution: &str, facts: &NativeLocalAppendCompactFacts) -> Array {
156
+ let coordinate = &facts.coordinate;
157
+ let out = Array::new();
158
+ out.push(&JsValue::from_str(&coordinate.hash));
159
+ out.push(&number_to_row(resolution, coordinate.hash_number));
160
+ out.push(&JsValue::from_str(&coordinate.gid));
161
+ out.push(&numbers_to_rows(resolution, &coordinate.coordinates));
162
+ out.push(&JsValue::from_bool(coordinate.assigned_to_range_boundary));
163
+ out.push(&JsValue::from_f64(coordinate.requested_replicas as f64));
164
+ out
165
+ }
166
+
167
+ fn push_optional_trim_result(row: &Array, trim_hashes: Vec<String>, document_trimmed: bool) {
168
+ if trim_hashes.is_empty() {
169
+ if document_trimmed {
170
+ row.push(&JsValue::UNDEFINED);
171
+ row.push(&JsValue::TRUE);
172
+ }
173
+ return;
174
+ }
175
+ row.push(&strings_to_array(trim_hashes));
176
+ if document_trimmed {
177
+ row.push(&JsValue::TRUE);
178
+ }
179
+ }
180
+
181
+ fn ensure_batch_append_lens(
182
+ len: usize,
183
+ wall_times: &BigUint64Array,
184
+ logicals: &Uint32Array,
185
+ gids: &Array,
186
+ gids_label: &str,
187
+ meta_datas: &Array,
188
+ document_keys: &Array,
189
+ ) -> Result<(), JsValue> {
190
+ ensure_same_len(len, wall_times.length() as usize, "batch wall times")?;
191
+ ensure_same_len(len, logicals.length() as usize, "batch logicals")?;
192
+ ensure_same_len(len, gids.length() as usize, gids_label)?;
193
+ ensure_same_len(len, meta_datas.length() as usize, "batch meta data")?;
194
+ ensure_same_len(len, document_keys.length() as usize, "batch document keys")?;
195
+ Ok(())
196
+ }
197
+
198
+ fn ensure_batch_projection_lens(
199
+ len: usize,
200
+ plan_ids: &Uint32Array,
201
+ encoded_documents: Option<&Array>,
202
+ signers: &Array,
203
+ ) -> Result<(), JsValue> {
204
+ ensure_same_len(
205
+ len,
206
+ plan_ids.length() as usize,
207
+ "batch document projection plan ids",
208
+ )?;
209
+ if let Some(encoded_documents) = encoded_documents {
210
+ ensure_same_len(
211
+ len,
212
+ encoded_documents.length() as usize,
213
+ "batch document projection encoded documents",
214
+ )?;
215
+ }
216
+ ensure_same_len(
217
+ len,
218
+ signers.length() as usize,
219
+ "batch document projection signers",
220
+ )?;
221
+ Ok(())
222
+ }
223
+
224
+ fn required_projection_encoded_document(
225
+ encoded_documents: &Array,
226
+ index: u32,
227
+ ) -> Result<JsValue, JsValue> {
228
+ let encoded_document = encoded_documents.get(index);
229
+ if encoded_document.is_undefined() || encoded_document.is_null() {
230
+ return Err(JsValue::from_str(
231
+ "Expected batch document projection encoded document",
232
+ ));
233
+ }
234
+ Ok(encoded_document)
235
+ }
236
+
237
+ impl NativePeerbitBackbone {
238
+ fn copy_append_inputs_profiled(
239
+ &mut self,
240
+ meta_data: JsValue,
241
+ 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);
245
+ let payload_data = payload_data.to_vec();
246
+ if let Some(started) = input_copy_started {
247
+ self.append_profile.input_copy_ms += js_sys::Date::now() - started;
248
+ }
249
+ (meta_data, payload_data)
250
+ }
251
+
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);
254
+ let hash_number = hash_number_u64(&self.resolution, hash_digest_bytes)?;
255
+ if let Some(started) = hash_number_started {
256
+ self.append_profile.hash_number_ms += js_sys::Date::now() - started;
257
+ }
258
+ Ok(hash_number)
259
+ }
260
+
261
+ #[allow(clippy::too_many_arguments)]
262
+ fn prepare_no_next_log_append_profiled(
263
+ &mut self,
264
+ wall_time: u64,
265
+ logical: u32,
266
+ gid: String,
267
+ entry_type: u8,
268
+ meta_data: Option<Vec<u8>>,
269
+ payload_data: &[u8],
270
+ trim_length_to: Option<usize>,
271
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>), JsValue> {
272
+ let profile_enabled = self.append_profile_enabled;
273
+ let log_started = profile_enabled.then(js_sys::Date::now);
274
+ let mut log_profile = NativeLogAppendProfile::default();
275
+ let result = if let Some(trim_length_to) = trim_length_to {
276
+ self.log
277
+ .prepare_entry_v0_plain_entry_commit_no_next_facts_core_profiled_and_put_with_builder_trim_hashes_borrowed(
278
+ &self.builder,
279
+ &mut self.blocks,
280
+ wall_time,
281
+ logical,
282
+ gid,
283
+ entry_type,
284
+ meta_data,
285
+ payload_data,
286
+ trim_length_to,
287
+ profile_enabled.then_some(&mut log_profile),
288
+ )?
289
+ } else {
290
+ (
291
+ self.log
292
+ .prepare_entry_v0_plain_entry_commit_no_next_facts_core_profiled_and_put_with_builder_borrowed(
293
+ &self.builder,
294
+ &mut self.blocks,
295
+ wall_time,
296
+ logical,
297
+ gid,
298
+ entry_type,
299
+ meta_data,
300
+ payload_data,
301
+ profile_enabled.then_some(&mut log_profile),
302
+ )?,
303
+ Vec::new(),
304
+ )
305
+ };
306
+ if let Some(started) = log_started {
307
+ self.append_profile.log_total_ms += js_sys::Date::now() - started;
308
+ self.append_profile.add_log_profile(&log_profile);
309
+ }
310
+ Ok(result)
311
+ }
312
+
313
+ #[allow(clippy::too_many_arguments)]
314
+ fn prepare_latest_log_append_profiled(
315
+ &mut self,
316
+ wall_time: u64,
317
+ logical: u32,
318
+ gid: String,
319
+ next_hashes: Vec<String>,
320
+ entry_type: u8,
321
+ meta_data: Option<Vec<u8>>,
322
+ payload_data: &[u8],
323
+ trim_length_to: Option<usize>,
324
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>), JsValue> {
325
+ let profile_enabled = self.append_profile_enabled;
326
+ let log_started = profile_enabled.then(js_sys::Date::now);
327
+ let mut log_profile = NativeLogAppendProfile::default();
328
+ let result = self
329
+ .log
330
+ .prepare_entry_v0_plain_entry_commit_facts_core_profiled_and_put_with_builder_trim_hashes_borrowed(
331
+ &self.builder,
332
+ &mut self.blocks,
333
+ wall_time,
334
+ logical,
335
+ gid,
336
+ next_hashes,
337
+ entry_type,
338
+ meta_data,
339
+ payload_data,
340
+ trim_length_to,
341
+ profile_enabled.then_some(&mut log_profile),
342
+ )?;
343
+ if let Some(started) = log_started {
344
+ self.append_profile.log_total_ms += js_sys::Date::now() - started;
345
+ self.append_profile.add_log_profile(&log_profile);
346
+ }
347
+ Ok(result)
348
+ }
349
+
350
+ #[allow(clippy::too_many_arguments)]
351
+ fn prepare_committed_log_append_rows_profiled(
352
+ &mut self,
353
+ wall_time: u64,
354
+ logical: u32,
355
+ gid: String,
356
+ next_hashes: Vec<String>,
357
+ entry_type: u8,
358
+ meta_data: Option<Vec<u8>>,
359
+ payload_data: Vec<u8>,
360
+ trim_length_to: Option<usize>,
361
+ resolve_trimmed_entries: bool,
362
+ ) -> Result<(NativeCommittedEntryFacts, Vec<String>, Array, Array), JsValue> {
363
+ let profile_enabled = self.append_profile_enabled;
364
+ let log_started = profile_enabled.then(js_sys::Date::now);
365
+ let mut log_profile = NativeLogAppendProfile::default();
366
+ let (entry_facts, trimmed_entries, trim_hashes) = if resolve_trimmed_entries {
367
+ let (entry_facts, trimmed_entries) = self
368
+ .log
369
+ .prepare_entry_v0_plain_entry_commit_facts_core_profiled_and_put_with_builder(
370
+ &self.builder,
371
+ &mut self.blocks,
372
+ wall_time,
373
+ logical,
374
+ gid,
375
+ next_hashes,
376
+ entry_type,
377
+ meta_data,
378
+ payload_data,
379
+ trim_length_to,
380
+ profile_enabled.then_some(&mut log_profile),
381
+ )?;
382
+ let trim_hashes = trimmed_entries
383
+ .iter()
384
+ .map(|entry| entry.hash.clone())
385
+ .collect::<Vec<_>>();
386
+ (entry_facts, trimmed_entries, trim_hashes)
387
+ } else {
388
+ let (entry_facts, trim_hashes) = self
389
+ .log
390
+ .prepare_entry_v0_plain_entry_commit_facts_core_profiled_and_put_with_builder_trim_hashes(
391
+ &self.builder,
392
+ &mut self.blocks,
393
+ wall_time,
394
+ logical,
395
+ gid,
396
+ next_hashes,
397
+ entry_type,
398
+ meta_data,
399
+ payload_data,
400
+ trim_length_to,
401
+ profile_enabled.then_some(&mut log_profile),
402
+ )?;
403
+ (entry_facts, Vec::new(), trim_hashes)
404
+ };
405
+ if let Some(started) = log_started {
406
+ self.append_profile.log_total_ms += js_sys::Date::now() - started;
407
+ self.append_profile.add_log_profile(&log_profile);
408
+ }
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());
411
+ if let Some(started) = entry_row_started {
412
+ self.append_profile.entry_row_ms += js_sys::Date::now() - started;
413
+ }
414
+ let trim_rows_started = profile_enabled.then(js_sys::Date::now);
415
+ let trim_rows = if resolve_trimmed_entries {
416
+ native_backbone_trim_entries_to_rows(trimmed_entries)
417
+ } else {
418
+ Array::new()
419
+ };
420
+ if let Some(started) = trim_rows_started {
421
+ self.append_profile.trim_rows_ms += js_sys::Date::now() - started;
422
+ }
423
+ Ok((entry_facts, trim_hashes, entry_row, trim_rows))
424
+ }
425
+
426
+ #[allow(clippy::too_many_arguments)]
427
+ fn plan_batch_compact_coordinates_profiled(
428
+ &mut self,
429
+ coordinate_inputs: Vec<NativeLocalAppendCompactInput>,
430
+ replicas: usize,
431
+ role_age_ms: f64,
432
+ now: &str,
433
+ self_hash: &str,
434
+ self_replicating: bool,
435
+ 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);
439
+ let coordinate_facts = commit_local_appends_for_gids_compact_core(
440
+ &mut self.shared_log,
441
+ coordinate_inputs,
442
+ replicas,
443
+ role_age_ms,
444
+ now,
445
+ self_hash,
446
+ self_replicating,
447
+ true,
448
+ true,
449
+ )?;
450
+ if let Some(started) = coordinate_plan_started {
451
+ self.append_profile.coordinate_plan_ms += js_sys::Date::now() - started;
452
+ }
453
+ if coordinate_facts.len() != expected_len {
454
+ return Err(JsValue::from_str(mismatch_label));
455
+ }
456
+ Ok(coordinate_facts)
457
+ }
458
+
459
+ #[allow(clippy::too_many_arguments)]
460
+ fn commit_append_document_index_profiled(
461
+ &mut self,
462
+ document_index_commit: Option<DocumentIndexAppendCommit>,
463
+ wall_time: u64,
464
+ hash: &str,
465
+ gid: &str,
466
+ payload_size: u32,
467
+ plain_put_payload_data: Option<&[u8]>,
468
+ delete_trimmed_document_heads: bool,
469
+ trim_hashes: &[String],
470
+ ) -> Result<bool, JsValue> {
471
+ let document_index_started = self.append_profile_enabled.then(js_sys::Date::now);
472
+ self.put_document_index_for_append_with_plain_put_payload(
473
+ document_index_commit,
474
+ wall_time,
475
+ hash,
476
+ gid,
477
+ payload_size,
478
+ plain_put_payload_data,
479
+ )?;
480
+ let document_trimmed_heads_processed = delete_trimmed_document_heads
481
+ && self.delete_documents_by_context_heads_profiled(trim_hashes);
482
+ if let Some(started) = document_index_started {
483
+ self.append_profile.document_index_commit_ms += js_sys::Date::now() - started;
484
+ }
485
+ Ok(document_trimmed_heads_processed)
486
+ }
487
+
488
+ fn resolve_latest_document_append_context(
489
+ &self,
490
+ document_index_commit: &mut DocumentIndexAppendCommit,
491
+ fallback_gid: String,
492
+ ) -> Result<(Option<DocumentContextFacts>, String, Vec<String>), JsValue> {
493
+ let previous_context = self.document_context_facts_by_key(&document_index_commit.key)?;
494
+ let known_existing = previous_context.is_some();
495
+ let gid = previous_context
496
+ .as_ref()
497
+ .map(|context| context.gid.clone())
498
+ .unwrap_or(fallback_gid);
499
+ let next_hashes = previous_context
500
+ .as_ref()
501
+ .map(|context| vec![context.head.clone()])
502
+ .unwrap_or_default();
503
+ if document_index_commit.existing_created.is_none() {
504
+ document_index_commit.existing_created =
505
+ previous_context.as_ref().map(|context| context.created);
506
+ }
507
+ document_index_commit.previous_context = previous_context.clone();
508
+ document_index_commit.known_existing = known_existing;
509
+ Ok((previous_context, gid, next_hashes))
510
+ }
511
+
512
+ fn put_document_index_for_facts_row(
513
+ &mut self,
514
+ row: &Array,
515
+ document_index_commit: DocumentIndexAppendCommit,
516
+ wall_time: u64,
517
+ document_gid: &str,
518
+ payload_size: u32,
519
+ ) -> Result<(), JsValue> {
520
+ let entry_row = if row.length() == 2 && Array::is_array(&row.get(0)) {
521
+ array_from_value(row.get(0), "native trim document index entry row")?
522
+ } else {
523
+ row.clone()
524
+ };
525
+ let document_hash = string_field(&entry_row, 0, "document index entry hash")?;
526
+ self.put_document_index_for_append(
527
+ Some(document_index_commit),
528
+ wall_time,
529
+ &document_hash,
530
+ document_gid,
531
+ payload_size,
532
+ )
533
+ }
534
+
535
+ fn put_document_index_for_append(
536
+ &mut self,
537
+ document_index_commit: Option<DocumentIndexAppendCommit>,
538
+ wall_time: u64,
539
+ hash: &str,
540
+ gid: &str,
541
+ payload_size: u32,
542
+ ) -> Result<(), JsValue> {
543
+ self.put_document_index_for_append_with_plain_put_payload(
544
+ document_index_commit,
545
+ wall_time,
546
+ hash,
547
+ gid,
548
+ payload_size,
549
+ None,
550
+ )
551
+ }
552
+
553
+ fn put_document_index_for_append_with_plain_put_payload(
554
+ &mut self,
555
+ document_index_commit: Option<DocumentIndexAppendCommit>,
556
+ wall_time: u64,
557
+ hash: &str,
558
+ gid: &str,
559
+ payload_size: u32,
560
+ plain_put_payload_data: Option<&[u8]>,
561
+ ) -> Result<(), JsValue> {
562
+ let Some(document_index_commit) = document_index_commit else {
563
+ return Ok(());
564
+ };
565
+ let prepared = self.prepare_document_index_append_put(
566
+ document_index_commit,
567
+ wall_time,
568
+ hash,
569
+ gid,
570
+ payload_size,
571
+ plain_put_payload_data,
572
+ )?;
573
+ self.commit_prepared_document_index_append_put(prepared);
574
+ Ok(())
575
+ }
576
+
577
+ #[allow(clippy::too_many_arguments)]
578
+ fn prepare_document_index_append_put(
579
+ &mut self,
580
+ document_index_commit: DocumentIndexAppendCommit,
581
+ wall_time: u64,
582
+ hash: &str,
583
+ gid: &str,
584
+ payload_size: u32,
585
+ plain_put_payload_data: Option<&[u8]>,
586
+ ) -> Result<PreparedDocumentIndexAppendPut, JsValue> {
587
+ let record_previous_signer = document_index_commit
588
+ .required_previous_signer_public_key
589
+ .is_some();
590
+ let key = document_index_commit.key;
591
+ let previous_head = document_index_commit
592
+ .previous_context
593
+ .as_ref()
594
+ .map(|context| context.head.clone());
595
+ let byte_element_index_limit = document_index_commit.byte_element_index_limit;
596
+ let known_existing = document_index_commit.known_existing;
597
+ let profile_enabled = self.append_profile_enabled;
598
+ let context_started = profile_enabled.then(js_sys::Date::now);
599
+ let context_suffix = encode_document_context_suffix(
600
+ document_index_commit.existing_created.unwrap_or(wall_time),
601
+ wall_time,
602
+ hash,
603
+ gid,
604
+ payload_size,
605
+ )?;
606
+ if let Some(started) = context_started {
607
+ self.append_profile.document_index_context_encode_ms += js_sys::Date::now() - started;
608
+ }
609
+ let value_prefix_bytes = match document_index_commit.value_prefix {
610
+ DocumentIndexValuePrefix::Bytes(bytes) => bytes,
611
+ DocumentIndexValuePrefix::Projection {
612
+ encoded_document,
613
+ plan,
614
+ signer,
615
+ } => match plan {
616
+ DocumentIndexProjectionPlan::Inline(plan) => {
617
+ project_document_index_simple_bytes_with_plan(
618
+ &encoded_document,
619
+ &plan,
620
+ document_index_commit.existing_created.unwrap_or(wall_time),
621
+ wall_time,
622
+ hash,
623
+ gid,
624
+ payload_size,
625
+ signer.as_deref(),
626
+ )?
627
+ }
628
+ 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
+ })?;
632
+ project_document_index_simple_bytes_with_plan(
633
+ &encoded_document,
634
+ plan,
635
+ document_index_commit.existing_created.unwrap_or(wall_time),
636
+ wall_time,
637
+ hash,
638
+ gid,
639
+ payload_size,
640
+ signer.as_deref(),
641
+ )?
642
+ }
643
+ },
644
+ DocumentIndexValuePrefix::PlainPutPayloadIdentity => plain_put_payload_data
645
+ .map(plain_put_document_bytes_from_payload)
646
+ .transpose()?
647
+ .ok_or_else(|| JsValue::from_str("Missing plain put payload for document index"))?
648
+ .to_vec(),
649
+ DocumentIndexValuePrefix::PlainPutPayloadProjection { plan, signer } => {
650
+ let encoded_document = plain_put_payload_data
651
+ .map(plain_put_document_bytes_from_payload)
652
+ .transpose()?
653
+ .ok_or_else(|| {
654
+ JsValue::from_str("Missing plain put payload for document projection")
655
+ })?;
656
+ match plan {
657
+ DocumentIndexProjectionPlan::Inline(plan) => {
658
+ project_document_index_simple_bytes_with_plan(
659
+ encoded_document,
660
+ &plan,
661
+ document_index_commit.existing_created.unwrap_or(wall_time),
662
+ wall_time,
663
+ hash,
664
+ gid,
665
+ payload_size,
666
+ signer.as_deref(),
667
+ )?
668
+ }
669
+ 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
+ })?;
673
+ project_document_index_simple_bytes_with_plan(
674
+ encoded_document,
675
+ plan,
676
+ document_index_commit.existing_created.unwrap_or(wall_time),
677
+ wall_time,
678
+ hash,
679
+ gid,
680
+ payload_size,
681
+ signer.as_deref(),
682
+ )?
683
+ }
684
+ }
685
+ }
686
+ };
687
+ let parts = self.prepare_document_encoded_parts_put(
688
+ key,
689
+ value_prefix_bytes,
690
+ context_suffix,
691
+ byte_element_index_limit,
692
+ known_existing,
693
+ Some(hash),
694
+ previous_head.as_deref(),
695
+ true,
696
+ )?;
697
+ Ok(PreparedDocumentIndexAppendPut {
698
+ parts,
699
+ previous_signer_head: record_previous_signer.then(|| hash.to_string()),
700
+ })
701
+ }
702
+
703
+ fn commit_prepared_document_index_append_put(
704
+ &mut self,
705
+ prepared: PreparedDocumentIndexAppendPut,
706
+ ) {
707
+ let PreparedDocumentIndexAppendPut {
708
+ parts,
709
+ previous_signer_head,
710
+ } = prepared;
711
+ let previous_signer = previous_signer_head.map(|head| (parts.key.clone(), head));
712
+ self.commit_prepared_document_encoded_parts_put(parts);
713
+ if let Some((key, head)) = previous_signer {
714
+ self.put_document_previous_signer_fact(key, head, self.local_public_key.clone(), true);
715
+ }
716
+ }
717
+
718
+ fn delete_documents_by_context_heads_profiled(&mut self, heads: &[String]) -> bool {
719
+ if heads.is_empty() {
720
+ return false;
721
+ }
722
+ let started = self.append_profile_enabled.then(js_sys::Date::now);
723
+ let deleted = self.delete_documents_by_context_heads(heads);
724
+ if let Some(started) = started {
725
+ self.append_profile.document_index_trim_delete_ms += js_sys::Date::now() - started;
726
+ }
727
+ deleted
728
+ }
729
+
730
+ fn delete_documents_by_context_heads(&mut self, heads: &[String]) -> bool {
731
+ if heads.is_empty() {
732
+ return false;
733
+ }
734
+ let Some(field) = self.document_context_head_field else {
735
+ return false;
736
+ };
737
+ for head in heads {
738
+ if let Some(key) = self.document_key_by_head.remove(head) {
739
+ self.delete_document_inner(&key, true);
740
+ continue;
741
+ }
742
+ if let Some(key) = self
743
+ .document_index
744
+ .exact_first(&FieldPath::Id(field), &FieldValue::from(head.clone()))
745
+ {
746
+ self.delete_document_inner(&key, true);
747
+ }
748
+ }
749
+ true
750
+ }
751
+ }