1use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9use serde_json::{Map, Value};
10
11use crate::previous_version::PreviousVersionContextConfig;
12pub type QueryValue = Value;
14pub type QueryRow = Map<String, QueryValue>;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct FetchedBlob {
20 pub blob_id: String,
21 pub byte_length: i64,
22 pub bytes: Vec<u8>,
23 pub media_type: Option<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum TimeBucketUnit {
28 Month,
29}
30
31const MAX_TIME_BUCKET_MS: i64 = 253_402_300_799_999;
32
33fn utc_year_month(timestamp_ms: i64) -> Result<(i64, i64), String> {
34 if !(0..=MAX_TIME_BUCKET_MS).contains(×tamp_ms) {
35 return Err(
36 "sync.invalid_request: time bucket timestamps must fall from 1970 through 9999"
37 .to_owned(),
38 );
39 }
40 let z = timestamp_ms / 86_400_000 + 719_468;
41 let era = z.div_euclid(146_097);
42 let day_of_era = z - era * 146_097;
43 let year_of_era =
44 (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
45 let mut year = year_of_era + era * 400;
46 let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
47 let month_prime = (5 * day_of_year + 2) / 153;
48 let month = month_prime + if month_prime < 10 { 3 } else { -9 };
49 if month <= 2 {
50 year += 1;
51 }
52 Ok((year, month))
53}
54
55pub fn creation_time_bucket(created_at_ms: i64, unit: TimeBucketUnit) -> Result<String, String> {
57 match unit {
58 TimeBucketUnit::Month => {
59 let (year, month) = utc_year_month(created_at_ms)?;
60 Ok(format!("{year:04}-{month:02}"))
61 }
62 }
63}
64
65pub fn last(count: usize, unit: TimeBucketUnit, now_ms: i64) -> Result<Vec<String>, String> {
67 if !(1..=1_200).contains(&count) {
68 return Err(
69 "sync.invalid_request: time bucket count must be from 1 through 1200".to_owned(),
70 );
71 }
72 match unit {
73 TimeBucketUnit::Month => {
74 let (year, month) = utc_year_month(now_ms)?;
75 let current = year * 12 + month - 1;
76 if current - (count as i64 - 1) < 1970 * 12 {
77 return Err(
78 "sync.invalid_request: every returned UTC month must fall from 1970 through 9999"
79 .to_owned(),
80 );
81 }
82 Ok((0..count)
83 .rev()
84 .map(|offset| {
85 let value = current - offset as i64;
86 format!(
87 "{:04}-{:02}",
88 value.div_euclid(12),
89 value.rem_euclid(12) + 1
90 )
91 })
92 .collect())
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
101pub struct WindowBase {
102 pub table: String,
103 pub variable: String,
104 pub fixed_scopes: Vec<(String, Vec<String>)>,
106 pub params: Option<String>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
116#[serde(rename_all = "camelCase")]
117pub struct WindowState {
118 pub units: Vec<String>,
120 pub pending: Vec<String>,
122}
123
124#[cfg(test)]
125mod time_bucket_tests {
126 use super::{creation_time_bucket, last, TimeBucketUnit};
127
128 #[test]
129 fn utc_month_buckets_cross_year_boundaries() {
130 let now = 1_770_508_800_000; assert_eq!(
132 creation_time_bucket(now, TimeBucketUnit::Month).unwrap(),
133 "2026-02"
134 );
135 assert_eq!(
136 last(3, TimeBucketUnit::Month, now).unwrap(),
137 vec!["2025-12", "2026-01", "2026-02"]
138 );
139 }
140
141 #[test]
142 fn utc_month_buckets_reject_unbounded_input() {
143 assert!(last(0, TimeBucketUnit::Month, 0).is_err());
144 assert!(last(1_201, TimeBucketUnit::Month, 0).is_err());
145 assert!(last(2, TimeBucketUnit::Month, 0).is_err());
146 assert!(creation_time_bucket(-1, TimeBucketUnit::Month).is_err());
147 }
148}
149
150#[derive(Debug, Clone, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct TableChange {
153 pub table: String,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub scope_keys: Option<Vec<String>>,
156}
157
158#[derive(Debug, Clone, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct WindowChange {
161 pub base_key: String,
162 pub table: String,
163 pub units: Vec<String>,
164}
165
166#[derive(Debug, Clone, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct SyncStatusSnapshot {
169 pub current_schema_version: i32,
170 pub outbox: usize,
171 pub upgrading: bool,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub lease_state: Option<LeaseState>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub schema_floor: Option<SchemaFloor>,
176 pub sync_needed: bool,
177 pub previous_version_context: PreviousVersionStatus,
180}
181
182#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq)]
184#[serde(rename_all = "camelCase")]
185pub struct PreviousVersionStatus {
186 pub present: bool,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub created_at_ms: Option<i64>,
189}
190
191pub const CLIENT_DIAGNOSTICS_VERSION: u8 = 1;
192pub const MAX_DIAGNOSTIC_EXPECTED_SUBSCRIPTIONS: usize = 256;
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
195#[serde(rename_all = "camelCase", deny_unknown_fields)]
196pub struct ExpectedDiagnosticSubscription {
197 pub id: String,
198 pub table: String,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
202#[serde(rename_all = "camelCase", deny_unknown_fields)]
203pub struct ClientDiagnosticsRequest {
204 #[serde(default)]
205 pub expected_subscriptions: Vec<ExpectedDiagnosticSubscription>,
206}
207
208#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
209#[serde(rename_all = "camelCase")]
210pub struct ClientDiagnosticsHost {
211 pub kind: String,
212 pub role: String,
213 pub connectivity: String,
214 pub realtime: String,
215}
216
217#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
218#[serde(rename_all = "camelCase")]
219pub struct DiagnosticSubscription {
220 pub id: String,
221 pub table: String,
222 pub state: String,
223 pub complete: bool,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub cursor: Option<i64>,
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub reason_code: Option<String>,
228}
229
230#[derive(Debug, Clone, Serialize, Default, PartialEq, Eq)]
231#[serde(rename_all = "camelCase")]
232pub struct DiagnosticRoundCounters {
233 pub pushed: u32,
234 pub applied: usize,
235 pub rejected: usize,
236 pub retryable: usize,
237 pub conflicts: u32,
238 pub commits_applied: u32,
239 pub segment_rows_applied: u32,
240 pub bootstrapping: usize,
241 pub resets: usize,
242 pub revoked: usize,
243 pub failed: usize,
244 pub deferred_commits: usize,
245}
246
247#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
248#[serde(rename_all = "camelCase")]
249pub struct DiagnosticLastRound {
250 pub status: String,
251 pub started_at_ms: i64,
252 pub completed_at_ms: i64,
253 pub duration_ms: i64,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub counters: Option<DiagnosticRoundCounters>,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub error_code: Option<String>,
258}
259
260#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
261#[serde(rename_all = "camelCase")]
262pub struct DiagnosticLastChange {
263 pub revision: String,
264 pub recorded_at_ms: i64,
265 pub tables: Vec<String>,
266 pub windows: Vec<String>,
267 pub domains_truncated: bool,
268 pub status_changed: bool,
269 pub conflicts_changed: bool,
270 pub rejections_changed: bool,
271 pub outcomes_changed: bool,
272}
273
274#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
275#[serde(rename_all = "camelCase")]
276pub struct ClientDiagnosticsStorage {
277 pub status: String,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub database_bytes_approx: Option<i64>,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub pending_outbox_bytes_approx: Option<i64>,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub retained_outcome_bytes_approx: Option<i64>,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub retained_outcome_entries: Option<i64>,
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub blob_cache_bytes_approx: Option<i64>,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub pressure_reason_code: Option<String>,
290}
291
292#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
293#[serde(rename_all = "camelCase")]
294pub struct ClientDiagnosticsSnapshot {
295 pub version: u8,
296 pub captured_at_ms: i64,
297 pub host: ClientDiagnosticsHost,
298 pub security_lifecycle: String,
299 pub schema: ClientDiagnosticsSchema,
300 pub replica: ClientDiagnosticsReplica,
301 pub lease: ClientDiagnosticsLease,
302 pub subscriptions: Vec<DiagnosticSubscription>,
303 pub subscriptions_truncated: bool,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub last_round: Option<DiagnosticLastRound>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub last_change: Option<DiagnosticLastChange>,
308 pub storage: ClientDiagnosticsStorage,
309}
310
311#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
312#[serde(rename_all = "camelCase")]
313pub struct ClientDiagnosticsSchema {
314 pub current_version: i32,
315 pub upgrading: bool,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub required_version: Option<i32>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub latest_version: Option<i32>,
320}
321
322#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
323#[serde(rename_all = "camelCase")]
324pub struct ClientDiagnosticsReplica {
325 pub local_revision: String,
326 pub sync_needed: bool,
327 pub pending_outbox: usize,
328}
329
330#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
331#[serde(rename_all = "camelCase")]
332pub struct ClientDiagnosticsLease {
333 pub state: String,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub expires_at_ms: Option<i64>,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 pub error_code: Option<String>,
338}
339
340#[derive(Debug, Clone, Serialize)]
342#[serde(rename_all = "camelCase")]
343pub struct ClientChangeBatch {
344 pub revision: String,
345 pub tables: Vec<TableChange>,
346 pub windows: Vec<WindowChange>,
347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub status: Option<SyncStatusSnapshot>,
349 pub conflicts_changed: bool,
350 pub rejections_changed: bool,
351 pub outcomes_changed: bool,
352}
353
354#[derive(Debug, Clone, Serialize)]
355#[serde(tag = "kind", rename_all = "camelCase")]
356pub enum SyncIntent {
357 None,
358 Interactive,
359 Background {
360 #[serde(rename = "delayMs")]
361 delay_ms: u64,
362 },
363}
364
365#[derive(Debug, Clone, Serialize)]
366pub struct CommandEffects {
367 pub sync: SyncIntent,
368}
369
370impl CommandEffects {
371 #[must_use]
372 pub fn none() -> Self {
373 Self {
374 sync: SyncIntent::None,
375 }
376 }
377
378 #[must_use]
379 pub fn interactive() -> Self {
380 Self {
381 sync: SyncIntent::Interactive,
382 }
383 }
384}
385
386#[derive(Debug, Clone)]
387pub struct WindowCoverage {
388 pub base: WindowBase,
389 pub units: Vec<String>,
390}
391
392#[derive(Debug, Clone, Serialize)]
393#[serde(rename_all = "camelCase")]
394pub struct WindowUnitRef {
395 pub base_key: String,
396 pub unit: String,
397}
398
399#[derive(Debug, Clone, Serialize)]
400#[serde(rename_all = "camelCase")]
401pub struct CoverageSnapshot {
402 pub complete: bool,
403 pub pending: Vec<WindowUnitRef>,
404 pub missing: Vec<WindowUnitRef>,
405}
406
407#[derive(Debug, Clone, Serialize)]
408#[serde(rename_all = "camelCase")]
409pub struct QuerySnapshot {
410 pub revision: String,
411 pub rows: Vec<QueryRow>,
412 pub coverage: CoverageSnapshot,
413}
414
415impl WindowState {
416 #[must_use]
418 pub fn complete(&self, unit: &str) -> bool {
419 self.units.iter().any(|u| u == unit) && !self.pending.iter().any(|u| u == unit)
420 }
421}
422
423#[derive(Debug, Clone)]
425pub enum Mutation {
426 Upsert {
427 table: String,
428 values: Map<String, Value>,
429 base_version: Option<i64>,
430 },
431 Delete {
432 table: String,
433 row_id: String,
434 base_version: Option<i64>,
435 },
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
439#[serde(rename_all = "camelCase")]
440pub struct SchemaFloor {
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub required_schema_version: Option<i32>,
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub latest_schema_version: Option<i32>,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
451#[serde(rename_all = "camelCase")]
452pub struct LeaseState {
453 #[serde(skip_serializing_if = "Option::is_none")]
454 pub lease_id: Option<String>,
455 #[serde(skip_serializing_if = "Option::is_none")]
456 pub expires_at_ms: Option<i64>,
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub error_code: Option<String>,
459}
460
461#[derive(Debug, Clone, Serialize)]
463#[serde(rename_all = "camelCase")]
464pub struct PresencePeer {
465 pub actor_id: String,
466 pub client_id: String,
467 pub doc: serde_json::Value,
468}
469
470#[derive(Debug, Clone, Serialize, Default)]
471#[serde(rename_all = "camelCase")]
472pub struct SyncReport {
473 pub pushed: u32,
474 pub applied: Vec<String>,
475 pub rejected: Vec<String>,
476 pub retryable: Vec<String>,
477 pub conflicts: u32,
478 pub commits_applied: u32,
479 pub segment_rows_applied: u32,
480 pub bootstrapping: Vec<String>,
481 pub resets: Vec<String>,
482 pub revoked: Vec<String>,
483 pub failed: Vec<String>,
484 pub deferred_commits: usize,
485 #[serde(skip_serializing_if = "Option::is_none")]
486 pub schema_floor: Option<SchemaFloor>,
487}
488
489#[derive(Debug, Clone)]
492pub enum SyncOutcome {
493 Ok(SyncReport),
494 Failed { error_code: String, message: String },
495}
496
497impl SyncOutcome {
498 pub fn to_json(&self) -> Value {
499 match self {
500 SyncOutcome::Ok(report) => {
501 let mut map = Map::new();
502 map.insert("ok".to_owned(), Value::Bool(true));
503 map.insert(
504 "report".to_owned(),
505 serde_json::to_value(report).expect("report serializes"),
506 );
507 Value::Object(map)
508 }
509 SyncOutcome::Failed {
510 error_code,
511 message,
512 } => {
513 let mut map = Map::new();
514 map.insert("ok".to_owned(), Value::Bool(false));
515 map.insert("errorCode".to_owned(), Value::from(error_code.clone()));
516 map.insert("message".to_owned(), Value::from(message.clone()));
517 Value::Object(map)
518 }
519 }
520 }
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
524#[serde(rename_all = "camelCase")]
525pub struct ConflictRecord {
526 pub client_commit_id: String,
527 pub op_index: i32,
528 pub table: String,
529 pub row_id: String,
530 pub code: String,
531 pub message: String,
532 pub server_version: i64,
533 pub server_row: Map<String, Value>,
536 #[serde(default, skip_serializing_if = "Vec::is_empty")]
541 pub conflict_columns: Vec<String>,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 pub operation: Option<CommitOperation>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
549#[serde(rename_all = "camelCase", deny_unknown_fields)]
550pub struct RejectionDetails {
551 #[serde(skip_serializing_if = "Option::is_none")]
552 pub field_paths: Option<Vec<String>>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub reason: Option<String>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 pub required_action: Option<String>,
557 #[serde(skip_serializing_if = "Option::is_none")]
558 pub references: Option<BTreeMap<String, String>>,
559}
560
561impl RejectionDetails {
562 pub(crate) fn parse(raw: &str) -> Result<Self, String> {
563 if raw.len() > 4_096 {
564 return Err("rejection details exceed 4096 encoded bytes".to_owned());
565 }
566 let details: Self = serde_json::from_str(raw)
567 .map_err(|error| format!("invalid rejection details: {error}"))?;
568 details.validate()?;
569 Ok(details)
570 }
571
572 fn validate(&self) -> Result<(), String> {
573 let mut members = 0;
574 if let Some(paths) = &self.field_paths {
575 members += 1;
576 if paths.is_empty() || paths.len() > 32 {
577 return Err("fieldPaths must contain 1-32 paths".to_owned());
578 }
579 let mut seen = std::collections::BTreeSet::new();
580 for path in paths {
581 if path.len() > 160 || !path.split('.').all(valid_identifier) || !seen.insert(path)
582 {
583 return Err("fieldPaths contains an invalid or duplicate path".to_owned());
584 }
585 }
586 }
587 if let Some(reason) = &self.reason {
588 members += 1;
589 if !valid_token(reason, 96) {
590 return Err("reason must be a lowercase stable token".to_owned());
591 }
592 }
593 if let Some(action) = &self.required_action {
594 members += 1;
595 if !valid_token(action, 96) {
596 return Err("requiredAction must be a lowercase stable token".to_owned());
597 }
598 }
599 if let Some(references) = &self.references {
600 members += 1;
601 if references.is_empty() || references.len() > 16 {
602 return Err("references must contain 1-16 entries".to_owned());
603 }
604 for (key, value) in references {
605 if !valid_token(key, 64)
606 || value.is_empty()
607 || value.len() > 256
608 || value.trim() != value
609 || value.chars().any(char::is_control)
610 {
611 return Err("references contains an invalid key or value".to_owned());
612 }
613 }
614 }
615 if members == 0 {
616 return Err("rejection details must not be empty".to_owned());
617 }
618 Ok(())
619 }
620}
621
622fn valid_identifier(segment: &str) -> bool {
623 let mut chars = segment.chars();
624 matches!(chars.next(), Some(first) if first == '_' || first.is_ascii_alphabetic())
625 && chars.all(|character| character == '_' || character.is_ascii_alphanumeric())
626}
627
628fn valid_token(token: &str, max: usize) -> bool {
629 if token.is_empty() || token.len() > max || !token.starts_with(|c: char| c.is_ascii_lowercase())
630 {
631 return false;
632 }
633 let mut previous_separator = false;
634 for character in token.chars() {
635 if character.is_ascii_lowercase() || character.is_ascii_digit() {
636 previous_separator = false;
637 } else if matches!(character, '.' | '_' | '-') && !previous_separator {
638 previous_separator = true;
639 } else {
640 return false;
641 }
642 }
643 !previous_separator
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
647#[serde(rename_all = "camelCase")]
648pub struct RejectionRecord {
649 pub client_commit_id: String,
650 pub op_index: i32,
651 pub code: String,
652 pub message: String,
653 pub retryable: bool,
654 #[serde(skip_serializing_if = "Option::is_none")]
655 pub details: Option<RejectionDetails>,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub operation: Option<CommitOperation>,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
662#[serde(rename_all = "camelCase")]
663pub struct CommitOperation {
664 pub table: String,
665 pub row_id: String,
666 pub op: String,
667 #[serde(skip_serializing_if = "Option::is_none")]
668 pub base_version: Option<i64>,
669 #[serde(skip_serializing_if = "Option::is_none")]
670 pub values: Option<Map<String, Value>>,
671}
672
673#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
674#[serde(rename_all = "snake_case")]
675pub enum CommitOutcomeStatus {
676 Applied,
677 Cached,
678 Conflict,
679 Rejected,
680}
681
682#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
683#[serde(rename_all = "snake_case")]
684pub enum CommitOutcomeResolution {
685 Active,
686 ResolvedKeepServer,
687 Superseded,
688 Dismissed,
689}
690
691#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
692#[serde(tag = "status", rename_all = "snake_case")]
693pub enum CommitOperationOutcome {
694 Applied {
695 #[serde(rename = "opIndex")]
696 op_index: i32,
697 },
698 Conflict {
699 conflict: ConflictRecord,
700 },
701 Error {
702 rejection: RejectionRecord,
703 },
704}
705
706#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
707#[serde(rename_all = "camelCase")]
708pub struct CommitOutcome {
709 pub sequence: i64,
710 pub client_commit_id: String,
711 pub status: CommitOutcomeStatus,
712 pub recorded_at_ms: i64,
713 pub results: Vec<CommitOperationOutcome>,
714 #[serde(skip_serializing_if = "Option::is_none")]
717 pub operations: Option<Vec<CommitOperation>>,
718 pub resolution: CommitOutcomeResolution,
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub resolved_at_ms: Option<i64>,
721 #[serde(skip_serializing_if = "Option::is_none")]
722 pub replacement_client_commit_id: Option<String>,
723}
724
725#[derive(Debug, Clone, Deserialize, Default)]
726#[serde(rename_all = "camelCase")]
727pub struct CommitOutcomeQuery {
728 pub limit: Option<usize>,
729 #[serde(default)]
730 pub active_only: bool,
731}
732
733#[derive(Debug, Clone, Deserialize)]
734#[serde(rename_all = "camelCase")]
735pub struct ResolveCommitOutcomeInput {
736 pub client_commit_id: String,
737 pub resolution: CommitOutcomeResolution,
738 pub replacement_client_commit_id: Option<String>,
739}
740
741#[derive(Debug, Clone, Serialize)]
742#[serde(rename_all = "camelCase")]
743pub struct RowState {
744 pub row_id: String,
745 pub version: i64,
748 pub values: Map<String, Value>,
749}
750
751#[derive(Debug, Clone, Serialize)]
752#[serde(rename_all = "camelCase")]
753pub struct SubscriptionStateView {
754 pub id: String,
755 pub table: String,
756 pub status: String,
758 pub cursor: i64,
759 pub has_resume_token: bool,
760 #[serde(skip_serializing_if = "Option::is_none")]
761 pub effective_scopes: Option<Value>,
762 #[serde(skip_serializing_if = "Option::is_none")]
763 pub reason_code: Option<String>,
764}
765
766#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
770#[serde(rename_all = "camelCase", deny_unknown_fields)]
771pub struct LocalDataPurgeTarget {
772 pub table: String,
773 pub selectors: BTreeMap<String, Vec<String>>,
774}
775
776#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
779#[serde(rename_all = "camelCase", deny_unknown_fields)]
780pub struct LocalDataPurgeInput {
781 pub purge_id: String,
782 pub targets: Vec<LocalDataPurgeTarget>,
783}
784
785#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
787#[serde(rename_all = "camelCase")]
788pub struct LocalDataPurgeResult {
789 pub already_applied: bool,
790 pub purged_rows: usize,
791 pub dropped_commits: usize,
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
796#[serde(rename_all = "camelCase", deny_unknown_fields)]
797pub struct LocalDataRebootstrapInput {
798 pub rebootstrap_id: String,
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
803#[serde(rename_all = "camelCase")]
804pub struct LocalDataRebootstrapResult {
805 pub already_applied: bool,
806 pub retained_commits: usize,
807 pub reset_subscriptions: usize,
808}
809
810#[derive(Debug, Clone, Default)]
812pub struct ClientLimits {
813 pub limit_commits: Option<i32>,
814 pub limit_snapshot_rows: Option<i32>,
815 pub max_snapshot_pages: Option<i32>,
816 pub accept: Option<u8>,
819 pub blob_cache_max_bytes: Option<i64>,
824 pub outcome_retention_max_entries: Option<usize>,
827 pub previous_version_context: Option<PreviousVersionContextConfig>,
831}